pipeDetail.vue 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094
  1. <template>
  2. <!-- 单位基本信息 -->
  3. <div class="plan-section mb-4">
  4. <div class="section-title">
  5. <span>受检单位基本信息</span>
  6. </div>
  7. <el-descriptions :column="3" border>
  8. <el-descriptions-item label="单位名称">{{ unitInfo.unitName }}</el-descriptions-item>
  9. <el-descriptions-item label="单位地址">{{ unitInfo.unitAddress }}</el-descriptions-item>
  10. <!-- <el-descriptions-item label="使用单位联系人">{{ unitInfo.contact }}</el-descriptions-item>
  11. <el-descriptions-item label="使用单位联系电话">{{ unitInfo.contactPhone }}</el-descriptions-item>
  12. <el-descriptions-item label="区域">{{ unitInfo.equipDistrictName }}</el-descriptions-item>
  13. <el-descriptions-item label="街道">{{ unitInfo.equipStreetName }}</el-descriptions-item> -->
  14. </el-descriptions>
  15. </div>
  16. <div class="plan-section mb-4">
  17. <div class="section-title">
  18. <span>设备信息</span>
  19. </div>
  20. <!-- 搜索工作栏 -->
  21. <ContentWrap>
  22. <section class="flex gap-2">
  23. <el-form
  24. class="flex-1"
  25. :model="queryParams"
  26. ref="queryFormRef"
  27. :inline="true"
  28. label-width="135px"
  29. >
  30. <!-- 基本信息查询部分 -->
  31. <el-row :gutter="24">
  32. <el-col :span="8">
  33. <el-form-item class="!w-full !mr-0px" label="区域" prop="equipDistrict">
  34. <AreaSelect
  35. v-model="queryParams.equipDistrict"
  36. placeholder="请选择区域"
  37. class="!w-full area-select"
  38. multiple
  39. collapse-tags
  40. collapse-tags-tooltip
  41. :clearable="true"
  42. :area-type="queryParams.areaType"
  43. @clear="handleAreaClear"
  44. @change="handleAreaChange"
  45. />
  46. </el-form-item>
  47. </el-col>
  48. <el-col :span="8">
  49. <el-form-item class="!w-full !mr-0px" label="街道" prop="equipStreet">
  50. <StreetSelect
  51. v-model="queryParams.equipStreet"
  52. :district-ids="queryParams.equipDistrict"
  53. placeholder="请选择街道"
  54. multiple
  55. collapse-tags
  56. collapse-tags-tooltip
  57. :clearable="true"
  58. @clear="handleStreetClear"
  59. @change="handleStreetChange"
  60. />
  61. </el-form-item>
  62. </el-col>
  63. <!-- <el-col :span="8">-->
  64. <!-- <el-form-item class="!w-full !mr-0px" label="设备注册代码" prop="equipCode">-->
  65. <!-- <el-input-->
  66. <!-- v-model="queryParams.equipCode"-->
  67. <!-- placeholder="请输入设备注册代码"-->
  68. <!-- clearable-->
  69. <!-- @keyup.enter="handleQuery"-->
  70. <!-- />-->
  71. <!-- </el-form-item>-->
  72. <!-- </el-col>-->
  73. <!-- <el-col :span="8">
  74. <el-form-item
  75. class="!w-full !mr-0px"
  76. label="社会统一信用代码"
  77. prop="socialCreditCode"
  78. >
  79. <el-input
  80. class="!w-full"
  81. v-model="queryParams.socialCreditCode"
  82. placeholder="请输入社会统一信用代码"
  83. clearable
  84. />
  85. </el-form-item>
  86. </el-col> -->
  87. <!-- <el-col :span="8">
  88. <el-form-item class="!w-full !mr-0px" label="使用证编号" prop="useCertCode">
  89. <el-input
  90. class="!w-full"
  91. v-model="queryParams.useCertCode"
  92. placeholder="请输入使用证编号"
  93. clearable
  94. />
  95. </el-form-item>
  96. </el-col> -->
  97. <ElCollapseTransition v-if="isSearchExpanded">
  98. <el-col :span="8">
  99. <el-form-item class="!w-full !mr-0px" label="临检时间" prop="checkDate">
  100. <div class="w-full flex gap-10px">
  101. <el-select v-model="datePickerType" class="!w-[100px]">
  102. <el-option label="时间段" value="daterange" />
  103. <el-option label="月份" value="month" />
  104. </el-select>
  105. <el-date-picker
  106. v-if="datePickerType === 'daterange'"
  107. v-model="daterange"
  108. type="daterange"
  109. value-format="YYYY-MM-DD HH:mm:ss"
  110. start-placeholder="开始日期"
  111. end-placeholder="结束日期"
  112. class="flex-1"
  113. />
  114. <!-- @change="handleDateChange" -->
  115. <el-date-picker
  116. v-else
  117. v-model="month"
  118. type="month"
  119. value-format="YYYY-MM"
  120. placeholder="选择月份"
  121. class="flex-1"
  122. />
  123. <!-- @change="handleMonthChange" -->
  124. </div>
  125. </el-form-item>
  126. </el-col>
  127. </ElCollapseTransition>
  128. <ElCollapseTransition v-if="isSearchExpanded">
  129. <el-col :span="8">
  130. <el-form-item class="!w-full !mr-0px" label="部门" prop="relateDepartment">
  131. <DeptSelect
  132. class="!w-full"
  133. v-model="queryParams.relateDepartment"
  134. placeholder="请选择部门"
  135. clearable
  136. />
  137. </el-form-item>
  138. </el-col>
  139. </ElCollapseTransition>
  140. <ElCollapseTransition v-if="isSearchExpanded">
  141. <el-col :span="8">
  142. <el-form-item class="!w-full !mr-0px" label="使用状态" prop="useStatus">
  143. <el-select
  144. v-model="queryParams.useStatus"
  145. placeholder="选择使用状态"
  146. clearable
  147. collapse-tags
  148. collapse-tags-tooltip
  149. disabled
  150. >
  151. <el-option
  152. v-for="dict in getStrDictOptions(DICT_TYPE.SYSTEM_EQUIP_BOILER_STATUS)"
  153. :key="dict.value"
  154. :label="dict.label"
  155. :value="dict.value"
  156. />
  157. </el-select>
  158. </el-form-item>
  159. </el-col>
  160. </ElCollapseTransition>
  161. <ElCollapseTransition v-if="isSearchExpanded">
  162. <el-col :span="12">
  163. <el-form-item class="!w-full !mr-0px" label="管道归类" prop="typeList">
  164. <el-checkbox-group
  165. v-model="queryParams.typeList"
  166. class="flex flex-wrap gap-2"
  167. @change="handleTypeListChange"
  168. >
  169. <el-checkbox
  170. v-for="dict in containerTypeOptions"
  171. :key="dict.value"
  172. :label="dict.label"
  173. :value="dict.value"
  174. />
  175. </el-checkbox-group>
  176. </el-form-item>
  177. </el-col>
  178. </ElCollapseTransition>
  179. </el-row>
  180. </el-form>
  181. <div class="flex-[0_0_260px] flex items-start">
  182. <!-- 操作按钮 -->
  183. <el-button type="primary" @click="handleQuery">
  184. <Icon icon="ep:search" class="mr-5px" /> 搜索
  185. </el-button>
  186. <el-button @click="resetQuery">
  187. <Icon icon="ep:refresh" class="mr-5px" /> 重置
  188. </el-button>
  189. <el-button link class="!h-[32px] flex" @click="isSearchExpanded = !isSearchExpanded">
  190. <el-icon>
  191. <ArrowUp v-if="isSearchExpanded" />
  192. <ArrowDown v-else />
  193. </el-icon>
  194. {{ isSearchExpanded ? '收起' : '展开' }}
  195. </el-button>
  196. </div>
  197. </section>
  198. </ContentWrap>
  199. <!-- 列表 -->
  200. <ContentWrap>
  201. <el-row class="mb-2">
  202. <!-- <el-button
  203. type="primary"
  204. @click="() => handleBatchSchedule('regular')"
  205. :disabled="selectedRows.length === 0"
  206. >
  207. <Icon icon="ep:calendar" class="mr-5px" /> 定期约检
  208. </el-button>
  209. <el-button
  210. type="primary"
  211. @click="() => handleBatchSchedule('year')"
  212. :disabled="selectedRows.length === 0"
  213. >
  214. <Icon icon="ep:calendar" class="mr-5px" /> 年度约检
  215. </el-button>
  216. <el-button
  217. type="primary"
  218. @click="() => handleBatchSchedule('expired')"
  219. :disabled="selectedRows.length === 0"
  220. >
  221. <Icon icon="ep:calendar" class="mr-5px" /> 超年限约检
  222. </el-button>
  223. <el-button
  224. type="primary"
  225. @click="() => handleBatchSchedule('')"
  226. :disabled="selectedRows.length === 0"
  227. >
  228. <Icon icon="ep:calendar" class="mr-5px" /> 批量排期
  229. </el-button>-->
  230. <el-button
  231. type="primary"
  232. @click="() => handleBatchSchedule('')"
  233. :disabled="selectedRows.length === 0"
  234. >约检
  235. </el-button>
  236. <el-button type="primary" @click="handleBatchEditFn" :disabled="selectedRows.length === 0">
  237. <Icon icon="ep:edit" class="mr-5px" /> 批量修改约检联系人
  238. </el-button>
  239. <el-button type="primary" @click="() => handleToRoute('AcceptOrder1')">约检单</el-button>
  240. <el-button type="primary" @click="() => handleToRoute('TaskOrder')">任务单</el-button>
  241. </el-row>
  242. <el-table
  243. v-loading="loading"
  244. ref="mainTableRef"
  245. :data="list"
  246. border
  247. @selection-change="handleSelectionChange"
  248. @sort-change="handleSortChange"
  249. :row-key="(row) => row.id"
  250. :row-class-name="getMainRowClassName"
  251. @row-click="handleMainRowClick"
  252. @expand-change="handleExpandChange"
  253. :expand-row-keys="expandRowKeys"
  254. >
  255. <el-table-column type="selection" width="50" fixed="left"/>
  256. <el-table-column type="expand">
  257. <template #default="props">
  258. <div class="ml-15px mr-15px">
  259. <el-table
  260. :data="props.row.pipes" border
  261. :header-cell-style="{background: '#f5f7fa', color: '#606266'}"
  262. :ref="(el) => setDetailTableRef(el, props.row.id)"
  263. @selection-change="(selection) => handleDetailSelectionChange(selection, props.row)"
  264. :row-class-name="getRowClassName"
  265. class="inner-table"
  266. @row-click="handleRowClick"
  267. >
  268. <el-table-column type="selection" width="50" fixed="left"/>
  269. <el-table-column type="index" label="序号" width="60" align="center" :index="indexMethod1" />
  270. <el-table-column label="注册代码" prop="pipeRegCode" min-width="120" show-overflow-tooltip/>
  271. <el-table-column label="管道名称" prop="pipeName" min-width="120" show-overflow-tooltip/>
  272. <el-table-column label="管道编号" prop="pipeNo" min-width="120" show-overflow-tooltip/>
  273. <el-table-column label="管道级别" prop="pipeLevel" min-width="120" show-overflow-tooltip/>
  274. <el-table-column label="管道品种" prop="pipeType" min-width="120" show-overflow-tooltip/>
  275. <el-table-column label="长度" prop="pipeLength" min-width="100" show-overflow-tooltip/>
  276. <el-table-column label="管道材质" prop="pipeMaterial" min-width="120" show-overflow-tooltip/>
  277. <el-table-column label="材料标准" prop="materialStandard" min-width="120" show-overflow-tooltip/>
  278. <el-table-column label="定期安全状况等级" prop="legalSafetyStatusLevel" min-width="130" show-overflow-tooltip/>
  279. <el-table-column label="年度安全状况等级" prop="yearSafetyStatusLevel" min-width="130" show-overflow-tooltip/>
  280. <el-table-column label="下次定期检验" prop="nextLegalCheckDate" align="center" min-width="160" show-overflow-tooltip>
  281. <template #default="{ row }">
  282. <div>
  283. <div v-if="row.nextLegalCheckDate">
  284. <span
  285. class="text-xs">{{
  286. formatDate(row.nextLegalCheckDate, 'YYYY-MM-DD')
  287. }}</span>
  288. <div v-if="row.planLegalCheckDate" class="text-xs text-[#2D5FBD]">
  289. (排期时间:{{ row.planLegalCheckDate }})
  290. </div>
  291. </div>
  292. <span v-else>-</span>
  293. </div>
  294. </template>
  295. </el-table-column>
  296. <el-table-column label="下次年度检查" prop="nextYearCheckDate" align="center" min-width="160"
  297. show-overflow-tooltip>
  298. <template #default="{ row }">
  299. <div>
  300. <div v-if="row.nextYearCheckDate">
  301. <span
  302. class="text-xs">{{
  303. formatDate(row.nextYearCheckDate, 'YYYY-MM-DD')
  304. }}</span>
  305. <div v-if="row.planYearCheckDate" class="text-xs text-[#2D5FBD]">
  306. (排期时间:{{ row.planYearCheckDate }})
  307. </div>
  308. </div>
  309. <span v-else>-</span>
  310. </div>
  311. </template>
  312. </el-table-column>
  313. </el-table>
  314. </div>
  315. </template>
  316. </el-table-column>
  317. <el-table-column type="index" label="序号" width="60" align="center" :index="indexMethod" />
  318. <el-table-column
  319. label="工程号"
  320. align="center"
  321. prop="projectNo"
  322. min-width="120"
  323. >
  324. <template #default="{ row }">
  325. <div
  326. v-if="row.projectNo"
  327. class="cursor-pointer"
  328. @click.stop="toggleExpand(row)"
  329. >
  330. <div class="flex items-center justify-center gap-1 schedule-link">
  331. <span class="text-xs color-blue">{{row.projectNo}}</span>
  332. </div>
  333. </div>
  334. </template>
  335. </el-table-column>
  336. <el-table-column label="工程名称"
  337. align="center"
  338. prop="projectName"
  339. min-width="120"
  340. show-overflow-tooltip
  341. />
  342. <el-table-column
  343. label="下次定期检验"
  344. align="center"
  345. prop="nextLegalCheckDate"
  346. min-width="140"
  347. sortable="custom"
  348. >
  349. <template #default="{ row }">
  350. <div
  351. v-if="row.nextLegalCheckDate"
  352. >
  353. <!-- class="cursor-pointer">-->
  354. <!-- @click="handleSingleSchedule(row, 'year')" > -->
  355. <!-- <div class="flex items-center justify-center gap-1 schedule-link">-->
  356. <div class="flex items-center justify-center gap-1">
  357. <span class="text-xs">{{formatDate(row.nextLegalCheckDate, 'YYYY-MM-DD')}}</span>
  358. <!-- <Icon icon="ep:calendar" class="text-xs" />-->
  359. </div>
  360. <!-- <div v-if="row.yearRefuseReasonDict || row.yearRefuseReason" class="text-xs text-red-500 mt-1">
  361. (拒绝检验:{{
  362. getRefuseResonText(row.yearRefuseReasonDict, row.yearRefuseReason)
  363. }})
  364. </div>
  365. <div v-else-if="row.yearAcceptDate" class="text-xs text-[#3D9E5F] mt-1">
  366. (已受理:{{ formatArrayDate(row.yearAcceptDate) }})
  367. </div>
  368. <div v-else-if="row.yearAppointmentDate" class="text-xs text-[#FF9A3D] mt-1">
  369. (待约检:{{ formatArrayDate(row.yearAppointmentDate) }})
  370. </div>-->
  371. <div v-if="row.planLegalCheckDate" class="text-xs text-[#2D5FBD] mt-1">
  372. (排期时间:{{ row.planLegalCheckDate }})
  373. </div>
  374. </div>
  375. <span v-else>-</span>
  376. </template>
  377. </el-table-column>
  378. <el-table-column
  379. label="下次年度检查"
  380. align="center"
  381. prop="nextYearCheckDate"
  382. min-width="150"
  383. sortable="custom"
  384. >
  385. <template #default="{ row }">
  386. <div
  387. v-if="row.nextYearCheckDate"
  388. >
  389. <!-- class="cursor-pointer"> -->
  390. <!-- @click="handleSingleSchedule(row, 'expired')" > -->
  391. <!-- <div class="flex items-center justify-center gap-1 schedule-link">-->
  392. <div class="flex items-center justify-center gap-1">
  393. <span class="text-xs">{{ formatDate(row.nextYearCheckDate, 'YYYY-MM-DD') }}</span>
  394. <!-- <Icon icon="ep:calendar" class="text-xs" />-->
  395. </div>
  396. <!-- <div v-if="row.expiredReasonDict || row.expiredRefuseReason" class="text-xs text-red-500 mt-1">-->
  397. <!-- (拒绝检验:{{-->
  398. <!-- getRefuseResonText(row.expiredReasonDict, row.expiredRefuseReason)-->
  399. <!-- }})-->
  400. <!-- </div>-->
  401. <!-- <div v-else-if="row.expiredAcceptDate" class="text-xs text-[#3D9E5F] mt-1">-->
  402. <!-- (已受理:{{ formatArrayDate(row.expiredAcceptDate) }})-->
  403. <!-- </div>-->
  404. <!-- <div v-else-if="row.expiredAppointmentDate" class="text-xs text-[#FF9A3D] mt-1">-->
  405. <!-- (待约检:{{ formatArrayDate(row.expiredAppointmentDate) }})-->
  406. <!-- </div>-->
  407. <div v-if="row.planYearCheckDate" class="text-xs text-[#2D5FBD] mt-1">
  408. (排期时间:{{ row.planYearCheckDate }})
  409. </div>
  410. </div>
  411. <span v-else>-</span>
  412. </template>
  413. </el-table-column>
  414. <!-- 区域 -->
  415. <el-table-column
  416. label="区域/街道"
  417. align="center"
  418. prop="pipeStreet"
  419. min-width="120"
  420. show-overflow-tooltip
  421. >
  422. <template #default="{ row }">
  423. <span class="text-xs">{{ row.equipStreetName ?? row.equipDistrictName}}</span>
  424. </template>
  425. </el-table-column>
  426. <!-- 管道类别 -->
  427. <el-table-column label="管道类别" align="center" prop="pipeCategory">
  428. <template #default="scope">
  429. {{ containerTypeOptions.find(i => i.value == scope.row.pipeCategory)?.label }}
  430. </template>
  431. </el-table-column>
  432. <el-table-column
  433. label="约检联系人"
  434. align="center"
  435. prop="contact"
  436. min-width="120"
  437. show-overflow-tooltip
  438. />
  439. <el-table-column
  440. label="约检联系人电话"
  441. align="center"
  442. prop="contactPhone"
  443. min-width="140"
  444. show-overflow-tooltip
  445. />
  446. </el-table>
  447. <!-- 分页 -->
  448. <Pagination
  449. :total="total"
  450. v-model:page="queryParams.pageNo"
  451. v-model:limit="queryParams.pageSize"
  452. @pagination="handleQuery"
  453. />
  454. </ContentWrap>
  455. </div>
  456. <!-- 约检弹窗 -->
  457. <PlanScheduleEquipPipeDialog
  458. ref="planScheduleEquipPipeDialogRef"
  459. :source="200"
  460. :equip-list="selectedRows"
  461. :equip-detail-list="selectedDetailRows"
  462. :unitInfo="unitInfo"
  463. @success="handleScheduleSuccess"
  464. />
  465. <PipeBatchEditForm ref="formRef" @success="handleScheduleSuccess" />
  466. </template>
  467. <script setup lang="ts" name="PlanNewDetail">
  468. import {DICT_TYPE, getStrDictOptions, StringDictDataType} from '@/utils/dict'
  469. import {useMessage} from '@/hooks/web/useMessage'
  470. import {PlanNewPageVO} from '@/api/pressure/planScheduling'
  471. import AreaSelect from '../../system/equipcontainer/components/AreaSelect.vue'
  472. import StreetSelect from '../../system/equipcontainer/components/StreetSelect.vue'
  473. import DeptSelect from './components/DeptSelect.vue'
  474. import dayjs from 'dayjs'
  475. import {useUserStore} from '@/store/modules/user'
  476. import {formatArrayDate, formatDate} from '@/utils/formatTime'
  477. import PipeBatchEditForm from './components/PipeBatchEditForm.vue'
  478. import {cloneDeep} from 'lodash-es'
  479. import {isEmpty} from '@/utils/is'
  480. import {useRouter} from 'vue-router'
  481. import {usePlanNewStore} from '@/store/modules/planNew'
  482. import {ArrowDown, ArrowUp} from '@element-plus/icons-vue'
  483. import {EquipBoilerSchedulingEquipVO} from "@/api/pressure2/equipboilerscheduling";
  484. import {
  485. PipeEquipmentApi,
  486. PipeEquipmentDetailVO,
  487. PipeEquipmentVO
  488. } from "@/api/pressure2/pipeequipment";
  489. import PlanScheduleEquipPipeDialog
  490. from "./components/PlanScheduleEquipPipeDialog.vue";
  491. import {EquipPipeSchedulingApi, PipePlanSchedulingVO} from "@/api/pressure2/pipescheduling";
  492. import PlanScheduleBoilerDialog
  493. from "@/views/pressure2/planNew/components/PlanScheduleEquipBoilerDialog.vue";
  494. import {ref} from "vue";
  495. const router = useRouter()
  496. const props = defineProps({
  497. source: {
  498. type: String as PropType<string>,
  499. default: 'pressure'
  500. }
  501. })
  502. const currentDefaultYear = [
  503. dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss'),
  504. dayjs().endOf('year').format('YYYY-MM-DD HH:mm:ss')
  505. ]
  506. const message = useMessage()
  507. const userStore = useUserStore()
  508. const datePickerType = ref<'daterange' | 'month'>('month') // 修改时间选择类型定义
  509. const daterange = ref<string[]>([]) // 日期选择值
  510. const month = ref<string>('') // 月份选择值
  511. const areaStreetMap = ref(new Map<number, number[]>()) // 记录每个区域下已选择的街道
  512. const loading = ref(false) // 列表的加载中
  513. const equipIds = ref<string[]>([]) // 设备ID列表
  514. const list = ref<PipeEquipmentVO[]>([]) // 列表的数据
  515. const total = ref(0) // 列表的总页数
  516. const selectedRows = ref<PipeEquipmentVO[]>([]) // 选中的行
  517. const selectedDetailRows = ref<PipeEquipmentDetailVO[]>([]) // 选中的行
  518. const planScheduleEquipPipeDialogRef = ref<InstanceType<typeof PlanScheduleEquipPipeDialog>>() // 计划排期弹窗引用
  519. const selectedTypeList = ref<StringDictDataType[]>([]) // 选中的容器归类
  520. const formRef = ref()
  521. // 单位基本信息
  522. const unitInfo = ref({
  523. unitId: '',
  524. unitName: '',
  525. unitAddress: '',
  526. contact: '',
  527. contactPhone: '',
  528. equipDistrictName: '',
  529. equipStreetName: ''
  530. })
  531. const equipTypeMap = {
  532. 1: '容器'
  533. }
  534. const mainTableRef = ref()
  535. // 拒绝原因
  536. const refuseInspectedCategoryOptions = getStrDictOptions('refuseInspectedCategory')
  537. const getRefuseResonText = (dictCode: string, value: string) => {
  538. const resonObj = refuseInspectedCategoryOptions.find((item) => item.value === dictCode)
  539. return value || resonObj?.label || dictCode
  540. }
  541. const queryParams = ref<Recordable>({
  542. pageNo: 1,
  543. pageSize: 20,
  544. equipCode: undefined as string | undefined,
  545. productNo: undefined as string | undefined,
  546. relateDepartment: undefined as string | string[] | undefined,
  547. equipDistrict: undefined as number[] | undefined,
  548. equipStreet: undefined as number[] | undefined,
  549. typeList: [] as string[],
  550. nextDate: [] as string[],
  551. useStatus: '100',
  552. areaType: 'all' as 'all' | 'gz'
  553. })
  554. const queryFormRef = ref() // 搜索的表单
  555. // 添加容器类型字典选项变量
  556. const containerTypeOptions = getStrDictOptions(DICT_TYPE.PIPE_TYPE)
  557. // 单条/批量排期相关
  558. const currentEquip = ref<PipeEquipmentVO>()
  559. const currentCheckType = ref<scheduleType>('')
  560. const expandedRowsCache = ref<Map<string, { pipes: any[], selectedIds: Set<string> }>>(new Map())
  561. // 标志位:防止展开时恢复选中状态触发 handleDetailSelectionChange 覆盖缓存
  562. const isRestoringSelection = ref(false)
  563. // 存储所有子表引用的对象
  564. const detailTableRefs = ref<Record<string, any>>({})
  565. const setDetailTableRef = (el: any, parentId: string) => {
  566. if (el) {
  567. detailTableRefs.value[parentId] = el;
  568. }
  569. }
  570. const getDetailTableRef = (parentId: string) => {
  571. return detailTableRefs.value[parentId];
  572. }
  573. const getMainRowClassName = ({row}) => {
  574. const isSelected = mainTableRef.value?.getSelectionRows().some((item) =>
  575. item.id === row.id
  576. )
  577. return isSelected ? 'selected-row' : ''
  578. }
  579. const getRowClassName = ({row}) => {
  580. // 找到当前行所在的分组,从 Map 中获取对应的表格实例
  581. const tableRef = getDetailTableRef(row.equipPipeId)
  582. if (tableRef) {
  583. const isSelected = tableRef.getSelectionRows().some((item) =>
  584. item.id === row.id
  585. )
  586. return isSelected ? 'selected-row' : ''
  587. }
  588. return ''
  589. }
  590. /** 处理行点击勾选 */
  591. const handleMainRowClick = (row) => {
  592. mainTableRef.value?.toggleRowSelection(row)
  593. }
  594. const handleRowClick = (row, event, column) => {
  595. // 如果点击的是选择列,不处理
  596. if (column.type === 'selection') {
  597. return
  598. }
  599. // 找到当前行所在的分组,从 Map 中获取对应的表格实例
  600. const tableRef = getDetailTableRef(row.equipPipeId)
  601. if (tableRef && tableRef.toggleRowSelection) {
  602. tableRef.toggleRowSelection(row)
  603. }
  604. }
  605. const handleExpandChange = (row, expandedRows: any[]) => {
  606. const rowId = row.id
  607. const isExpanded = expandedRows.some((r) => r.id === rowId)
  608. if (isExpanded) {
  609. // 展开时,从缓存中恢复或初始化
  610. if (!expandedRowsCache.value.has(rowId)) {
  611. // 首次展开,初始化缓存
  612. expandedRowsCache.value.set(rowId, {
  613. pipes: row.pipes || [],
  614. selectedIds: new Set<string>()
  615. })
  616. }
  617. // 恢复选中状态
  618. nextTick(() => {
  619. const cache = expandedRowsCache.value.get(rowId)
  620. const tableRef = getDetailTableRef(rowId)
  621. if (cache && tableRef && cache.pipes.length > 0) {
  622. // 设置标志位,防止触发 handleDetailSelectionChange
  623. isRestoringSelection.value = true
  624. cache.pipes.forEach((pipe) => {
  625. if (cache.selectedIds.has(pipe.id)) {
  626. tableRef.toggleRowSelection(pipe, true)
  627. }
  628. })
  629. // 恢复完成后,延迟重置标志位
  630. nextTick(() => {
  631. isRestoringSelection.value = false
  632. })
  633. }
  634. })
  635. } else {
  636. // 收起时,保存当前选中状态
  637. const tableRef = getDetailTableRef(rowId)
  638. if (tableRef) {
  639. const currentSelection = tableRef.getSelectionRows()
  640. const selectedIds = new Set(currentSelection.map((item: any) => item.id))
  641. expandedRowsCache.value.set(rowId, {
  642. pipes: row.pipes || [],
  643. selectedIds: selectedIds
  644. })
  645. }
  646. }
  647. // 更新展开的行 key 列表
  648. expandRowKeys.value = expandedRows.map((r) => r.id)
  649. }
  650. /** 打开弹窗 */
  651. const open = async (row: PlanNewPageVO) => {
  652. equipIds.value = [...new Set([row.equipIds, row.yearEquipIds, row.expiredEquipIds])]
  653. .filter((id) => id) // 过滤掉 null、undefined、空字符串
  654. .join(',')
  655. .split(',')
  656. // 设置单位信息
  657. unitInfo.value = {
  658. unitId: row.unitId || '',
  659. unitName: row.unitName || '',
  660. unitAddress: row.unitAddress || '',
  661. contact: row.contact || '',
  662. contactPhone: row.contactPhone || '',
  663. equipDistrictName: row.equipDistrictName || '',
  664. equipStreetName: row.equipStreetName || ''
  665. }
  666. // 初始化查询参数
  667. queryParams.value = {
  668. ...queryParams.value,
  669. unitCode: row.unitCode,
  670. unitName: row.unitName || '',
  671. unitId: row.unitId || '',
  672. }
  673. // 查询设备列表
  674. await handleQuery()
  675. }
  676. const indexMethod = (index: number) => {
  677. return index + 1
  678. }
  679. const indexMethod1 = (index: number) => {
  680. return index + 1
  681. }
  682. /**获取查询参数 */
  683. const getQueryParams = () => {
  684. const params = cloneDeep({
  685. scene: 0,
  686. pageNo: queryParams.value.pageNo,
  687. pageSize: queryParams.value.pageSize,
  688. unitCode: queryParams.value.unitCode,
  689. // unitName: queryParams.value.unitName,
  690. unitId: queryParams.value.unitId,
  691. // equipIds: equipIds.value,
  692. equipCode: queryParams.value.equipCode,
  693. relateDepartment: queryParams.value.relateDepartment,
  694. productNo: queryParams.value.productNo,
  695. equipDistrict: queryParams.value.equipDistrict,
  696. equipStreet: queryParams.value.equipStreet,
  697. pipeCategory: queryParams.value.typeList?.length === 1 ? queryParams.value.typeList[0] : undefined,
  698. pipeCategorys: queryParams.value.typeList || [],
  699. typeList: queryParams.value.typeList || [],
  700. nextDate: (queryParams.value.nextDate || []).map((item) => dayjs(item).valueOf()),
  701. useStatus: queryParams.value.useStatus ? [+queryParams.value.useStatus] : []
  702. })
  703. if (datePickerType.value === 'daterange') {
  704. params.nextDate = daterange.value ? daterange.value.map((item) => dayjs(item).valueOf()) : []
  705. } else if (datePickerType.value === 'month') {
  706. params.nextDate = month.value
  707. ? [dayjs(month.value).startOf('month').valueOf(), dayjs(month.value).endOf('month').valueOf()]
  708. : []
  709. }
  710. // 移除空值
  711. Object.keys(params).forEach((key) => {
  712. if (isEmpty(params[key])) {
  713. delete params[key]
  714. }
  715. })
  716. return params
  717. }
  718. const isSearchExpanded = ref(false)
  719. /** 搜索按钮操作 */
  720. const handleQuery = async () => {
  721. loading.value = true
  722. try {
  723. // 关闭所有已展开的行
  724. // expandRows.value.forEach(row => {
  725. // tableRef.value?.toggleRowExpansion(row, false)
  726. // })
  727. // 清空展开行记录
  728. expandRowKeys.value = []
  729. expandRows.value = []
  730. selectedRows.value = []
  731. selectedDetailRows.value = []
  732. const params = getQueryParams()
  733. // 调用后端API获取数据
  734. const res = await EquipPipeSchedulingApi.getPlanSchedulingPipesList(params)
  735. console.log(res)
  736. list.value = res.list
  737. total.value = res.total
  738. } finally {
  739. loading.value = false
  740. }
  741. }
  742. /** 重置按钮操作 */
  743. const resetQuery = () => {
  744. queryFormRef.value.resetFields()
  745. // 重置页码
  746. queryParams.value.pageNo = 1
  747. // 清空选中的容器归类对象
  748. selectedTypeList.value = []
  749. // 重置日期值
  750. datePickerType.value = 'daterange'
  751. // daterange.value = [dayjs().startOf('year').format('YYYY-MM-DD HH:mm:ss'), dayjs().endOf('year').format('YYYY-MM-DD HH:mm:ss')]
  752. daterange.value = []
  753. month.value = ''
  754. // 重新查询
  755. handleQuery()
  756. }
  757. /** 处理子表选择变化 */
  758. const handleDetailSelectionChange = (selection: any[], mainRow: any) => {
  759. // 如果正在恢复选中状态,不更新缓存(避免展开时 toggleRowSelection 触发此函数覆盖缓存)
  760. if (isRestoringSelection.value) {
  761. return
  762. }
  763. const rowId = mainRow.id
  764. // 更新缓存中的选中状态(如果缓存不存在则创建)
  765. if (!expandedRowsCache.value.has(rowId)) {
  766. // 如果该行还未展开,初始化缓存
  767. expandedRowsCache.value.set(rowId, {
  768. pipes: mainRow.pipes || [],
  769. selectedIds: new Set<string>()
  770. })
  771. }
  772. const cache = expandedRowsCache.value.get(rowId)!
  773. cache.selectedIds = new Set(selection.map((item: any) => item.id))
  774. // 更新全局选中的子表数据
  775. selectedDetailRows.value = selectedDetailRows.value.filter((item) => item.equipPipeId !== mainRow.id)
  776. selectedDetailRows.value = [...selectedDetailRows.value.filter(row => !selection.some(sel => sel.id === row.id)), ...selection]
  777. // 如果子表有选中项,则自动选中主表行;否则取消选中主表行
  778. nextTick(() => {
  779. if (selection.length > 0) {
  780. // 子表有选中项,选中主表行
  781. mainTableRef.value.toggleRowSelection(mainRow, true);
  782. } else if (!selectedDetailRows.value.map(row => row.equipPipeId).includes(mainRow.id)) {
  783. const currentlySelected = selectedRows.value.some(row => row.id === mainRow.id);
  784. if (currentlySelected && selection.length === 0) {
  785. // 只有当主行当前是选中状态且子表没有任何选中项时才取消主行选择
  786. mainTableRef.value.toggleRowSelection(mainRow, false);
  787. }
  788. }
  789. });
  790. }
  791. /** 表格选择框变化 */
  792. const handleSelectionChange = async (selection: any[]) => {
  793. // 计算取消选择的行
  794. const deselectedRows = selectedRows.value.filter(
  795. item => !selection.includes(item)
  796. );
  797. selectedRows.value = selection
  798. // 清除取消选中行的子表选择(包括展开的和收起的)
  799. deselectedRows.forEach(item => {
  800. const rowId = item.id
  801. // 如果该行是展开状态,直接清除表格选中
  802. const tableRef = getDetailTableRef(rowId)
  803. if (tableRef) {
  804. tableRef.clearSelection()
  805. }
  806. // 无论是否展开,都清除缓存中的选中状态
  807. if (expandedRowsCache.value.has(rowId)) {
  808. expandedRowsCache.value.get(rowId)!.selectedIds.clear()
  809. }
  810. // 从全局选中的子表数据中移除
  811. selectedDetailRows.value = selectedDetailRows.value.filter(
  812. detailRow => detailRow.equipPipeId !== rowId
  813. )
  814. })
  815. }
  816. type scheduleType = 'regular' | 'year' | ''
  817. /** 处理单条记录排期 */
  818. const handleSingleSchedule = (row: PipeEquipmentVO, type: scheduleType) => {
  819. currentEquip.value = row
  820. currentCheckType.value = type
  821. planScheduleEquipPipeDialogRef.value?.open()
  822. }
  823. /** 处理批量排期 */
  824. const handleBatchSchedule = (type: scheduleType) => {
  825. if (selectedDetailRows.value.length === 0) {
  826. message.warning('请至少选择一条记录')
  827. return
  828. }
  829. currentCheckType.value = type
  830. planScheduleEquipPipeDialogRef.value?.open(type)
  831. }
  832. /** 处理批量修改 */
  833. const handleBatchEditFn = () => {
  834. if (selectedRows.value.length === 0) {
  835. message.warning('请至少选择一条记录')
  836. return
  837. }
  838. formRef.value.open(selectedRows.value, '1')
  839. }
  840. /** 约检单/任务单 跳转查询 */
  841. const handleToRoute = (routeName: 'AcceptOrder1' | 'TaskOrder') => {
  842. const { unitName } = unref(currentCheckData) || {}
  843. router.push({
  844. name: routeName,
  845. query: {
  846. unitName: unitName,
  847. filterCancel: '400'
  848. }
  849. })
  850. }
  851. /** 排期成功处理 */
  852. const handleScheduleSuccess = () => {
  853. selectedRows.value = []
  854. handleQuery()
  855. }
  856. /** 处理容器归类变化 */
  857. const handleTypeListChange = (values: string[]) => {
  858. selectedTypeList.value = containerTypeOptions.filter((dict) => values.includes(dict.value))
  859. }
  860. /** 处理表格排序 */
  861. const handleSortChange = ({ prop, order }) => {
  862. if (!prop || !order) {
  863. list.value.sort((a, b) => 0) // 重置排序
  864. return
  865. }
  866. list.value.sort((a, b) => {
  867. const aValue = a[prop] || ''
  868. const bValue = b[prop] || ''
  869. if (order === 'ascending') {
  870. return aValue > bValue ? 1 : -1
  871. } else {
  872. return aValue < bValue ? 1 : -1
  873. }
  874. })
  875. }
  876. /** 处理区域变化 */
  877. const handleAreaChange = (areas: number[]) => {
  878. // 获取移除的区域(通过比较之前的区域列表和现在的区域列表)
  879. const prevAreas = Array.from(areaStreetMap.value.keys())
  880. const removedAreas = prevAreas.filter((area) => !areas.includes(area))
  881. // 移除取消选择的区域下的街道
  882. if (removedAreas.length > 0) {
  883. const currentStreets = queryParams.value.equipStreet || []
  884. removedAreas.forEach((areaId) => {
  885. const streetsToRemove = areaStreetMap.value.get(areaId) || []
  886. // 从当前选中的街道中移除这些街道
  887. queryParams.value.equipStreet = currentStreets.filter(
  888. (streetId) => !streetsToRemove.includes(streetId)
  889. )
  890. // 从映射中移除该区域
  891. areaStreetMap.value.delete(areaId)
  892. })
  893. }
  894. }
  895. /** 处理区域清空 */
  896. const handleAreaClear = () => {
  897. // 清空所有选中的街道
  898. queryParams.value.equipStreet = []
  899. // 清空区域-街道映射
  900. areaStreetMap.value.clear()
  901. }
  902. /** 处理街道变化 */
  903. const handleStreetChange = (streets: number[], areaId: number) => {
  904. // 更新区域-街道映射
  905. if (queryParams.value.equipDistrict?.includes(areaId)) {
  906. areaStreetMap.value.set(areaId, streets)
  907. }
  908. }
  909. /** 处理街道清空 */
  910. const handleStreetClear = () => {
  911. // 清空所有区域下的街道选择
  912. areaStreetMap.value.clear()
  913. }
  914. /** 处理日期变化 */
  915. const handleDateChange = (val: [string, string] | null) => {
  916. daterange.value = val || []
  917. queryParams.value.nextDate = val
  918. ? [
  919. dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
  920. dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
  921. ]
  922. : []
  923. }
  924. /** 处理月份变化 */
  925. const handleMonthChange = (val: string | null) => {
  926. if (!val) {
  927. queryParams.value.nextDate = []
  928. return
  929. }
  930. const date = dayjs(val)
  931. queryParams.value.nextDate = [
  932. date.startOf('month').format('YYYY-MM-DD HH:mm:ss'),
  933. date.endOf('month').format('YYYY-MM-DD HH:mm:ss')
  934. ]
  935. month.value = val
  936. }
  937. // 已展开行
  938. const expandRowKeys = ref<string[]>([])
  939. const expandRows = ref([])
  940. const toggleExpand = async (row: any) => {
  941. const key = row.id
  942. // 第一次点击展开,第二次点击同一行收起
  943. if (expandRowKeys.value.includes(key)) {
  944. expandRowKeys.value = expandRowKeys.value.filter((key) => key !== key)
  945. expandRows.value = expandRows.value.filter((row) => row.id !== key)
  946. selectedDetailRows.value = selectedDetailRows.value.filter((row) => row.equipPipeId !== key)
  947. tableRef.value.toggleRowExpansion(row, false)
  948. tableRef.value.toggleRowSelection(row, false)
  949. } else {
  950. expandRowKeys.value.push(key)
  951. expandRows.value.push(row)
  952. tableRef.value.toggleRowExpansion(row, true)
  953. await EquipPipeSchedulingApi.getPipeEquipmentDetailListByPipeEquipmentId( key,{equipIds:[]}).then((res) => {
  954. let find = list.value.find(item => item.id === key);
  955. find.detailSaveReqVOS = res.list
  956. })
  957. }
  958. }
  959. // 监听日期类型变化
  960. watch(datePickerType, (newVal) => {
  961. // 清空日期相关字段
  962. // daterange.value = newVal === 'daterange' ? daterange.value = cloneDeep(currentDefaultYear) : []
  963. daterange.value = []
  964. month.value = ''
  965. queryParams.value.nextDate = []
  966. })
  967. const { getPlanNewData } = usePlanNewStore()
  968. const currentCheckData = ref<PlanNewPageVO>()
  969. onMounted(() => {
  970. const { id } = (router.currentRoute.value.query || {}) as unknown as { id: string }
  971. const data = getPlanNewData(id)
  972. if (data) {
  973. currentCheckData.value = data
  974. open(data)
  975. }
  976. })
  977. defineExpose({
  978. open
  979. })
  980. </script>
  981. <style lang="scss" scoped>
  982. .plan-section {
  983. padding: 16px;
  984. border: 1px solid #ebeef5;
  985. border-radius: 4px;
  986. .section-title {
  987. display: flex;
  988. padding: 8px 16px;
  989. margin: -16px -16px 16px;
  990. font-weight: bold;
  991. background-color: #f5f7fa;
  992. border-bottom: 1px solid #ebeef5;
  993. align-items: center;
  994. }
  995. }
  996. .schedule-link {
  997. color: var(--el-color-primary);
  998. &:hover {
  999. color: var(--el-color-primary-light-3);
  1000. }
  1001. }
  1002. :deep(.area-select) {
  1003. .area-cascader {
  1004. flex: 1;
  1005. }
  1006. }
  1007. :deep(.inner-table .el-checkbox__input.is-checked .el-checkbox__inner),
  1008. :deep(.inner-table .el-checkbox__input.is-indeterminate .el-checkbox__inner) {
  1009. background-color: #67c23a; // 绿色
  1010. border-color: #67c23a;
  1011. }
  1012. :deep(.selected-row){
  1013. background-color: #ecf5ff !important;
  1014. }
  1015. // 子表选中行特殊背景色(浅绿色)
  1016. :deep(.inner-table .selected-row) {
  1017. background-color: #e8f5e9 !important;
  1018. }
  1019. </style>