AuditCheckRecord.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129
  1. <template>
  2. <div class="template-edit">
  3. <!-- 批量校核校核显示 -->
  4. <div class="btn-group" v-if="isBatch">
  5. <div class="left">
  6. <el-button
  7. v-for="report in getRecheckList"
  8. :type="currentSelectedReport.reportId == report.reportId ? 'primary' : 'default'"
  9. :key="report.reportId"
  10. @click="() => handleSelectedReport(report)"
  11. plain
  12. >{{ report.reportName }}</el-button>
  13. </div>
  14. <div class="right">
  15. <template v-if="pageType === 'check'">
  16. <el-button type="primary" v-if="equipId && route.name !== 'InspectionPlanAudit'" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
  17. <el-button type="primary" v-if="showViewFile" plain @click="handleUploadItem">查看附件</el-button>
  18. <el-button type="success" @click="handlePass">通过</el-button>
  19. <el-button type="primary" @click="handleRevert">回退</el-button>
  20. </template>
  21. <el-button type="default" @click="handleClose">取消</el-button>
  22. </div>
  23. </div>
  24. <!-- 批量校核校核显示 -->
  25. <div class="audit-container" :style="{}"> <!-- 批量校核单独校核的高度不一样 -->
  26. <div ref="auditCheckRecordRef" class="content" v-loading="loading">
  27. <!-- <el-scrollbar :height="contentHeight">-->
  28. <!-- <VuePdfEmbed-->
  29. <!-- :height="500"-->
  30. <!-- :width="contentWidth"-->
  31. <!-- :source="recordSource"-->
  32. <!-- v-if="recordSource"-->
  33. <!-- :text-layer="false"-->
  34. <!-- :annotation-layer="false"-->
  35. <!-- @rendered="handlePdfRendered"-->
  36. <!-- />-->
  37. <!-- </el-scrollbar>-->
  38. <SpreadViewer :initData="initData" ref="spreadRef"/>
  39. </div>
  40. <!-- <SpreadDesigner-->
  41. <!-- ref="spreadDesignerRef"-->
  42. <!-- :fullScreen="true"-->
  43. <!-- :showFullScreenOperationBtn="false"-->
  44. <!-- @init="handleDesignerInit"-->
  45. <!-- style="display: none"-->
  46. <!-- />-->
  47. <div class="operation-panel">
  48. <!-- 单独校核显示 -->
  49. <div class="default-toolbar" v-if="!isBatch">
  50. <template v-if="pageType === 'check'">
  51. <el-button type="primary" v-if="equipId && route.name !== 'InspectionPlanAudit'" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
  52. <el-button type="success" @click="handlePass">通过</el-button>
  53. <el-button type="primary" @click="handleRevert">回退</el-button>
  54. </template>
  55. <el-button type="primary" v-if="showViewFile" plain @click="handleUploadItem">查看附件</el-button>
  56. <el-button type="default" @click="handleClose">取消</el-button>
  57. </div>
  58. <!-- 单独校核显示 end -->
  59. <div class="operation-inner">
  60. <!-- 流转记录 -->
  61. <div class="operation-item">
  62. <div class="item-header"> 回退原因 </div>
  63. <div class="item-content">
  64. <el-form
  65. ref="returnFormRef"
  66. :model="returnForm"
  67. :rules="returnFormRules"
  68. label-position="right"
  69. label-width="80px"
  70. >
  71. <!-- 批量校核 -->
  72. <el-form-item label="退回报告" v-if="isBatch">
  73. <el-select
  74. v-model="selectedRollBackIds"
  75. placeholder="请选择退回报告"
  76. style="width: 100%"
  77. multiple
  78. clearable
  79. >
  80. <el-option v-for="item in getRecheckList" :key="item.reportId" :value="item.reportId" :label="item.reportName"/>
  81. </el-select>
  82. </el-form-item>
  83. <el-form-item label="退回阶段" v-if="showRollbackStage">
  84. <el-select
  85. v-model="selectedRollbackStatus"
  86. placeholder="请选择回退阶段"
  87. style="width: 100%"
  88. >
  89. <el-option label="记录录入" :value="PressureCheckerMyTaskStatus.RECORD_INPUT" />
  90. </el-select>
  91. </el-form-item>
  92. <el-form-item label="回退原因" prop="reason">
  93. <el-input
  94. v-model="returnForm.reason"
  95. type="textarea"
  96. :rows="5"
  97. maxlength="100"
  98. placeholder="请输入回退原因"
  99. />
  100. </el-form-item>
  101. </el-form>
  102. </div>
  103. </div>
  104. <div class="operation-item">
  105. <div class="item-header"> 流转记录 </div>
  106. <div class="item-content">
  107. <el-empty v-if="!recordList" description="暂无流转记录" :image-size="120" />
  108. <SmartTable
  109. v-else
  110. ref="smartTableRef"
  111. v-model:pageNo="pageNo"
  112. v-model:pageSize="pageSize"
  113. v-model:total="total"
  114. v-model:columns="columns"
  115. :showRefresh="false"
  116. :useBorderLayout="false"
  117. :data="recordList"
  118. :buttons="[]"
  119. :isPagination="total > 0"
  120. @on-page-no-change="() => fetchRecordList()"
  121. @on-page-size-change="() => fetchRecordList()"
  122. />
  123. </div>
  124. </div>
  125. </div>
  126. </div>
  127. <Dialog v-model="isShowAuditDialog" width="400" title="请选择审核人">
  128. <CustomLargeListSelect
  129. v-model="form.recheckUser"
  130. :fetchFunc="getUserList"
  131. label-key="nickname"
  132. value-key="id"
  133. searchKeyProp="nickName"
  134. @change="handleChangeEntrustUnit"
  135. />
  136. <template #footer>
  137. <el-button type="primary" @click="handleAuditSelectConfirm">保 存</el-button>
  138. <el-button @click="handleAuditSelectCancel">取 消</el-button>
  139. </template>
  140. </Dialog>
  141. </div>
  142. </div>
  143. <!-- 设备档案弹窗 -->
  144. <EquipBoilerForm ref="EquipBoilerFormRef" />
  145. <ReportListUploadModal ref="reportListUploadModalRef" :selectedItem="currentReport" :reportList="allReportList" />
  146. <CustomDialog
  147. ref="customUnitDialogRef"
  148. title="选择审批人"
  149. v-model="approvalUserVisible"
  150. width="650px"
  151. :dialogAttrs="{ zIndex: 10006 }"
  152. >
  153. <el-form ref="approveFormRef" :model="approveFormData" :rules="approveFormRules" label-width="135px">
  154. <el-form-item label="审批人" prop="inspectionApproveId">
  155. <el-select v-model="approveFormData.inspectionApproveId" clearable placeholder="请选择">
  156. <el-option
  157. v-for="item in optionList.schemeApproveList.list"
  158. :key="item.id"
  159. :label="item.nickname"
  160. :value="item.id"
  161. />
  162. </el-select>
  163. </el-form-item>
  164. </el-form>
  165. <template #footer>
  166. <el-button @click="approvalUserVisible = false">取消</el-button>
  167. <el-button type="primary" @click="handleSubmitUser">确定</el-button>
  168. </template>
  169. </CustomDialog>
  170. </template>
  171. <script setup lang="tsx">
  172. // const RejectDialog = defineAsyncComponent(() => import('@/views/pressure/components/RejectDialog.vue'))
  173. import SmartTable from '@/components/SmartTable/SmartTable'
  174. import VuePdfEmbed from 'vue-pdf-embed'
  175. import 'vue-pdf-embed/dist/styles/annotationLayer.css'
  176. import 'vue-pdf-embed/dist/styles/textLayer.css'
  177. import { debounce, update } from 'lodash-es'
  178. import { BoilerTaskOrderApi, ReportItemVO } from '@/api/pressure2/boilertaskorder'
  179. import { getUserList } from '@/api/common/user'
  180. import { useRoute } from 'vue-router'
  181. import dayjs from 'dayjs'
  182. import {
  183. PressureCheckerMyTaskStatusMap,
  184. PressureCheckerMyTaskStatus,
  185. PressureTaskOrderStatusMap,
  186. PressureCheckerRecheckStatusMap,
  187. PressureReportType
  188. } from '@/utils/constants'
  189. import * as UserApi from '@/api/system/user'
  190. import { uploadFile } from '@/api/common/index'
  191. import { ElMessageBox } from 'element-plus'
  192. import ReportListUploadModal from '@/views/pressure2/boilerchecker/components/reportListUploadModal.vue'
  193. import EquipBoilerForm from '@/components/EquipBoilerForm/index.vue'
  194. import * as GC from "@grapecity-software/spread-sheets";
  195. import {DynamicTbValApi} from "@/api/pressure2/dynamictbval";
  196. import {ref, watch} from "vue";
  197. import {getPDF, getStandardTemplateInfo} from "@/api/laboratory/standard/template";
  198. import {buildFileUrl} from "@/utils";
  199. import axios from "axios";
  200. import SpreadDesigner from "@/components/SpreadDesigner/index.vue";
  201. import {editReport} from "@/utils/reportUtil";
  202. import {DynamicTbColApi} from "@/api/pressure2/dynamictbcol";
  203. import {InitParams} from "@/components/DynamicReport/SpreadInterface";
  204. import {PipeTaskOrderApi} from "@/api/pressure2/pipetaskorder";
  205. import {SpreadViewer} from "@/components/DynamicReport";
  206. const route = useRoute()
  207. const routeNameTypes = {
  208. 'BoilerCheckerRecordCheck': '记录校核审核',
  209. 'AuditInspectionCommentsNotice': '检验意见通知书审核',
  210. 'majorIssueCluesNotice': '重大线索通知审核'
  211. }
  212. const getRouteName = computed(() => route.name)
  213. // 校核人选择对话框状态
  214. const isShowAuditDialog = ref(false)
  215. const approveFormRef = ref()
  216. const approveFormData = ref<Recordable>({})
  217. const approveFormRules = reactive({
  218. inspectionApproveId: [{ required: true, message: '请选择批准人', trigger: 'change' }]
  219. })
  220. const approvalUserVisible = ref(false)
  221. const optionList = reactive<{
  222. schemeApproveList: Recordable[]
  223. }>({
  224. schemeApproveList: []
  225. })
  226. const form = ref<Record<string, any>>({
  227. recheckUser: {}
  228. })
  229. const props = defineProps({
  230. visible: {
  231. type: Boolean
  232. },
  233. reportUrl: {
  234. type: String,
  235. default: ''
  236. },
  237. recheckInfo: {
  238. type: Object,
  239. default: () => ({})
  240. },
  241. pageType: {
  242. type: String,
  243. default: 'check'
  244. },
  245. auditId: {
  246. type: String,
  247. default: ''
  248. },
  249. id: {
  250. type: String,
  251. default: ''
  252. },
  253. equipId: { // 设备Id
  254. type: String,
  255. default: ''
  256. },
  257. apiParams: {
  258. required: true,
  259. type: Object,
  260. default: () => ({})
  261. },
  262. showRollbackStage: {
  263. type: Boolean,
  264. default: true
  265. },
  266. rejectFn: { // 回退接口
  267. required: true,
  268. type: Function,
  269. default: () => {}
  270. },
  271. passFn: { // 通过审核接口
  272. required: true,
  273. type: Function,
  274. default: () => {}
  275. },
  276. recordFn: { // 流转记录接口
  277. required: true,
  278. type: Function,
  279. default: () => {}
  280. },
  281. reportType: {
  282. required: true,
  283. type: Number,
  284. default: undefined
  285. },
  286. templateId:{
  287. required: true,
  288. type: String,
  289. default: ''
  290. },
  291. useType:{
  292. required: true,
  293. type: String,
  294. default: ''
  295. },
  296. manualUrl:{
  297. required: true,
  298. type: String,
  299. default: ''
  300. },
  301. rowInfo:{
  302. required: true,
  303. type: Object,
  304. default: () => ({})
  305. }
  306. })
  307. const emits = defineEmits(['update:visible', 'close', 'update', 'update:recheckInfo'])
  308. const loading = ref(true)
  309. const recordSource = ref('')
  310. const flag = ref<boolean>(true)
  311. // 报告附件信息
  312. const allReportList = ref<ReportItemVO[]>([])
  313. const currentReport = ref<ReportItemVO>()
  314. const template = ref('')
  315. const currentSelectedReport = ref({})
  316. const getRecheckList = computed(() => {
  317. currentSelectedReport.value = props.recheckInfo?.recheckList && props.recheckInfo?.recheckList.length ? props.recheckInfo?.recheckList[0] : {}
  318. return props.recheckInfo?.recheckList
  319. })
  320. const isBatch = computed(() => !!getRecheckList.value && getRecheckList.value.length > 1)
  321. // 加载pdf
  322. let previewSpread = null
  323. const pdfLoad = ref(true)
  324. const fetchTemplateData = async () => {
  325. let res
  326. let apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
  327. switch(props.reportType) {
  328. case PressureReportType['WORKINSTRUCTION']:
  329. case PressureReportType['INSPECTIONPLAN']:
  330. case PressureReportType['MAINQUESTION']:
  331. case PressureReportType['SUGGUESTION']:
  332. res = {templateId: props.templateId}
  333. break
  334. default:
  335. res = await BoilerTaskOrderApi.getTaskOrderItemReportRecord(apiParamsId)
  336. }
  337. template.value = res.templateId
  338. const templateRes = await getStandardTemplateInfo({id: res.templateId})
  339. if (templateRes && templateRes.fileUrl) {
  340. const fileUrl = buildFileUrl(templateRes.fileUrl)
  341. const response = await axios.get(fileUrl, {responseType: 'blob'});
  342. return new Blob([response.data], {type: response.headers['content-type']});
  343. }
  344. return null
  345. }
  346. const handleDesignerInit = async (instance) => {
  347. try {
  348. previewSpread = instance.getWorkbook()
  349. let blob = await fetchTemplateData();
  350. if (!blob) return;
  351. let apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
  352. previewSpread.open(blob, async () => {
  353. console.log('预览加载完成');
  354. await DynamicTbValApi.getDynamicTbValByRefId(apiParamsId).then(async res => {
  355. let sheet1 = previewSpread.sheets[0];
  356. for (let i = 0; i < previewSpread.sheets.length; i++) {
  357. let dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource({});
  358. if (res && res.length) {
  359. // 设置数据
  360. let sheetData = {};
  361. res.forEach(i => sheetData[i.colCode] = i.valValue);
  362. dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource(sheetData);
  363. }
  364. sheet1.setDataSource(dataSource1);
  365. let imgCol = await DynamicTbColApi.getImgDynamicTbColByTbId(template.value)
  366. imgCol = imgCol.map(i => i.colCode)
  367. await editReport(sheet1,null,imgCol)
  368. }
  369. })
  370. loading.value = false
  371. pdfLoad.value = !pdfLoad.value
  372. }, (error) => {
  373. console.error('文件打开失败:', error);
  374. });
  375. } catch (error) {
  376. console.error('预览加载失败:', error);
  377. }
  378. }
  379. const savePreview = () => {
  380. const formData = new FormData()
  381. previewSpread.save(async function (blob) {
  382. formData.append('file', blob)
  383. const response = await getPDF(formData)
  384. if (response) {
  385. const flow = new Blob([response], { type: 'application/pdf' })
  386. recordSource.value = URL.createObjectURL(flow)
  387. }
  388. }, (e) => {
  389. }, {
  390. includeBindingSource: true
  391. })
  392. }
  393. // watch(pdfLoad, async (newValue) => {
  394. // savePreview()
  395. // })
  396. // 获取报告pdf
  397. /*watch(() => [props.reportUrl, props.reportType, getRecheckList.value], async ([reportUrl, reportType, getRecheckList]) => {
  398. // 批量不走以下逻辑
  399. if(!reportUrl || getRecheckList?.length > 0) return
  400. switch(reportType) {
  401. case PressureReportType['WORKINSTRUCTION']:
  402. case PressureReportType['INSPECTIONPLAN']:
  403. case PressureReportType['MAINQUESTION']:
  404. recordSource.value = props.reportUrl
  405. break
  406. case PressureReportType['SUGGUESTION']:
  407. default:
  408. // const url = buildFileUrl(props.reportUrl)
  409. const data = {
  410. fileType: 200,
  411. type: 100,
  412. reportId: props.auditId
  413. }
  414. const pdfBlob = await BoilerTaskOrderApi.getReportPreview({...data})
  415. recordSource.value = URL.createObjectURL(pdfBlob)
  416. }
  417. }, {
  418. immediate: true
  419. })*/
  420. // 切换校核报告
  421. const handleSelectedReport = (report) => {
  422. currentSelectedReport.value = report
  423. }
  424. /* watch(() => currentSelectedReport.value, async (currentSelectedReport) => {
  425. if(!Object.keys(currentSelectedReport).length) return
  426. const data = {
  427. fileType: 200,
  428. type: 100,
  429. reportId: currentSelectedReport?.reportId
  430. }
  431. const pdfBlob = await BoilerTaskOrderApi.getReportPreview({...data})
  432. recordSource.value = URL.createObjectURL(pdfBlob)
  433. }, {
  434. immediate: true
  435. })
  436. */
  437. const spreadRef=ref();
  438. const initData=ref<InitParams>(
  439. {
  440. templateId: '',
  441. refId: '',
  442. refName:'',
  443. insId:'',
  444. opType: 1, // 0:excel,1: pdf
  445. manualUrl: '',
  446. });
  447. const initPreview = async ()=>{
  448. // const reportId = props.selectedItem?.id;
  449. // const refId = showReportPdfType.value === 'report' ? "report_" + reportId :
  450. // showReportPdfType.value === 'result' ? "result_" + reportId : reportId
  451. // const templateId = showReportPdfType.value === 'report' ? templateParams.value.reportTemplateId :
  452. // showReportPdfType.value === 'result' ? templateParams.value.resultTemplateId : templateParams.value.templateId
  453. let res
  454. let apiParamsId;
  455. switch(props.reportType) {
  456. case PressureReportType['SUGGUESTION']:
  457. apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
  458. break
  459. case PressureReportType['WORKINSTRUCTION']:
  460. case PressureReportType['INSPECTIONPLAN']:
  461. case PressureReportType['MAINQUESTION']:
  462. default:
  463. apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
  464. break
  465. }
  466. // if (props.useType === 'checkNotice'){
  467. // apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
  468. // }else{
  469. // apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
  470. // }
  471. //let apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
  472. switch(props.reportType) {
  473. case PressureReportType['WORKINSTRUCTION']:
  474. case PressureReportType['INSPECTIONPLAN']:
  475. case PressureReportType['MAINQUESTION']:
  476. case PressureReportType['SUGGUESTION']:
  477. res = {templateId: props.templateId}
  478. break
  479. default:
  480. res = await BoilerTaskOrderApi.getTaskOrderItemReportRecord(apiParamsId)
  481. }
  482. template.value = res.templateId
  483. initData.value.templateId = template.value;
  484. initData.value.refId = apiParamsId;
  485. initData.value.opType = 1;
  486. if (props.reportType === PressureReportType['INSPECTIONPLAN']){
  487. initData.value.manualUrl = props.manualUrl;
  488. }
  489. //spreadRef.value?.reloadView();
  490. setTimeout(()=>{
  491. spreadRef.value?.reloadView();
  492. },50)
  493. console.log('initPreview', initData.value)
  494. }
  495. const handlePassFn = ()=>{
  496. ElMessageBox.confirm(isBatch.value ? '确定批量通过吗?' : '确定通过吗?', '提示', {
  497. confirmButtonText: '确定',
  498. cancelButtonText: '取消',
  499. type: 'warning'
  500. }).then(async () => {
  501. // TODO: 批量通过
  502. let params = {}
  503. if(getRecheckList.value && getRecheckList.value?.length) {
  504. params = {
  505. ids: getRecheckList.value.map(item => item.reportId)
  506. }
  507. } else {
  508. params = props.apiParams
  509. }
  510. if([PressureReportType.INSPECTIONPLAN,PressureReportType.SUGGUESTION].includes(props.reportType) && props.rowInfo?.currentNode?.includes("审核")) {
  511. params = {
  512. ...params,
  513. ratifyId: approveFormData.value.inspectionApproveId,
  514. }
  515. }
  516. const passResult = await props.passFn(params)
  517. // 校核通过后,判断当前报告是否为主报告
  518. // 如果是,查询主报告是否有子报告: 1、有子报告的条件下,是否所有子报告都办结;2、没有子报告 这两种条件下都可以直接提交审核
  519. // 否:直接关闭校核详情页面
  520. console.log(passResult);
  521. if(passResult && props.reportType !== PressureReportType['MAIN']) {
  522. ElMessage.success('通过成功')
  523. emits('update')
  524. handleClose()
  525. } else if(passResult && props.reportType === PressureReportType['MAIN']) {
  526. // 查询主报告下的所有子报告
  527. const getSubReportList = await handleCheckoutAllSubReport()
  528. if(getSubReportList === false) return
  529. if(!getSubReportList || !getSubReportList?.length) {
  530. //handleSubmitToApproval('该设备所有子报告已办结,是否提交至报告审核?')
  531. ElMessage.success('通过成功')
  532. emits('update')
  533. handleClose()
  534. } else {
  535. // 查询未办结的报告列表
  536. const unFinishList = getSubReportList.filter(report => report.taskStatus !== PressureCheckerMyTaskStatus.CANCELLATION && report.taskStatus !== PressureCheckerMyTaskStatus.REPORT_END)
  537. if(!unFinishList.length) {
  538. // 没有未办结的子报告,可以提交审核
  539. handleSubmitToApproval('该设备所有子报告已办结,是否提交至报告审核?')
  540. } else {
  541. // 存在未办结的子报告,关闭校核页面
  542. ElMessage.success('通过成功')
  543. emits('update')
  544. handleClose()
  545. }
  546. }
  547. } else {
  548. ElMessage.error('出错啦!')
  549. }
  550. }).catch(() => {
  551. console.log('用户取消操作!')
  552. })
  553. }
  554. // 通过
  555. const handlePass = () => {
  556. const canSubmit = canSubmitMainReportType()
  557. if(!canSubmit) return
  558. //检验方案审批需要选择校核人
  559. if ([PressureReportType.INSPECTIONPLAN,PressureReportType.SUGGUESTION].includes(props.reportType) && props.rowInfo?.currentNode?.includes("审核")){
  560. getSchemeReviewerList()
  561. return;
  562. }
  563. handlePassFn()
  564. }
  565. // 主报告提交至报告审核环节
  566. const handleSubmitToApproval = (title) => {
  567. ElMessageBox.confirm(title, '提示', {
  568. confirmButtonText: '确定',
  569. cancelButtonText: '取消',
  570. type: 'warning'
  571. }).then(async () => {
  572. let res = await UserApi.getApprovalDetail({}) // 判断是否有审批信息
  573. if (res && res.approveUser) {
  574. form.value = Object.assign(form.value, {recheckUser: res.approveUser})
  575. } else {
  576. form.value.recheckUser = {}
  577. }
  578. isShowAuditDialog.value = true
  579. }).catch(() => {
  580. emits('update')
  581. ElMessage.success('通过成功')
  582. handleClose()
  583. })
  584. }
  585. // 获取主报告下的所有子报告
  586. const handleCheckoutAllSubReport = async () => {
  587. try {
  588. const subReport = await BoilerTaskOrderApi.subReportApi({id: props.id})
  589. return (subReport || []).filter(report => report.taskStatus !== PressureCheckerMyTaskStatus['REPORT_END'])
  590. } catch (err) {
  591. console.error('获取主报告的子报告列表报错了')
  592. return false
  593. }
  594. }
  595. // 取消
  596. const handleClose = () => {
  597. emits('update:visible', false)
  598. emits('close')
  599. }
  600. // pdf组件渲染完成后的回调函数
  601. const handlePdfRendered = () => {
  602. loading.value = false
  603. }
  604. const contentWidth = ref(1000)
  605. const contentHeight = ref(500)
  606. const auditCheckRecordRef = ref()
  607. const handleWindowResize = debounce(() => {
  608. contentWidth.value = auditCheckRecordRef.value.clientWidth > 1000 ? 1000 : auditCheckRecordRef.value.clientWidth
  609. contentHeight.value = auditCheckRecordRef.value.clientHeight
  610. }, 100)
  611. // 获取任务详情数据
  612. const reportList = ref<ReportItemVO[]>([])
  613. async function fetchTaskDetail() {
  614. if(!props.id) return
  615. loading.value = true
  616. try {
  617. const response = await BoilerTaskOrderApi.getTaskOrderOrderItem(props.id)
  618. if(!response || !response.reportList || !Array.isArray(response.reportList)) return
  619. // 过滤报告列表 - 只显示状态 >= CONFIRMED 的项目 已认领 400
  620. const filteredReportList = response.reportList.filter(
  621. (item) => item.taskStatus >= PressureCheckerMyTaskStatus.CONFIRMED
  622. )
  623. reportList.value = filteredReportList
  624. console.log('reportList.value', reportList.value)
  625. const recheckList = props.recheckInfo?.recheckList || [{...props.apiParams, reportId: props.apiParams.id || props.auditId}]
  626. console.log('recheckList', recheckList)
  627. const reportIds = recheckList.map(v=> v.reportId)
  628. allReportList.value = response.reportList.filter(report => reportIds.includes(report.id))
  629. currentReport.value = response.reportList.find(report => report.id === (props.apiParams.id || props.auditId))
  630. console.log('allReportList', allReportList.value)
  631. console.log('currentReport', currentReport.value)
  632. } catch (error) {
  633. console.error('获取任务详情失败:', error)
  634. ElMessage.error('获取任务详情失败')
  635. reportList.value = []
  636. } finally {
  637. loading.value = false
  638. }
  639. }
  640. // 当意见通知书未办结时,主报告不能流转到后续流程(提交校核)
  641. const canSubmitMainReportType = ()=>{
  642. // 不是主报告,直接返回true
  643. if(props.reportType !== PressureReportType['MAIN']) return true
  644. // 判断检验意见通知书的状态
  645. const report = unref(reportList).find(x => x.reportType === PressureReportType['SUGGUESTION'])
  646. console.log('检验意见通知书', report)
  647. // 不存在检验意见通知书返回true
  648. if(!report) return true
  649. // 检验意见通知书已办结,返回true
  650. if(report.taskStatus === PressureCheckerMyTaskStatus['REPORT_END']) return true
  651. ElMessage.warning('该报告存在未办结检验意见通知书,无法通过校核请退回报告到记录录入')
  652. return false
  653. }
  654. onMounted(() => {
  655. contentWidth.value = auditCheckRecordRef.value.clientWidth > 1000 ? 1000 : auditCheckRecordRef.value.clientWidth
  656. contentHeight.value = auditCheckRecordRef.value.clientHeight
  657. window.addEventListener('resize', handleWindowResize);
  658. // 获取当前报告的所有子报告
  659. fetchTaskDetail()
  660. fetchTemplateData()
  661. initPreview()
  662. })
  663. onUnmounted(() => {
  664. window.removeEventListener('resize', handleWindowResize);
  665. })
  666. // 回退
  667. const selectedRollbackStatus = ref(500)
  668. const selectedRollBackIds = ref([])
  669. const returnForm = ref({
  670. reason: ''
  671. })
  672. const returnFormRules = ref({
  673. reason: [{required: true, message: '请输入回退原因', trigger: 'blur'}]
  674. })
  675. const returnFormRef = ref()
  676. const handleRevert = () => {
  677. returnFormRef.value.validate(async (valid) => {
  678. if(!valid) return ElMessage.error('请填写回退原因')
  679. const params = {
  680. ...props.apiParams,
  681. reason: returnForm.value.reason,
  682. }
  683. // 批量回退的参数
  684. if(getRecheckList.value && getRecheckList.value?.length) {
  685. delete params?.id
  686. delete params?.reason
  687. params['returnReason'] = returnForm.value.reason
  688. params['ids'] = getRecheckList.value?.length === 1 ? [getRecheckList.value[0].reportId] : selectedRollBackIds.value
  689. }
  690. props.rejectFn(params).then((returnRes)=>{
  691. if(returnRes) {
  692. if(getRecheckList.value && getRecheckList.value?.length > (params['ids']) || 0) {
  693. // 批量部分退回
  694. const unReturnList = getRecheckList.value.filter(x => !selectedRollBackIds.value.includes(x.reportId))
  695. emits('update:recheckInfo', {...props.recheckInfo, recheckList: unReturnList})
  696. selectedRollBackIds.value = []
  697. returnForm.value.reason = ''
  698. ElMessage.success(`回退成功`)
  699. return
  700. }
  701. // 批量全部退回判断 && 非批量校核逻辑
  702. emits('update')
  703. handleClose()
  704. ElMessage.success('回退成功')
  705. }
  706. })
  707. // const returnRes = await props.rejectFn(params)
  708. // if(returnRes) {
  709. // if(getRecheckList.value && getRecheckList.value?.length > (params['ids']) || 0) {
  710. // // 批量部分退回
  711. // const unReturnList = getRecheckList.value.filter(x => !selectedRollBackIds.value.includes(x.reportId))
  712. // emits('update:recheckInfo', {...props.recheckInfo, recheckList: unReturnList})
  713. // selectedRollBackIds.value = []
  714. // returnForm.value.reason = ''
  715. // ElMessage.success(`回退成功`)
  716. // return
  717. // }
  718. // // 批量全部退回判断 && 非批量校核逻辑
  719. // emits('update')
  720. // handleClose()
  721. // ElMessage.success('回退成功')
  722. // }
  723. })
  724. }
  725. // 流转记录
  726. const pageNo = ref(1)
  727. const pageSize = ref(10)
  728. const total = ref(0)
  729. const recordList = ref([])
  730. const columns = ref([
  731. {
  732. label: '环节',
  733. prop: 'process',
  734. render: (row, process) => {
  735. if(getRouteName.value === 'BoilerCheckerRecordCheck')
  736. return PressureCheckerMyTaskStatusMap[process]
  737. else
  738. return row.processName + "-" + PressureTaskOrderStatusMap[process]
  739. }
  740. },
  741. {
  742. label: '处理人',
  743. prop: '',
  744. render: (row) => {
  745. return <div>
  746. <p>{row.createUser.nickname}</p>
  747. <p>({row.createUser.employeeNo})</p>
  748. </div>
  749. }
  750. },
  751. {
  752. label: '处理时间',
  753. prop: 'createTime',
  754. render: (row, value) => {
  755. return !value ? '-' : dayjs(value).format('YYYY-MM-DD HH:mm:ss')
  756. }
  757. },
  758. {
  759. label: '结果',
  760. prop: 'result',
  761. render: (row, result) => {
  762. return formatResult(result)
  763. // return ['100', '200'].includes(result) ? resultMap[result] : !result ? '-' : PressureCheckerRecheckStatusMap[Number(result)]
  764. // return result === '100'
  765. // ?
  766. // getRouteName.value === 'AuditInspectionCommentsNotice' ? '拒绝' : '通过'
  767. // :
  768. // (
  769. // result === '200'
  770. // ?
  771. // getRouteName.value === 'AuditInspectionCommentsNotice' ? '通过' : '拒绝'
  772. // :
  773. // !result ? '-' : PressureCheckerRecheckStatusMap[Number(result)]
  774. // )
  775. }
  776. },
  777. {
  778. label: '描述',
  779. prop: 'remark',
  780. render: (row, value) => {
  781. return !value ? '-' : value
  782. }
  783. }
  784. ])
  785. const resultMap = {
  786. '100': '通过',
  787. '200': '拒绝',
  788. }
  789. // 结果格式化
  790. const formatResult = (result) => {
  791. if (result === 100 || result === '100'){
  792. return '通过'
  793. }else if (result === 200 || result === '200'){
  794. return '拒绝'
  795. }else{
  796. return result || '-'
  797. }
  798. }
  799. // 记录校核:审核
  800. const fetchRecordList = async () => {
  801. // 流转记录接口数据
  802. const params = {
  803. pageNo: pageNo.value,
  804. pageSize: pageSize.value,
  805. reportId: props.auditId
  806. }
  807. try {
  808. const response = await BoilerTaskOrderApi.getTaskOrderItemReportRecordPage(params)
  809. // 根据实际接口返回的数据结构获取数据
  810. recordList.value = response.list
  811. total.value = response.total
  812. } catch (error: any) {
  813. console.error('获取流转记录失败:', error)
  814. ElMessage.error('获取流转记录失败,请稍后重试')
  815. recordList.value = []
  816. }
  817. }
  818. const handleChangeEntrustUnit = (unit: Record<string, any>) => {
  819. form.value.recheckUser = unit
  820. }
  821. // 上传主报告文件流
  822. const handleUploadAPIReportPreviewBlob = async (submitApprovalFn) => {
  823. if(props.reportType !== PressureReportType['MAIN']) return
  824. // 获取文件流
  825. const blob = await BoilerTaskOrderApi.getReportPreview({
  826. reportId: props.apiParams.id,
  827. type: 300, // 报告模板
  828. fileType: 100 // xlsx
  829. })
  830. if(blob) {
  831. // 上传文件流
  832. const formData = new FormData()
  833. formData.append('file', blob)
  834. const response = await uploadFile(formData)
  835. // 保存报告编制的url
  836. const saveResult = await BoilerTaskOrderApi.saveReportPrepare({
  837. id: props.apiParams.id,
  838. prepareUrl: response,
  839. })
  840. if(saveResult) {
  841. console.log('上传文件流成功!')
  842. // 提交审核
  843. await submitApprovalFn()
  844. } else {
  845. console.error('上传文件流失败!')
  846. }
  847. }
  848. }
  849. const handleSubmitMainReportApproval = async () => {
  850. const submitResult = await BoilerTaskOrderApi.submitReportAudit({
  851. ...props.apiParams,
  852. approveId: form.value?.recheckUser?.id
  853. })
  854. if (submitResult) {
  855. ElMessage.success('提交审核成功!')
  856. isShowAuditDialog.value = false
  857. emits('update')
  858. // 这里可以做页面刷新
  859. handleClose()
  860. }
  861. }
  862. // 选择审核人的保存回调
  863. const handleAuditSelectConfirm = async () => {
  864. // 这里需要查询主报告文件流,模拟报告编制后,再提交审核
  865. handleUploadAPIReportPreviewBlob(handleSubmitMainReportApproval)
  866. }
  867. const handleAuditSelectCancel = async () => {
  868. isShowAuditDialog.value = false
  869. handleClose()
  870. }
  871. // 重大线索告知 && 检验意见通知书的流转记录
  872. const fetchOtherRecordList = async () => {
  873. try {
  874. // console.log(props)
  875. let apiParamsId = props.id;
  876. if ([PressureReportType.SUGGUESTION].includes(props.reportType)){
  877. apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0];
  878. }
  879. const response = await props.recordFn({id: apiParamsId, reportType: props.reportType})
  880. recordList.value = response
  881. total.value = 0
  882. } catch (error: any) {
  883. console.error('获取流转记录失败:', error)
  884. ElMessage.error('获取流转记录失败,请稍后重试')
  885. recordList.value = []
  886. }
  887. }
  888. if(getRouteName.value === 'BoilerCheckerRecordCheck') {
  889. fetchRecordList()
  890. } else {
  891. fetchOtherRecordList()
  892. }
  893. // 设备档案弹窗
  894. const EquipBoilerFormRef = ref<InstanceType<typeof EquipBoilerForm>>()
  895. const handleViewEquipment = () => {
  896. EquipBoilerFormRef.value.open(props.equipId)
  897. }
  898. // 查看附件
  899. const showViewFile = computed(() => {
  900. console.log('props.reportType', props.reportType)
  901. return props.reportType && ![PressureReportType.INSPECTIONPLAN, PressureReportType.WORKINSTRUCTION, PressureReportType.MAINQUESTION].includes(props.reportType)
  902. })
  903. const reportListUploadModalRef = ref<InstanceType<typeof ReportListUploadModal>>()
  904. const handleUploadItem = () => {
  905. reportListUploadModalRef.value?.openModal(false)
  906. }
  907. // 获取审核人信息
  908. const getSchemeReviewerList = async () => {
  909. try {
  910. optionList.schemeApproveList = await BoilerTaskOrderApi.getAuditList({
  911. roleCode: 'Boiler_Department_Head'
  912. })
  913. approvalUserVisible.value = true
  914. } catch (error) {}
  915. }
  916. const handleSubmitUser = () => {
  917. approveFormRef.value?.validate(async (valid) => {
  918. if (valid) {
  919. approvalUserVisible.value = false
  920. handlePassFn()
  921. }
  922. })
  923. }
  924. </script>
  925. <style lang="scss" scoped>
  926. .btn-group {
  927. display: flex;
  928. justify-content: space-between;
  929. padding-bottom: 12px;
  930. max-width: 100%;
  931. overflow: hidden;
  932. gap: 48px;
  933. .left {
  934. display: flex;
  935. align-items: center;
  936. flex-wrap: nowrap;
  937. flex: 1;
  938. overflow-x: auto;
  939. .title {
  940. white-space: nowrap;
  941. }
  942. }
  943. }
  944. .default-toolbar {
  945. display: flex;
  946. justify-content: flex-end;
  947. gap: 8px;
  948. margin-bottom: 20px;
  949. white-space: nowrap;
  950. ::v-deep .el-button {
  951. margin-left: 0;
  952. }
  953. }
  954. .template-edit {
  955. position: absolute;
  956. left: 0;
  957. right: 0;
  958. top: 0;
  959. bottom: 0;
  960. z-index: 1000;
  961. padding: 20px 20px 0;
  962. box-sizing: border-box;
  963. background-color: #fff;
  964. .audit-container {
  965. height: calc(100vh - 154px);
  966. display: flex;
  967. align-items: stretch;
  968. .content {
  969. height: calc(100% - 10px);
  970. display: flex;
  971. justify-content: center;
  972. padding: 10px 40px;
  973. flex: 1;
  974. border: 1px solid var(--el-border-color);
  975. // border-right: 1px solid var(--el-border-color);
  976. box-sizing: border-box;
  977. overflow-y: auto;
  978. }
  979. .operation-panel {
  980. display: flex;
  981. flex-direction: column;
  982. flex-basis: 440px;
  983. height: calc(100% - 10px);
  984. padding: 10px;
  985. box-sizing: border-box;
  986. border: 1px solid var(--el-border-color);
  987. border-left: 0;
  988. overflow: hidden;
  989. }
  990. .operation-inner {
  991. display: flex;
  992. flex-direction: column;
  993. position: relative;
  994. flex: 1;
  995. background-color: #fff;
  996. box-sizing: border-box;
  997. overflow: hidden;
  998. }
  999. .operation-item {
  1000. /*max-height: calc(100% / 3);*/
  1001. margin-bottom: 10px;
  1002. overflow-y: auto;
  1003. .item-header {
  1004. position: sticky;
  1005. top: 0;
  1006. left: 0;
  1007. z-index: 1000;
  1008. width: 100%;
  1009. height: 28px;
  1010. padding-left: 20px;
  1011. font-size: 16px;
  1012. line-height: 28px;
  1013. color: #fff;
  1014. background: #fff url('@/assets/imgs/pressure/my-task-detail-operation-bg.png') no-repeat
  1015. left top;
  1016. background-size: 100% 28px;
  1017. }
  1018. .item-content {
  1019. padding: 10px 0;
  1020. font-size: 14px;
  1021. overflow-y: auto;
  1022. box-sizing: border-box;
  1023. .el-empty {
  1024. width: 100%;
  1025. height: 150px;
  1026. padding: 0;
  1027. box-sizing: border-box;
  1028. }
  1029. :deep(.smart-table-inner) {
  1030. .toolbar {
  1031. margin: 0 !important;
  1032. }
  1033. }
  1034. }
  1035. &:first-child {
  1036. overflow: hidden;
  1037. }
  1038. &:last-child {
  1039. max-height: unset;
  1040. flex: 1;
  1041. }
  1042. }
  1043. }
  1044. // .content {
  1045. // display: flex;
  1046. // justify-content: center;
  1047. // margin-top: 20px;
  1048. // border-radius: 4px;
  1049. // border: 1px solid var(--el-border-color-light);
  1050. // background-color: #fff;
  1051. // height: calc(100% - 82px);
  1052. // overflow-y: scroll;
  1053. // overflow-x: hidden;
  1054. // .empty-text {
  1055. // width: 100%;
  1056. // height: 100%;
  1057. // display: flex;
  1058. // justify-content: center;
  1059. // align-items: center;
  1060. // font-size: 28px;
  1061. // color: rgba($color: #000000, $alpha: 0.2);
  1062. // }
  1063. // }
  1064. }
  1065. </style>