taskDetail.vue 31 KB

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