detail.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195
  1. <!--
  2. 危险作业票详情(pcView)
  3. 布局与字段顺序参考 EHS 子应用「危险作业票」动火作业查看态
  4. (kd-aiot-frontend-ehs/src/views/DangerousWorkList/add.vue,isView=true):
  5. 所属危险作业 / 作业类型 / 申请单位 / 申请人 / 作业地点
  6. / 作业开始时间 / 作业结束时间 / 作业票模板 / 作业票内容 / 备注
  7. 作业票内容按 EHS 内置作业票模板渲染(不同作业类型套用不同模板);
  8. 页面用 tab 区分「作业票信息」与「后续环节」(tab 栏在上方);
  9. 动火、受限空间、高处作业、吊装作业、临时用电作业五类作业共用本页面与 submit.vue,按作业类型分发到各自的组件:
  10. 动火作业 fireFlowNodes.js + components/fireTicketDetail.vue + components/flowNodeForm.vue
  11. 受限空间 confinedSpaceFlowNodes.js + components/confinedSpaceFlow.vue
  12. 高处作业 heightFlowNodes.js + components/heightFlow.vue
  13. 吊装作业 hoistingFlowNodes.js + components/hoistingFlow.vue
  14. 临时用电 electricityFlowNodes.js + components/electricityFlow.vue
  15. 「后续环节」tab 顶部为当前处理环节(可填写),下方为全部环节的查看内容;
  16. 环节填写值通过 getTableValue 提供给审批组件(submit.vue),
  17. 在审批通过前合并写入作业票内容(extensionJson)
  18. -->
  19. <template>
  20. <div class="ticket-detail" v-loading="loading">
  21. <!-- 概要信息 -->
  22. <div class="detail-summary">
  23. <span class="summary-title">
  24. <i class="el-icon-document" />
  25. {{ templateName || '作业票信息' }}
  26. <el-tag v-if="statusText" :type="statusTag" size="mini">
  27. {{ statusText }}
  28. </el-tag>
  29. </span>
  30. <div class="summary-extra">
  31. <span v-if="ticketCode">作业票编号:{{ ticketCode }}</span>
  32. <span v-if="ticket.createTime">申请时间:{{ ticket.createTime }}</span>
  33. <span v-if="ticket.processInstanceId">
  34. 流程实例:{{ ticket.processInstanceId }}
  35. </span>
  36. </div>
  37. </div>
  38. <el-tabs v-model="activeTab" type="card" class="detail-tabs">
  39. <!-- 作业票信息 -->
  40. <el-tab-pane label="作业票信息" name="info">
  41. <el-form
  42. ref="formRef"
  43. :model="form"
  44. label-width="120px"
  45. size="small"
  46. disabled
  47. class="el-form-box"
  48. >
  49. <el-row :gutter="20">
  50. <el-col :span="12">
  51. <el-form-item label="所属危险作业:">
  52. <el-input :value="workName" disabled placeholder="-" />
  53. </el-form-item>
  54. </el-col>
  55. <el-col :span="12">
  56. <el-form-item label="作业类型:">
  57. <el-select
  58. :value="ticket.workType"
  59. placeholder="-"
  60. style="width: 100%"
  61. >
  62. <el-option
  63. v-for="item in workTypeOptions"
  64. :key="item.value"
  65. :label="item.label"
  66. :value="item.value"
  67. />
  68. </el-select>
  69. </el-form-item>
  70. </el-col>
  71. </el-row>
  72. <el-row :gutter="20">
  73. <el-col :span="12">
  74. <el-form-item label="申请单位:">
  75. <el-input
  76. :value="ticket.applyDeptName"
  77. disabled
  78. placeholder="-"
  79. />
  80. </el-form-item>
  81. </el-col>
  82. <el-col :span="12">
  83. <el-form-item label="申请人:">
  84. <el-input
  85. :value="ticket.applicantName"
  86. disabled
  87. placeholder="-"
  88. />
  89. </el-form-item>
  90. </el-col>
  91. </el-row>
  92. <el-row :gutter="20">
  93. <el-col :span="12">
  94. <el-form-item label="作业地点:">
  95. <el-input
  96. :value="ticket.workLocation"
  97. disabled
  98. placeholder="-"
  99. />
  100. </el-form-item>
  101. </el-col>
  102. <el-col :span="12">
  103. <el-form-item label="作业开始时间:">
  104. <el-input :value="ticket.startTime" disabled placeholder="-" />
  105. </el-form-item>
  106. </el-col>
  107. </el-row>
  108. <el-row :gutter="20">
  109. <el-col :span="12">
  110. <el-form-item label="作业结束时间:">
  111. <el-input :value="ticket.endTime" disabled placeholder="-" />
  112. </el-form-item>
  113. </el-col>
  114. </el-row>
  115. <el-form-item label="作业票模板:">
  116. <el-input :value="templateName" disabled placeholder="-" />
  117. </el-form-item>
  118. <el-form-item label="作业票内容:">
  119. <ticket-content-view
  120. v-if="template"
  121. :template="template"
  122. :extension="extension"
  123. />
  124. <div v-else class="empty-text">
  125. {{
  126. ticket.workType
  127. ? '未匹配到该作业类型的作业票模板,展示已保存的原始内容'
  128. : '暂无作业票内容'
  129. }}
  130. </div>
  131. <!-- 未匹配到模板时的降级展示:按 key-value 列出原始内容 -->
  132. <div v-if="!template && hasExtension" class="raw-extension">
  133. <div v-for="(value, key) in extension" :key="key" class="raw-row">
  134. <span class="raw-key">{{ key }}</span>
  135. <span class="raw-value">{{ value }}</span>
  136. </div>
  137. </div>
  138. </el-form-item>
  139. <el-form-item label="备注:">
  140. <el-input
  141. :value="remark"
  142. type="textarea"
  143. :rows="3"
  144. disabled
  145. placeholder="-"
  146. />
  147. </el-form-item>
  148. </el-form>
  149. </el-tab-pane>
  150. <!-- 作业类型专项内容(先适配动火作业票) -->
  151. <el-tab-pane
  152. v-if="isFireWork || currentFireNode"
  153. label="后续环节"
  154. name="flow"
  155. >
  156. <!-- 当前处理环节:可填写 -->
  157. <div v-if="currentFireNode" class="current-node-box">
  158. <flow-node-form
  159. ref="nodeForm"
  160. :node="currentFireNode"
  161. :value="nodeForm"
  162. @input="handleNodeInput"
  163. />
  164. </div>
  165. <div v-else class="node-tip">
  166. 未识别到当前处理环节,以下为全部环节的查看内容
  167. </div>
  168. <!-- 全部环节(查看态,当前环节已在上方单独展示,这里排除避免重复) -->
  169. <fire-ticket-detail
  170. :ticket="ticket"
  171. :extension="extension"
  172. :variables="variables"
  173. :current-node-key="currentFireNode ? currentFireNode.key : ''"
  174. :exclude-node-keys="currentFireNode ? [currentFireNode.key] : []"
  175. />
  176. </el-tab-pane>
  177. <!-- 作业类型专项内容(受限空间作业票,独立组件实现) -->
  178. <el-tab-pane
  179. v-if="isConfinedSpaceWork || currentConfinedNode"
  180. label="后续环节"
  181. name="flowConfined"
  182. >
  183. <confined-space-flow
  184. ref="confinedFlow"
  185. :node="currentConfinedNode"
  186. :nodes="confinedFlowNodes"
  187. :ticket="ticket"
  188. :extension="extension"
  189. :variables="variables"
  190. />
  191. </el-tab-pane>
  192. <!-- 作业类型专项内容(高处作业票,独立组件实现) -->
  193. <el-tab-pane
  194. v-if="isHeightWork || currentHeightNode"
  195. label="后续环节"
  196. name="flowHeight"
  197. >
  198. <height-flow
  199. ref="heightFlow"
  200. :node="currentHeightNode"
  201. :nodes="heightFlowNodes"
  202. :ticket="ticket"
  203. :extension="extension"
  204. :variables="variables"
  205. :task-label="currentTaskName || taskDefinitionKey"
  206. />
  207. </el-tab-pane>
  208. <!-- 作业类型专项内容(吊装作业票,独立组件实现) -->
  209. <el-tab-pane
  210. v-if="isHoistingWork || currentHoistingNode"
  211. label="后续环节"
  212. name="flowHoisting"
  213. >
  214. <hoisting-flow
  215. ref="hoistingFlow"
  216. :node="currentHoistingNode"
  217. :nodes="hoistingFlowNodes"
  218. :ticket="ticket"
  219. :extension="extension"
  220. :variables="variables"
  221. :task-label="currentTaskName || taskDefinitionKey"
  222. />
  223. </el-tab-pane>
  224. <!-- 作业类型专项内容(临时用电作业票,独立组件实现) -->
  225. <el-tab-pane
  226. v-if="isElectricityWork || currentElectricityNode"
  227. label="后续环节"
  228. name="flowElectricity"
  229. >
  230. <electricity-flow
  231. ref="electricityFlow"
  232. :node="currentElectricityNode"
  233. :nodes="electricityFlowNodes"
  234. :ticket="ticket"
  235. :extension="extension"
  236. :variables="variables"
  237. :task-label="currentTaskName || taskDefinitionKey"
  238. />
  239. </el-tab-pane>
  240. </el-tabs>
  241. </div>
  242. </template>
  243. <script>
  244. import {
  245. ticketGetById,
  246. workGetById,
  247. ticketUpdate
  248. } from '@/api/bpm/components/dangerousWork';
  249. import { getDictByCode } from '@/api/sys';
  250. import { getWorkTicketTemplate } from './templates';
  251. import { resolveWorkTypeLabel, BUILT_IN_WORK_TYPES } from './workTypes';
  252. import { matchFireFlowNode } from './fireFlowNodes';
  253. import {
  254. getCurrentTaskInfo,
  255. loadProcessFormVariables,
  256. loadNodeValues,
  257. buildNodeVariables,
  258. buildNodePayload,
  259. mergeExtension,
  260. buildTicketUpdateParams,
  261. parseJsonObject
  262. } from './fireFlowTask';
  263. // 受限空间作业:独立实现,动火相关文件不做任何改动
  264. import {
  265. matchConfinedSpaceFlowNode,
  266. CONFINED_SPACE_FLOW_NODES
  267. } from './confinedSpaceFlowNodes';
  268. // 高处作业:独立实现,动火 / 受限空间相关文件不做任何改动
  269. import { matchHeightFlowNode, HEIGHT_FLOW_NODES } from './heightFlowNodes';
  270. // 吊装作业:独立实现,动火 / 受限空间 / 高处作业相关文件不做任何改动
  271. import {
  272. matchHoistingFlowNode,
  273. HOISTING_FLOW_NODES
  274. } from './hoistingFlowNodes';
  275. // 临时用电作业:独立实现,其它作业类型相关文件不做任何改动
  276. import {
  277. matchElectricityFlowNode,
  278. ELECTRICITY_FLOW_NODES
  279. } from './electricityFlowNodes';
  280. import TicketContentView from './components/ticketContentView.vue';
  281. import FireTicketDetail from './components/fireTicketDetail.vue';
  282. import FlowNodeForm from './components/flowNodeForm.vue';
  283. import ConfinedSpaceFlow from './components/confinedSpaceFlow.vue';
  284. import HeightFlow from './components/heightFlow.vue';
  285. import HoistingFlow from './components/hoistingFlow.vue';
  286. import ElectricityFlow from './components/electricityFlow.vue';
  287. // 作业票状态:1-待提交、2-审批中、3-已通过、4-已驳回
  288. const TICKET_STATUS = {
  289. 1: '待提交',
  290. 2: '审批中',
  291. 3: '已通过',
  292. 4: '已驳回'
  293. };
  294. // 作业票编号可能落在扩展数据的不同 key 上
  295. const TICKET_CODE_KEYS = ['permit_no', 'ticket_no', 'apply_no', 'code'];
  296. export default {
  297. name: 'TestDetail',
  298. components: {
  299. TicketContentView,
  300. FireTicketDetail,
  301. FlowNodeForm,
  302. ConfinedSpaceFlow,
  303. HeightFlow,
  304. HoistingFlow,
  305. ElectricityFlow
  306. },
  307. props: {
  308. // 作业票 id
  309. businessId: {
  310. default: ''
  311. },
  312. businessCode: {
  313. default: ''
  314. },
  315. taskId: {
  316. default: ''
  317. },
  318. id: {
  319. default: ''
  320. },
  321. taskDefinitionKey: {
  322. default: ''
  323. },
  324. businessType: {
  325. default: ''
  326. },
  327. fromUser: {
  328. default: ''
  329. },
  330. createTime: {
  331. default: ''
  332. }
  333. },
  334. data() {
  335. return {
  336. loading: false,
  337. // 当前 tab:info-作业票信息、flow-后续环节
  338. activeTab: 'info',
  339. // 作业票
  340. ticket: {},
  341. // 所属危险作业
  342. work: {},
  343. // 作业票内容(extensionJson 解析结果)
  344. extension: {},
  345. // 流程变量(流程实例 formVariables,后续环节数据来源)
  346. variables: {},
  347. // 匹配到的作业票模板
  348. template: null,
  349. // 作业类型字典(ehs_hazardous_work_type)
  350. workTypeDict: [],
  351. // el-form 的 model(查看态仅用于结构占位)
  352. form: {},
  353. // 当前待办任务的名称(任务定义 key 之外的第二重匹配依据)
  354. currentTaskName: '',
  355. // 当前环节的填写值
  356. nodeForm: {}
  357. };
  358. },
  359. computed: {
  360. // 作业类型名称
  361. workTypeName() {
  362. return (
  363. resolveWorkTypeLabel(this.ticket.workType, this.workTypeDict) || '-'
  364. );
  365. },
  366. // 作业类型下拉:优先后端字典,字典未配置时用内置作业类型兜底
  367. workTypeOptions() {
  368. if (this.workTypeDict.length) {
  369. return this.workTypeDict.map((item) => ({
  370. value: item.dictCode,
  371. label: item.dictValue
  372. }));
  373. }
  374. return BUILT_IN_WORK_TYPES.map((item) => ({
  375. value: item.code,
  376. label: item.name
  377. }));
  378. },
  379. // 所属危险作业名称(作业票详情不返回,按 workId 再查)
  380. workName() {
  381. return this.ticket.workName || this.work.workName || '';
  382. },
  383. // 作业票模板名称
  384. templateName() {
  385. return this.template?.name || '';
  386. },
  387. // 备注
  388. remark() {
  389. return this.extension.remark || '';
  390. },
  391. /**
  392. * 是否动火作业
  393. */
  394. isFireWork() {
  395. return this.template?.key === 'fire';
  396. },
  397. /**
  398. * 当前处理环节(动火)
  399. * 任务定义 key 在不同平台不一致,用「任务定义 key + 任务名称」一起匹配
  400. * 非动火作业直接返回 null:动火与受限空间的环节名称有重合
  401. * (如「气体分析」「安全交底」),不加限制会串台
  402. */
  403. currentFireNode() {
  404. if (!this.isFireWork) {
  405. return null;
  406. }
  407. return matchFireFlowNode(this.taskDefinitionKey, this.currentTaskName);
  408. },
  409. /**
  410. * 是否受限空间作业
  411. */
  412. isConfinedSpaceWork() {
  413. return this.template?.key === 'confined_space';
  414. },
  415. /**
  416. * 受限空间作业的全部后续环节
  417. */
  418. confinedFlowNodes() {
  419. return CONFINED_SPACE_FLOW_NODES;
  420. },
  421. /**
  422. * 当前处理环节(受限空间)
  423. * 任务定义 key 在不同平台不一致,用「任务定义 key + 任务名称」一起匹配
  424. */
  425. currentConfinedNode() {
  426. if (!this.isConfinedSpaceWork) {
  427. return null;
  428. }
  429. return matchConfinedSpaceFlowNode(
  430. this.taskDefinitionKey,
  431. this.currentTaskName
  432. );
  433. },
  434. /**
  435. * 是否高处作业
  436. */
  437. isHeightWork() {
  438. return this.template?.key === 'height';
  439. },
  440. /**
  441. * 高处作业的全部后续环节
  442. */
  443. heightFlowNodes() {
  444. return HEIGHT_FLOW_NODES;
  445. },
  446. /**
  447. * 当前处理环节(高处作业)
  448. * 任务定义 key 在不同平台不一致,用「任务定义 key + 任务名称」一起匹配
  449. */
  450. currentHeightNode() {
  451. if (!this.isHeightWork) {
  452. return null;
  453. }
  454. return matchHeightFlowNode(this.taskDefinitionKey, this.currentTaskName);
  455. },
  456. /**
  457. * 是否吊装作业
  458. */
  459. isHoistingWork() {
  460. return this.template?.key === 'hoisting';
  461. },
  462. /**
  463. * 吊装作业的全部后续环节
  464. */
  465. hoistingFlowNodes() {
  466. return HOISTING_FLOW_NODES;
  467. },
  468. /**
  469. * 当前处理环节(吊装作业)
  470. * 任务定义 key 在不同平台不一致,用「任务定义 key + 任务名称」一起匹配
  471. */
  472. currentHoistingNode() {
  473. if (!this.isHoistingWork) {
  474. return null;
  475. }
  476. return matchHoistingFlowNode(
  477. this.taskDefinitionKey,
  478. this.currentTaskName
  479. );
  480. },
  481. /**
  482. * 是否临时用电作业
  483. */
  484. isElectricityWork() {
  485. return this.template?.key === 'electricity';
  486. },
  487. /**
  488. * 临时用电作业的全部后续环节
  489. */
  490. electricityFlowNodes() {
  491. return ELECTRICITY_FLOW_NODES;
  492. },
  493. /**
  494. * 当前处理环节(临时用电作业)
  495. * 任务定义 key 在不同平台不一致,用「任务定义 key + 任务名称」一起匹配
  496. */
  497. currentElectricityNode() {
  498. if (!this.isElectricityWork) {
  499. return null;
  500. }
  501. return matchElectricityFlowNode(
  502. this.taskDefinitionKey,
  503. this.currentTaskName
  504. );
  505. },
  506. // 作业票状态文案
  507. statusText() {
  508. return (
  509. this.ticket.statusName || TICKET_STATUS[this.ticket.status] || ''
  510. );
  511. },
  512. // 作业票状态标签色
  513. statusTag() {
  514. const typeMap = {
  515. 1: 'info',
  516. 2: 'warning',
  517. 3: 'success',
  518. 4: 'danger'
  519. };
  520. return typeMap[this.ticket.status] || 'info';
  521. },
  522. // 作业票编号
  523. ticketCode() {
  524. const key = TICKET_CODE_KEYS.find(
  525. (item) => this.extension[item] !== undefined && this.extension[item]
  526. );
  527. return key ? this.extension[key] : '';
  528. },
  529. // 是否有扩展内容
  530. hasExtension() {
  531. return Object.keys(this.extension).length > 0;
  532. }
  533. },
  534. watch: {
  535. businessId: {
  536. immediate: true,
  537. handler(id) {
  538. if (id) {
  539. this.load(id);
  540. }
  541. }
  542. },
  543. // 处理节点落在后续环节时,默认切到「后续环节」tab
  544. // 必须在当前环节识别完成之后再切:
  545. // 任务名称要等流程待办接口返回后才知道,
  546. // 提前判断拿到的是空任务名称,按名称匹配的流程就切不过去
  547. taskDefinitionKey() {
  548. this.loadCurrentNode().then(() => {
  549. this.switchToFlowTab();
  550. });
  551. }
  552. },
  553. methods: {
  554. async load(id) {
  555. this.loading = true;
  556. this.reset();
  557. try {
  558. const [ticket, dict] = await Promise.all([
  559. ticketGetById(id),
  560. this.loadWorkTypeDict()
  561. ]);
  562. this.ticket = ticket || {};
  563. this.extension = parseJsonObject(this.ticket.extensionJson);
  564. this.workTypeDict = dict;
  565. // 按作业类型匹配作业票模板
  566. this.template = getWorkTicketTemplate(
  567. this.ticket.workType,
  568. this.workTypeName === '-' ? '' : this.workTypeName
  569. );
  570. // 作业票详情不返回所属作业名称,按 workId 再查一次
  571. if (this.ticket.workId) {
  572. try {
  573. this.work = (await workGetById(this.ticket.workId)) || {};
  574. } catch (e) {
  575. this.work = {};
  576. }
  577. }
  578. // 后续环节(风险辨识、安全措施落实、各环节意见等)取流程变量
  579. await this.loadVariables();
  580. // 识别当前处理环节并回显
  581. await this.loadCurrentNode();
  582. // 当前处理环节落在后续环节时,自动切到「后续环节」tab
  583. this.switchToFlowTab();
  584. } catch (e) {
  585. this.$message.error(e?.message || '查询作业票详情失败');
  586. } finally {
  587. this.loading = false;
  588. }
  589. },
  590. // 流程变量:作业票后续环节的数据来源
  591. async loadVariables() {
  592. const { vars } = await loadProcessFormVariables(this.id);
  593. this.variables = vars;
  594. },
  595. /**
  596. * 识别当前处理环节并回显其内容
  597. * 任务定义 key 由流程设计器决定,取不到时用任务名称兜底
  598. */
  599. async loadCurrentNode() {
  600. this.currentTaskName = '';
  601. this.nodeForm = {};
  602. if (!this.id) {
  603. return;
  604. }
  605. const info = await getCurrentTaskInfo(this.id, this.taskDefinitionKey);
  606. this.currentTaskName = info.name;
  607. // 受限空间作业:环节内容由受限空间自己的组件回显,这里不参与
  608. if (this.currentConfinedNode) {
  609. return;
  610. }
  611. // 高处作业:环节内容由高处作业自己的组件回显,这里不参与
  612. if (this.currentHeightNode) {
  613. return;
  614. }
  615. // 吊装作业:环节内容由吊装作业自己的组件回显,这里不参与
  616. if (this.currentHoistingNode) {
  617. return;
  618. }
  619. // 临时用电作业:环节内容由临时用电自己的组件回显,这里不参与
  620. if (this.currentElectricityNode) {
  621. return;
  622. }
  623. const node = this.currentFireNode;
  624. if (!node) {
  625. return;
  626. }
  627. // 环节数据统一存放在作业票内容(extensionJson)里,流程变量仅作兼容兜底
  628. this.nodeForm = loadNodeValues(node, [this.extension, this.variables]);
  629. },
  630. /**
  631. * 切到当前处理环节对应的「后续环节」tab
  632. *
  633. * 必须在当前环节识别完成之后调用:任务名称要等流程待办接口返回后才知道,
  634. * 提前判断拿到的是空任务名称,按任务名称匹配的流程就切不过去。
  635. */
  636. switchToFlowTab() {
  637. if (this.currentFireNode) {
  638. this.activeTab = 'flow';
  639. return;
  640. }
  641. if (this.currentConfinedNode) {
  642. this.activeTab = 'flowConfined';
  643. return;
  644. }
  645. if (this.currentHeightNode) {
  646. this.activeTab = 'flowHeight';
  647. return;
  648. }
  649. if (this.currentHoistingNode) {
  650. this.activeTab = 'flowHoisting';
  651. return;
  652. }
  653. if (this.currentElectricityNode) {
  654. this.activeTab = 'flowElectricity';
  655. }
  656. },
  657. // 环节填写值变化
  658. handleNodeInput(val) {
  659. this.nodeForm = val;
  660. },
  661. /**
  662. * 当前环节表单校验
  663. */
  664. async validateNodeForm() {
  665. if (this.isConfinedSpaceWork) {
  666. return this.validateConfinedNodeForm();
  667. }
  668. if (this.isHeightWork) {
  669. return this.validateHeightNodeForm();
  670. }
  671. if (this.isHoistingWork) {
  672. return this.validateHoistingNodeForm();
  673. }
  674. if (this.isElectricityWork) {
  675. return this.validateElectricityNodeForm();
  676. }
  677. if (!this.currentFireNode || !this.$refs.nodeForm) {
  678. return true;
  679. }
  680. return this.$refs.nodeForm.validate();
  681. },
  682. /**
  683. * 受限空间作业的当前环节表单校验
  684. */
  685. async validateConfinedNodeForm() {
  686. if (!this.currentConfinedNode || !this.$refs.confinedFlow) {
  687. return true;
  688. }
  689. return this.$refs.confinedFlow.validate();
  690. },
  691. /**
  692. * 高处作业的当前环节表单校验
  693. */
  694. async validateHeightNodeForm() {
  695. if (!this.currentHeightNode || !this.$refs.heightFlow) {
  696. return true;
  697. }
  698. return this.$refs.heightFlow.validate();
  699. },
  700. /**
  701. * 吊装作业的当前环节表单校验
  702. */
  703. async validateHoistingNodeForm() {
  704. if (!this.currentHoistingNode || !this.$refs.hoistingFlow) {
  705. return true;
  706. }
  707. return this.$refs.hoistingFlow.validate();
  708. },
  709. /**
  710. * 临时用电作业的当前环节表单校验
  711. */
  712. async validateElectricityNodeForm() {
  713. if (!this.currentElectricityNode || !this.$refs.electricityFlow) {
  714. return true;
  715. }
  716. return this.$refs.electricityFlow.validate();
  717. },
  718. /**
  719. * 供审批组件(submit.vue)取当前环节数据
  720. * 取数的同时把环节内容合并进作业票内容(extensionJson)并落库,
  721. * 保证审批通过后数据已经落在作业票上
  722. * @returns {Promise<{valid: boolean, variables: object, payload: object, saved: boolean, message: string}>}
  723. */
  724. async getTableValue() {
  725. if (this.isConfinedSpaceWork) {
  726. return this.getConfinedTableValue();
  727. }
  728. if (this.isHeightWork) {
  729. return this.getHeightTableValue();
  730. }
  731. if (this.isHoistingWork) {
  732. return this.getHoistingTableValue();
  733. }
  734. if (this.isElectricityWork) {
  735. return this.getElectricityTableValue();
  736. }
  737. if (!this.currentFireNode) {
  738. return {
  739. valid: true,
  740. variables: {},
  741. payload: {},
  742. saved: true,
  743. message: ''
  744. };
  745. }
  746. const valid = await this.validateNodeForm();
  747. const payload = buildNodePayload(this.currentFireNode, this.nodeForm);
  748. const variables = buildNodeVariables(
  749. this.currentFireNode,
  750. this.nodeForm
  751. );
  752. if (!valid) {
  753. return {
  754. valid,
  755. variables,
  756. payload,
  757. saved: false,
  758. message: '请填写当前环节的必填内容'
  759. };
  760. }
  761. const saved = await this.updateExtension(payload);
  762. return {
  763. valid,
  764. variables,
  765. payload,
  766. saved: saved.saved,
  767. message: saved.message
  768. };
  769. },
  770. /**
  771. * 取受限空间作业当前环节的数据(供审批组件 submit.vue 使用)
  772. * 取数的同时把环节内容合并进作业票内容(extensionJson)并落库,
  773. * 保证审批通过后数据已经落在作业票上
  774. * @returns {Promise<{valid: boolean, variables: object, payload: object, saved: boolean, message: string}>}
  775. */
  776. async getConfinedTableValue() {
  777. const flow = this.$refs.confinedFlow;
  778. if (!this.currentConfinedNode || !flow) {
  779. return {
  780. valid: true,
  781. variables: {},
  782. payload: {},
  783. saved: true,
  784. message: ''
  785. };
  786. }
  787. const valid = await this.validateConfinedNodeForm();
  788. const payload = flow.getPayload();
  789. const variables = flow.getVariables();
  790. if (!valid) {
  791. return {
  792. valid,
  793. variables,
  794. payload,
  795. saved: false,
  796. message: '请填写当前环节的必填内容'
  797. };
  798. }
  799. const saved = await this.updateExtension(payload);
  800. return {
  801. valid,
  802. variables,
  803. payload,
  804. saved: saved.saved,
  805. message: saved.message
  806. };
  807. },
  808. /**
  809. * 取高处作业当前环节的数据(供审批组件 submit.vue 使用)
  810. * 取数的同时把环节内容合并进作业票内容(extensionJson)并落库,
  811. * 保证审批通过后数据已经落在作业票上
  812. * @returns {Promise<{valid: boolean, variables: object, payload: object, saved: boolean, message: string}>}
  813. */
  814. async getHeightTableValue() {
  815. const flow = this.$refs.heightFlow;
  816. if (!this.currentHeightNode || !flow) {
  817. return {
  818. valid: true,
  819. variables: {},
  820. payload: {},
  821. saved: true,
  822. message: ''
  823. };
  824. }
  825. const valid = await this.validateHeightNodeForm();
  826. const payload = flow.getPayload();
  827. const variables = flow.getVariables();
  828. if (!valid) {
  829. return {
  830. valid,
  831. variables,
  832. payload,
  833. saved: false,
  834. message: '请填写当前环节的必填内容'
  835. };
  836. }
  837. const saved = await this.updateExtension(payload);
  838. return {
  839. valid,
  840. variables,
  841. payload,
  842. saved: saved.saved,
  843. message: saved.message
  844. };
  845. },
  846. /**
  847. * 取吊装作业当前环节的数据(供审批组件 submit.vue 使用)
  848. * 取数的同时把环节内容合并进作业票内容(extensionJson)并落库,
  849. * 保证审批通过后数据已经落在作业票上
  850. * @returns {Promise<{valid: boolean, variables: object, payload: object, saved: boolean, message: string}>}
  851. */
  852. async getHoistingTableValue() {
  853. const flow = this.$refs.hoistingFlow;
  854. if (!this.currentHoistingNode || !flow) {
  855. return {
  856. valid: true,
  857. variables: {},
  858. payload: {},
  859. saved: true,
  860. message: ''
  861. };
  862. }
  863. const valid = await this.validateHoistingNodeForm();
  864. const payload = flow.getPayload();
  865. const variables = flow.getVariables();
  866. if (!valid) {
  867. return {
  868. valid,
  869. variables,
  870. payload,
  871. saved: false,
  872. message: '请填写当前环节的必填内容'
  873. };
  874. }
  875. const saved = await this.updateExtension(payload);
  876. return {
  877. valid,
  878. variables,
  879. payload,
  880. saved: saved.saved,
  881. message: saved.message
  882. };
  883. },
  884. /**
  885. * 取临时用电作业当前环节的数据(供审批组件 submit.vue 使用)
  886. * 取数的同时把环节内容合并进作业票内容(extensionJson)并落库,
  887. * 保证审批通过后数据已经落在作业票上
  888. * @returns {Promise<{valid: boolean, variables: object, payload: object, saved: boolean, message: string}>}
  889. */
  890. async getElectricityTableValue() {
  891. const flow = this.$refs.electricityFlow;
  892. if (!this.currentElectricityNode || !flow) {
  893. return {
  894. valid: true,
  895. variables: {},
  896. payload: {},
  897. saved: true,
  898. message: ''
  899. };
  900. }
  901. const valid = await this.validateElectricityNodeForm();
  902. const payload = flow.getPayload();
  903. const variables = flow.getVariables();
  904. if (!valid) {
  905. return {
  906. valid,
  907. variables,
  908. payload,
  909. saved: false,
  910. message: '请填写当前环节的必填内容'
  911. };
  912. }
  913. const saved = await this.updateExtension(payload);
  914. return {
  915. valid,
  916. variables,
  917. payload,
  918. saved: saved.saved,
  919. message: saved.message
  920. };
  921. },
  922. /**
  923. * 把环节内容合并进作业票内容(extensionJson)并保存
  924. */
  925. async updateExtension(payload) {
  926. if (!Object.keys(payload || {}).length) {
  927. return { saved: true, message: '' };
  928. }
  929. if (!this.businessId && !this.ticket?.id) {
  930. return { saved: false, message: '缺少作业票信息,无法保存环节内容' };
  931. }
  932. const extension = mergeExtension(this.extension, payload);
  933. const params = buildTicketUpdateParams(
  934. this.ticket,
  935. extension,
  936. this.businessId
  937. );
  938. try {
  939. await ticketUpdate(params);
  940. // 同步本地内容,避免详情页显示旧值
  941. this.extension = extension;
  942. return { saved: true, message: '' };
  943. } catch (e) {
  944. return {
  945. saved: false,
  946. message: e?.message || '环节内容保存失败'
  947. };
  948. }
  949. },
  950. /** 供审批组件提交时调用,保存业务数据 */
  951. async save(data) {
  952. if (this.isConfinedSpaceWork) {
  953. return this.saveConfined(data);
  954. }
  955. if (this.isHeightWork) {
  956. return this.saveHeight(data);
  957. }
  958. if (this.isHoistingWork) {
  959. return this.saveHoisting(data);
  960. }
  961. if (this.isElectricityWork) {
  962. return this.saveElectricity(data);
  963. }
  964. const payload =
  965. data && data.payload
  966. ? data.payload
  967. : buildNodePayload(this.currentFireNode, this.nodeForm);
  968. if (!Object.keys(payload).length) {
  969. return data;
  970. }
  971. try {
  972. const res = await this.updateExtension(payload);
  973. if (!res.saved) {
  974. console.error('保存环节内容失败', res.message);
  975. }
  976. } catch (e) {
  977. console.error('保存环节内容失败', e);
  978. }
  979. return data;
  980. },
  981. /**
  982. * 受限空间作业:供审批组件提交时调用,保存业务数据
  983. */
  984. async saveConfined(data) {
  985. const flow = this.$refs.confinedFlow;
  986. const payload =
  987. data && data.payload
  988. ? data.payload
  989. : flow
  990. ? flow.getPayload()
  991. : {};
  992. if (!Object.keys(payload).length) {
  993. return data;
  994. }
  995. try {
  996. const res = await this.updateExtension(payload);
  997. if (!res.saved) {
  998. console.error('保存环节内容失败', res.message);
  999. }
  1000. } catch (e) {
  1001. console.error('保存环节内容失败', e);
  1002. }
  1003. return data;
  1004. },
  1005. /**
  1006. * 高处作业:供审批组件提交时调用,保存业务数据
  1007. */
  1008. async saveHeight(data) {
  1009. const flow = this.$refs.heightFlow;
  1010. const payload =
  1011. data && data.payload
  1012. ? data.payload
  1013. : flow
  1014. ? flow.getPayload()
  1015. : {};
  1016. if (!Object.keys(payload).length) {
  1017. return data;
  1018. }
  1019. try {
  1020. const res = await this.updateExtension(payload);
  1021. if (!res.saved) {
  1022. console.error('保存环节内容失败', res.message);
  1023. }
  1024. } catch (e) {
  1025. console.error('保存环节内容失败', e);
  1026. }
  1027. return data;
  1028. },
  1029. /**
  1030. * 吊装作业:供审批组件提交时调用,保存业务数据
  1031. */
  1032. async saveHoisting(data) {
  1033. const flow = this.$refs.hoistingFlow;
  1034. const payload =
  1035. data && data.payload
  1036. ? data.payload
  1037. : flow
  1038. ? flow.getPayload()
  1039. : {};
  1040. if (!Object.keys(payload).length) {
  1041. return data;
  1042. }
  1043. try {
  1044. const res = await this.updateExtension(payload);
  1045. if (!res.saved) {
  1046. console.error('保存环节内容失败', res.message);
  1047. }
  1048. } catch (e) {
  1049. console.error('保存环节内容失败', e);
  1050. }
  1051. return data;
  1052. },
  1053. /**
  1054. * 临时用电作业:供审批组件提交时调用,保存业务数据
  1055. */
  1056. async saveElectricity(data) {
  1057. const flow = this.$refs.electricityFlow;
  1058. const payload =
  1059. data && data.payload
  1060. ? data.payload
  1061. : flow
  1062. ? flow.getPayload()
  1063. : {};
  1064. if (!Object.keys(payload).length) {
  1065. return data;
  1066. }
  1067. try {
  1068. const res = await this.updateExtension(payload);
  1069. if (!res.saved) {
  1070. console.error('保存环节内容失败', res.message);
  1071. }
  1072. } catch (e) {
  1073. console.error('保存环节内容失败', e);
  1074. }
  1075. return data;
  1076. },
  1077. // 作业类型字典:{dictCode, dictValue} 形式
  1078. async loadWorkTypeDict() {
  1079. try {
  1080. const items = await getDictByCode('ehs_hazardous_work_type');
  1081. return (items || []).map((item) => ({
  1082. dictCode: item.key,
  1083. dictValue: item.label
  1084. }));
  1085. } catch (e) {
  1086. return [];
  1087. }
  1088. },
  1089. reset() {
  1090. this.ticket = {};
  1091. this.work = {};
  1092. this.extension = {};
  1093. this.variables = {};
  1094. this.template = null;
  1095. this.currentTaskName = '';
  1096. this.nodeForm = {};
  1097. this.activeTab = 'info';
  1098. }
  1099. }
  1100. };
  1101. </script>
  1102. <style lang="scss" scoped>
  1103. .ticket-detail {
  1104. .detail-summary {
  1105. display: flex;
  1106. align-items: center;
  1107. justify-content: space-between;
  1108. padding: 0 4px 10px;
  1109. }
  1110. .summary-title {
  1111. font-size: 15px;
  1112. font-weight: 500;
  1113. color: #303133;
  1114. i {
  1115. color: #409eff;
  1116. margin-right: 4px;
  1117. }
  1118. ::v-deep .el-tag {
  1119. margin-left: 8px;
  1120. }
  1121. }
  1122. .summary-extra {
  1123. font-size: 12px;
  1124. color: #909399;
  1125. line-height: 20px;
  1126. span {
  1127. margin-left: 14px;
  1128. }
  1129. }
  1130. .detail-tabs {
  1131. ::v-deep .el-tabs__content {
  1132. padding-top: 12px;
  1133. }
  1134. }
  1135. .current-node-box {
  1136. padding: 16px;
  1137. margin-bottom: 16px;
  1138. border: 1px solid #d9ecff;
  1139. border-radius: 4px;
  1140. background: #f5faff;
  1141. }
  1142. .node-tip {
  1143. padding: 10px 0;
  1144. font-size: 13px;
  1145. color: #909399;
  1146. }
  1147. .el-form-box {
  1148. max-height: calc(100vh - 320px);
  1149. overflow-y: auto;
  1150. padding-right: 10px;
  1151. }
  1152. .empty-text {
  1153. padding: 10px 0;
  1154. font-size: 13px;
  1155. color: #909399;
  1156. }
  1157. .raw-extension {
  1158. padding: 8px 12px;
  1159. background: #f6f7f9;
  1160. border-radius: 4px;
  1161. }
  1162. .raw-row {
  1163. display: flex;
  1164. font-size: 13px;
  1165. line-height: 24px;
  1166. word-break: break-all;
  1167. }
  1168. .raw-key {
  1169. flex: 0 0 180px;
  1170. color: #909399;
  1171. }
  1172. .raw-value {
  1173. flex: 1;
  1174. color: #303133;
  1175. }
  1176. }
  1177. </style>