BoilerPlanScheduleEquipDialog.vue 33 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  1. <template>
  2. <el-dialog
  3. :title="title"
  4. v-model="dialogVisible"
  5. :width="'800px'"
  6. append-to-body
  7. destroy-on-close
  8. >
  9. <div class="flex flex-col">
  10. <div class="w-full">
  11. <el-form
  12. ref="formRef"
  13. :model="formData"
  14. :rules="formRules"
  15. label-width="120px"
  16. >
  17. <!-- 内部检验配置 -->
  18. <div v-if="!isBatch && checkType === 'in' || isBatch" class="plan-section">
  19. <div class="section-title">
  20. <span>内部检验</span>
  21. <span class="section-info">
  22. 选中设备数量: {{ inEquipCount }} &nbsp;&nbsp; 最近临期时间: {{ inLatestTime=='无'?inLatestTime:dayjs(inLatestTime).format('YYYY-MM-DD') }}
  23. </span>
  24. </div>
  25. <el-form-item label="内检日期" prop="inDate">
  26. <el-date-picker
  27. v-model="formData.inDate"
  28. type="date"
  29. placeholder="选择日期"
  30. value-format="YYYY-MM-DD"
  31. :disabled="formData.inNoSchedule"
  32. :disabled-date="(time) => time.getTime() < Date.now() - 8.64e7"
  33. class="!w-240px"
  34. />
  35. <el-checkbox
  36. v-if="isBatch"
  37. v-model="formData.inNoSchedule"
  38. class="ml-4"
  39. @change="handleInNoScheduleChange"
  40. :disabled="isBatch ? inEquipCount === 0 : checkType !== 'in'"
  41. >
  42. 无需安排
  43. </el-checkbox>
  44. <el-checkbox
  45. v-model="formData.inIsOrderConfirm"
  46. class="ml-4"
  47. :disabled="formData.inNoSchedule || nextInCheckCount === 0"
  48. >
  49. 由前台约检确认
  50. </el-checkbox>
  51. </el-form-item>
  52. <el-form-item label="检验员" prop="inTeamList">
  53. <div class="flex items-center gap-2" :class="{ 'opacity-50': formData.inNoSchedule }">
  54. <div v-if="inSelectedCheckers.length > 0" class="selected-checkers">
  55. <el-tag
  56. v-for="checker in inSelectedCheckers"
  57. :key="checker.memberId"
  58. class="mx-1"
  59. closable
  60. @close="removeInChecker(checker.memberId)"
  61. >
  62. <span v-if="checker.isLeader" class="leader-tag">组</span>
  63. {{ checker.member?.nickname }}
  64. </el-tag>
  65. </div>
  66. <el-button
  67. type="primary"
  68. link
  69. @click="openCheckerSelect('in')"
  70. :disabled="formData.inNoSchedule"
  71. >
  72. 选择检验员
  73. </el-button>
  74. </div>
  75. </el-form-item>
  76. </div>
  77. <!-- 外部检验配置 -->
  78. <div v-if="!isBatch && checkType === 'out' || isBatch" class="plan-section">
  79. <div class="section-title">
  80. <span>外部检验</span>
  81. <span class="section-info">
  82. 选中设备数量: {{ outEquipCount }} &nbsp;&nbsp; 最近临期时间: {{ outLatestTime=='无'?outLatestTime:dayjs(outLatestTime).format('YYYY-MM-DD') }}
  83. </span>
  84. </div>
  85. <el-form-item label="外检日期" prop="outDate">
  86. <el-date-picker
  87. v-model="formData.outDate"
  88. type="date"
  89. placeholder="选择日期"
  90. value-format="YYYY-MM-DD"
  91. :disabled="formData.outNoSchedule"
  92. :disabled-date="(time) => time.getTime() < Date.now() - 8.64e7"
  93. class="!w-240px"
  94. />
  95. <el-checkbox
  96. v-if="isBatch"
  97. v-model="formData.outNoSchedule"
  98. class="ml-4"
  99. @change="handleOutNoScheduleChange"
  100. :disabled="isBatch ? outEquipCount === 0 : checkType !== 'out'"
  101. >
  102. 无需安排
  103. </el-checkbox>
  104. <el-checkbox
  105. v-model="formData.outIsOrderConfirm"
  106. class="ml-4"
  107. :disabled="formData.outNoSchedule || nextOutCheckCount === 0"
  108. >
  109. 由前台约检确认
  110. </el-checkbox>
  111. </el-form-item>
  112. <el-form-item label="检验员" prop="outTeamList">
  113. <div class="flex items-center gap-2" :class="{ 'opacity-50': formData.outNoSchedule }">
  114. <div v-if="outSelectedCheckers.length > 0" class="selected-checkers">
  115. <el-tag
  116. v-for="checker in outSelectedCheckers"
  117. :key="checker.memberId"
  118. class="mx-1"
  119. closable
  120. @close="removeOutChecker(checker.memberId)"
  121. >
  122. <span v-if="checker.isLeader" class="leader-tag">组</span>
  123. {{ checker.member?.nickname }}
  124. </el-tag>
  125. </div>
  126. <el-button
  127. type="primary"
  128. link
  129. @click="openCheckerSelect('out')"
  130. :disabled="formData.outNoSchedule"
  131. >
  132. 选择检验员
  133. </el-button>
  134. </div>
  135. </el-form-item>
  136. </div>
  137. <!-- 耐压检验配置 -->
  138. <div v-if="!isBatch && checkType === 'pre' || isBatch" class="plan-section">
  139. <div class="section-title">
  140. <span>耐压检验</span>
  141. <span class="section-info">
  142. 选中设备数量: {{ preEquipCount }} &nbsp;&nbsp; 最近临期时间: {{ preLatestTime=='无'?preLatestTime:dayjs(preLatestTime).format('YYYY-MM-DD') }}
  143. </span>
  144. </div>
  145. <el-form-item label="耐压检验日期" prop="preDate">
  146. <el-date-picker
  147. v-model="formData.preDate"
  148. type="date"
  149. placeholder="选择日期"
  150. value-format="YYYY-MM-DD"
  151. :disabled="formData.preNoSchedule"
  152. :disabled-date="(time) => time.getTime() < Date.now() - 8.64e7"
  153. class="!w-240px"
  154. />
  155. <el-checkbox
  156. v-if="isBatch"
  157. v-model="formData.preNoSchedule"
  158. class="ml-4"
  159. @change="handlepreNoScheduleChange"
  160. :disabled="isBatch ? preEquipCount === 0 : checkType !== 'pre'"
  161. >
  162. 无需安排
  163. </el-checkbox>
  164. <el-checkbox
  165. v-model="formData.pressureIsOrderConfirm"
  166. class="ml-4"
  167. :disabled="formData.preNoSchedule || nextPreCheckCount === 0"
  168. >
  169. 由前台约检确认
  170. </el-checkbox>
  171. </el-form-item>
  172. <el-form-item label="检验员" prop="preTeamList">
  173. <div class="flex items-center gap-2" :class="{ 'opacity-50': formData.preNoSchedule }">
  174. <div v-if="preSelectedCheckers.length > 0" class="selected-checkers">
  175. <el-tag
  176. v-for="checker in preSelectedCheckers"
  177. :key="checker.memberId"
  178. class="mx-1"
  179. closable
  180. @close="removePreChecker(checker.memberId)"
  181. >
  182. <span v-if="checker.isLeader" class="leader-tag">组</span>
  183. {{ checker.member?.nickname }}
  184. </el-tag>
  185. </div>
  186. <el-button
  187. type="primary"
  188. link
  189. @click="openCheckerSelect('pre')"
  190. :disabled="formData.preNoSchedule"
  191. >
  192. 选择检验员
  193. </el-button>
  194. </div>
  195. </el-form-item>
  196. </div>
  197. <!-- 检验项目 -->
  198. <div
  199. class="checkItemContent"
  200. v-for="checkItem in checkItemList"
  201. v-show="showCheck(checkItem)"
  202. :key="checkItem.inspectionNature"
  203. >
  204. <div class="content-title">
  205. {{ checkItem.inspectionNatureName }}
  206. </div>
  207. <div class="inspection-grid">
  208. <template v-if="checkItem.itemList.length > 0">
  209. <!-- Grid 容器 -->
  210. <div
  211. class="grid-container"
  212. :style="{
  213. 'grid-template-columns':
  214. checkItem.itemList.length < 2
  215. ? `repeat(${checkItem.itemList.length}, 1fr)`
  216. : `repeat(2, 1fr)`
  217. }"
  218. >
  219. <!-- 循环渲染子项(模拟数据) -->
  220. <div class="grid-item" v-for="(item, index) in checkItem.itemList" :key="index">
  221. <el-checkbox
  222. v-model="item.use"
  223. :disabled="item.isMainProject === '1'"
  224. />
  225. <!-- @change="(val) => handleCheckItemSelectedChange(item, val)"-->
  226. <div style="display: flex; align-items: center; gap: 4px;">
  227. <span v-if="item.isMainProject === '1'" style="display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; background-color: #6cbcf5; color: #fff; font-size: 12px; border-radius: 2px; font-weight: bold;">主</span>
  228. <span>{{ item.name }}</span>
  229. </div>
  230. <span>
  231. (
  232. <el-button link type="primary" @click="() => {
  233. openFeeDialog(item.inspectionNature,item.templateId)
  234. }"
  235. >费用:{{ item.fee || '无' }}</el-button
  236. >
  237. <!-- >费用:{{ getCheckItemFeeType(item) }}</el-button-->
  238. )
  239. </span>
  240. </div>
  241. </div>
  242. </template>
  243. <el-empty style="height: 200px;" v-else :description="`暂无【${checkItem.inspectionNatureName}】检验项目`" />
  244. </div>
  245. </div>
  246. </el-form>
  247. </div>
  248. </div>
  249. <template #footer>
  250. <div class="flex justify-end">
  251. <el-button @click="handleCancel">取消</el-button>
  252. <el-button type="primary" :loading="submitLoading" @click="handleConfirm">确定</el-button>
  253. </div>
  254. </template>
  255. <!-- 费用计算弹窗 -->
  256. <el-dialog
  257. title="修改费用"
  258. v-model="showCalcCheckItemFeeDialog"
  259. width="500px"
  260. >
  261. <el-form>
  262. <div class="form-row">
  263. <span class="info-label ml-50px">原费用:</span>
  264. <span class="info-value fee-amount">{{ oldAmount || 0 }}</span>
  265. </div>
  266. <el-form-item label="新费用:" class="fee-form-item ml-50px">
  267. <el-input-number
  268. v-model="newAmount"
  269. class="!w-100px"
  270. :min="0"
  271. controls-position="right"
  272. />
  273. </el-form-item>
  274. </el-form>
  275. <template #footer>
  276. <div class="flex justify-end">
  277. <el-button @click="showCalcCheckItemFeeDialog = false">取消</el-button>
  278. <el-button type="primary" @click="confirmFee">确定
  279. </el-button>
  280. </div>
  281. </template>
  282. </el-dialog>
  283. <!-- 检验员选择弹窗 -->
  284. <el-dialog
  285. v-model="checkerSelectVisible"
  286. :title="checkerSelectTitle"
  287. append-to-body
  288. width="600px"
  289. >
  290. <CheckerSelect
  291. v-model="currentSelectedCheckers"
  292. @change="handleCheckerChange"
  293. :max="100"
  294. />
  295. <template #footer>
  296. <div class="flex justify-end">
  297. <el-button @click="checkerSelectVisible = false">取消</el-button>
  298. <el-button type="primary" @click="confirmCheckerSelect">确定</el-button>
  299. </div>
  300. </template>
  301. </el-dialog>
  302. </el-dialog>
  303. </template>
  304. <script setup lang="ts">
  305. import { FormInstance } from 'element-plus'
  306. import { useMessage } from '@/hooks/web/useMessage'
  307. import CheckerSelect from '@/views/pressure2/components/CheckerSelect/index.vue'
  308. import type { CheckerItem } from '@/views/pressure2/components/CheckerSelect'
  309. import {BoilerPlanSchedulingEquipEditVO,EquipBoilerSchedulingApi} from "@/api/pressure2/equipboilerscheduling";
  310. import dayjs from "dayjs";
  311. import {PressureBoilerCheckType, PressureBoilerCheckTypeMap} from "@/utils/constants";
  312. import {EquipBoilerApi} from "@/api/pressure2/equipboiler";
  313. import {BoilerTaskOrderApi} from "@/api/pressure2/boilertaskorder";
  314. const message = useMessage()
  315. const props = defineProps({
  316. equipInfo: {
  317. type: Object as PropType<BoilerPlanSchedulingEquipEditVO>,
  318. required: false
  319. },
  320. equipList: {
  321. type: Array as PropType<BoilerPlanSchedulingEquipEditVO[]>,
  322. default: () => []
  323. },
  324. checkType: {
  325. type: String as PropType<'in' | 'out' | 'pre' | ''>,
  326. default: ''
  327. },
  328. isBatch: {
  329. type: Boolean,
  330. default: false
  331. }
  332. })
  333. const {querySchedulingCheckItemList } = BoilerTaskOrderApi
  334. const emit = defineEmits(['success', 'close'])
  335. const dialogVisible = ref(false)
  336. const formRef = ref<FormInstance>()
  337. const submitLoading = ref(false) // 提交按钮 loading
  338. const title = computed(() => props.isBatch ? '批量计划排期' : '计划排期')
  339. // 各类型检验员选择状态
  340. const inSelectedCheckers = ref<any[]>([])
  341. const outSelectedCheckers = ref<any[]>([])
  342. const preSelectedCheckers = ref<any[]>([])
  343. // 检验员选择弹窗相关
  344. const checkerSelectVisible = ref(false)
  345. const currentSelectType = ref('') // 当前选择的检验类型: in/out/pre
  346. const currentSelectedCheckers = ref<CheckerItem[]>([])
  347. const tempSelectedCheckers = ref<CheckerItem[]>([]) // 临时存储选择的检验员
  348. // 检验项目
  349. const checkItemList = ref<any[]>([])
  350. const showCalcCheckItemFeeDialog = ref(false)
  351. const oldAmount = ref<number>(0)
  352. const newAmount = ref<number>(0)
  353. const checkerSelectTitle = computed(() => {
  354. const typeMap = {
  355. in: '内部检验',
  356. out: '外部检验',
  357. pre: '耐压检验'
  358. }
  359. return `选择${typeMap[currentSelectType.value]}检验员`
  360. })
  361. const formData = ref({
  362. // 内部检验
  363. inDate: '',
  364. inNoSchedule: false,
  365. inTeamList: [],
  366. inIsOrderConfirm: true,
  367. // 外部检验
  368. outDate: '',
  369. outNoSchedule: false,
  370. outTeamList: [],
  371. outIsOrderConfirm: true,
  372. // 耐压检验
  373. preDate: '',
  374. preNoSchedule: false,
  375. preTeamList: [],
  376. pressureIsOrderConfirm: true
  377. })
  378. // 表单验证规则
  379. const formRules = {
  380. inDate: [{
  381. validator: (rule, value, callback) => {
  382. if (!formData.value.inNoSchedule && !value) {
  383. callback(new Error('请选择内检日期'))
  384. } else {
  385. callback()
  386. }
  387. },
  388. trigger: 'change'
  389. }],
  390. inTeamList: [{
  391. validator: (rule, value, callback) => {
  392. if (!formData.value.inNoSchedule && (!value || value.length === 0)) {
  393. callback(new Error('请选择检验员'))
  394. } else {
  395. callback()
  396. }
  397. },
  398. trigger: 'change'
  399. }],
  400. outDate: [{
  401. validator: (rule, value, callback) => {
  402. if (!formData.value.outNoSchedule && !value) {
  403. callback(new Error('请选择外检日期'))
  404. } else {
  405. callback()
  406. }
  407. },
  408. trigger: 'change'
  409. }],
  410. outTeamList: [{
  411. validator: (rule, value, callback) => {
  412. if (!formData.value.outNoSchedule && (!value || value.length === 0)) {
  413. callback(new Error('请选择检验员'))
  414. } else {
  415. callback()
  416. }
  417. },
  418. trigger: 'change'
  419. }],
  420. preDate: [{
  421. validator: (rule, value, callback) => {
  422. if (!formData.value.preNoSchedule && !value) {
  423. callback(new Error('请选择耐压检验日期'))
  424. } else {
  425. callback()
  426. }
  427. },
  428. trigger: 'change'
  429. }],
  430. preTeamList: [{
  431. validator: (rule, value, callback) => {
  432. if (!formData.value.preNoSchedule && (!value || value.length === 0)) {
  433. callback(new Error('请选择检验员'))
  434. } else {
  435. callback()
  436. }
  437. },
  438. trigger: 'change'
  439. }]
  440. }
  441. // 计算内部检验设备数量和最近临检时间
  442. const inEquipCount = computed(() => {
  443. if (!props.isBatch) return props.equipInfo?.nextInCheckDate ? 1 : 0
  444. return props.equipList.filter(item => item.nextInCheckDate).length
  445. })
  446. const inLatestTime = computed(() => {
  447. if (!props.isBatch) return props.equipInfo?.nextInCheckDate || '无'
  448. const dates = props.equipList
  449. .map(item => item.nextInCheckDate)
  450. .filter(date => date)
  451. .sort((a, b) => {
  452. const dateA = new Date(a)
  453. const dateB = new Date(b)
  454. return dateA.getTime() - dateB.getTime()
  455. })
  456. return dates.length > 0 ? dayjs(dates[0]).format('YYYY-MM-DD') : '无'
  457. })
  458. // 计算外部检验设备数量和最近临检时间
  459. const outEquipCount = computed(() => {
  460. if (!props.isBatch) return props.equipInfo?.nextOutCheckDate ? 1 : 0
  461. return props.equipList.filter(item => item.nextOutCheckDate).length
  462. })
  463. const outLatestTime = computed(() => {
  464. if (!props.isBatch) return props.equipInfo?.nextOutCheckDate || '无'
  465. const dates = props.equipList
  466. .map(item => item.nextOutCheckDate)
  467. .filter(date => date)
  468. .sort((a, b) => {
  469. const dateA = new Date(a)
  470. const dateB = new Date(b)
  471. return dateA.getTime() - dateB.getTime()
  472. })
  473. return dates.length > 0 ? dayjs(dates[0]).format('YYYY-MM-DD') : '无'
  474. })
  475. // 计算耐压检验设备数量和最近临检时间
  476. const preEquipCount = computed(() => {
  477. if (!props.isBatch) return props.equipInfo?.nextPressureCheckDate ? 1 : 0
  478. return props.equipList.filter(item => item.nextPressureCheckDate).length
  479. })
  480. const preLatestTime = computed(() => {
  481. if (!props.isBatch) return props.equipInfo?.nextPressureCheckDate || '无'
  482. const dates = props.equipList
  483. .map(item => item.nextPressureCheckDate)
  484. .filter(date => date)
  485. .sort((a, b) => {
  486. const dateA = new Date(a)
  487. const dateB = new Date(b)
  488. return dateA.getTime() - dateB.getTime()
  489. })
  490. return dates.length > 0 ? dayjs(dates[0]).format('YYYY-MM-DD') : '无'
  491. })
  492. /** 打开检验员选择弹窗 */
  493. const openCheckerSelect = (type) => {
  494. currentSelectType.value = type
  495. checkerSelectVisible.value = true
  496. // 根据类型设置当前选中的检验员
  497. const checkerMap = {
  498. in: inSelectedCheckers,
  499. out: outSelectedCheckers,
  500. pre: preSelectedCheckers
  501. }
  502. tempSelectedCheckers.value = [...checkerMap[type].value]
  503. currentSelectedCheckers.value = [...tempSelectedCheckers.value]
  504. }
  505. /** 处理检验员变化 */
  506. const handleCheckerChange = (checkers) => {
  507. tempSelectedCheckers.value = checkers
  508. }
  509. const feeInspectionNature = ref()
  510. const feeTemplateId = ref()
  511. const openFeeDialog = (inspectionNature,templateId) =>{
  512. showCalcCheckItemFeeDialog.value = true
  513. let item = checkItemList.value.find(item => item.inspectionNature == inspectionNature).itemList.find(i => i.templateId == templateId)
  514. feeInspectionNature.value = inspectionNature
  515. feeTemplateId.value = templateId
  516. oldAmount.value = item.fee || 0
  517. newAmount.value = item.fee || 0
  518. }
  519. const confirmFee = () => {
  520. let item = checkItemList.value.find(item => item.inspectionNature == feeInspectionNature.value).itemList.find(i => i.templateId == feeTemplateId.value)
  521. item.fee = newAmount.value;
  522. newAmount.value = 0
  523. showCalcCheckItemFeeDialog.value = false;
  524. }
  525. /**
  526. * 根据无需安排展示项目
  527. * @param checkItem
  528. */
  529. const showCheck = (checkItem) =>{
  530. if (checkItem.inspectionNature == PressureBoilerCheckType.IN){
  531. return !formData.value.inNoSchedule
  532. }
  533. if (checkItem.inspectionNature == PressureBoilerCheckType.OUT){
  534. return !formData.value.outNoSchedule
  535. }
  536. if (checkItem.inspectionNature == PressureBoilerCheckType.PRESSURE){
  537. return !formData.value.preNoSchedule
  538. }
  539. return true
  540. }
  541. // 查询项目列表
  542. const handleQueryCheckItemList = async () => {
  543. const req = []
  544. if (props.equipList.length > 0){
  545. for (const item of props.equipList) {
  546. req.push(item.id)
  547. }
  548. }else {
  549. req.push(props.equipInfo.id)
  550. }
  551. const equipIds = await EquipBoilerApi.getNameByIds(req)
  552. checkItemList.value = []
  553. const checkTypes = [
  554. PressureBoilerCheckType.IN,
  555. PressureBoilerCheckType.OUT,
  556. PressureBoilerCheckType.PRESSURE
  557. ];
  558. for (const type of checkTypes) {
  559. const params = {
  560. orderId: "0",
  561. itemIds: equipIds.map(item => item.id),
  562. equipmentCategory: 300,
  563. inspectionNature: [type]
  564. };
  565. const queryResult = await querySchedulingCheckItemList(params);
  566. checkItemList.value.push({
  567. inspectionNatureName: PressureBoilerCheckTypeMap[type],
  568. inspectionNature: type,
  569. itemList: (queryResult || []).map((item) => (
  570. {
  571. ...item,
  572. isAutoAmount: '0', // 批量模式下默认为'0'
  573. inspectionNature: type,
  574. use: item.use
  575. }
  576. ))
  577. });
  578. }
  579. }
  580. /** 监听日期变化 */
  581. watch(() => formData.value.inDate, (newDate) => {
  582. if (newDate && props.isBatch) {
  583. // 只有在未设置且未勾选"无需安排"的情况下才自动填充
  584. if (!formData.value.outDate && !formData.value.outNoSchedule) {
  585. formData.value.outDate = newDate
  586. }
  587. if (!formData.value.preDate && !formData.value.preNoSchedule) {
  588. formData.value.preDate = newDate
  589. }
  590. }
  591. })
  592. watch(() => formData.value.outDate, (newDate) => {
  593. if (newDate && props.isBatch) {
  594. if (!formData.value.inDate && !formData.value.inNoSchedule) {
  595. formData.value.inDate = newDate
  596. }
  597. if (!formData.value.preDate && !formData.value.preNoSchedule) {
  598. formData.value.preDate = newDate
  599. }
  600. }
  601. })
  602. watch(() => formData.value.preDate, (newDate) => {
  603. if (newDate && props.isBatch) {
  604. if (!formData.value.inDate && !formData.value.inNoSchedule) {
  605. formData.value.inDate = newDate
  606. }
  607. if (!formData.value.outDate && !formData.value.outNoSchedule) {
  608. formData.value.outDate = newDate
  609. }
  610. }
  611. })
  612. /** 监听检验员变化 */
  613. const syncCheckers = (sourceType: string, checkers: any[]) => {
  614. if (!props.isBatch) return
  615. const typeMap = {
  616. in: ['out', 'pre'],
  617. out: ['in', 'pre'],
  618. pre: ['in', 'out']
  619. }
  620. typeMap[sourceType].forEach(type => {
  621. const noScheduleKey = `${type}NoSchedule`
  622. const checkerMap = {
  623. in: inSelectedCheckers,
  624. out: outSelectedCheckers,
  625. pre: preSelectedCheckers
  626. }
  627. // 只有在未选择检验员且未勾选"无需安排"的情况下才自动同步
  628. if (!formData.value[noScheduleKey] && checkerMap[type].value.length === 0) {
  629. checkerMap[type].value = [...checkers]
  630. updateTeamList(type)
  631. }
  632. })
  633. }
  634. /** 修改 confirmCheckerSelect 方法 */
  635. const confirmCheckerSelect = () => {
  636. const checkerMap = {
  637. in: inSelectedCheckers,
  638. out: outSelectedCheckers,
  639. pre: preSelectedCheckers
  640. }
  641. // 更新对应类型的检验员列表
  642. checkerMap[currentSelectType.value].value = [...tempSelectedCheckers.value]
  643. // 同步到其他类型(仅在批量模式下)
  644. if (props.isBatch) {
  645. syncCheckers(currentSelectType.value, tempSelectedCheckers.value)
  646. }
  647. // 更新 formData 中的 teamList
  648. const teamListMap = {
  649. in: 'inTeamList',
  650. out: 'outTeamList',
  651. pre: 'preTeamList'
  652. }
  653. // 按 groupTeamId 分组
  654. const groupedTeams = tempSelectedCheckers.value.reduce((acc, checker) => {
  655. const { groupTeamId } = checker
  656. if (!acc[groupTeamId]) {
  657. acc[groupTeamId] = {
  658. groupTeamId,
  659. leaderId: '',
  660. memberIdList: []
  661. }
  662. }
  663. if (checker.isLeader) {
  664. acc[groupTeamId].leaderId = checker.memberId
  665. } else {
  666. acc[groupTeamId].memberIdList.push(checker.memberId)
  667. }
  668. return acc
  669. }, {})
  670. formData.value[teamListMap[currentSelectType.value]] = Object.values(groupedTeams)
  671. checkerSelectVisible.value = false
  672. }
  673. /** 移除检验员 */
  674. const removeInChecker = (memberId) => {
  675. inSelectedCheckers.value = inSelectedCheckers.value.filter(c => c.memberId !== memberId)
  676. updateTeamList('in')
  677. }
  678. const removeOutChecker = (memberId) => {
  679. outSelectedCheckers.value = outSelectedCheckers.value.filter(c => c.memberId !== memberId)
  680. updateTeamList('out')
  681. }
  682. const removePreChecker = (memberId) => {
  683. preSelectedCheckers.value = preSelectedCheckers.value.filter(c => c.memberId !== memberId)
  684. updateTeamList('pre')
  685. }
  686. /** 更新 teamList */
  687. const updateTeamList = (type) => {
  688. const checkerMap = {
  689. in: inSelectedCheckers,
  690. out: outSelectedCheckers,
  691. pre: preSelectedCheckers
  692. }
  693. const teamListMap = {
  694. in: 'inTeamList',
  695. out: 'outTeamList',
  696. pre: 'preTeamList'
  697. }
  698. const groupedTeams = checkerMap[type].value.reduce((acc, checker) => {
  699. const { groupTeamId } = checker
  700. if (!acc[groupTeamId]) {
  701. acc[groupTeamId] = {
  702. groupTeamId,
  703. leaderId: '',
  704. memberIdList: []
  705. }
  706. }
  707. if (checker.isLeader) {
  708. acc[groupTeamId].leaderId = checker.memberId
  709. } else {
  710. acc[groupTeamId].memberIdList.push(checker.memberId)
  711. }
  712. return acc
  713. }, {})
  714. formData.value[teamListMap[type]] = Object.values(groupedTeams)
  715. }
  716. /** 打开弹窗 */
  717. const open = () => {
  718. dialogVisible.value = true
  719. // 使用 nextTick 确保获取到最新的 props 值
  720. nextTick(() => {
  721. // 重置所有检验员选择
  722. inSelectedCheckers.value = []
  723. outSelectedCheckers.value = []
  724. preSelectedCheckers.value = []
  725. handleQueryCheckItemList()
  726. // 重置表单数据
  727. formData.value = {
  728. inDate: '',
  729. inNoSchedule: false,
  730. inTeamList: [],
  731. inIsOrderConfirm: true,
  732. outDate: '',
  733. outNoSchedule: false,
  734. outTeamList: [],
  735. outIsOrderConfirm: true,
  736. preDate: '',
  737. preNoSchedule: false,
  738. preTeamList: [],
  739. pressureIsOrderConfirm: true
  740. }
  741. // 根据实际情况设置无需安排的状态
  742. if (props.isBatch) {
  743. formData.value.inNoSchedule = inEquipCount.value === 0
  744. formData.value.outNoSchedule = outEquipCount.value === 0
  745. formData.value.preNoSchedule = preEquipCount.value === 0
  746. } else {
  747. // 单设备模式下,根据检验类型来判断
  748. formData.value.inNoSchedule = props.checkType !== 'in'
  749. formData.value.outNoSchedule = props.checkType !== 'out'
  750. formData.value.preNoSchedule = props.checkType !== 'pre'
  751. }
  752. })
  753. }
  754. /** 取消操作 */
  755. const handleCancel = () => {
  756. dialogVisible.value = false
  757. emit('close')
  758. }
  759. /** 确认操作 */
  760. const handleConfirm = async () => {
  761. const form = unref(formRef)
  762. if (!form) return
  763. submitLoading.value = true
  764. try {
  765. await form.validate()
  766. // 组装新的数据结构
  767. const submitData: { taskList: any[] } = {
  768. taskList: []
  769. }
  770. // 获取设备ID列表
  771. const inEquipIds = props.isBatch
  772. ? props.equipList.filter(item => item.nextInCheckDate).map(item => item.id).join(',')
  773. : props.equipInfo?.id
  774. const outEquipIds = props.isBatch
  775. ? props.equipList.filter(item => item.nextOutCheckDate).map(item => item.id).join(',')
  776. : props.equipInfo?.id
  777. const preEquipIds = props.isBatch
  778. ? props.equipList.filter(item => item.nextPressureCheckDate).map(item => item.id).join(',')
  779. : props.equipInfo?.id
  780. // 处理定期检验
  781. if (!formData.value.inNoSchedule && formData.value.inDate && inEquipIds) {
  782. let actualAmount = 0
  783. checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.IN).itemList.filter(i => i.use).forEach(i => {
  784. actualAmount += i.fee
  785. })
  786. submitData.taskList.push({
  787. equipIds: inEquipIds,
  788. type: '100',
  789. hasOrderConfirm: formData.value.inIsOrderConfirm,
  790. date: formData.value.inDate,
  791. teamList: formData.value.inTeamList,
  792. actualAmount,
  793. checkItemList: checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.IN).itemList.filter(i => i.use).map(
  794. i => {
  795. return {
  796. //reportId: i.templateId,
  797. reportId: i.connectId,
  798. newAmount: i.fee
  799. }
  800. }
  801. )
  802. })
  803. }
  804. // 处理年度检查
  805. if (!formData.value.outNoSchedule && formData.value.outDate && outEquipIds) {
  806. let actualAmount = 0
  807. checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.OUT).itemList.filter(i => i.use).forEach(i => {
  808. actualAmount += i.fee
  809. })
  810. submitData.taskList.push({
  811. equipIds: outEquipIds,
  812. type: '200',
  813. hasOrderConfirm: formData.value.outIsOrderConfirm,
  814. date: formData.value.outDate,
  815. teamList: formData.value.outTeamList,
  816. actualAmount,
  817. checkItemList: checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.OUT).itemList.filter(i => i.use).map(
  818. i => {
  819. return {
  820. //reportId: i.templateId,
  821. reportId: i.connectId,
  822. newAmount: i.fee
  823. }
  824. }
  825. )
  826. })
  827. }
  828. // 处理超年限检验
  829. if (!formData.value.preNoSchedule && formData.value.preDate && preEquipIds) {
  830. let actualAmount = 0
  831. checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.PRESSURE).itemList.filter(i => i.use).forEach(i => {
  832. actualAmount += i.fee
  833. })
  834. submitData.taskList.push({
  835. equipIds: preEquipIds,
  836. type: '300',
  837. hasOrderConfirm: formData.value.pressureIsOrderConfirm,
  838. date: formData.value.preDate,
  839. teamList: formData.value.preTeamList,
  840. actualAmount,
  841. checkItemList: checkItemList.value.find(i => i.inspectionNature == PressureBoilerCheckType.PRESSURE).itemList.filter(i => i.use).map(
  842. i => {
  843. return {
  844. //reportId: i.templateId,
  845. reportId: i.connectId,
  846. newAmount: i.fee
  847. }
  848. }
  849. )
  850. })
  851. }
  852. //console.log(submitData)
  853. await EquipBoilerSchedulingApi.planSchedulingAssign(submitData)
  854. message.success('保存成功')
  855. dialogVisible.value = false
  856. emit('success')
  857. } catch (error) {
  858. message.error('保存失败')
  859. } finally {
  860. submitLoading.value = false
  861. }
  862. }
  863. // 处理"无需安排"变化
  864. const handleInNoScheduleChange = (val: boolean) => {
  865. if (val) {
  866. formData.value.inDate = ''
  867. inSelectedCheckers.value = []
  868. formData.value.inTeamList = []
  869. }
  870. }
  871. const handleOutNoScheduleChange = (val: boolean) => {
  872. if (val) {
  873. formData.value.outDate = ''
  874. outSelectedCheckers.value = []
  875. formData.value.outTeamList = []
  876. }
  877. }
  878. const handlepreNoScheduleChange = (val: boolean) => {
  879. if (val) {
  880. formData.value.preDate = ''
  881. preSelectedCheckers.value = []
  882. formData.value.preTeamList = []
  883. }
  884. }
  885. // 向父组件暴露方法
  886. defineExpose({
  887. open
  888. })
  889. </script>
  890. <style lang="scss" scoped>
  891. .plan-section {
  892. margin-bottom: 24px;
  893. padding: 16px;
  894. border: 1px solid #EBEEF5;
  895. border-radius: 4px;
  896. .section-title {
  897. margin: -16px -16px 16px -16px;
  898. padding: 8px 16px;
  899. background-color: #F5F7FA;
  900. border-bottom: 1px solid #EBEEF5;
  901. font-weight: bold;
  902. display: flex;
  903. align-items: center;
  904. .section-info {
  905. font-weight: normal;
  906. font-size: 13px;
  907. color: #606266;
  908. margin-left: 26px;
  909. }
  910. }
  911. }
  912. .selected-checkers {
  913. display: inline-flex;
  914. flex-wrap: wrap;
  915. gap: 8px;
  916. .el-tag {
  917. margin: 0;
  918. &:hover {
  919. cursor: default;
  920. }
  921. }
  922. }
  923. .leader-tag {
  924. display: inline-block;
  925. width: 14px;
  926. height: 14px;
  927. line-height: 12px;
  928. text-align: center;
  929. border: 1px solid #4475d6;
  930. font-size: 10px;
  931. margin-right: 4px;
  932. color: #4475d6;
  933. }
  934. .content-title {
  935. display: flex;
  936. justify-content: flex-start;
  937. align-items: center;
  938. width: 100%;
  939. height: 36px;
  940. line-height: 36px;
  941. background-color: var(--el-color-primary-light-9);
  942. &::before {
  943. content: '';
  944. height: 70%;
  945. width: 4px;
  946. margin-right: 12px;
  947. background-color: var(--el-color-primary);
  948. }
  949. }
  950. /* 外层容器 */
  951. .inspection-grid {
  952. width: 100%;
  953. padding: 16px;
  954. }
  955. /* 标题行 */
  956. .grid-header {
  957. font-size: 16px;
  958. font-weight: bold;
  959. text-align: center;
  960. height: 38px;
  961. line-height: 38px;
  962. border: 1px solid var(--el-border-color-light);
  963. border-bottom: 0;
  964. background-color: var(--el-color-primary-light-9);
  965. }
  966. /* Grid 容器核心配置 */
  967. .grid-container {
  968. display: grid;
  969. /* 5列等宽,列间距 12px,行间距 16px */
  970. grid-template-columns: repeat(5, 1fr);
  971. align-items: center; /* 垂直居中 */
  972. grid-gap: 1px;
  973. background-color: var(--el-border-color-light);
  974. border: 1px solid var(--el-border-color-light);
  975. }
  976. /* 子项布局:复选框 + 文本 + 图标 */
  977. .grid-item {
  978. display: flex;
  979. align-items: center;
  980. padding-left: 20px;
  981. padding-right: 12px;
  982. height: 52px;
  983. background-color: #fff;
  984. .el-checkbox {
  985. margin-right: 6px;
  986. }
  987. }
  988. .form-row {
  989. display: flex;
  990. align-items: center;
  991. .info-label {
  992. font-weight: bold;
  993. text-align: right;
  994. margin-right: 10px;
  995. }
  996. .info-value {
  997. flex: 1;
  998. }
  999. }
  1000. .fee-amount {
  1001. font-weight: bold;
  1002. color: #e6a23c;
  1003. font-size: 16px;
  1004. }
  1005. .fee-form-item {
  1006. margin-top: 20px;
  1007. :deep(.el-form-item__label) {
  1008. font-weight: bold;
  1009. }
  1010. }
  1011. </style>