taskDetail.vue 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. <template>
  2. <!-- 我的任务详情 -->
  3. <div class="task-detail-layout" :class="{ 'fullscreen-mode': isFullscreen }">
  4. <div class="detail-header">
  5. <div class="flex items-center pl-14px flex-[0_0_450px]">
  6. 任务详情
  7. <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowOnlyMyItems" size="small" @change="handleShowOnlyMyItems">只看我</el-checkbox>
  8. <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowConcludeItems" size="small" @change="handleShowConcludeItemsItems">报告未办结</el-checkbox>
  9. <el-button class="ml-15px" type="primary" size="small" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
  10. <el-button class="ml-15px" type="primary" size="small" plain @click="() => toTaskOrderDetail()">任务单</el-button>
  11. </div>
  12. <el-divider direction="vertical" class="mx-10px" />
  13. <div id="teleport-btn" ref="teleportBtnRef" class="teleport-btn"></div>
  14. <!-- <el-button class="ml-10px" type="primary" size="small" plain @click="handleToggleFullscreen">-->
  15. <!-- {{ isFullscreen ? '退出全屏' : '全屏' }}-->
  16. <!-- </el-button>-->
  17. <div class="detail-header-back">
  18. <el-button circle type="default" plain @click="handleToggleFullscreen">
  19. <Icon
  20. :icon="isFullScreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'"
  21. color="var(--el-color-info)"
  22. hover-color="var(--el-color-primary)"
  23. />
  24. </el-button>
  25. <el-button type="default" plain @click="() => handleBack()">返回</el-button></div>
  26. </div>
  27. <div class="task-detail-container flex">
  28. <!-- 左侧:检验项目列表 -->
  29. <div class="left-panel flex-[0_0_400px]">
  30. <ContentWrap title="检验项目清单" class="h-full flex flex-col" :bodyStyle="{padding: '10px', flex: 1, overflow: 'hidden'}">
  31. <template #header>
  32. <el-button @click="handleAddItem" type="primary" size="small" :disabled="taskInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem">添加检验项目</el-button>
  33. </template>
  34. <!-- 检验项目列表组件 -->
  35. <InspectionItemList
  36. :report-list="reportList"
  37. :orderInfo="taskInfo"
  38. :equipmentIds="[taskOrderItem?.equipId]"
  39. :orderItemIds="[taskOrderItem?.id]"
  40. :selected-item="selectedItem"
  41. @item-select="handleItemSelect"
  42. :task-order-item="taskOrderItem"
  43. @modify-checker="handleModifyCheckerForItem"
  44. @void-item="handleVoidItem"
  45. @set-pipe-detail="setPipeDetail"
  46. @sync-all-report="handleSyncReportData"
  47. @sort-report="handleRefresh"
  48. @refresh="handleRefresh"
  49. @correlation-report="handleCorrelationReport"
  50. @template-confirm="handleTemplateConfirm"
  51. @apply-template="handleApplyTemplate"
  52. @update-template="handleUpdateTemplateUrl"
  53. @getData="getData"
  54. />
  55. </ContentWrap>
  56. </div>
  57. <div class="right-panel flex-1 overflow-hidden">
  58. <StatusOperationPanel
  59. ref="statusOperationPanelRef"
  60. v-model:selected-item="selectedItem"
  61. :task-info="taskInfo"
  62. :task-order-item="taskOrderItem"
  63. :report-list="reportList"
  64. :history-list="historyList"
  65. :taskId="taskId"
  66. :teleportBtnRef="teleportBtnRef"
  67. :is-fullscreen="isFullscreen"
  68. @refresh="handleRefresh"
  69. @modify-checker="handleModifyChecker"
  70. @template-confirm="handleTemplateConfirm"
  71. @item-select="handleItemSelect"
  72. />
  73. </div>
  74. </div>
  75. </div>
  76. <AddOrEditCheckItemForEquipment
  77. v-if="selectedNewInspectionItem"
  78. v-model="selectedNewInspectionItem"
  79. :orderInfo="taskInfo"
  80. :selectedIds="selectedReportIds"
  81. :equipmentIds="[taskOrderItem?.equipId]"
  82. :reportList="reportList"
  83. :allReportList="allReportList"
  84. :taskOrderItem="taskOrderItem"
  85. :checkUsers="checkUsers"
  86. @refresh="() => handleRefresh()"
  87. />
  88. <!-- 修改检验员对话框 -->
  89. <el-dialog
  90. v-model="modifyCheckerDialogVisible"
  91. title="修改检验员"
  92. width="80%"
  93. :before-close="handleCancelModifyChecker"
  94. >
  95. <div class="mb-4" v-if="selectedItem">
  96. <div class="text-sm text-gray-600 mb-2">
  97. 项目:{{ selectedItem.reportName || '检验项目' }}
  98. </div>
  99. <div class="text-sm text-gray-600">
  100. 当前检验员:{{
  101. selectedCheckersInfo.map((item) => item?.nickname).join('、') || '暂无'
  102. }}
  103. </div>
  104. </div>
  105. <CheckerSelectBox :checkUsers="checkUsers" v-model="selectedCheckers" :max="1" @change="handleCheckerChange" />
  106. <template #footer>
  107. <div class="dialog-footer">
  108. <el-button @click="handleCancelModifyChecker">取消</el-button>
  109. <el-button type="primary" @click="handleConfirmModifyChecker"> 确认修改 </el-button>
  110. </div>
  111. </template>
  112. </el-dialog>
  113. <!-- 设置管线对话框 -->
  114. <el-dialog
  115. v-model="setPipeDetailDialogVisible"
  116. title="设置对应管线"
  117. width="40%"
  118. :before-close="handleCancelSetPipeDetail"
  119. >
  120. <div class="mb-4" v-if="selectedItem">
  121. <div class="text-sm text-gray-600 mb-2">
  122. 项目:{{ selectedItem.reportName || '检验项目' }}
  123. </div>
  124. <el-select
  125. v-model="selectedPipeDetail"
  126. placeholder="请选择对应管线"
  127. clearable
  128. filterable
  129. style="width: 100%"
  130. >
  131. <el-option
  132. v-for="pipe in pipeDetailList"
  133. :key="pipe.id"
  134. :label="pipe.pipeNo + '-' + pipe.pipeName"
  135. :value="pipe.id"
  136. />
  137. </el-select>
  138. </div>
  139. <template #footer>
  140. <div class="dialog-footer">
  141. <el-button @click="handleCancelSetPipeDetail">取消</el-button>
  142. <el-button type="primary" @click="handleConfirmSetPipeDetail"> 确认 </el-button>
  143. </div>
  144. </template>
  145. </el-dialog>
  146. <!-- 设备档案弹窗 -->
  147. <EquipPipeForm ref="EquipPipeFormRef" :selectedItem="selectedItem" :allReportList="allReportList" />
  148. <!-- 设备选择弹窗 -->
  149. <el-dialog
  150. v-model="equipmentSelectDialogVisible"
  151. title="选择设备"
  152. width="600px"
  153. >
  154. <div class="equipment-list">
  155. <div
  156. v-for="equip in equipmentList"
  157. :key="equip.id"
  158. class="equipment-item"
  159. @click="handleEquipmentClick(equip)"
  160. >
  161. {{ equip.projectNo }} - {{ equip.projectName }}
  162. </div>
  163. </div>
  164. <template #footer>
  165. <div class="dialog-footer">
  166. <el-button @click="equipmentSelectDialogVisible = false">取消</el-button>
  167. </div>
  168. </template>
  169. </el-dialog>
  170. </template>
  171. <script setup lang="ts">
  172. import {onMounted, ref, onUnmounted} from 'vue'
  173. import { useRoute } from 'vue-router'
  174. import { ElMessage, ElMessageBox } from 'element-plus'
  175. import {
  176. PipeTaskOrderApi,
  177. PipeTaskOrderDetailVO,
  178. ReportItemVO,
  179. PipeTaskOrderOrderItemVO
  180. } from '@/api/pressure2/pipetaskorder'
  181. import { UserVO } from '@/api/system/user'
  182. import { PressureCheckerMyTaskStatus, PressureReportType } from '@/utils/constants'
  183. import CheckerSelectBox from '@/views/pressure2/equipboilerscheduling/components/CheckerSelectBox.vue'
  184. import InspectionItemList from './components/InspectionItemList.vue'
  185. import StatusOperationPanel from './components/StatusOperationPanel.vue'
  186. import { useUserStore } from '@/store/modules/user'
  187. import AddOrEditCheckItemForEquipment from '@/views/pressure2/pipetaskorder/components/AddOrEditCheckItemForEquipment.vue'
  188. import { PressureTaskOrderTaskStatus } from '@/utils/constants'
  189. import { useTagsViewStore } from '@/store/modules/tagsView'
  190. import { useEmitt } from '@/hooks/web/useEmitt'
  191. import EquipPipeForm from '@/components/EquipPipeForm/index.vue'
  192. defineOptions({ name: 'PipeCheckerTaskDetail' })
  193. const tagsViewStore = useTagsViewStore()
  194. const route = useRoute()
  195. const router = useRouter()
  196. const userStore = useUserStore()
  197. const { emitter } = useEmitt()
  198. const teleportBtnRef = ref<HTMLDivElement>()
  199. // 全屏控制
  200. const isFullscreen = ref(true)
  201. const handleToggleFullscreen = () => {
  202. isFullscreen.value = !isFullscreen.value
  203. }
  204. // 基础数据
  205. const taskId = ref<string | null>(null)
  206. const orderId = ref<string | null>(null)
  207. const loading = ref(true)
  208. const taskInfo = ref<PipeTaskOrderDetailVO | null>(null)
  209. const allReportList = ref<ReportItemVO[]>([])
  210. const reportList = ref<ReportItemVO[]>([])
  211. const historyList = ref<any[]>([])
  212. const checkUsers = ref<UserVO[]>([])
  213. const taskOrderItem = ref<PipeTaskOrderOrderItemVO | null>(null)
  214. // 主报告处于 报告审核审核/报告审批/报告办结,是则不能添加检验项目
  215. const canAddReportItem = computed(()=>{
  216. const disabledStatus = [PressureCheckerMyTaskStatus.REPORT_AUDIT, PressureCheckerMyTaskStatus.REPORT_APPROVE, PressureCheckerMyTaskStatus.REPORT_END]
  217. const mainReport = unref(reportList).find(x => x.reportType === PressureReportType['MAIN'])
  218. // console.log(mainReport, 'mainReport')
  219. return mainReport && disabledStatus.includes(mainReport.taskStatus)
  220. })
  221. // 选择状态
  222. const selectedItem = ref<ReportItemVO | null>(null)
  223. // 对话框状态
  224. const addItemDialogVisible = ref(false)
  225. const modifyCheckerDialogVisible = ref(false)
  226. const setPipeDetailDialogVisible = ref(false)
  227. const pipeDetailList = ref([])
  228. const selectedPipeDetail = ref('')
  229. const selectedCheckers = ref<string[]>([])
  230. const selectedCheckersInfo = ref<any[]>([])
  231. async function getOrderHistoryVersion(id: string) {
  232. try {
  233. const response = await PipeTaskOrderApi.getSafetyCheckRecordVersionPage({
  234. // id: id,
  235. pageNo: 1,
  236. pageSize: 100,
  237. businessType: 0,
  238. orderItemReportId: selectedItem.value?.id
  239. })
  240. //console.log('response', response)
  241. historyList.value = response.list || []
  242. //console.log('historyList.value', historyList.value)
  243. } catch (error) {
  244. console.error('获取历史版本失败:', error)
  245. ElMessage.error('获取历史版本失败')
  246. }
  247. }
  248. // 添加检验项目弹窗表单字段
  249. const addInspectionItemForm = ref({
  250. templateId: [],
  251. checker: {}
  252. })
  253. const selectedNewInspectionItem = ref(false)
  254. const handleRemoveTag = (value) => {
  255. addInspectionItemForm.value.templateId = addInspectionItemForm.value.templateId.filter((x: any) => x?.name !== value)
  256. }
  257. const handleClearSelectedInspectionItem = () => {
  258. addInspectionItemForm.value.templateId = []
  259. }
  260. // 检验员列表弹窗
  261. const checkerSelectVisible = ref(false)
  262. const currentSelectedCheckerIds = ref([])
  263. const confirmCheckerSelect = () => {
  264. checkerSelectVisible.value = false
  265. }
  266. const handleSelectedNewChecker = () => {
  267. checkerSelectVisible.value = true
  268. }
  269. const handleInspectionItemCheckerChange = (checkerInfo) => {
  270. addInspectionItemForm.value.checker = checkerInfo?.[0]?.member
  271. }
  272. // 添加检验项目
  273. const selectedReportIds = computed(() => reportList.value.map(x => {
  274. return x.reportType === 600 ? x.id : x.templateId || x.id
  275. }))
  276. const handleAddItem = () => {
  277. selectedNewInspectionItem.value = true
  278. addInspectionItemForm.value.checker = userStore.getUser
  279. }
  280. const selectedInspectionItem = computed(() => {
  281. const selectedList = addInspectionItemForm.value.templateId.length > 0 ? addInspectionItemForm.value.templateId : []
  282. return selectedList.map((item:any ) => item?.name || item?.reportName)
  283. })
  284. const handleConfirmAddItems = async (selectedItems) => {
  285. //console.log('selectedItems', selectedItems)
  286. addInspectionItemForm.value.templateId = selectedItems
  287. // await handleRefresh()
  288. }
  289. // 确认添加项目
  290. const handleConfirmToAddItem = async () => {
  291. try {
  292. const params = {
  293. orderItemId: taskOrderItem.value?.id ,
  294. templateIds: addInspectionItemForm.value.templateId.map((x: any) => x?.id),
  295. checkId: addInspectionItemForm.value.checker?.id
  296. }
  297. const result = await PipeTaskOrderApi.addReportV3(params)
  298. if(result) {
  299. ElMessage.success(`成功添加 ${addInspectionItemForm.value.templateId.length} 个检验项目`)
  300. handleCloseNewInspectionItem()
  301. await handleRefresh()
  302. }
  303. } catch (error) {
  304. ElMessage.error('添加检验项目失败')
  305. }
  306. }
  307. // 关闭添加项目弹窗
  308. const handleCloseNewInspectionItem = () => {
  309. selectedNewInspectionItem.value = false
  310. addInspectionItemForm.value = {
  311. templateId: [],
  312. checker: {}
  313. }
  314. }
  315. // 获取任务详情数据
  316. async function fetchTaskDetail11(id: string) {
  317. loading.value = true
  318. try {
  319. //debugger;
  320. const response = await PipeTaskOrderApi.getTaskOrderOrderItem(id)
  321. taskInfo.value = response.taskOrder
  322. checkUsers.value = response.checkUsers
  323. taskOrderItem.value = response.taskOrderItem
  324. // 过滤报告列表 - 只显示状态 >= CONFIRMED 的项目 已认领 400
  325. const filteredReportList = response.reportList.filter(
  326. (item) => item.taskStatus >= PressureCheckerMyTaskStatus.CONFIRMED
  327. ).filter(v=> {
  328. // 如果是检验方案和指导书
  329. if([600, 700].includes(v.reportType as number)){
  330. // return [200].includes(v.status)
  331. return false
  332. }
  333. return true
  334. })
  335. allReportList.value = response.reportList.filter(v=> {
  336. // 如果是检验方案和指导书
  337. if([600, 700].includes(v.reportType as number)){
  338. return [200, 400].includes(v.status)
  339. }
  340. return true
  341. })
  342. reportList.value = filteredReportList
  343. // 智能选择:保持当前选中项目或选择第一个项目
  344. if (reportList.value.length > 0) {
  345. let targetItem: ReportItemVO | undefined = undefined
  346. // 如果当前有选中项目,尝试在新列表中找到相同的项目
  347. if (selectedItem.value) {
  348. targetItem = reportList.value.find((item) => item.id === selectedItem.value?.id)
  349. }
  350. // 如果当前选中项目不存在于新列表中,或者没有选中项目,选择第一个项目
  351. if (!targetItem) {
  352. targetItem = reportList.value[0]
  353. }
  354. selectedItem.value = targetItem
  355. }
  356. //console.log('reportList.value', reportList.value)
  357. } catch (error) {
  358. console.error('获取任务详情失败:', error)
  359. ElMessage.error('获取任务详情失败')
  360. taskInfo.value = null
  361. reportList.value = []
  362. } finally {
  363. loading.value = false
  364. }
  365. }
  366. // 项目选择事件处理
  367. const handleItemSelect = async (item: ReportItemVO) => {
  368. selectedItem.value = item
  369. await getOrderHistoryVersion(taskId.value)
  370. }
  371. // 刷新数据
  372. const handleRefresh = async () => {
  373. if (taskId.value) {
  374. await fetchTaskDetail(taskId.value)
  375. await getOrderHistoryVersion(taskId.value)
  376. }
  377. }
  378. // 修改检验员
  379. const handleModifyChecker = (item?: ReportItemVO) => {
  380. const targetItem = item || selectedItem.value
  381. if (!targetItem) {
  382. ElMessage.warning('请先选择要修改检验员的项目')
  383. return
  384. }
  385. // 如果传入了item参数,更新选中项目
  386. if (item) {
  387. selectedItem.value = item
  388. }
  389. const currentCheckers: string[] = []
  390. if (targetItem.checkUsers && targetItem.checkUsers.length > 0) {
  391. const checkerIds = targetItem.checkUsers.map((user) => String(user.id))
  392. currentCheckers.push(...checkerIds)
  393. }
  394. selectedCheckers.value = [...new Set(currentCheckers)]
  395. // selectedCheckers.value = []
  396. modifyCheckerDialogVisible.value = true
  397. }
  398. const handleCheckerChange = (checkerInfo: any[]) => {
  399. selectedCheckers.value = checkerInfo
  400. }
  401. const handleConfirmModifyChecker = async () => {
  402. if (!selectedItem.value) {
  403. ElMessage.warning('请选择项目')
  404. return
  405. }
  406. try {
  407. if (selectedCheckers.value.length < 1){
  408. ElMessage.error('请选择检验员!');
  409. return;
  410. }
  411. const updateData = {
  412. id: selectedItem.value.id,
  413. userList: selectedCheckers.value.map(item => item.id)
  414. }
  415. console.log('updateData', updateData)
  416. await PipeTaskOrderApi.updateReportUsers(updateData)
  417. ElMessage.success('成功修改检验员')
  418. modifyCheckerDialogVisible.value = false
  419. await handleRefresh()
  420. } catch (error: any) {
  421. console.error('修改检验员失败:', error)
  422. ElMessage.error('修改检验员失败,请稍后重试')
  423. }
  424. }
  425. const handleCancelModifyChecker = () => {
  426. modifyCheckerDialogVisible.value = false
  427. selectedCheckers.value = []
  428. selectedCheckersInfo.value = []
  429. }
  430. const handleCancelSetPipeDetail = () => {
  431. setPipeDetailDialogVisible.value = false
  432. selectedPipeDetail.value = ''
  433. }
  434. const handleConfirmSetPipeDetail = async () => {
  435. if (!selectedPipeDetail.value) {
  436. ElMessage.warning('请选择对应管线')
  437. return
  438. }
  439. try {
  440. const params = {
  441. id: selectedItem.value.id,
  442. pipeDetailId: selectedPipeDetail.value
  443. }
  444. await PipeTaskOrderApi.setPipeDetail(params)
  445. ElMessage.success('成功设置报告对应管线')
  446. setPipeDetailDialogVisible.value = false
  447. await handleRefresh()
  448. } catch (error: any) {
  449. if (error !== 'cancel') {
  450. console.error('报告设置对应管线失败:', error)
  451. ElMessage.error('报告设置对应管线失败,请稍后重试')
  452. }
  453. }
  454. }
  455. const statusOperationPanelRef = ref<InstanceType<typeof StatusOperationPanel>>()
  456. const handleCorrelationReport = ()=>{
  457. //console.log('selectedItem.value', selectedItem.value)
  458. statusOperationPanelRef.value?.handleShowAssociationOperationManual()
  459. }
  460. // 应用项目回调
  461. const handleApplyTemplate = (defaultJson: Recordable) => {
  462. statusOperationPanelRef.value?.handleApplyTemplate(defaultJson)
  463. }
  464. // 更新模板回调
  465. const handleUpdateTemplateUrl = (item: ReportItemVO) => {
  466. statusOperationPanelRef.value?.handleUpdateTemplateUrl(item)
  467. }
  468. const getData = (callback: (data: Recordable) => void) => {
  469. const data = statusOperationPanelRef.value?.getData()
  470. callback(data)
  471. }
  472. // 模板确认(从StatusOperationPanel传递上来)
  473. const handleTemplateConfirm = (templateUrl?: string) => {
  474. handleRefresh()
  475. }
  476. const handleBack = () => {
  477. if(route.query?.type === 'PipeMyTask') {
  478. emitter.emit('refresh-my-task-pipe')
  479. } else if(route.query?.type === 'PipeReportPreparationList') {
  480. emitter.emit('refresh-report-preparation-list-pipe')
  481. } else if(route.query?.type === 'reportAudit') {
  482. emitter.emit('refresh-report-reportAudit-pipe')
  483. } else if(route.query?.type === 'reportRatify') {
  484. emitter.emit('refresh-report-reportRatify-pipe')
  485. }
  486. tagsViewStore.closeSelectedTag(route)
  487. router.push({
  488. name: route?.query?.type,
  489. })
  490. }
  491. // 修改检验员(单个项目)
  492. const handleModifyCheckerForItem = (item: ReportItemVO) => {
  493. selectedItem.value = item
  494. const currentCheckers: string[] = []
  495. if (item.checkUsers && item.checkUsers.length > 0) {
  496. const checkerIds = item.checkUsers.map((user) => String(user.id))
  497. currentCheckers.push(...checkerIds)
  498. }
  499. if (selectedCheckersInfo.value.length == 0){
  500. selectedCheckersInfo.value = item.checkUsers
  501. }
  502. selectedCheckers.value = [...new Set(currentCheckers)]
  503. // selectedCheckers.value = []
  504. modifyCheckerDialogVisible.value = true
  505. }
  506. //设置对应管线
  507. const setPipeDetail = async (item: ReportItemVO) => {
  508. selectedItem.value = item
  509. selectedPipeDetail.value = selectedItem.value.pipeDetailId || ''
  510. //获取管线
  511. const params = {
  512. orderId: orderId.value
  513. }
  514. const response = await PipeTaskOrderApi.getDetailByOrderId(params)
  515. pipeDetailList.value = response || []
  516. setPipeDetailDialogVisible.value = true
  517. }
  518. // 作废单个项目
  519. const handleVoidItem = async (item: ReportItemVO) => {
  520. // 校验操作指导书是否关联了独走报告,如果有则进行二次确认
  521. if(item.reportType === PressureReportType.WORKINSTRUCTION){
  522. // 校验操作指导书是否关联了独走报告
  523. const hasWorkInstruction = reportList.value.some((reportItem) => reportItem.instructionId === item.id)
  524. if(hasWorkInstruction){
  525. const confirm = await ElMessageBox.confirm(
  526. `操作指导书 ${item.reportName} 已被检验项目关联,作废后将清除关联关系,是否确认?`,
  527. '确认作废',
  528. {
  529. confirmButtonText: '确认作废',
  530. cancelButtonText: '取消',
  531. type: 'warning'
  532. }
  533. )
  534. if(confirm !== 'confirm') return
  535. }
  536. }
  537. try {
  538. const { value: voidReason } = await ElMessageBox.prompt(
  539. `确定要作废项目 ${item.reportName} 吗?`,
  540. '作废项目',
  541. {
  542. confirmButtonText: '确认作废',
  543. cancelButtonText: '取消',
  544. inputPlaceholder: '请输入作废原因',
  545. inputType: 'textarea',
  546. inputValidator: (value: string) => {
  547. if (!value || !value.trim()) {
  548. return '作废原因不能为空'
  549. }
  550. if (value.trim().length < 5) {
  551. return '作废原因至少需要5个字符'
  552. }
  553. if (value.trim().length > 200) {
  554. return '作废原因不能超过200个字符'
  555. }
  556. return true
  557. },
  558. inputErrorMessage: '请输入有效的作废原因'
  559. }
  560. )
  561. if (voidReason && voidReason.trim()) {
  562. await PipeTaskOrderApi.cancelReport({
  563. id: item.id,
  564. reason: voidReason.trim()
  565. })
  566. ElMessage.success(`成功作废项目 ${item.reportName}`)
  567. await handleRefresh()
  568. }
  569. } catch (error: any) {
  570. if (error !== 'cancel') {
  571. console.error('作废项目失败:', error)
  572. ElMessage.error('作废项目失败,请稍后重试')
  573. }
  574. }
  575. }
  576. // 组件挂载时获取数据
  577. (async () => {
  578. const idFromQuery = route.query.id
  579. const orderIdQuery = route.query.orderId
  580. if (typeof idFromQuery === 'string' && idFromQuery) {
  581. taskId.value = idFromQuery
  582. if (orderIdQuery) {
  583. orderId.value = orderIdQuery
  584. }
  585. await fetchTaskDetail(idFromQuery)
  586. await getOrderHistoryVersion(idFromQuery)
  587. const activeDetailItemId= localStorage.getItem('activePipeDetailItemId')
  588. if (activeDetailItemId){
  589. await handleItemSelect(reportList.value.find( item=> item.id === activeDetailItemId))
  590. localStorage.removeItem('activePipeDetailItemId')
  591. }
  592. } else {
  593. taskInfo.value = null
  594. loading.value = false
  595. }
  596. })();
  597. // 监听设备编辑成功事件
  598. const handleEquipmentEditSuccess = async () => {
  599. console.log('收到设备编辑成功事件,同步报表')
  600. try {
  601. await PipeTaskOrderApi.syncAllReportDataByOrderId({
  602. orderId: orderId.value
  603. })
  604. ElMessage.success('报表数据同步成功')
  605. // 同步成功后刷新页面数据
  606. await handleRefresh()
  607. } catch (error) {
  608. console.error('同步报表数据失败:', error)
  609. ElMessage.error('同步报表数据失败')
  610. }
  611. }
  612. // 注册事件监听
  613. emitter.on('equipment-edit-success', handleEquipmentEditSuccess)
  614. // 组件卸载时移除监听器,避免重复调用
  615. onUnmounted(() => {
  616. emitter.off('equipment-edit-success', handleEquipmentEditSuccess)
  617. })
  618. watch(() => route.path, async () => {
  619. if (route.path === '/gdjy/my-task/pipe/detail') {
  620. const idFromQuery = route.query.id
  621. if (typeof idFromQuery === 'string' && idFromQuery) {
  622. taskId.value = idFromQuery
  623. await fetchTaskDetail(idFromQuery)
  624. await getOrderHistoryVersion(idFromQuery)
  625. } else {
  626. taskInfo.value = null
  627. loading.value = false
  628. }
  629. }
  630. })
  631. // 设备档案弹窗
  632. const EquipPipeFormRef = ref<InstanceType<typeof EquipPipeForm>>()
  633. const equipmentSelectDialogVisible = ref(false)
  634. const equipmentList = ref<any[]>([])
  635. const handleViewEquipment = async () => {
  636. try {
  637. // 获取订单关联的所有设备
  638. const res = await PipeTaskOrderApi.getEquipsByOrderId(orderId.value)
  639. equipmentList.value = res || []
  640. if (equipmentList.value.length === 0) {
  641. ElMessage.warning('暂无设备信息')
  642. return
  643. }
  644. // 如果只有一个设备,直接跳转到编辑页
  645. if (equipmentList.value.length === 1) {
  646. router.push({
  647. name: 'PipeEquipmentForm',
  648. query: {
  649. type: 'update',
  650. id: equipmentList.value[0].id
  651. }
  652. })
  653. } else {
  654. // 如果有多个设备,显示选择弹窗
  655. equipmentSelectDialogVisible.value = true
  656. }
  657. } catch (error) {
  658. console.error('获取设备列表失败:', error)
  659. ElMessage.error('获取设备列表失败')
  660. }
  661. }
  662. const handleEquipmentClick = (equip: any) => {
  663. equipmentSelectDialogVisible.value = false
  664. // 延时300ms后再跳转,让弹窗关闭动画完成
  665. setTimeout(() => {
  666. router.push({
  667. name: 'PipeEquipmentForm',
  668. query: {
  669. type: 'update',
  670. id: equip.id
  671. }
  672. })
  673. }, 300)
  674. }
  675. const toTaskOrderDetail = () => {
  676. isFullscreen.value = false
  677. router.push({
  678. name: 'PipeTaskOrderView',
  679. query: {
  680. id: orderId.value,
  681. type: 'checker'
  682. }
  683. })
  684. }
  685. const handleSyncReportData = async () => {
  686. try {
  687. const response = await PipeTaskOrderApi.syncAllReportData({
  688. refId: selectedItem.value.id,
  689. })
  690. if (response){
  691. ElMessage.success('同步数据成功')
  692. handleRefresh()
  693. }else{
  694. ElMessage.error('同步数据失败,请稍后重试')
  695. }
  696. } catch (error: any) {
  697. console.error('同步数据失败:', error)
  698. ElMessage.error('同步数据失败,请稍后重试')
  699. }
  700. }
  701. //新加功能
  702. const {id: userId = ''} = userStore.getUser || {}
  703. const isMainChecker = () => {
  704. const {mainCheckerUser} = taskOrderItem.value
  705. const {id: mainCheckerUserId = ''} = mainCheckerUser || {}
  706. return mainCheckerUserId === userId
  707. }
  708. const isShowOnlyMyItems = ref(false)
  709. const isShowConcludeItems = ref(false)
  710. const handleShowOnlyMyItems = (value) => {
  711. isShowOnlyMyItems.value = value
  712. getReportList()
  713. }
  714. const handleShowConcludeItemsItems = (value) => {
  715. isShowConcludeItems.value = value
  716. getReportList()
  717. }
  718. const getReportList = (activeReportId = '')=>{
  719. // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
  720. if(!isShowOnlyMyItems.value){
  721. reportList.value = isShowConcludeItems.value ? filteredReportList.value.map( item => item )?.filter(
  722. (item) => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END) : filteredReportList.value.map( item => item )
  723. } else {
  724. const documentTypes = [
  725. PressureReportType.WORKINSTRUCTION,
  726. PressureReportType.INSPECTIONPLAN,
  727. PressureReportType.MAINQUESTION
  728. ]
  729. reportList.value = filteredReportList.value.filter(v=> {
  730. if(documentTypes.includes(v.reportType as number)){
  731. return true
  732. }
  733. return v.checkUsers ? v.checkUsers?.some(member=> member?.id === userId) : false
  734. })
  735. if (isShowConcludeItems.value) {
  736. reportList.value = reportList.value?.filter(item => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END)
  737. }
  738. }
  739. // 智能选择:保持当前选中项目或选择第一个项目
  740. if (reportList.value.length > 0) {
  741. let targetItem: ReportItemVO | undefined = undefined
  742. // 如果当前有选中项目,尝试在新列表中找到相同的项目
  743. if (selectedItem.value) {
  744. targetItem = reportList.value.find((item) => item.id === selectedItem.value?.id)
  745. }
  746. if(activeReportId) {
  747. targetItem = reportList.value.find((item) => item.id === activeReportId)
  748. }
  749. // 如果当前选中项目不存在于新列表中,或者没有选中项目,选择第一个项目
  750. if (!targetItem) {
  751. targetItem = reportList.value[0]
  752. }
  753. selectedItem.value = targetItem as ReportItemVO
  754. }
  755. }
  756. const filteredReportList = ref<ReportItemVO[]>([])
  757. const filteredConcludeReportList = ref<ReportItemVO[]>([])
  758. // 获取任务详情数据
  759. async function fetchTaskDetail(id: string, activeReportId = '') {
  760. loading.value = true
  761. try {
  762. const response = await PipeTaskOrderApi.getTaskOrderOrderItem(id)
  763. taskInfo.value = response.taskOrder
  764. checkUsers.value = response.checkUsers
  765. taskOrderItem.value = response.taskOrderItem || {}
  766. const showAllReport = route.query?.showAllReport //如果是所有报告,就不根据检验员过滤
  767. //暂不需要默认只看我的
  768. //if(!showAllReport) {isShowOnlyMyItems.value = !isMainChecker()}
  769. isShowConcludeItems.value = false
  770. // 过滤报告列表 - 只显示状态 >= CONFIRMED 的项目 已认领 400
  771. filteredReportList.value = response.reportList.filter(
  772. (item) => item.taskStatus >= PressureCheckerMyTaskStatus.CONFIRMED
  773. ).filter(v=> {
  774. // 如果是检验方案和指导书
  775. if([600].includes(v.reportType as number)){
  776. return [200].includes(v.status)
  777. } else if( [500].includes(v.reportType as number)){
  778. return [200].includes(v.status)
  779. }
  780. // if([700].includes(v.reportType as number)){
  781. // return [200, 400].includes(v.status)
  782. // }
  783. return true
  784. })//.reverse()
  785. filteredConcludeReportList.value = response.reportList.filter(
  786. (item) => item.taskStatus >= PressureCheckerMyTaskStatus.REPORT_END
  787. ).filter(v=> {
  788. // 如果是检验方案和指导书
  789. if([600].includes(v.reportType as number)){
  790. return [200].includes(v.status)
  791. } else if( [500].includes(v.reportType as number)){
  792. return [200].includes(v.status)
  793. }
  794. // if([700].includes(v.reportType as number)){
  795. // return [200, 400].includes(v.status)
  796. // }
  797. return true
  798. })//.reverse()
  799. allReportList.value = response.reportList.filter(v=> {
  800. // 如果是检验方案和指导书
  801. if([600, 700].includes(v.reportType as number)){
  802. return [200, 400].includes(v.status)
  803. } else if([500].includes(v.reportType as number)){
  804. return [200].includes(v.status)
  805. }
  806. return true
  807. })//.reverse()
  808. // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
  809. getReportList(activeReportId)
  810. } catch (error) {
  811. console.error('获取任务详情失败:', error)
  812. ElMessage.error('获取任务详情失败')
  813. taskInfo.value = {} as PipeTaskOrderDetailVO
  814. reportList.value = []
  815. } finally {
  816. loading.value = false
  817. }
  818. }
  819. </script>
  820. <style lang="scss" scoped>
  821. .task-detail-layout {
  822. width: 100%;
  823. height: calc(
  824. 97vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height)
  825. ) !important;
  826. overflow: hidden;
  827. border: 1px solid var(--el-border-color);
  828. position: relative;
  829. transition: all 0.3s ease;
  830. &.fullscreen-mode {
  831. position: fixed;
  832. top: 0;
  833. left: 0;
  834. width: 100vw !important;
  835. height: 100vh !important;
  836. z-index: 500;
  837. background: var(--el-bg-color);
  838. }
  839. .task-detail-container {
  840. align-items: stretch;
  841. height: calc(100% - 50px);
  842. }
  843. .detail-header {
  844. height: 42px;
  845. padding: 8px 14px;
  846. border-bottom: 1px solid var(--el-border-color);
  847. display: flex;
  848. justify-content: flex-start;
  849. align-items: center;
  850. &-back {
  851. flex: 1;
  852. display: flex;
  853. justify-content: flex-end;
  854. }
  855. }
  856. .left-panel {
  857. height: 100%;
  858. border: 1px solid var(--el-border-color);
  859. border-width: 0 1px 0 0;
  860. .v-content-wrap {
  861. border: 0;
  862. }
  863. :deep(.el-card__header) {
  864. .items-center {
  865. display: flex;
  866. justify-content: space-between;
  867. align-items: center;
  868. flex-wrap: wrap;
  869. gap: 10px;
  870. }
  871. .font-700 {
  872. font-weight: normal;
  873. }
  874. .pl-20px {
  875. flex-grow: unset;
  876. padding-left: 0;
  877. }
  878. }
  879. }
  880. .right-panel {
  881. height: 100%;
  882. padding: 10px 10px 0;
  883. box-sizing: border-box;
  884. }
  885. }
  886. .checker-box {
  887. display: flex;
  888. flex-direction: row;
  889. align-items: center;
  890. gap: 12px;
  891. }
  892. .equipment-list {
  893. max-height: 400px;
  894. overflow-y: auto;
  895. }
  896. .equipment-item {
  897. padding: 12px 16px;
  898. margin-bottom: 8px;
  899. border: 1px solid var(--el-border-color);
  900. border-radius: 4px;
  901. cursor: pointer;
  902. transition: all 0.3s;
  903. &:hover {
  904. background-color: var(--el-color-primary-light-9);
  905. border-color: var(--el-color-primary);
  906. color: var(--el-color-primary);
  907. }
  908. &:last-child {
  909. margin-bottom: 0;
  910. }
  911. }
  912. </style>