瀏覽代碼

修复合并后错误

xuzhancheng 1 周之前
父節點
當前提交
4230c98229
共有 51 個文件被更改,包括 5237 次插入1170 次删除
  1. 5 0
      yudao-ui-admin-vue3/src/api/pressure2/boilertaskorder/index.ts
  2. 13 0
      yudao-ui-admin-vue3/src/api/pressure2/pipeInput/index.ts
  3. 6 1
      yudao-ui-admin-vue3/src/api/pressure2/pipetaskorder/index.ts
  4. 214 0
      yudao-ui-admin-vue3/src/api/pressure2/standard/template.ts
  5. 7 0
      yudao-ui-admin-vue3/src/components/pressure2/BwDocEditor/index.ts
  6. 355 0
      yudao-ui-admin-vue3/src/components/pressure2/BwDocEditor/src/DocEditor.vue
  7. 16 22
      yudao-ui-admin-vue3/src/components/DynamicReport/BatchUploadFile.vue
  8. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/CreateBatchUploadImages.ts
  9. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/CustomUploadFile.vue
  10. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/ImageSelectorDialog.vue
  11. 4 6
      yudao-ui-admin-vue3/src/components/DynamicReport/SpreadEditor.vue
  12. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/SpreadInterface.ts
  13. 78 11
      yudao-ui-admin-vue3/src/components/DynamicReport/SpreadViewer.vue
  14. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/index.ts
  15. 0 0
      yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/tempIF.ts
  16. 355 0
      yudao-ui-admin-vue3/src/utils/pressure2/formatTime.ts
  17. 22 1
      yudao-ui-admin-vue3/src/views/pressure/checker/AuditInspectionCommentsNotice.vue
  18. 10 6
      yudao-ui-admin-vue3/src/views/pressure2/boilerReportCheck/index.vue
  19. 121 0
      yudao-ui-admin-vue3/src/views/pressure2/boilerReportPreparationList/BoilerReportList.vue
  20. 11 7
      yudao-ui-admin-vue3/src/views/pressure2/boilerReportPreparationList/index.vue
  21. 18 7
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/AuditCheckRecord.vue
  22. 489 0
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/BoilerAuditInspectionCommentsNotice.vue
  23. 545 114
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/InspectionItemList.vue
  24. 633 441
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/StatusOperationPanel.vue
  25. 12 2
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/useTaskProgress.ts
  26. 405 18
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/myTask.vue
  27. 188 12
      yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/taskDetail.vue
  28. 1 1
      yudao-ui-admin-vue3/src/views/pressure2/boilertaskorder/components/AddOrEditCheckItemForPart.vue
  29. 6 7
      yudao-ui-admin-vue3/src/views/pressure2/dynamictb/DynamicTbForm.vue
  30. 3 1
      yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/BoilerPlanScheduleDialog.vue
  31. 14 7
      yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/CheckerSelectBox.vue
  32. 46 6
      yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/index.vue
  33. 36 7
      yudao-ui-admin-vue3/src/views/pressure2/pipeReportCheck/index.vue
  34. 121 0
      yudao-ui-admin-vue3/src/views/pressure2/pipeReportPreparationList/PipeReportList.vue
  35. 47 8
      yudao-ui-admin-vue3/src/views/pressure2/pipeReportPreparationList/index.vue
  36. 18 6
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/AuditCheckRecord.vue
  37. 448 100
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/InspectionItemList.vue
  38. 471 260
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/StatusOperationPanel.vue
  39. 11 1
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/useTaskProgress.ts
  40. 232 18
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/myTask.vue
  41. 1 1
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/recordCheck.vue
  42. 184 11
      yudao-ui-admin-vue3/src/views/pressure2/pipechecker/taskDetail.vue
  43. 4 2
      yudao-ui-admin-vue3/src/views/pressure2/pipeequipment/components/PipeEquipmentDetailList.vue
  44. 1 1
      yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipePlanScheduleEquipDialog.vue
  45. 1 0
      yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipelineDetailList.vue
  46. 79 79
      yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/detail.vue
  47. 2 1
      yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/index.vue
  48. 1 1
      yudao-ui-admin-vue3/src/views/pressure2/pipetaskorder/components/AddOrEditCheckItemForEquipment.vue
  49. 1 2
      yudao-ui-admin-vue3/src/views/pressure2/planNew/components/PlanScheduleEquipBoilerDialog.vue
  50. 1 2
      yudao-ui-admin-vue3/src/views/pressure2/planNew/components/PlanScheduleEquipPipeDialog.vue
  51. 1 0
      yudao-ui-admin-vue3/src/views/pressure2/planNew/pipeDetail.vue

+ 5 - 0
yudao-ui-admin-vue3/src/api/pressure2/boilertaskorder/index.ts

@@ -499,6 +499,11 @@ export const BoilerTaskOrderApi = {
     return await request.put({ url: `/pressure2/boiler-task-order/syncReportData`, data })
   },
 
+  // 同步所有报表数据
+  syncAllReportData: async (data: any) => {
+    return await request.put({ url: `/pressure2/boiler-task-order/syncAllReportData`, data })
+  },
+
   /**
    * 查询检验通知书列表
    * @param params

+ 13 - 0
yudao-ui-admin-vue3/src/api/pressure2/pipeInput/index.ts

@@ -0,0 +1,13 @@
+import request from "@/config/axios";
+
+export const PipeInputApi = {
+  // 下载项目
+  handleDownload: async (data: any) => {
+    return request.download2({url: `/pressure2/pipe-task-order-input/download`, data})
+  },
+
+  // 打印项目 - 返回 PDF blob
+  handlePrint: async (data: any) => {
+    return request.download2({url: `/pressure2/pipe-task-order-input/print`, data})
+  }
+}

+ 6 - 1
yudao-ui-admin-vue3/src/api/pressure2/pipetaskorder/index.ts

@@ -275,7 +275,7 @@ export const PipeTaskOrderApi = {
   },
   // 保存报告编制
   saveReportPrepare: async (data: any) => {
-    return await request.put({ url: `/pressure2/pipe-task-order/order-item/report/prepare/save`, data })
+    return await request.put({ url: `/pressure2/pipe-task-order/order-item/report/prepare/save`, data ,timeout: 120000 })
   },
   // 提交报告审核
   submitReportAudit: async (data: any) => {
@@ -523,6 +523,11 @@ export const PipeTaskOrderApi = {
     return await request.put({ url: `/pressure2/pipe-task-order/syncReportData`, data })
   },
 
+  // 同步所有报表数据
+  syncAllReportData: async (data: any) => {
+    return await request.put({ url: `/pressure2/pipe-task-order/syncAllReportData`, data })
+  },
+
   // 通过orderID查询管道设备详情
   getEquipsByOrderId: async (orderId: string) => {
     return await request.get({ url: `/pressure2/pipe-task-order/getEquipsByOrderId?orderId=` + orderId })

+ 214 - 0
yudao-ui-admin-vue3/src/api/pressure2/standard/template.ts

@@ -0,0 +1,214 @@
+import request from '@/config/axios'
+
+interface TemplateType {
+  name: string
+  type: string
+  classId: string
+  signType: string
+  sort: number
+  status: number
+  versionNumber: string
+  bindingPathSchema: string
+  file: File
+}
+
+interface UpdateTemplateType extends TemplateType {
+  id: string
+}
+
+interface TemplateQueryParams {
+  pageNo: number;
+  pageSize: number;
+  name?: string;
+  type?: string;
+  classId?: string;
+  signType?: string;
+  sort?: number;
+  status?: number;
+  versionNumber?: string;
+  bindingPathSchema?: string;
+  file?: File;
+}
+
+export const createStandardTemplate = (data: TemplateType) => {
+  return request.post({
+    url: '/system/standard-template/create',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data
+  })
+}
+
+export const createStandardTemplateV2 = (data: TemplateType) => {
+  return request.post({
+    url: '/system/standard-template/create',
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    },
+    data
+  })
+}
+
+export const updateStandardTemplate = (data: UpdateTemplateType) => {
+  return request.put({
+    url: '/system/standard-template/update',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    data
+  })
+}
+
+export const updateStandardTemplateStatus = (data: {id: string, status: number}) => {
+  return request.put({
+    url: '/system/standard-template/updateStatus',
+    data
+  })
+}
+
+export const deleteStandardTemplate = (params: {id: string}) => {
+  return request.delete({
+    url: '/system/standard-template/delete',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    params
+  })
+}
+
+export const getStandardTemplateList = (params: TemplateQueryParams) => {
+  return request.get({
+    url: '/system/standard-template/page',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    params
+  })
+}
+
+export const exportStandardTemplateList = (params: TemplateQueryParams) => {
+  return request.get({
+    url: '/system/standard-template/export-excel',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded'
+    },
+    params
+  })
+}
+
+export const getStandardTemplateInfo = (params: {id: string}) => {
+  return request.get({
+    url: '/system/standard-template/get',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded',
+      'Response-Type': "blob"
+    },
+    params
+  })
+}
+
+export const getStandardTemplateInfoV2 = (params: {id: string}) => {
+  return request.get({
+    url: '/system/standard-template/v2/get',
+    headers: {
+      'Content-Type': 'application/x-www-form-urlencoded',
+      'Response-Type': "blob"
+    },
+    params
+  })
+}
+
+// 获得标准模版初始化数据源绑定分页
+export const getTmplateInitData = (params: {pageNo: number, pageSize: number, type: string, classId: string}) => {
+  return request.get({url: '/system/standard-template-init-data/page', params })
+}
+
+// excel转pdf
+export const getPDF = (data) => {
+  return request.download2({
+    url: '/system/standard-template/getPdf',
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+      'Accept': 'application/pdf'
+    },
+    responseType: 'blob'
+  })
+}
+// excel转pdf
+export const getPDF2 = (data) => {
+  return request.download2({
+    url: '/pressure2/standard-file/getPdf',
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+      'Accept': 'application/pdf'
+    },
+    responseType: 'blob'
+  })
+}
+// 获取文件流
+export const getFile = ( url) => {
+  return request.download({
+    url: url,
+    responseType: 'blob'
+  })
+}
+// 用于测试的接口
+export const getTestPdf = (data) => {
+  return request.download2({
+    url: '/system/standard-template/getTestPdf',
+    data,
+    headers: {
+      'Content-Type': 'multipart/form-data',
+      'Accept': 'application/pdf'
+    },
+    responseType: 'blob'
+  })
+}
+
+/**
+ * 保存字段释义列表
+ * @param id 模板ID
+ * @param bindingPathNameJson 字段释义列表
+ */
+export const updateNameSchemaJson = (data:any)=> {
+  return request.put({
+    url: '/pressure/report-template/updateBindingPathNameJson',
+    data
+  })
+}
+
+// 保存drawio画图数据
+export const saveDrawioData = (data:any) => {
+  return request.post({
+    url: '/system/template-business-parameter/create',
+    data
+  })
+}
+// 删除drawio画图数据
+export const deleteDrawioData = (params: {id: string}) => {
+  return request.delete({
+    url: '/system/template-business-parameter/delete',
+    params
+  })
+}
+// 更新drawio画图数据
+export const updateDrawioData = (data:any) => {
+  return request.put({
+    url: '/system/template-business-parameter/update',
+    data
+  })
+}
+// 查询drawio画图数据
+export const getDrawioData = (params:any) => {
+  return request.get({
+    url: '/system/template-business-parameter/page',
+    params
+  })
+}
+// 更新模板标识
+export const updateTemplateCode = (data: any) => {
+  return request.put({url: '/system/standard-template/updateCode', data})
+}

+ 7 - 0
yudao-ui-admin-vue3/src/components/pressure2/BwDocEditor/index.ts

@@ -0,0 +1,7 @@
+import DocEditor from './src/DocEditor.vue';
+
+interface DocEditorEvents {
+  closeEditor:Function;
+}
+
+export {DocEditor,DocEditorEvents};

+ 355 - 0
yudao-ui-admin-vue3/src/components/pressure2/BwDocEditor/src/DocEditor.vue

@@ -0,0 +1,355 @@
+<template>
+  <div id="bwDocumentEditor" ></div>
+</template>
+<script setup lang="ts">
+import {ref, onMounted,watch} from "vue";
+
+const documentServerUrl = import.meta.env.VITE_ONLY_OFFICE_SERVER_URL;
+onMounted(()=>{
+  if(!window.DocsAPI){
+    const script = document.createElement('script');
+    script.src = documentServerUrl+'web-apps/apps/api/documents/api.js';
+    script.onload = () => console.log('DocsAPI Script loaded and ready');
+    script.onerror = () => console.error('DocsAPI Failed to load the script');
+    document.head.appendChild(script);
+  }
+
+})
+defineOptions({ name: 'DocEditor' })
+const docEditor=ref<any>(null);
+const props = defineProps({
+  visible: {
+    type: Boolean,
+    default: false
+  },
+  config: {
+    type: Object,
+    default: ()=>({document:{key:null},events:{}})
+  }
+})
+
+const closeEditor=()=>{
+  docEditor.value?.destroyEditor();
+}
+
+defineExpose({closeEditor});
+
+const onDocumentReady=(e)=>{
+   //var oDocument = window.Api?.GetDocument();
+   //var a=window.DocsAPI;
+   emit("docReady",e);
+}
+
+watch(()=>props.config.document.key,(newVal,oldVal)=>{
+  if(oldVal){
+    if(docEditor.value){
+      docEditor.value.destroyEditor();
+    }
+  }
+   if(newVal){
+     if(window.DocsAPI){
+       let newconfig:any={...props.config,events:{onDocumentReady}};
+       /*
+        newconfig.editorConfig.customization.layout={toolbar: {
+           collaboration: {
+             mailmerge: true,
+           },
+           draw: false,
+           file: {
+             close: true,
+             info: true,
+             save: true,
+             settings: true,
+           },
+           home: {},
+           layout: true,
+           plugins: true,
+           protect: true,
+           references: true,
+           save: true,
+           view: {
+             navigation: true,
+           },
+         }};*/
+
+       const config = {
+         document: {
+           fileType: "docx",
+           isForm: true,
+           info: {
+             favorite: true,
+             folder: "Example Files",
+             owner: "John Smith",
+             sharingSettings: [
+               {
+                 permissions: "Full Access",
+                 user: "John Smith",
+               },
+               {
+                 isLink: true,
+                 permissions: "Read Only",
+                 user: "External link",
+               },
+             ],
+             uploaded: "2010-07-07 3:46 PM",
+           },
+           key: "Khirz6zTPdfd7",
+           permissions: {
+             chat: true,
+             comment: true,
+             commentGroups: [{
+               edit: ["Group2", ""],
+               remove: [""],
+               view: "",
+             }],
+             copy: true,
+             deleteCommentAuthorOnly: false,
+             download: true,
+             edit: true,
+             editCommentAuthorOnly: false,
+             fillForms: true,
+             modifyContentControl: true,
+             modifyFilter: true,
+             print: true,
+             protect: true,
+             review: true,
+             reviewGroups: ["Group1", "Group2", ""],
+             userInfoGroups: ["Group1", ""],
+           },
+           referenceData: {
+             fileKey: "BCFA2CED",
+             instanceId: "https://example.com",
+           },
+           title: "Example Document Title.docx",
+           url: "https://example.com/url-to-example-document.docx",
+         },
+         documentType: "word",
+         editorConfig: {
+           actionLink: "ACTION_DATA",
+           callbackUrl: "https://example.com/url-to-callback.ashx",
+           coEditing: {
+             mode: "fast",
+             change: true,
+           },
+           createUrl: "https://example.com/url-to-create-document/",
+           customization: {
+             about: true,
+             anonymous: {
+               request: true,
+               label: "Guest",
+             },
+             autosave: true,
+             close: {
+               visible: true,
+               text: "Close file",
+             },
+             comments: true,
+             compactHeader: false,
+             compactToolbar: false,
+             compatibleFeatures: false,
+             customer: {
+               address: "My City, 123a-45",
+               info: "Some additional information",
+               logo: "https://example.com/logo-big.png",
+               logoDark: "https://example.com/dark-logo-big.png",
+               mail: "john@example.com",
+               name: "John Smith and Co.",
+               phone: "123456789",
+               www: "example.com",
+             },
+             features: {
+               featuresTips: true,
+               roles: true,
+               spellcheck: {
+                 mode: true,
+                 change: true,
+               },
+               tabBackground: {
+                 mode: "header",
+                 change: true,
+               },
+               tabStyle: {
+                 mode: "fill",
+                 change: true,
+               },
+             },
+             feedback: {
+               url: "https://example.com",
+               visible: true,
+             },
+             font: {
+               name: "Arial",
+               size: "11px",
+             },
+             forcesave: false,
+             forceWesternFontSize: false,
+             goback: {
+               blank: true,
+               text: "Open file location",
+               url: "https://example.com",
+             },
+             help: true,
+             hideNotes: false,
+             hideRightMenu: true,
+             hideRulers: false,
+             integrationMode: "embed",
+             layout: {
+               header: {
+                 editMode: true,
+                 save: true,
+                 user: true,
+                 users: true,
+               },
+               leftMenu: {
+                 mode: true,
+                 navigation: true,
+                 spellcheck: true,
+               },
+               rightMenu: {
+                 mode: true,
+               },
+               statusBar: {
+                 actionStatus: true,
+                 docLang: true,
+                 textLang: true,
+               },
+               toolbar: {
+                 collaboration: {
+                   mailmerge: true,
+                 },
+                 draw: true,
+                 file: {
+                   close: true,
+                   info: true,
+                   save: true,
+                   settings: true,
+                 },
+                 home: {},
+                 layout: true,
+                 plugins: true,
+                 protect: true,
+                 references: true,
+                 save: true,
+                 view: {
+                   navigation: true,
+                 },
+               },
+             },
+             loaderLogo: "https://example.com/loader-logo.png",
+             loaderName: "The document is loading, please wait...",
+             logo: {
+               image: "https://example.com/logo.png",
+               imageDark: "https://example.com/dark-logo.png",
+               imageLight: "https://example.com/light-logo.png",
+               url: "https://example.com",
+               visible: true,
+             },
+             macros: true,
+             macrosMode: "warn",
+             mentionShare: true,
+             mobile: {
+               forceView: true,
+               info: false,
+               standardView: false,
+             },
+             plugins: true,
+             pointerMode: "select",
+             review: {
+               hideReviewDisplay: false,
+               showReviewChanges: false,
+               reviewDisplay: "original",
+               trackChanges: true,
+               hoverMode: false,
+             },
+             showHorizontalScroll: true,
+             showVerticalScroll: true,
+             slidePlayerBackground: "#000000",
+             submitForm: {
+               visible: true,
+               resultMessage: "text",
+             },
+             suggestFeature: true,
+             toolbarHideFileName: false,
+             uiTheme: "theme-dark",
+             unit: "cm",
+             wordHeadingsColor: "#00ff00",
+             zoom: 100,
+           },
+           embedded: {
+             embedUrl: "https://example.com/embedded?doc=exampledocument1.docx",
+             fullscreenUrl: "https://example.com/embedded?doc=exampledocument1.docx#fullscreen",
+             saveUrl: "https://example.com/download?doc=exampledocument1.docx",
+             shareUrl: "https://example.com/view?doc=exampledocument1.docx",
+             toolbarDocked: "top",
+           },
+           lang: "en",
+           mode: "edit",
+           plugins: {
+             autostart: [
+               "asc.{0616AE85-5DBE-4B6B-A0A9-455C4F1503AD}",
+               "asc.{FFE1F462-1EA2-4391-990D-4CC84940B754}",
+             ],
+             options: {
+               "all": {
+                 keyAll: "valueAll",
+               },
+               "asc.{38E022EA-AD92-45FC-B22B-49DF39746DB4}": {
+                 keyYoutube: "valueYoutube",
+               },
+             },
+             pluginsData: [
+               "https://example.com/plugin1/config.json",
+               "https://example.com/plugin2/config.json",
+             ],
+           },
+           recent: [
+             {
+               folder: "Example Files",
+               title: "exampledocument1.docx",
+               url: "https://example.com/exampledocument1.docx",
+             },
+             {
+               folder: "Example Files",
+               title: "exampledocument2.docx",
+               url: "https://example.com/exampledocument2.docx",
+             },
+           ],
+           region: "en-US",
+           templates: [
+             {
+               image: "https://example.com/exampletemplate1.png",
+               title: "exampletemplate1.docx",
+               url: "https://example.com/url-to-create-template1",
+             },
+             {
+               image: "https://example.com/exampletemplate2.png",
+               title: "exampletemplate2.docx",
+               url: "https://example.com/url-to-create-template2",
+             },
+           ],
+           user: {
+             group: "Group1,Group2",
+             id: "78e1e841",
+             image: "https://example.com/url-to-user-avatar.png",
+             name: "John Smith",
+           },
+         },
+         height: "100%",
+         token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.LwimMJA3puF3ioGeS-tfczR3370GXBZMIL-bdpu4hOU",
+         type: "desktop",
+         width: "100%",
+       };
+      console.log("打印json",JSON.stringify(config));
+      docEditor.value=new window.DocsAPI.DocEditor("bwDocumentEditor", newconfig);
+      emit("docCreate",{target:docEditor.value});
+     }
+   }
+})
+
+const emit = defineEmits(['success', 'update:visible','docReady','docCreate']) // 定义 success 事件,用于操作成功后的回调
+
+</script>
+
+<style scoped lang="scss">
+
+</style>

+ 16 - 22
yudao-ui-admin-vue3/src/components/DynamicReport/BatchUploadFile.vue

@@ -1,10 +1,10 @@
 <template>
-  <el-button type="primary" @click="handleShowUploadDialog">批量上传文件</el-button>
-  <el-drawer
+  <el-button type="primary" plain @click="handleShowUploadDialog">批量插入图片</el-button>
+  <el-dialog
     title="上传文件"
     v-model="showUploadDialog"
-    direction="rtl"
-    :size="800"
+    :width="800"
+    append-to-body
     class="upload-modal"
   >
     <div class="upload-container">
@@ -17,11 +17,13 @@
         @handleApply="handleApply"
       />
     </div>
-    <div class="dialog-footer">
-      <el-button type="primary" @click="handleTemplateImage">一键应用</el-button>
-      <el-button class="ml16" @click="handleCloseShowUploadDialog">取消</el-button>
-    </div>
-  </el-drawer>
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button type="primary" @click="handleTemplateImage">一键应用</el-button>
+        <el-button @click="handleCloseShowUploadDialog">取消</el-button>
+      </div>
+    </template>
+  </el-dialog>
 </template>
 <script lang="ts" setup>
 import CreateBatchUploadImages from './CreateBatchUploadImages'
@@ -48,6 +50,7 @@ const handleTemplateImage = async () => {
   for (const file of formData.value.filePaths) {
     await handleApply(file)
   }
+  handleCloseShowUploadDialog()
 }
 const handleApply = async (file) => {
   const base = await urlToBase64(buildFileUrl(file.url))
@@ -59,7 +62,7 @@ const handleApply = async (file) => {
   let b = file.url.indexOf('/') > -1;
   let split = b ? file.url.split('/') : file.url;
   const name = {
-    url: file.url,
+    url: b ? split[split.length - 1] : file.url,
     path: path,
     x: 100,
     y: 50,
@@ -113,23 +116,14 @@ const emit = defineEmits(['uploadImg'])
   margin-top: 20px;
 }
 
-::v-deep .el-drawer__body {
-  padding: 0 20px 100px;
+::v-deep .el-dialog__body {
+  padding: 20px;
 }
 
 .dialog-footer {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  padding: 20px 20px 20px 0;
-  z-index: 1000;
-  display: flex;
-  justify-content: center;
-  background-color: #fff;
-  width: 100%;
   display: flex;
   justify-content: flex-end;
+  gap: 16px;
 }
 
 .ml16 {

yudao-ui-admin-vue3/src/components/DynamicReport/CreateBatchUploadImages.ts → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/CreateBatchUploadImages.ts


yudao-ui-admin-vue3/src/components/DynamicReport/CustomUploadFile.vue → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/CustomUploadFile.vue


yudao-ui-admin-vue3/src/components/DynamicReport/ImageSelectorDialog.vue → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/ImageSelectorDialog.vue


+ 4 - 6
yudao-ui-admin-vue3/src/components/DynamicReport/SpreadEditor.vue

@@ -78,12 +78,10 @@ import * as GC from '@grapecity-software/spread-sheets'
 import SpreadDesigner from '@/components/SpreadDesigner/index.vue'
 import {useTagsViewStore} from '@/store/modules/tagsView'
 import {
-  updateStandardTemplate
-} from '@/api/laboratory/standard/template'
-import{
   createStandardTemplateV2,
-  getStandardTemplateInfoV2,
-} from '@/api/pressure2/comm/template'
+  getStandardTemplateInfo,
+  updateStandardTemplate
+} from '@/api/pressure2/standard/template'
 import {useRoute, useRouter} from 'vue-router'
 import {buildFileUrl} from '@/utils'
 import {DynamicTbApi} from '@/api/pressure2/dynamictb'
@@ -211,7 +209,7 @@ let isNewTemplate = false;
 // 获取模版详情
 const fetchTemplateData = async () => {
   loading.value = true
-  const templateRes = await getStandardTemplateInfoV2({ id: props.formData.id })
+  const templateRes = await getStandardTemplateInfo({ id: props.formData.id })
   const spread = designer.getWorkbook()
 
   if (templateRes && templateRes.id) {

yudao-ui-admin-vue3/src/components/DynamicReport/SpreadInterface.ts → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/SpreadInterface.ts


+ 78 - 11
yudao-ui-admin-vue3/src/components/DynamicReport/SpreadViewer.vue

@@ -1,12 +1,34 @@
 <template>
-  <div v-show="showSpread" class="lab-designer-container" v-loading="loading" >
+  <div v-show="showSpread"
+       :class="[!isFullscreen ? 'lab-designer-container' : 'lab-designer-container-fullscreen']"
+       v-loading="loading" >
     <div class="header-row">
-      <div class="title"></div>
+      <div class="title">
+        <slot name="title"></slot>
+      </div>
       <div class="unit-footer-btns">
-        <el-button type="primary" @click="handleGenerate(true)">生成续页</el-button>
-        <BatchUploadFile @uploadImg="addPic" :colList="colListData" />
-        <el-button type="primary" @click="handleSave">保存</el-button>
-        <el-button type="primary" @click="openPdf">预览PDF</el-button>
+        <el-dropdown trigger="click">
+          <el-button type="primary" plain style="margin-right: 12px;">
+            更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
+          </el-button>
+          <template #dropdown>
+            <el-dropdown-menu>
+              <el-dropdown-item @click="handleGenerate(true)">
+                <div class="dropdown-item-content">
+                  <el-button type="warning" plain style="margin-right: 12px;">生成续页</el-button>
+                </div>
+              </el-dropdown-item>
+              <el-dropdown-item>
+                <div class="dropdown-item-content">
+                  <BatchUploadFile @uploadImg="addPic" :colList="colListData" />
+                </div>
+              </el-dropdown-item>
+            </el-dropdown-menu>
+          </template>
+        </el-dropdown>
+        <el-button type="success" @click="handleSave">保存</el-button>
+        <el-button type="primary" @click="openPdf">预览 PDF</el-button>
+        <el-button type="danger" @click="close">关闭</el-button>
       </div>
     </div>
     <div v-loading="loading" ref="previewContainer" class="spread-container"></div>
@@ -35,7 +57,7 @@ import 'vue-pdf-embed/dist/styles/annotationLayer.css'
 import 'vue-pdf-embed/dist/styles/textLayer.css'
 import * as ExcelIO from "@grapecity-software/spread-excelio"
 import SpreadDesigner from '@/components/SpreadDesigner/index.vue'
-import { Printer, Download, Refresh } from '@element-plus/icons-vue'
+import { Printer, Download, Refresh, DocumentAdd, Picture, ArrowDown } from '@element-plus/icons-vue'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import {
   getTmplateInitData,
@@ -62,6 +84,11 @@ const props = defineProps({
   initData: {
     type: Object as PropType<InitParams>,
     default: ()=>({}),
+    required: true
+  },
+  isFullscreen:{
+    type: Boolean,
+    default: false,
     required: false
   }
 });
@@ -131,6 +158,10 @@ const initPreview = async () => {
   loading.value = true;
   // showSpread.value=true;
   // showPdf.value=false;
+  if (props.initData.opType == 0) {
+    showSpread.value = true;
+    pdfLoading.value = false;
+  }
 
   previewSpread?.destroy();
 
@@ -139,11 +170,11 @@ const initPreview = async () => {
     let blob = await fetchTemplateData();
     if (!blob) return;
 
-    previewSpread.open(blob, () => {
+    await previewSpread.open(blob, async () => {
       console.log('预览加载完成');
       let sheets = previewSpread.sheets;
       //DynamicTbValApi.getDynamicTbValByIns(props.instanceId)
-      DynamicTbValApi.getDynamicTbInsAndValByRefId(props.initData)
+      await DynamicTbValApi.getDynamicTbInsAndValByRefId(props.initData)
       .then(async res => {
 
         let dataSource1 = new GC.Spread.Sheets.Bindings.CellBindingSource({});
@@ -177,7 +208,7 @@ const initPreview = async () => {
         if (props.initData.opType == 1) {
           showSpread.value = false;
           pdfLoading.value = true;
-          openPdf();
+          await openPdf();
         } else {
           loading.value = false;
         }
@@ -313,6 +344,11 @@ const handleSave = () => {
     });
   }
 }
+
+const close = () => {
+  emit('close')
+}
+
 let generateData
 let sheetNum = ref(2)
 /**
@@ -320,6 +356,10 @@ let sheetNum = ref(2)
  * @param {boolean} isSetTimeout 是否设置定时器
  */
 const handleGenerate = (isSetTimeout) => {
+  if (!generateData || !generateData.sheetName){
+    ElMessage.error('请配置模板续页名称')
+    return
+  }
   const generate = async () => {
 
     const sheet = previewSpread.getSheetFromName(generateData.sheetName)
@@ -458,8 +498,23 @@ onUnmounted(()=>{
   flex-direction: column;
 
   align-items: stretch;
-  height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height));
+  /*height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 140px);*/
+  height: calc(100vh - var(--top-tool-height) - 5px );
+  .spread-designer-container {
+    width: calc(100% - 440px);
+    height: 100%;
+    padding: 0;
+  }
+}
 
+.lab-designer-container-fullscreen {
+  position: relative;
+  display: flex;
+  flex-direction: column;
+
+  align-items: stretch;
+  /*height: calc(100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) - 140px);*/
+  height: 100%;
   .spread-designer-container {
     width: calc(100% - 440px);
     height: 100%;
@@ -482,6 +537,18 @@ onUnmounted(()=>{
   color: #303133;
 }
 
+.unit-footer-btns {
+  align-items: center;
+}
+
+.dropdown-item-content {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  justify-content: center;
+  width: 100%;
+}
+
 .spread-container {
   flex: 1;
   background: #f5f7fa;

yudao-ui-admin-vue3/src/components/DynamicReport/index.ts → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/index.ts


yudao-ui-admin-vue3/src/components/DynamicReport/tempIF.ts → yudao-ui-admin-vue3/src/components/pressure2/DynamicReport/tempIF.ts


+ 355 - 0
yudao-ui-admin-vue3/src/utils/pressure2/formatTime.ts

@@ -0,0 +1,355 @@
+import dayjs from 'dayjs'
+import type { TableColumnCtx } from 'element-plus'
+
+/**
+ * 日期快捷选项适用于 el-date-picker
+ */
+export const defaultShortcuts = [
+  {
+    text: '今天',
+    value: () => {
+      return new Date()
+    }
+  },
+  {
+    text: '昨天',
+    value: () => {
+      const date = new Date()
+      date.setTime(date.getTime() - 3600 * 1000 * 24)
+      return [date, date]
+    }
+  },
+  {
+    text: '最近七天',
+    value: () => {
+      const date = new Date()
+      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
+      return [date, new Date()]
+    }
+  },
+  {
+    text: '最近 30 天',
+    value: () => {
+      const date = new Date()
+      date.setTime(date.getTime() - 3600 * 1000 * 24 * 30)
+      return [date, new Date()]
+    }
+  },
+  {
+    text: '本月',
+    value: () => {
+      const date = new Date()
+      date.setDate(1) // 设置为当前月的第一天
+      return [date, new Date()]
+    }
+  },
+  {
+    text: '今年',
+    value: () => {
+      const date = new Date()
+      return [new Date(`${date.getFullYear()}-01-01`), date]
+    }
+  }
+]
+
+/**
+ * 时间日期转换
+ * @param date 当前时间,new Date() 格式
+ * @param format 需要转换的时间格式字符串
+ * @description format 字符串随意,如 `YYYY-MM、YYYY-MM-DD`
+ * @description format 季度:"YYYY-MM-DD HH:mm:ss QQQQ"
+ * @description format 星期:"YYYY-MM-DD HH:mm:ss WWW"
+ * @description format 几周:"YYYY-MM-DD HH:mm:ss ZZZ"
+ * @description format 季度 + 星期 + 几周:"YYYY-MM-DD HH:mm:ss WWW QQQQ ZZZ"
+ * @returns 返回拼接后的时间字符串
+ */
+export function formatDate(date: Date, format?: string): string {
+  // 日期不存在,则返回空
+  if (!date) {
+    return ''
+  }
+  // 日期存在,则进行格式化
+  return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''
+}
+
+/**
+ * 获取当前的日期+时间
+ */
+export function getNowDateTime() {
+  return dayjs()
+}
+
+/**
+ * 获取当前日期是第几周
+ * @param dateTime 当前传入的日期值
+ * @returns 返回第几周数字值
+ */
+export function getWeek(dateTime: Date): number {
+  const temptTime = new Date(dateTime.getTime())
+  // 周几
+  const weekday = temptTime.getDay() || 7
+  // 周1+5天=周六
+  temptTime.setDate(temptTime.getDate() - weekday + 1 + 5)
+  let firstDay = new Date(temptTime.getFullYear(), 0, 1)
+  const dayOfWeek = firstDay.getDay()
+  let spendDay = 1
+  if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1
+  firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay)
+  const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000)
+  return Math.ceil(d / 7)
+}
+
+/**
+ * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`
+ * @param param 当前时间,new Date() 格式或者字符串时间格式
+ * @param format 需要转换的时间格式字符串
+ * @description param 10秒:  10 * 1000
+ * @description param 1分:   60 * 1000
+ * @description param 1小时: 60 * 60 * 1000
+ * @description param 24小时:60 * 60 * 24 * 1000
+ * @description param 3天:   60 * 60* 24 * 1000 * 3
+ * @returns 返回拼接后的时间字符串
+ */
+export function formatPast(param: string | Date, format = 'YYYY-MM-DD HH:mm:ss'): string {
+  // 传入格式处理、存储转换值
+  let t: any, s: number
+  // 获取js 时间戳
+  let time: number = new Date().getTime()
+  // 是否是对象
+  typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param)
+  // 当前时间戳 - 传入时间戳
+  time = Number.parseInt(`${time - t}`)
+  if (time < 10000) {
+    // 10秒内
+    return '刚刚'
+  } else if (time < 60000 && time >= 10000) {
+    // 超过10秒少于1分钟内
+    s = Math.floor(time / 1000)
+    return `${s}秒前`
+  } else if (time < 3600000 && time >= 60000) {
+    // 超过1分钟少于1小时
+    s = Math.floor(time / 60000)
+    return `${s}分钟前`
+  } else if (time < 86400000 && time >= 3600000) {
+    // 超过1小时少于24小时
+    s = Math.floor(time / 3600000)
+    return `${s}小时前`
+  } else if (time < 259200000 && time >= 86400000) {
+    // 超过1天少于3天内
+    s = Math.floor(time / 86400000)
+    return `${s}天前`
+  } else {
+    // 超过3天
+    const date = typeof param === 'string' || 'object' ? new Date(param) : param
+    return formatDate(date, format)
+  }
+}
+
+/**
+ * 时间问候语
+ * @param param 当前时间,new Date() 格式
+ * @description param 调用 `formatAxis(new Date())` 输出 `上午好`
+ * @returns 返回拼接后的时间字符串
+ */
+export function formatAxis(param: Date): string {
+  const hour: number = new Date(param).getHours()
+  if (hour < 6) return '凌晨好'
+  else if (hour < 9) return '早上好'
+  else if (hour < 12) return '上午好'
+  else if (hour < 14) return '中午好'
+  else if (hour < 17) return '下午好'
+  else if (hour < 19) return '傍晚好'
+  else if (hour < 22) return '晚上好'
+  else return '夜里好'
+}
+
+/**
+ * 将毫秒,转换成时间字符串。例如说,xx 分钟
+ *
+ * @param ms 毫秒
+ * @returns {string} 字符串
+ */
+export function formatPast2(ms: number): string {
+  const day = Math.floor(ms / (24 * 60 * 60 * 1000))
+  const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24)
+  const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60)
+  const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60)
+  if (day > 0) {
+    return day + ' 天' + hour + ' 小时 ' + minute + ' 分钟'
+  }
+  if (hour > 0) {
+    return hour + ' 小时 ' + minute + ' 分钟'
+  }
+  if (minute > 0) {
+    return minute + ' 分钟'
+  }
+  if (second > 0) {
+    return second + ' 秒'
+  } else {
+    return 0 + ' 秒'
+  }
+}
+
+/**
+ * element plus 的时间 Formatter 实现,使用 YYYY-MM-DD HH:mm:ss 格式
+ *
+ * @param row 行数据
+ * @param column 字段
+ * @param cellValue 字段值
+ */
+export function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
+  return cellValue ? formatDate(cellValue) : ''
+}
+
+/**
+ * element plus 的时间 Formatter 实现,使用 YYYY-MM-DD 格式
+ *
+ * @param row 行数据
+ * @param column 字段
+ * @param cellValue 字段值
+ */
+export function dateFormatter2(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {
+  return cellValue ? formatDate(cellValue, 'YYYY-MM-DD') : ''
+}
+
+/**
+ * 设置起始日期,时间为00:00:00
+ * @param param 传入日期
+ * @returns 带时间00:00:00的日期
+ */
+export function beginOfDay(param: Date): Date {
+  return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0)
+}
+
+/**
+ * 设置结束日期,时间为23:59:59
+ * @param param 传入日期
+ * @returns 带时间23:59:59的日期
+ */
+export function endOfDay(param: Date): Date {
+  return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59)
+}
+
+/**
+ * 计算两个日期间隔天数
+ * @param param1 日期1
+ * @param param2 日期2
+ */
+export function betweenDay(param1: Date, param2: Date): number {
+  param1 = convertDate(param1)
+  param2 = convertDate(param2)
+  // 计算差值
+  return Math.floor((param2.getTime() - param1.getTime()) / (24 * 3600 * 1000))
+}
+
+/**
+ * 日期计算
+ * @param param1 日期
+ * @param param2 添加的时间
+ */
+export function addTime(param1: Date, param2: number): Date {
+  param1 = convertDate(param1)
+  return new Date(param1.getTime() + param2)
+}
+
+/**
+ * 日期转换
+ * @param param 日期
+ */
+export function convertDate(param: Date | string): Date {
+  if (typeof param === 'string') {
+    return new Date(param)
+  }
+  return param
+}
+
+/**
+ * 指定的两个日期, 是否为同一天
+ * @param a 日期 A
+ * @param b 日期 B
+ */
+export function isSameDay(a: dayjs.ConfigType, b: dayjs.ConfigType): boolean {
+  if (!a || !b) return false
+
+  const aa = dayjs(a)
+  const bb = dayjs(b)
+  return aa.year() == bb.year() && aa.month() == bb.month() && aa.day() == bb.day()
+}
+
+/**
+ * 获取一天的开始时间、截止时间
+ * @param date 日期
+ * @param days 天数
+ */
+export function getDayRange(
+  date: dayjs.ConfigType,
+  days: number
+): [dayjs.ConfigType, dayjs.ConfigType] {
+  const day = dayjs(date).add(days, 'd')
+  return getDateRange(day, day)
+}
+
+/**
+ * 获取最近7天的开始时间、截止时间
+ */
+export function getLast7Days(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastWeekDay = dayjs().subtract(7, 'd')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastWeekDay, yesterday)
+}
+
+/**
+ * 获取最近30天的开始时间、截止时间
+ */
+export function getLast30Days(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastMonthDay = dayjs().subtract(30, 'd')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastMonthDay, yesterday)
+}
+
+/**
+ * 获取最近1年的开始时间、截止时间
+ */
+export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
+  const lastYearDay = dayjs().subtract(1, 'y')
+  const yesterday = dayjs().subtract(1, 'd')
+  return getDateRange(lastYearDay, yesterday)
+}
+
+/**
+ * 获取指定日期的开始时间、截止时间
+ * @param beginDate 开始日期
+ * @param endDate 截止日期
+ */
+export function getDateRange(
+  beginDate: dayjs.ConfigType,
+  endDate: dayjs.ConfigType
+): [string, string] {
+  return [
+    dayjs(beginDate).startOf('d').format('YYYY-MM-DD HH:mm:ss'),
+    dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')
+  ]
+}
+
+/**
+ * 格式化数组格式的日期
+ * @param dateArr 日期数组 [年, 月, 日]
+ * @returns 格式化后的日期字符串,格式:YYYY-MM-DD
+ */
+export function formatArrayDate(dateArr: number[] | null): string {
+  if (!dateArr || dateArr.length !== 3) {
+    return ''
+  }
+  const [year, month, day] = dateArr
+  return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
+}
+
+
+export function formatDate1(date: Date, format?: string): string {
+  // 日期不存在,则返回空
+  if (!date) {
+    return ''
+  }
+  // 日期存在,则进行格式化
+  return date ? dayjs(date).format(format ?? 'YYYY-MM-DD') : ''
+}

+ 22 - 1
yudao-ui-admin-vue3/src/views/pressure/checker/AuditInspectionCommentsNotice.vue

@@ -1,4 +1,6 @@
 <template>
+  <el-tabs v-model="equipTabType" type="border-card">
+    <el-tab-pane label="容器" name="container">
   <SmartTable
     ref="smartTableRef"
     v-model:columns="getColumnsByReportType"
@@ -49,6 +51,14 @@
     @update="() => getAuditListByReportType()"
   />
   <EquipContainerForm ref="EquipContainerFormRef" />
+  </el-tab-pane>
+  <el-tab-pane v-if="checkRole(['boiler_audit','admin'])" label="锅炉" name="boiler">
+    <BoilerAuditInspectionCommentsNotice/>
+  </el-tab-pane>
+  <el-tab-pane v-if="checkRole(['pipe_audit','admin'])"  label="管道" name="pipe" style="min-height: 700px">
+    <PipeAuditInspectionCommentsNotice/>
+  </el-tab-pane>
+  </el-tabs>
 </template>
 <script setup lang="tsx">
 import AuditCheckRecord from './AuditCheckRecord.vue'
@@ -69,12 +79,15 @@ import { buildFileUrl } from '@/utils'
 import { cloneDeep } from 'lodash-es'
 import { usePageLoading } from '@/hooks/web/usePageLoading'
 import { useUserStore } from '@/store/modules/user'
+import {checkRole} from "@/utils/permission";
+import PipeAuditInspectionCommentsNotice from '../../pressure2/pipechecker/PipeAuditInspectionCommentsNotice.vue'
+import BoilerAuditInspectionCommentsNotice from '../../pressure2/boilerchecker/BoilerAuditInspectionCommentsNotice.vue'
 
 const { loadDone, loadStart } = usePageLoading()
 
 const userStore = useUserStore()
 const userInfo = computed(() => userStore.user)
-
+const equipTabType = ref()
 const route = useRoute()
 const PressureTaskOrderStatusMap = {
   [PressureTaskOrderStatus.AUDITING]: route.name === 'WorkInstructionAudit'? '批准中' : '审核中',
@@ -929,6 +942,14 @@ onMounted(() => {
   }
   smartTableRef.value?.setSearchForm(searchFormData.value)
   getAuditListByReportType()
+
+  if (checkRole(['boiler_audit', 'admin'])){
+    equipTabType.value = 'boiler'
+  }else if (checkRole(['pipe_audit', 'admin'])){
+    equipTabType.value = 'pipe'
+  }else {
+    equipTabType.value = 'container'
+  }
 })
 </script>
 <style lang="scss" scoped></style>

+ 10 - 6
yudao-ui-admin-vue3/src/views/pressure2/boilerReportCheck/index.vue

@@ -114,12 +114,12 @@
 
       <!-- 检验项目 -->
       <el-table-column label="检验项目" prop="reportDOList" min-width="200px">
-        <template #default="scope">
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -133,7 +133,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -164,7 +164,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -198,7 +198,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -216,6 +216,9 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #default="scope">
+          <BoilerReportList :row="scope.row"/>
         </template>
       </el-table-column>
       <el-table-column label="主报告状态" align="center" prop="taskStatus" min-width="150px">
@@ -339,6 +342,7 @@ import { getUserList } from '@/api/common/user'
 import { useUserStore } from '@/store/modules/user'
 import { cloneDeep } from 'lodash-es'
 import { useEmitt } from '@/hooks/web/useEmitt'
+import BoilerReportList from "@/views/pressure2/boilerReportPreparationList/BoilerReportList.vue";
 const userStore = useUserStore()
 const userInfo = computed(() => userStore.user)
 const route = useRoute()

+ 121 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilerReportPreparationList/BoilerReportList.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class="report-list-container">
+    <span
+      v-for="item in row.reportDOList"
+      :key="item?.id"
+      class="report-item"
+    >
+      <span
+        class="color-dot"
+        :style="{ backgroundColor: getStatusColor(item.taskStatus) }"
+      ></span>
+      <span
+        class="report-name"
+        :style="{ color: getStatusColor(item.taskStatus) }"
+        @click="() => handleClick(row, item)"
+      >
+        {{ getReportName(item) }}
+      </span>
+      <span
+        v-if="item?.fee"
+        class="report-fee"
+        :style="{ color: getStatusColor(item.taskStatus) }"
+      >
+        ({{ item?.fee }})
+      </span>
+    </span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import {useRouter} from "vue-router";
+
+interface ReportItem {
+  id: string
+  reportName: string
+  taskStatus: number
+  fee?: string | number
+  itemPartId?: string
+  [key: string]: any
+}
+
+const router = useRouter()
+const props = defineProps<{
+  row
+}>()
+
+const PartTypeList = getStrDictOptions(DICT_TYPE.PRESSURE2_BOILER_PART_TYPE)
+
+const getTypeName = (type: string) => {
+  const item = PartTypeList.find(item => item.value === type)
+  return item ? item.label : ''
+}
+
+const getReportName = (item: ReportItem) => {
+  if (!item.itemPartId || item.itemPartId === '0') {
+    return item.reportName
+  }
+  return `${item.reportName}(${getTypeName(item.itemPartId)})`
+}
+
+const getStatusColor = (status: number): string => {
+  const statusMap: Record<number, string> = {
+    100: '#5B9BD5',  // 待录入
+    400: '#70AD47',  // 记录录入
+    500: '#9973C2',  // 记录校核
+    520: '#FFC000',  // 报告编制
+    600: '#ED7D31',  // 报告审核
+    700: '#FF8DC7',  // 报告审批
+    800: '#303133'   // 报告办结
+  }
+  return statusMap[status] || '#A7D78B'
+}
+
+const handleClick = (row, item: ReportItem) => {
+  if (row.isClaim === false) {
+    return
+  }
+  localStorage.setItem('activeBoilerDetailItemId', item.id)
+  router.push({
+    name: 'BoilerCheckerTaskDetail',
+    query: {id: row.id,orderId:row.orderId, type: 'BoilerMyTask'},
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.report-list-container {
+  display: inline;
+  line-height: 1.8;
+}
+
+.report-item {
+  display: inline-block;
+  white-space: nowrap;
+  margin-right: 12px;
+
+  .color-dot {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    vertical-align: middle;
+    margin-right: 4px;
+  }
+
+  .report-name {
+    cursor: pointer;
+    transition: opacity 0.2s;
+
+    &:hover {
+      opacity: 0.8;
+      text-decoration: underline;
+    }
+  }
+
+  .report-fee {
+    margin-left: 2px;
+  }
+}
+</style>

+ 11 - 7
yudao-ui-admin-vue3/src/views/pressure2/boilerReportPreparationList/index.vue

@@ -117,13 +117,13 @@
       </el-table-column>
 
       <!-- 检验项目 -->
-      <el-table-column label="检验项目" prop="reportDOList" min-width="200px" align="center">
-        <template #default="scope">
+      <el-table-column label="检验项目" prop="reportDOList" :min-width="200" align="center">
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -137,7 +137,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -168,7 +168,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -202,7 +202,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -220,6 +220,9 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #default="scope">
+          <BoilerReportList :row="scope.row"/>
         </template>
       </el-table-column>
       <el-table-column label="主报告状态" align="center" prop="taskStatus" min-width="150px">
@@ -343,6 +346,7 @@ import { getUserList } from '@/api/common/user'
 import { useUserStore } from '@/store/modules/user'
 import { cloneDeep } from 'lodash-es'
 import { useEmitt } from '@/hooks/web/useEmitt'
+import BoilerReportList from "@/views/pressure2/boilerReportPreparationList/BoilerReportList.vue";
 const userStore = useUserStore()
 const userInfo = computed(() => userStore.user)
 const route = useRoute()

+ 18 - 7
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/AuditCheckRecord.vue

@@ -175,12 +175,11 @@ import ReportListUploadModal from '@/views/pressure2/boilerchecker/components/re
   import {getPDF, getStandardTemplateInfo} from "@/api/laboratory/standard/template";
   import {buildFileUrl} from "@/utils";
   import axios from "axios";
-  import SpreadDesigner from "@/components/SpreadDesigner/index.vue";
   import {editReport} from "@/utils/reportUtil";
   import {DynamicTbColApi} from "@/api/pressure2/dynamictbcol";
-  import {InitParams} from "@/components/DynamicReport/SpreadInterface";
+  import {InitParams} from "@/components/pressure2/DynamicReport/SpreadInterface";
   import {PipeTaskOrderApi} from "@/api/pressure2/pipetaskorder";
-  import {SpreadViewer} from "@/components/DynamicReport";
+  import {SpreadViewer} from "@/components/pressure2/DynamicReport";
   const route = useRoute()
   const routeNameTypes = {
     'BoilerCheckerRecordCheck': '记录校核审核',
@@ -416,11 +415,22 @@ import ReportListUploadModal from '@/views/pressure2/boilerchecker/components/re
 
     let res
     let apiParamsId;
-    if (props.useType === 'checkNotice'){
-      apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
-    }else{
-      apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+    switch(props.reportType) {
+      case PressureReportType['SUGGUESTION']:
+        apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
+        break
+      case PressureReportType['WORKINSTRUCTION']:
+      case PressureReportType['INSPECTIONPLAN']:
+      case PressureReportType['MAINQUESTION']:
+      default:
+        apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+        break
     }
+    // if (props.useType === 'checkNotice'){
+    //   apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
+    // }else{
+    //   apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+    // }
     //let apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
     switch(props.reportType) {
       case PressureReportType['WORKINSTRUCTION']:
@@ -432,6 +442,7 @@ import ReportListUploadModal from '@/views/pressure2/boilerchecker/components/re
       default:
         res = await BoilerTaskOrderApi.getTaskOrderItemReportRecord(apiParamsId)
     }
+
     template.value = res.templateId
 
     initData.value.templateId = template.value;

+ 489 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/BoilerAuditInspectionCommentsNotice.vue

@@ -0,0 +1,489 @@
+<template>
+  <SmartTable
+    ref="smartTableRef"
+    v-model:columns="getColumnsByReportType"
+    v-model:page-no="pageNo"
+    v-model:page-size="pageSize"
+    v-model:form-data="searchFormData"
+    :data="inspectionCommentsList"
+    :total="total"
+    :buttons="buttons"
+    @on-page-no-change="() => getAuditListByReportType()"
+    @on-page-size-change="() => getAuditListByReportType()"
+    @on-reset="() => {
+      filterStatus = '100'
+      handleStatusFilter()
+    }"
+    @on-search="() => getAuditListByReportType()"
+    @refresh="() => getAuditListByReportType()"
+  >
+    <template #toolbarRowExtra>
+      <el-radio-group
+        v-model="filterStatus"
+        @change="handleStatusFilter"
+        style="margin-bottom: 15px"
+      >
+        <el-radio-button label="全部" value="all" />
+        <el-radio-button label="已通过" value="200" />
+        <el-radio-button label="审核中" value="100" />
+        <el-radio-button label="已拒绝" value="300" />
+        <el-radio-button label="已作废" value="400" />
+      </el-radio-group>
+    </template>
+  </SmartTable>
+  <AuditCheckRecord
+    v-if="showAuditCheckRecord"
+    v-model:visible="showAuditCheckRecord"
+    :showRollbackStage="false"
+    :reportUrl="operationItem.reportUrl"
+    :auditId="operationItem.reportId"
+    :apiParams="auditApiParams"
+    :rejectFn="rejectOpinionNoticeApproval"
+    :passFn="passOpinionNoticeApproval"
+    :pageType="pageType"
+    :recordFn="getOpinionNoticeApprovalRecordList"
+    :reportType="getReportType"
+    :id="operationItem.id"
+    :templateId="templateId"
+    :useType="useType"
+    @close="handleCloseAuditDetail"
+    @update="() => getAuditListByReportType()"
+  />
+</template>
+<script setup lang="tsx">
+import AuditCheckRecord from './AuditCheckRecord.vue'
+import AuditInspectionCommentsNotice from './AuditInspectionCommentsNotice.vue'
+import SmartTable from '@/components/SmartTable/SmartTable'
+import dayjs from 'dayjs'
+import { useDictStore } from '@/store/modules/dict'
+import { TaskOrderApi } from '@/api/pressure/taskorder'
+import {
+  PressureTaskOrderStatusMap,
+  PressureTaskOrderStatus,
+  PressureReportType
+} from '@/utils/constants'
+import { useRoute } from 'vue-router'
+import _, { get } from 'lodash'
+import { SmartInstanceExpose, SmartTableColumn } from '@/types/table'
+import { buildFileUrl } from '@/utils'
+import {BoilerTaskOrderApi} from "@/api/pressure2/boilertaskorder";
+import {checkRole} from "@/utils/permission";
+const route = useRoute()
+const {
+  getOpinionNoticeApproval,
+  passOpinionNoticeApproval,
+  rejectOpinionNoticeApproval,
+  getOpinionNoticeApprovalRecordList // 没有分页
+} = TaskOrderApi
+const equipTabType = ref()
+
+const useType = "checkNotice"
+const dictStore = useDictStore()
+const auditText = ref('审核')
+const getReportType = computed(() => {
+  switch (route.name) {
+    case 'WorkInstructionAudit':
+      auditText.value = '批准'
+      return PressureReportType['WORKINSTRUCTION']
+    case 'InspectionPlanAudit':
+      auditText.value = '审批'
+      return PressureReportType['INSPECTIONPLAN']
+    case 'majorIssueCluesNotice':
+      // 重大问题线索通知
+      return PressureReportType['MAINQUESTION']
+    case 'AuditInspectionCommentsNotice':
+      // 检验意见通知书
+      return PressureReportType['SUGGUESTION']
+  }
+})
+
+// 检验性质  pressure_inspection_nature
+const getPressureInspectionNature = computed(
+  () => dictStore.getDictMap['pressure_inspection_nature']
+)
+const columns = ref<SmartTableColumn[]>([
+  {
+    label: '任务单号',
+    prop: 'orderNo',
+    search: {
+      type: 'input'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    }
+  },
+  {
+    label: '设备注册代码',
+    prop: 'equipCode',
+    width: 200,
+    search: {
+      type: 'input'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render(row, value) {
+      return (
+        <div style="text-align:center">
+          <div>{value}</div>
+          {row.productNo && (
+            <div class="mt-8px">
+              <el-tag type="success">{row.productNo}</el-tag>
+            </div>
+          )}{' '}
+        </div>
+      )
+    }
+  },
+  {
+    label: '出厂编号',
+    width: 120,
+    prop: 'productNo',
+    hidden: true,
+    search: {
+      type: 'input'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+  },
+  {
+    label: '使用单位',
+    prop: 'unitName',
+    search: {
+      type: 'input'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+  },
+  {
+    label: '检验性质',
+    prop: 'checkType',
+    search: {
+      type: 'select',
+      options: getPressureInspectionNature.value
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row, checkType) =>
+      getPressureInspectionNature.value.find((x) => x.value == checkType)?.label || '-'
+  },
+  {
+    label: '检验项目',
+    prop: 'reportName',
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+  },
+  {
+    label: '检验时间',
+    prop: 'checkDate',
+    search: {
+      type: 'daterange'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row, checkDate) => (!checkDate ? '-' : (checkDate || []).join('-'))
+  },
+  {
+    label: '状态',
+    prop: 'status',
+    // search: {
+    //   type: 'select',
+    //   options: Object.entries(PressureTaskOrderStatusMap).map(([value, label]) => ({value, label}))
+    // },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row, status) => (!status ? '-' : PressureTaskOrderStatusMap[status])
+  },
+  {
+    label: '当前流程',
+    prop: 'currentNode',
+    fieldProps: {
+      showOverflowTooltip: true,
+      headerAlign: 'center'
+    },
+    render: (row) => {
+      switch (row.status) {
+        case PressureTaskOrderStatus['CANCELLED']:
+          return '-'
+        case PressureTaskOrderStatus['APPROVED']:
+          return '审核通过'
+        case PressureTaskOrderStatus['AUDITING']:
+        case PressureTaskOrderStatus['REJECTED']:
+          return (
+            <div>
+              <p>当前流程:{row?.currentNode || '-'}</p>
+              <p>
+                状态:
+                {row.status === PressureTaskOrderStatus['AUDITING']
+                  ? '审核中'
+                  : `${row?.currentAuditor?.nickname}(${row?.currentAuditor?.employeeNo})拒绝`}
+              </p>
+            </div>
+          )
+        default:
+          return '-'
+      }
+    }
+  },
+  {
+    label: '退回原因',
+    prop: 'returnReason',
+    fieldProps: {
+      showOverflowTooltip: true,
+      headerAlign: 'center'
+    },
+    render: (row, returnReason) => returnReason || '-'
+  },
+  {
+    label: '审核人',
+    prop: 'currentAuditor',
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    search: {
+      type: 'selectUserModal',
+      prop: 'bpmUserId'
+    },
+    render: (row, val) => {
+      return !val ? '-' : val?.nickname
+    }
+  },
+  {
+    label: '提交人',
+    prop: 'submitUser',
+    search: {
+      type: 'input'
+    },
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row, submitUser) => {
+      return !submitUser ? '-' : submitUser?.nickname
+    }
+  },
+  {
+    label: '提交时间',
+    prop: 'submitTime',
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row, submitTime) =>
+      !submitTime ? '-' : dayjs(submitTime).format('YYYY-MM-DD HH:mm:ss')
+  },
+  {
+    label: '操作',
+    prop: 'operation',
+    fieldProps: {
+      showOverflowTooltip: true,
+      align: 'center'
+    },
+    render: (row) => {
+      return (
+        <div>
+          {PressureTaskOrderStatus['AUDITING'] == row.status && (
+            <el-button
+              link
+              type="primary"
+              onClick={() => handleOpenAuditDetailOrInfo(row, 'check')}
+            >
+              {auditText.value}
+            </el-button>
+          )}
+          {PressureTaskOrderStatus['AUDITING'] !== row.status && (
+            <el-button
+              link
+              type="primary"
+              onClick={() => handleOpenAuditDetailOrInfo(row, 'detail')}
+            >
+              详情
+            </el-button>
+          )}
+        </div>
+      )
+    }
+  }
+])
+const getColumnsByReportType = computed(() => {
+  switch (getReportType.value) {
+    case PressureReportType['SUGGUESTION']:
+      return columns.value.filter((col) => col.prop !== 'bpmUserId') as SmartTableColumn[]
+    case PressureReportType['WORKINSTRUCTION']:
+    case PressureReportType['INSPECTIONPLAN']:
+    case PressureReportType['MAINQUESTION']:
+      // const columnsTitle = [ '任务单号', '检验时间', '状态', '当前流程', '退回原因', '提交人', '审核人', '提交时间', '操作' ]
+      // return columns.value.filter((col) => columnsTitle.includes(col.label)) as SmartTableColumn[]
+      const columnsProp = ['orderNo', 'checkDate', 'status', 'currentNode', 'returnReason', 'submitUser', 'currentAuditor', 'submitTime', 'operation']
+      return columns.value.filter((col) => columnsProp.includes(col.prop as string)) as SmartTableColumn[]
+    default:
+      return [] as SmartTableColumn[]
+  }
+})
+const buttons = ref([])
+const pageNo = ref(1)
+const pageSize = ref(10)
+const total = ref(0)
+const inspectionCommentsList = ref([])
+const searchFormData = ref<any>({
+  status: '100' // 默认状态为审核中
+})
+
+// 状态筛选
+const filterStatus = ref('100') // 默认选中审核中
+// 获取检验意见通知书审核列表
+const fetchInspectionCommentsAuditList = async () => {
+  // TODO: 在这里获取审核列表数据
+  const params = {
+    pageNo: pageNo.value,
+    pageSize: pageSize.value,
+    reportType: getReportType.value,
+    ...searchFormData.value,
+    status: filterStatus.value
+  }
+  if (params.status == 'all') delete params.status
+  const response = await getOpinionNoticeApproval(params)
+  inspectionCommentsList.value = response.list
+  total.value = response.total
+}
+
+// 获取重大问题线索 & 检验方案 & 作业指导书的审核列表
+const handleGetOperationReportAuditList = async (reportType) => {
+  const params = {
+    pageNo: pageNo.value,
+    pageSize: pageSize.value,
+    reportType,
+    ...searchFormData.value,
+    status: filterStatus.value
+  }
+  if (params.status == 'all') delete params.status
+  const auditListResult = await BoilerTaskOrderApi.getMajorIssuesAuditList(params)
+  // const auditListResult = await TaskOrderApi.getMajorIssuesAuditList(params)
+
+  inspectionCommentsList.value = auditListResult.list
+  total.value = auditListResult.total
+}
+const getAuditListByReportType = () => {
+  switch (getReportType.value) {
+    case PressureReportType['SUGGUESTION']:
+      fetchInspectionCommentsAuditList()
+      break
+    case PressureReportType['WORKINSTRUCTION']:
+    case PressureReportType['INSPECTIONPLAN']:
+    case PressureReportType['MAINQUESTION']:
+      handleGetOperationReportAuditList(getReportType.value)
+      break
+    default:
+      return false
+  }
+}
+// watch(
+//   () => getReportType.value,
+//   (reportType) => {
+//     if (!reportType) return
+//     getAuditListByReportType()
+//   },
+//   {
+//     immediate: true
+//   }
+// )
+
+// 审核 && 查看详情
+const showAuditCheckRecord = ref(false)
+const auditApiParams = ref({})
+const operationItem = ref<any>({})
+const pageType = ref('check')
+const templateId = ref('')
+const handleOpenAuditDetailOrInfo = async (row, type) => {
+  operationItem.value = row
+  pageType.value = type
+  templateId.value = row.templateId
+  auditApiParams.value = {
+    ids: [row.id],
+    reportIds: [row.reportId],
+    reportType: getReportType.value
+  }
+  /*switch (getReportType.value) {
+    case PressureReportType['SUGGUESTION']:
+      auditApiParams.value = {
+        ids: [row.reportId]
+      }
+      break
+    case PressureReportType['WORKINSTRUCTION']:
+    case PressureReportType['INSPECTIONPLAN']:
+    case PressureReportType['MAINQUESTION']:
+      auditApiParams.value = {
+        ids: [row.id],
+        reportType: getReportType.value
+      }
+      let reportUrl = ''
+      if(PressureReportType['MAINQUESTION'] == getReportType.value && row.signFilePdf){
+        reportUrl = buildFileUrl(row.signFilePdf)
+      } else {
+        // 获取pdf文件流
+        const response = await TaskOrderApi.getReportPreview({
+          reportId: row.id,
+          fileType: 200
+        })
+        // 文件流转成url
+        if (response) {
+          const flow = new Blob([response], { type: 'application/pdf' })
+          reportUrl = window.URL.createObjectURL(flow)
+        }
+      }
+      operationItem.value = { ...row, reportId: row.id, reportUrl }
+      break
+    default:
+      return false
+  }*/
+  // 打开弹窗
+  showAuditCheckRecord.value = true
+}
+
+// 处理状态筛选
+const handleStatusFilter = () => {
+  if (filterStatus.value === 'all') {
+    // 清除状态筛选
+    searchFormData.value.status = undefined
+  } else {
+    // 设置状态筛选
+    searchFormData.value.status = filterStatus.value
+  }
+  // 重置页码并触发查询
+  pageNo.value = 1
+  getAuditListByReportType()
+}
+
+// 关闭审核详情回调
+const handleCloseAuditDetail = () => {
+  operationItem.value = false
+  showAuditCheckRecord.value = false
+}
+
+const smartTableRef = ref<SmartInstanceExpose>()
+onMounted(() => {
+  const { bpmUserId } = route.query
+  if (bpmUserId && PressureReportType['SUGGUESTION'] !== unref(getReportType)) {
+    searchFormData.value.bpmUserId = bpmUserId
+  }
+  smartTableRef.value?.setSearchForm(searchFormData.value)
+  getAuditListByReportType()
+
+})
+</script>
+<style lang="scss" scoped></style>

+ 545 - 114
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/InspectionItemList.vue

@@ -1,23 +1,76 @@
 <template>
   <div class="inspection-item-list">
+
+    <!-- 胶囊形状的 Tabs -->
+    <div class="capsule-tabs">
+      <el-badge
+        :value="getTotalCount(inspectionItems)"
+        :hidden="getTotalCount(inspectionItems) === 0"
+        :type="activeTab === 'inspection' ? 'primary' : 'danger'"
+        style="flex: 1"
+      >
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'inspection' }"
+          @click="activeTab = 'inspection'"
+        >
+          <span style="font-size: 12px">检验项目</span>
+        </div>
+      </el-badge>
+      <el-badge
+        :value="getTotalCount(documentItems)"
+        :hidden="getTotalCount(documentItems) === 0"
+        :type="activeTab === 'document' ? 'primary' : 'danger'"
+        style="flex: 1"
+      >
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'document' }"
+          @click="activeTab = 'document'"
+        >
+          <span style="font-size: 12px">记录文件</span>
+        </div>
+      </el-badge>
+    </div>
+
     <div class="list-content">
-      <el-scrollbar always>
-      <div v-if="props.reportList.length === 0" class="empty-state">
-        <el-empty description="暂无检验项目" :image-size="120" />
+      <div v-if="Object.keys(currentTabItems).length === 0" class="empty-state">
+        <el-empty
+          :description="`暂无${activeTab === 'inspection' ? '检验项目' : '记录文件'}`"
+          :image-size="120"
+        />
       </div>
 
       <div v-else class="item-list">
 
-        <div v-for="(reportGroup,type) in props.reportGroupList" :key="type" style="border: 1px lightblue solid;margin: 5px 0;padding: 3px">
-          <div style="margin: 3px;">
-            <h5>{{getTypeName(type)}}</h5>
-            <el-button @click="handleAddItemPart(type)" :disabled="orderInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem" type="primary" size="small" v-if="type != 0">
-              <Icon icon="ep:plus" class="mr-5px" /> 添加
-            </el-button>
-            <el-button @click="handleDeleteItemPart(type,reportGroup)" :disabled="orderInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem" size="small" v-if="type != 0">
-              <Icon icon="ep:delete" class="mr-5px" /> 删除
-            </el-button>
+        <div v-for="(reportGroup,type) in currentTabItems" :key="type" class="report-group-card">
+          <div class="group-header" v-if="type != 0">
+            <h5 class="group-title">{{getTypeName(type)}}</h5>
+            <div class="group-actions">
+              <el-button 
+                @click="handleAddItemPart(type)" 
+                :disabled="orderInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem" 
+                type="primary" 
+                size="small"
+                v-if="type != 0"
+                :icon="Plus"
+              >
+                添加
+              </el-button>
+              <el-button 
+                @click="handleDeleteItemPart(type,reportGroup)" 
+                :disabled="orderInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem" 
+                size="small"
+                v-if="type != 0"
+                :icon="Delete"
+                type="danger"
+                plain
+              >
+                删除
+              </el-button>
+            </div>
           </div>
+          <div v-else style="padding-top: 3px"></div>
           <div
             v-for="item in reportGroup"
             :key="item.id"
@@ -28,40 +81,66 @@
           >
             <div class="item-header">
               <div class="item-title-wrapper break-all">
-              <span
-                v-if="getReportTypeIcon(item.reportType)"
-                :class="getReportTypeIconClass(item.reportType)"
-              >
-                {{ getReportTypeIcon(item.reportType) }}
-              </span>
-                <span class="item-title">{{ item.reportName || '检验项目' }}({{ item.checkUsers.length ? item.checkUsers[0].nickname : '未分配' }})</span>
+                <div class="title-content">
+                  <div
+                    v-if="getReportTypeIcon(item.reportType)"
+                    :class="getReportTypeIconClass(item.reportType)"
+                  >
+                    {{ getReportTypeIcon(item.reportType) }}
+                  </div>
+                  <div class="item-title">
+                  <span
+                    v-if="activeTab == 'inspection'"
+                    :style="{color: getStatusBgColor(item.taskStatus) }"
+                  >
+                    <span>{{ item.reportName || '检验项目' }}</span>
+                    <span >({{ getCurrentStepName(item) }})</span>
+                  </span>
+                    <span v-else>
+                    {{ item.reportName || '检验项目' }}
+                  </span>
+                    <span style="color: red" v-if="isOtherReport(item) && getOtherStepName(item)">({{ getOtherStepName(item) }})</span>
+                    <span>&nbsp;&mdash;&nbsp;</span>
+                    <span v-if="activeTab == 'inspection'" :style="{color: getStatusBgColor(item.taskStatus) }">{{
+                        item.checkUsers.length ? item.checkUsers[0].nickname : '未分配'
+                      }}</span>
+                    <span v-else>{{ item.creatorUser ? item.creatorUser?.nickname : '' }}</span>
+                    <div
+                      class="text-[14px]"
+                      v-if="item.reportType === 300 && !item.instructionId && item.instructionTempId"
+                    >
+                      <el-button
+                        link
+                        :type="item.instructionId ? 'warning' : 'primary'"
+                        size="small"
+                        :disabled="isDisabledBtn(item)"
+                        @click.stop.prevent="() => handleCorrelationReport(item)"
+                      >
+                        {{ `(${item.instructionId ? '已' : '未'}关联操作指导书)` }}
+                      </el-button>
+                    </div>
+                  </div>
+                </div>
               </div>
-              <div class="pl-[28px] text-[14px]" v-if="item.reportType === 300">
-                <el-button link :type="item.instructionId ? 'warning' : 'primary'" size="small" @click.stop.prevent="()=>handleCorrelationReport(item)">
-                  {{  `(${item.instructionId ? '已' : '未'}关联操作指导书)` }}
-                </el-button>
-              </div>
-              <div class="status-wrapper">
-                <el-tag :type="getStatusColor(item.taskStatus)" size="small">
-                  {{ getReportStatusText(item) }}
-                  <!-- {{ PressureCheckerMyTaskStatusMap[item.taskStatus] || '未知状态' }} -->
-                </el-tag>
-                <!-- REPORT_END = 报告办结 800 -->
-                <!-- v-if="item.isAutoAmount === '1' || (item.reportType === 900 && item.isAutoAmount === '0' )" -->
-                <el-button  :disabled="item?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END']" link type="primary" @click.stop.prevent="() => handleInputCalcField(props.taskOrderItem, item)">(费用:{{ getCheckItemFeeType(item) }})</el-button>
-                <!-- <el-tag
-                  :type="getConclusionStatusType(item)"
-                  size="small"
-                  :title="getConclusionStatusText(item)"
+              <div v-if="activeTab == 'inspection'" class="fee-btn">
+                <el-button
+                  :disabled="item?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END']"
+                  link
+                  type="primary"
+                  @click.stop.prevent="() => handleInputCalcField(props.taskOrderItem, item)"
+                >
+                <span
+                  v-if="activeTab == 'inspection'"
                 >
-                  {{ getConclusionStatusText(item) }}
-                </el-tag> -->
+                  {{ getCheckItemFeeType(item) }}
+                </span>
+                  <span v-else>{{ getCheckItemFeeType(item) }}</span>
+                </el-button>
               </div>
             </div>
           </div>
         </div>
       </div>
-      </el-scrollbar>
     </div>
 
     <!-- 右键菜单 -->
@@ -97,6 +176,15 @@
         <span>修改检验员</span>
       </div>
       <div class="context-menu-divider"></div>
+      <div
+        class="context-menu-item"
+        :class="{ disabled: (contextMenuItem && !canSyncReport(contextMenuItem) || checkerIsLoginUserForSync) }"
+        @click="handleContextMenuCommand('syncAllReport')"
+      >
+        <el-icon><RefreshLeft /></el-icon>
+        <span>同步报表</span>
+      </div>
+      <div class="context-menu-divider"></div>
       <div 
         class="context-menu-item danger" 
         :class="{ disabled: (contextMenuItem && !canVoidItem(contextMenuItem) || checkerIsLoginUser) }"
@@ -146,7 +234,7 @@
 import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
 import calcCheckItemFee from '@/views/pressure2/boilertaskorder/components/calcCheckItemFee.vue'
 
-import { User, Delete, ArrowUp, ArrowDown } from '@element-plus/icons-vue'
+import {User, Delete, ArrowUp, ArrowDown, Setting, RefreshLeft, Plus} from '@element-plus/icons-vue'
 import {ElMessage, ElMessageBox} from 'element-plus'
 import { 
   PressureCheckerMyTaskStatusMap,
@@ -162,10 +250,11 @@ import { useUserStore } from '@/store/modules/user'
 import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
 import AddOrEditCheckItemForPart
   from "@/views/pressure2/boilertaskorder/components/AddOrEditCheckItemForPart.vue";
+import { useTaskProgress } from './useTaskProgress'
 
 interface Props {
   reportList: ReportItemVO[]
-  reportGroupList: []
+  reportGroupList: Record<string, ReportItemVO[]>
   orderInfo: []
   equipmentIds: []
   orderItemIds: []
@@ -182,6 +271,7 @@ interface Emits {
   (e: 'void-item', item: ReportItemVO): void
   (e: 'sort-report'): void
   (e: 'correlation-report', item: ReportItemVO): void
+  (e: 'sync-all-report', item: ReportItemVO)
 }
 
 
@@ -207,6 +297,11 @@ const getTypeName =(type) =>{
   return item ? item.label : '';
 }
 
+// 获取对象的数组子项总数
+const getTotalCount = (obj: Record<string, any[]>) => {
+  return Object.values(obj).reduce((total, arr) => total + arr.length, 0)
+}
+
 // 右键菜单相关
 const contextMenuRef = ref()
 const contextMenuVisible = ref(false)
@@ -339,6 +434,12 @@ const checkerIsLoginUser = computed(() => {
   return !(checkerUserIds?.includes(userStore?.user?.id) || props.taskOrderItem?.mainCheckerUser?.id === userStore?.user?.id)
 })
 
+const checkerIsLoginUserForSync = computed(() => {
+  //记录校核后不允许修改
+  const checkerUserIds = contextMenuItem.value?.checkUsers.map(checker => checker?.id)
+  return !(checkerUserIds?.includes(userStore?.user?.id) || props.taskOrderItem?.mainCheckerUser?.id === userStore?.user?.id)
+})
+
 // 判断是否可以上移
 const canMoveUp = (item: ReportItemVO): boolean => {
   const currentIndex = props.reportList.findIndex(report => report.id === item.id)
@@ -351,37 +452,77 @@ const canMoveDown = (item: ReportItemVO): boolean => {
   return currentIndex >= 0 && currentIndex < props.reportList.length - 1
 }
 
+// 判断是否允许同步报表数据
+const canSyncReport = (item: ReportItemVO): boolean => {
+  const forbiddenStatuses = [
+    PressureCheckerMyTaskStatus.REPORT_AUDIT, // 报告审核
+    PressureCheckerMyTaskStatus.REPORT_APPROVE, // 报告审批
+    PressureCheckerMyTaskStatus.REPORT_END // 报告办结
+  ]
+  return !forbiddenStatuses.includes(item.taskStatus)
+}
+
 // 处理报告移动
 const handleMoveReport = async (item: ReportItemVO, direction: 'up' | 'down') => {
   try {
-    const currentIndex = props.reportList.findIndex(report => report.id === item.id)
+    const sortList = activeTab.value === 'inspection' ? inspectionItems.value : documentItems.value
+    const currentIndex = sortList.findIndex((report) => report.id === item.id)
     if (currentIndex === -1) return
-    
+
     // 创建新的排序数组
-    const sortedItems = [...props.reportList]
-    
+    const sortedItems = cloneDeep(props.reportList)
+    const prevReport = sortList[currentIndex - 1]
+    const currentReport = sortList[currentIndex]
+    const nextReport = sortList[currentIndex + 1]
+    const prevSort = prevReport?.sort || 0
+    const curSort = currentReport.sort
+    const nextSort = nextReport?.sort || 0
+
     if (direction === 'up' && currentIndex > 0) {
-      // 上移:与前一个交换位置
-      [sortedItems[currentIndex - 1], sortedItems[currentIndex]] = 
-      [sortedItems[currentIndex], sortedItems[currentIndex - 1]]
+      // 上移:与前一个交换位置, 修改sort字段值
+      sortedItems.forEach((item) => {
+        if(item.id === prevReport.id){
+          item.sort = curSort
+        } else if(item.id === currentReport.id){
+          item.sort = prevSort
+        }
+      })
+      // sortedItems[currentIndex].sort = sortedItems[currentIndex-1].sort
+      // sortedItems[currentIndex-1].sort = curSort
+      // ;[sortedItems[currentIndex - 1], sortedItems[currentIndex]] = [
+      //   sortedItems[currentIndex],
+      //   sortedItems[currentIndex - 1]
+      // ]
     } else if (direction === 'down' && currentIndex < sortedItems.length - 1) {
-      // 下移:与后一个交换位置
-      [sortedItems[currentIndex], sortedItems[currentIndex + 1]] = 
-      [sortedItems[currentIndex + 1], sortedItems[currentIndex]]
+      // 下移:与后一个交换位置, 修改sort字段值
+      sortedItems.forEach((item) => {
+        if(item.id === currentReport.id){
+          item.sort = nextSort
+        } else if(item.id === nextReport.id){
+          item.sort = curSort
+        }
+      })
+      // sortedItems[currentIndex].sort = sortedItems[currentIndex+1].sort
+      // sortedItems[currentIndex+1].sort = curSort
+      // ;[sortedItems[currentIndex], sortedItems[currentIndex + 1]] = [
+      //   sortedItems[currentIndex + 1],
+      //   sortedItems[currentIndex]
+      // ]
     } else {
       return // 无法移动
     }
-    
+
     // 构造API参数,重新分配sort值(从1开始)
-    const items = sortedItems.map((report, index) => ({
+    const newItems = sortedItems.sort((a, b) =>  (a.sort || 0) - (b.sort || 0))
+    const items = newItems.map((report, index) => ({
       id: report.id,
       sort: index + 1
     }))
-    
+
     // 调用API
     await BoilerTaskOrderApi.sortReport({ items })
     ElMessage.success('排序已更新')
-    
+
     // 通知父组件刷新数据
     emit('sort-report')
   } catch (error) {
@@ -534,6 +675,10 @@ const handleContextMenuCommand = async (command: string) => {
       }
       emit('void-item', contextMenuItem.value)
       break
+    case 'syncAllReport':
+      if((contextMenuItem.value && !canSyncReport(contextMenuItem.value) || unref(checkerIsLoginUserForSync)) ) return
+      emit('sync-all-report', contextMenuItem.value)
+      break
   }
   
   contextMenuVisible.value = false
@@ -585,6 +730,107 @@ onUnmounted(() => {
   document.removeEventListener('click', handleDocumentClick)
   document.removeEventListener('contextmenu', handleDocumentContextMenu)
 })
+
+//新加
+// 使用任务进度逻辑
+const { getCurrentStepName } = useTaskProgress()
+
+// 检验项目类型:主报告(100)、子报告(200)、独审报告(300)、检验意见通知书(400)、分包项目(900)
+const inspectionTypes = [
+  PressureReportType.MAIN,
+  PressureReportType.SUB,
+  PressureReportType.SINGLE,
+  PressureReportType.SUGGUESTION,
+  PressureReportType.SUBCONTRACT
+]
+
+// 项目文件类型:操作指导书(700)、检验方案(600)、重大问题线索告知表(500)
+const documentTypes = [
+  PressureReportType.WORKINSTRUCTION,
+  PressureReportType.INSPECTIONPLAN,
+  PressureReportType.MAINQUESTION,
+]
+
+// 获取状态字体颜色
+const getStatusBgColor = (status: number): '#5B9BD5' | '#70AD47' | '#ED7D31' | '#9973C2' | '#FF8DC7' | '#FFC000' | '#A7D78B' | 'primary' | 'danger' => {
+  const statusMap: Record<number, '#5B9BD5' | '#70AD47' | '#FFC000'| '#9973C2' | '#ED7D31'| '#FF8DC7' | '#A7D78B' | 'primary' | 'danger'> = {
+    [PressureCheckerMyTaskStatus.CONFIRMED]: '#5B9BD5', //待录入
+    [PressureCheckerMyTaskStatus.RECORD_INPUT]: '#70AD47', //记录录入
+    [PressureCheckerMyTaskStatus.RECORD_CHECK]: '#9973C2', //记录校核
+    [PressureCheckerMyTaskStatus.REPORT_INPUT]: '#FFC000', //报告编制
+    [PressureCheckerMyTaskStatus.REPORT_AUDIT]: '#ED7D31', //报告审核
+    [PressureCheckerMyTaskStatus.REPORT_APPROVE]: '#FF8DC7', //报告审批
+    [PressureCheckerMyTaskStatus.REPORT_END]: '#303133' //报告办结
+  }
+  return statusMap[status] || '#A7D78B'
+}
+
+// 操作指导书名称
+const getWorkInstructionStepName = (item: ReportItemVO): string => {
+  switch (item.status) {
+    case 0:
+      return '待提交'
+    case 100:
+      return '待批准'
+    case 300:
+      return '退回'
+    default:
+      return ''
+  }
+}
+
+// Tabs 状态
+const activeTab = ref<'inspection' | 'document'>('inspection')
+// 根据类型分类数据 - 保持原有的分组结构
+const inspectionItems = computed(() => {
+  const result: Record<string, ReportItemVO[]> = {}
+  Object.entries(props.reportGroupList).forEach(([type, group]) => {
+    if (Array.isArray(group)) {
+      // const filteredItems = group.filter((item) => inspectionTypes.includes(item.reportType))
+      // if (filteredItems.length > 0) {
+      //   result[type] = filteredItems
+      // }
+      result[type] = group.filter((item) => inspectionTypes.includes(item.reportType))
+    }
+  })
+  return result
+})
+
+const documentItems = computed(() => {
+  const result: Record<string, ReportItemVO[]> = {}
+  Object.entries(props.reportGroupList).forEach(([type, group]) => {
+    if (Array.isArray(group)) {
+      const filteredItems = group.filter((item) => documentTypes.includes(item.reportType))
+      if (filteredItems.length > 0) {
+        result[type] = filteredItems
+      }
+    }
+  })
+  return result
+})
+
+// 当前激活的 tab 对应的数据
+const currentTabItems = computed(() => {
+  return activeTab.value === 'inspection' ? inspectionItems.value : documentItems.value
+})
+
+// 判断是否为其他报告类型
+const isOtherReport = (item: ReportItemVO): boolean => {
+  return [PressureReportType.WORKINSTRUCTION].includes(item.reportType)
+}
+
+// 获取其他报告类型的步骤名称
+const getOtherStepName = (item: ReportItemVO): string => {
+  if(PressureReportType.WORKINSTRUCTION == item.reportType) return getWorkInstructionStepName(item)
+  return getCurrentStepName(item)
+}
+
+// 独走报告关联操作指导书
+const isDisabledBtn = (item) => {
+  const checkerUserIds = item.checkUsers.map(checker => checker?.id)
+  return !(checkerUserIds?.includes(userStore?.user?.id))
+}
+
 </script>
 
 <style lang="scss" scoped>
@@ -592,116 +838,301 @@ onUnmounted(() => {
   height: 100%;
   display: flex;
   flex-direction: column;
-  
+
+  .capsule-tabs {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    padding: 12px 6px;
+    background: #f5f7fa;
+    border-radius: 8px;
+    margin: 8px;
+
+    .tab-item {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+      padding: 10px 10px;
+      font-size: 12px;
+      background: white;
+      border-radius: 20px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-size: 14px;
+      color: #606266;
+      font-weight: 500;
+      border: 2px solid transparent;
+      user-select: none;
+
+      &:hover {
+        color: #409eff;
+        border-color: #409eff;
+      }
+
+      &.active {
+        background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+        color: white;
+        border-color: #409eff;
+        box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+
+        // 激活状态下的 badge 样式调整
+        :deep(.el-badge__content) {
+          background-color: white;
+          color: #409eff;
+          border: 1px solid rgba(255, 255, 255, 0.3);
+        }
+      }
+
+      // el-badge 样式优化
+      :deep(.el-badge) {
+        display: flex;
+        align-items: center;
+      }
+
+      :deep(.el-badge__content) {
+        transform: translateY(-50%) translateX(50%);
+        font-weight: bold;
+      }
+    }
+  }
+
   .list-content {
     flex: 1;
     overflow-y: auto;
-    
+
     .empty-state {
       height: 100%;
       display: flex;
       align-items: center;
       justify-content: center;
     }
-    
+
     .item-list {
       padding: 8px;
-      margin-right: 4px;
-      
+
+      .report-group-card {
+        border: 1px solid #dcdfe6;
+        border-radius: 8px;
+        margin: 12px 0;
+        background-color: #fafafa;
+        transition: all 0.3s;
+        overflow: hidden;
+        padding: 1px;
+
+        &:hover {
+          box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+          border-color: #c0c4cc;
+        }
+
+        .group-header {
+          display: flex;
+          align-items: center;
+          justify-content: space-between;
+          padding: 10px 12px;
+          background: linear-gradient(135deg, #f5f7fa 0%, #e9ecef 100%);
+          border-bottom: 1px solid #dcdfe6;
+
+          .group-title {
+            margin: 0;
+            font-size: 14px;
+            font-weight: 600;
+            color: #303133;
+            flex: 1;
+          }
+
+          .group-actions {
+            display: flex;
+            gap: 8px;
+            align-items: center;
+
+            .el-button {
+              font-size: 12px;
+              padding: 6px 12px;
+
+              &.is-plain {
+                &:hover {
+                  background-color: lightcoral;
+                }
+              }
+            }
+          }
+        }
+      }
+
       .list-item {
         border: 1px solid #e4e7ed;
         border-radius: 6px;
-        padding: 12px;
-        margin-bottom: 8px;
+        // padding: 8px 12px;
+        margin-bottom: 6px;
         cursor: pointer;
         transition: all 0.2s;
         background-color: white;
-        
+        margin-top: 5px;
         &:hover {
           border-color: #409eff;
           box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+
+          .fee-btn {
+            border-color: #409eff !important;
+          }
         }
-        
+
         &.selected {
           border-color: #409eff;
           background-color: #f0f9ff;
+
+          .fee-btn {
+            border-color: #409eff !important;
+          }
         }
-        
+
         .item-header {
           display: flex;
-          flex-direction: column;
-          
+          // flex-direction: column;
+          // gap: 8px;
+
           .item-title-wrapper {
-            flex: 1;
             display: flex;
-            justify-content: flex-start;
             align-items: center;
-            gap: 8px;
-            margin-right: 8px;
-          }
-          
-          .item-title {
-            font-weight: 500;
-            color: #303133;
-            font-size: 14px;
-          }
-          
-          .report-type-icon {
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            width: 20px;
-            height: 20px;
-            background-color: var(--el-color-primary);
-            color: white;
-            border-radius: 3px;
-            font-size: 12px;
-            font-weight: bold;
-            flex-shrink: 0;
-            
-            &.main {
-              background-color: var(--el-color-primary); // 主报告使用系统primary颜色
-            }
-            
-            &.single {
-              background-color: #e6a23c; // 独审报告使用橙色
+            flex-wrap: wrap;
+            gap: 6px;
+            line-height: 1.5;
+            padding: 8px 12px;
+            flex: 1;
+
+            .title-content {
+              display: flex;
+              gap: 6px;
+              // flex-wrap: wrap;
+              flex: 1;
+              min-width: 0;
             }
 
-            &.sub {
-              background-color: #91d5ff; // 子报告使用蓝色
+            .report-type-icon {
+              display: inline-flex;
+              align-items: center;
+              justify-content: center;
+              width: 20px;
+              height: 20px;
+              background-color: var(--el-color-primary);
+              color: white;
+              border-radius: 3px;
+              font-size: 12px;
+              font-weight: bold;
+              flex-shrink: 0;
+
+              &.main {
+                background-color: var(--el-color-primary);
+              }
+
+              &.single {
+                background-color: #e6a23c;
+              }
+
+              &.sub {
+                background-color: #91d5ff;
+              }
+
+              &.subcontract {
+                background-color: #afadf6;
+              }
+
+              &.work {
+                background-color: #67c23a;
+              }
+
+              &.zdwtxsgz {
+                background-color: #ff579b;
+              }
+
+              &.jyfa {
+                background-color: #d6e645;
+              }
+
+              &.plan {
+                background-color: #909399;
+              }
+
+              &.question {
+                background-color: #f56c6c;
+              }
             }
-            &.subcontract {
-              background-color: #afadf6; // 分包项目使用蓝色
+
+            .item-title {
+              // display: inline-flex;
+              // flex-wrap: wrap;
+              font-weight: 500;
+              color: #303133;
+              font-size: 14px;
+              flex: 1;
+
+              > span {
+                // display: inline-flex;
+                // align-items: center;
+              }
+
+              .el-button {
+                padding: 0 4px;
+                height: auto;
+                line-height: 1.5;
+                font-size: 14px;
+                vertical-align: baseline;
+              }
             }
           }
-          
+
+          .fee-btn {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-basis: 60px;
+            border-left: 1px solid #e4e7ed;
+          }
+
+          .pl-\[28px\] {
+            padding-left: 28px;
+          }
+
           .status-wrapper {
-            max-width: 100%;
             display: flex;
-            align-items: flex-start;
+            align-items: center;
             flex-wrap: wrap;
             padding-left: 28px;
-            gap: 4px;
-            margin-top: 4px;
+            gap: 6px;
             box-sizing: border-box;
-          }
-          
 
+            .el-tag {
+              margin: 0;
+            }
+          }
         }
       }
     }
   }
 }
 
-// 响应式调整
 @media (max-width: 768px) {
-  .inspection-item-list {    
+  .inspection-item-list {
+    .capsule-tabs {
+      padding: 10px 12px;
+
+      .tab-item {
+        padding: 8px 16px;
+        font-size: 13px;
+      }
+    }
+
     .list-content .item-list .list-item {
       padding: 10px;
-      
+
       .item-header {
-        flex-wrap: wrap;
         gap: 6px;
+
+        .item-title-wrapper {
+          gap: 4px;
+        }
       }
     }
   }
@@ -774,4 +1205,4 @@ onUnmounted(() => {
   background-color: #e4e7ed;
   margin: 6px 0;
 }
-</style> 
+</style>

文件差異過大導致無法顯示
+ 633 - 441
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/StatusOperationPanel.vue


+ 12 - 2
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/useTaskProgress.ts

@@ -225,11 +225,21 @@ export const useTaskProgress = () => {
     return rollbackStages
   }
 
+  // 获取当前节点对应的名称
+  const getCurrentStepName = (selectedItem: ReportItemVO | null): string => {
+    if (!selectedItem) return ''
+    const steps = getStepsByType(selectedItem)
+    const currentStepIndex = getCurrentStep(selectedItem)
+    const i = selectedItem.taskStatus === PressureCheckerMyTaskStatus.REPORT_END ? currentStepIndex - 1 : currentStepIndex
+    return steps[i]?.title || ''
+  }
+
   return {
     getStepsByType,
     getCurrentStep,
     getStepStatus,
     getRollbackStages,
-    checkNeedAuditApproval
+    checkNeedAuditApproval,
+    getCurrentStepName
   }
-} 
+} 

+ 405 - 18
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/myTask.vue

@@ -227,12 +227,40 @@
       <el-radio-button label="已认领" value="claim"/>
       <el-radio-button label="待认领" value="unclaim"/>
     </el-radio-group>
+    <el-tooltip placement="bottom" effect="light" popper-class="status-help-tooltip">
+      <template #content>
+        <div class="status-help-content">
+          <div class="help-title">状态说明</div>
+          <div class="status-list">
+            <div
+              v-for="item in statusList"
+              :key="item.status"
+              class="status-item"
+            >
+              <div class="color-dot" :style="{ backgroundColor: item.color }"></div>
+              <span class="status-label">{{ item.label }}</span>
+            </div>
+          </div>
+        </div>
+      </template>
+      <el-icon class="help-icon" :size="18">
+        <QuestionFilled/>
+      </el-icon>
+    </el-tooltip>
   </ContentWrap>
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" ref="MyTaskTableListRef">
+    <el-table v-loading="loading" :data="list" :stripe="true" ref="MyTaskTableListRef"
+              border
+              class-name="cursor-pointer" @row-dblclick="handleRowDblclick">
       <el-table-column type="selection" align="center" width="55"/>
+      <el-table-column
+        label="剩余期限 (工作日)"
+        align="center"
+        prop="remainingDays"
+        min-width="140px"
+      />
       <el-table-column label="任务单号" align="center" prop="orderNo" min-width="150px"/>
       <el-table-column label="设备代码" align="center" prop="equipCode" min-width="200px"/>
       <el-table-column label="使用单位" align="center" prop="unitName" min-width="150px"/>
@@ -243,13 +271,13 @@
       </el-table-column>
 
       <!-- 检验项目 -->
-      <el-table-column label="检验项目" align="center" prop="reportDOList" min-width="200px">
-        <template #default="scope">
+      <el-table-column label="检验项目" align="center" prop="reportDOList" :min-width="200" >
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -263,7 +291,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -294,7 +322,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -328,7 +356,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -346,6 +374,9 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #default="scope">
+          <BoilerReportList :row="scope.row"/>
         </template>
       </el-table-column>
 
@@ -365,12 +396,6 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column
-        label="剩余期限 (工作日)"
-        align="center"
-        prop="remainingDays"
-        min-width="140px"
-      />
       <el-table-column label="检验时间" align="center" prop="checkDate" min-width="120px">
         <template #default="scope">
           {{ formatArrayDate(scope.row.checkDate) }}
@@ -435,6 +460,13 @@
             <el-button v-else link type="primary" @click="handleCheckInput(scope.row.id)">
               检验录入
             </el-button>
+            <el-button
+              link
+              type="primary"
+              @click="printProject(scope.row.reportDOList)"
+            >
+              打印项目
+            </el-button>
             <el-button
                 v-if="scope.row.taskStatus == 400 && scope.row.isClaim == true && scope.row.mainCheckerUser && userStore.user.id === scope.row.mainCheckerUser.id"
                 link
@@ -495,7 +527,47 @@
       @on-page-no-change="onPageNoChange"
     />
   </CustomDialog>
-
+  <CustomDialog
+    v-model="printProjectDialogVisible"
+    :close-on-click-modal="true"
+  >
+    <el-table
+      :data="printProjectList"
+      style="width: 100%"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column
+        type="selection"
+        align="center"
+      />
+      <el-table-column
+        label="序号"
+        width="60"
+        align="center"
+        type="index"
+      />
+      <el-table-column
+        label="项目"
+        align="center"
+        prop="reportName"
+      />
+      <el-table-column
+        label="项目状态"
+        align="center"
+        prop="taskStatus"
+      >
+        <template #default="scope">
+          <el-tag :type="getTypeColor(scope.row.taskStatus)">
+            {{ PressureTaskOrderTaskStatusMap[scope.row.taskStatus] }}
+          </el-tag>
+        </template>
+      </el-table-column>
+    </el-table>
+    <template #footer>
+      <el-button type="success" @click="handlePrint">打印</el-button>
+      <el-button type="danger" @click="handleDownload">下载</el-button>
+    </template>
+  </CustomDialog>
 </template>
 
 <script setup lang="ts">
@@ -504,19 +576,23 @@ import {formatArrayDate} from '@/utils/formatTime'
 //import SettingDialog from './components/SettingDialog.vue'
 import {BoilerTaskOrderApi, BoilerTaskOrderOrderItemVO} from '@/api/pressure2/boilertaskorder'
 import {
-  PressureBoilerCheckTypeMap,
+  PressureBoilerCheckTypeMap, PressureCheckerMyTaskStatus,
   PressureCheckerMyTaskStatusMap,
   PressureTaskOrderTaskStatus, PressureTaskOrderTaskStatusMap
 } from '@/utils/constants'
 import {ElMessageBox, ElMessage} from 'element-plus'
 import {useRoute, useRouter} from 'vue-router'
-import {ArrowDown, ArrowUp} from "@element-plus/icons-vue";
+import {ArrowDown, ArrowUp, QuestionFilled} from "@element-plus/icons-vue";
 import { useUserStore } from '@/store/modules/user'
 import {useEmitt} from "@/hooks/web/useEmitt";
 import endCheckForm from './components/endCheckForm.vue'
 import SmartTable from "@/components/SmartTable/SmartTable";
 import { getUserList } from '@/api/common/user'
 import SettingDialog from "@/views/pressure/checker/components/SettingDialog.vue";
+import {PipeInputApi} from "@/api/pressure2/pipeInput";
+import 'https://printjs-4de6.kxcdn.com/print.min.js'
+import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
+import BoilerReportList from "@/views/pressure2/boilerReportPreparationList/BoilerReportList.vue";
 
 const { emitter } = useEmitt()
 const router = useRouter()
@@ -542,6 +618,28 @@ const getTypeColor = (status: string | number) => {
   }
   return statusMap[status] || 'info'
 }
+const statusList = [
+  { status: PressureCheckerMyTaskStatus.CONFIRMED, label: '待录入', color: '#5B9BD5' },
+  { status: PressureCheckerMyTaskStatus.RECORD_INPUT, label: '记录录入', color: '#70AD47' },
+  { status: PressureCheckerMyTaskStatus.RECORD_CHECK, label: '记录校核', color: '#9973C2' },
+  { status: PressureCheckerMyTaskStatus.REPORT_INPUT, label: '报告编制', color: '#FFC000' },
+  { status: PressureCheckerMyTaskStatus.REPORT_AUDIT, label: '报告审核', color: '#ED7D31' },
+  { status: PressureCheckerMyTaskStatus.REPORT_APPROVE, label: '报告审批', color: '#FF8DC7' },
+  { status: PressureCheckerMyTaskStatus.REPORT_END, label: '报告办结', color: '#303133' }
+]
+// 获取状态字体颜色
+const getStatusBgColor = (status: number): '#5B9BD5' | '#70AD47' | '#ED7D31' | '#303133' |'#9973C2' | '#FF8DC7' | '#FFC000' | '#A7D78B' | 'primary' | 'danger' => {
+  const statusMap: Record<number, '#5B9BD5' | '#70AD47' | '#FFC000'| '#9973C2' | '#ED7D31'| '#FF8DC7' | '#A7D78B' | 'primary' | '#303133' | 'danger'> = {
+    [PressureCheckerMyTaskStatus.CONFIRMED]: '#5B9BD5', //待录入
+    [PressureCheckerMyTaskStatus.RECORD_INPUT]: '#70AD47', //记录录入
+    [PressureCheckerMyTaskStatus.RECORD_CHECK]: '#9973C2', //记录校核
+    [PressureCheckerMyTaskStatus.REPORT_INPUT]: '#FFC000', //报告编制
+    [PressureCheckerMyTaskStatus.REPORT_AUDIT]: '#ED7D31', //报告审核
+    [PressureCheckerMyTaskStatus.REPORT_APPROVE]: '#FF8DC7', //报告审批
+    [PressureCheckerMyTaskStatus.REPORT_END]: '#303133' //报告办结
+  }
+  return statusMap[status] || '#A7D78B'
+}
 
 /** 承压任务单 列表 */
 defineOptions({name: 'BoilerMyTask'})
@@ -722,6 +820,173 @@ const userColumns = ref<any[]>([
   }
 ])
 
+
+const printProjectDialogVisible = ref(false)
+const printProjectList = ref([])
+const selectionProject = ref([])
+const printProject = (reportDOList) => {
+  printProjectDialogVisible.value = true
+  printProjectList.value = reportDOList
+}
+
+const handleSelectionChange = (selection) => {
+  // 根据表格的序号重新排序 selection
+  const sortedSelection = selection.map(item => {
+    // 在原始数据中查找该项的索引
+    const index = printProjectList.value.findIndex(v => v.id === item.id)
+    return {
+      ...item,
+      _index: index // 临时存储原始索引
+    }
+  })
+
+  // 按照索引排序
+  sortedSelection.sort((a, b) => a._index - b._index)
+
+  // 移除临时索引字段
+  selectionProject.value = sortedSelection.map(({ _index, ...rest }) => rest)
+}
+
+const handleDownload = async () => {
+  let data: any[] = []
+  console.log(selectionProject)
+  selectionProject.value.forEach(
+    (item) => {
+      data.push({
+        taskStatus: item.taskStatus,
+        resultTemplateId: item.resultTemplateId,
+        templateId: item.templateId,
+        reportTemplateId: item.reportTemplateId,
+        reportUrl: item.reportUrl,
+        id: item.id,
+      })
+    }
+  )
+
+  try {
+    // 调用 API 获取 ZIP blob
+    const zipBlob = await PipeInputApi.handleDownload(data)
+
+    if (zipBlob) {
+      // 创建临时 URL
+      const zipUrl = URL.createObjectURL(zipBlob)
+
+      // 创建隐藏的 a 标签触发下载
+      const link = document.createElement('a')
+      link.href = zipUrl
+      link.download = '锅炉检测录入报告.zip'
+      document.body.appendChild(link)
+      link.click()
+
+      // 清理 DOM 和 URL
+      document.body.removeChild(link)
+      URL.revokeObjectURL(zipUrl)
+
+      ElMessage.success('下载成功')
+    } else {
+      ElMessage.error('获取下载文件失败')
+    }
+  } catch (error) {
+    console.error('下载失败:', error)
+    ElMessage.error('下载失败')
+  }
+}
+
+const handlePrint = async () => {
+  let data: any[] = []
+  selectionProject.value.forEach(
+    (item) => {
+      data.push({
+        taskStatus: item.taskStatus,
+        resultTemplateId: item.resultTemplateId,
+        templateId: item.templateId,
+        reportTemplateId: item.reportTemplateId,
+        reportUrl: item.reportUrl,
+        id: item.id,
+      })
+    }
+  )
+  let loadingInstance = ElLoading.service({
+    lock: true,
+    text: '正在打印...',
+    spinner: 'el-icon-loading',
+  })
+  try {
+    // 调用 API 获取 PDF blob
+    const pdfBlob = await PipeInputApi.handlePrint(data)
+
+    if (pdfBlob) {
+      // 创建临时 URL
+      const pdfUrl = URL.createObjectURL(pdfBlob)
+
+      // 使用 printJS 打印
+      printJS({
+        printable: pdfUrl,
+        type: 'pdf',
+        showModal: false,
+        onPrintDialogClose: () => {
+          // 打印对话框关闭后释放 URL
+          URL.revokeObjectURL(pdfUrl)
+        }
+      })
+    } else {
+      ElMessage.error('获取打印文件失败')
+    }
+  } catch (error) {
+    console.error('打印失败:', error)
+    ElMessage.error('打印失败')
+  }finally {
+    loadingInstance.close()
+  }
+}
+
+const PartTypeList = getStrDictOptions(DICT_TYPE.PRESSURE2_BOILER_PART_TYPE)
+const getTypeName = (type) => {
+  const item = PartTypeList.find(item => item.value === type);
+  return item ? item.label : '';
+}
+
+const getReportName = (item) => {
+  if (!item.itemPartId || item.itemPartId === '0') {
+    return item.reportName
+  }
+  return item.reportName +'(' + getTypeName(item.itemPartId) + ')'
+}
+
+const handleRowDblclick = (row: Record<string, any>) => {
+  if (row.isClaim === false) {
+    return
+  }
+  router.push({ name: 'BoilerCheckerTaskDetail', query: { id: row.id, type: 'BoilerMyTask' } })
+}
+const filterFn = (data, type = '1') => {
+  switch (type) {
+    case '1':
+      return data.filter((item) => {
+        return ![600, 700, 500].includes(item.reportType as number)
+      })
+    case '2':
+      return data.filter((item) => {
+        return [700].includes(item.reportType as number)
+      })
+    case '3':
+      return data.filter((item) => {
+        return [600].includes(item.reportType as number)
+      })
+    default:
+      return data
+  }
+}
+const handleToActiveDetail = (row, item) => {
+  if (row.isClaim === false) {
+    return
+  }
+  localStorage.setItem('activeBoilerDetailItemId', item.id)
+  router.push({
+    name: 'BoilerCheckerTaskDetail',
+    query: {id: row.id, type: 'BoilerMyTask',orderId:row.orderId},
+  })
+}
 const handleUserConfirm = () => {
   const selectRows = userTableRef.value.getTableRef().getSelectionRows()
   // 过滤组合合成options
@@ -790,4 +1055,126 @@ onUnmounted(() => {
 
 </script>
 
-<style lang="scss" scoped></style>
+<style lang="scss" scoped>
+.cursor-pointer .el-table__row {
+  cursor: pointer;
+}
+.custom-form-item :deep(.el-form-item__content) {
+  margin-left: 0 !important;
+}
+.report-item {
+  display: flex;
+  flex-direction: column;
+}
+.report-item-animated {
+  line-height: 1.5;
+}
+.help-icon {
+  color: #909399;
+  cursor: pointer;
+  font-size: 26px !important;
+  transition: all 0.3s ease;
+}
+.report-list-container {
+  display: inline;
+  line-height: 1.8;
+}
+
+.report-item {
+  display: inline-block;
+  white-space: nowrap;
+  margin-right: 12px;
+
+  .color-dot {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    vertical-align: middle;
+    margin-right: 4px;
+  }
+
+  .report-name {
+    cursor: pointer;
+    transition: opacity 0.2s;
+
+    &:hover {
+      opacity: 0.8;
+      text-decoration: underline;
+    }
+  }
+
+  .report-fee {
+    margin-left: 2px;
+  }
+}
+.help-icon:hover {
+  color: #409eff;
+  font-size: 26px !important;
+  transform: scale(1.1);
+}
+.status-help-tooltip {
+  padding: 0 !important;
+  border: none !important;
+}
+
+.status-help-content {
+  padding: 16px;
+  min-width: 180px;
+}
+
+.help-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin-bottom: 12px;
+  padding-bottom: 8px;
+  border-bottom: 2px solid #f0f0f0;
+}
+
+.status-list {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+}
+
+.status-item {
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  padding: 6px 8px;
+  border-radius: 6px;
+  transition: background-color 0.2s ease;
+}
+
+.status-item:hover {
+  background-color: #f5f7fa;
+}
+
+.color-dot {
+  width: 12px;
+  height: 12px;
+  border-radius: 50%;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
+  flex-shrink: 0;
+}
+
+.status-label {
+  font-size: 13px;
+  color: #606266;
+  white-space: nowrap;
+}
+.report-name-list {
+  position: relative;
+  .collapsed::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 15px;
+    background: linear-gradient(transparent, rgba(255, 255, 255, 0.9));
+    pointer-events: none;
+  }
+}
+</style>

+ 188 - 12
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/taskDetail.vue

@@ -2,15 +2,22 @@
   <!-- 我的任务详情 -->
   <div class="task-detail-layout">
     <div class="detail-header">
-      任务详情
-      <el-button class="ml-15px" type="primary" size="small" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
-      <el-button class="ml-15px" type="primary" size="small" v-if="showGenerateReport" plain @click="() => handleGenerateReportPdf()">
-        出具报告PDF</el-button>
+      <div class="flex items-center pl-14px flex-[0_0_520px]">
+        任务详情
+        <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowOnlyMyItems" size="small" @change="handleShowOnlyMyItems">只看我</el-checkbox>
+        <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowConcludeItems" size="small" @change="handleShowConcludeItemsItems">报告未办结</el-checkbox>
+        <el-button class="ml-15px" type="primary" size="small" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
+        <el-button class="ml-15px" type="primary" size="small" v-if="showGenerateReport" plain @click="() => handleGenerateReportPdf()">
+          出具报告</el-button>
+        <el-button class="ml-15px" type="primary" size="small" plain @click="() => toTaskOrderDetail()">任务单</el-button>
+      </div>
+      <el-divider direction="vertical" class="mx-10px" />
+      <div id="teleport-btn" ref="teleportBtnRef" class="teleport-btn"></div>
       <div class="detail-header-back"><el-button type="default" plain @click="() => handleBack()">返回</el-button></div>
     </div>
-    <el-row class="task-detail-container">
+    <div class="task-detail-container flex">
       <!-- 左侧:检验项目列表 -->
-      <el-col :span="4" class="left-panel">
+      <div class="left-panel flex-[0_0_450px]">
         <ContentWrap title="检验项目清单" class="h-full flex flex-col" :bodyStyle="{padding: '10px', flex: 1, overflow: 'hidden'}">
           <template #header>
             <el-button @click="handleAddItem" type="primary" size="small" :disabled="taskInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem">添加检验项目</el-button>
@@ -32,13 +39,14 @@
             @modify-checker="handleModifyCheckerForItem"
             @void-item="handleVoidItem"
             @sort-report="handleRefresh"
+            @sync-all-report="handleSyncReportData"
             @refresh="handleRefresh"
             @correlation-report="handleCorrelationReport"
             @template-confirm="handleTemplateConfirm"
           />
         </ContentWrap>
-      </el-col>
-      <el-col :span="20" class="right-panel">
+      </div>
+      <div class="right-panel flex-1 overflow-hidden">
         <StatusOperationPanel
           ref="statusOperationPanelRef"
           v-model:selected-item="selectedItem"
@@ -47,12 +55,14 @@
           :report-list="reportList"
           :history-list="historyList"
           :taskId="taskId"
+          :teleportBtnRef="teleportBtnRef"
           @refresh="handleRefresh"
           @modify-checker="handleModifyChecker"
           @template-confirm="handleTemplateConfirm"
+          @item-select="handleItemSelect"
         />
-      </el-col>
-    </el-row>
+      </div>
+    </div>
   </div>
 
   <AddOrEditCheckItemForEquipment
@@ -134,7 +144,7 @@ import { PressureTaskOrderTaskStatus } from '@/utils/constants'
 import { useTagsViewStore } from '@/store/modules/tagsView'
 import { useEmitt } from '@/hooks/web/useEmitt'
 import EquipBoilerForm from '@/components/EquipBoilerForm/index.vue'
-import {PipeTaskOrderApi} from "@/api/pressure2/pipetaskorder";
+import {PipeTaskOrderApi, PipeTaskOrderDetailVO} from "@/api/pressure2/pipetaskorder";
 
 defineOptions({ name: 'BoilerCheckerTaskDetail' })
 
@@ -144,6 +154,8 @@ const router = useRouter()
 const userStore = useUserStore()
 const { emitter } = useEmitt()
 
+const teleportBtnRef = ref<HTMLDivElement>()
+
 // 基础数据
 const taskId = ref<string | null>(null)
 const loading = ref(true)
@@ -285,7 +297,7 @@ const handleCloseNewInspectionItem = () => {
 }
 
 // 获取任务详情数据
-async function fetchTaskDetail(id: string) {
+async function fetchTaskDetail1(id: string) {
   loading.value = true
   try {
     //debugger;
@@ -548,6 +560,11 @@ const handleVoidItem = async (item: ReportItemVO) => {
     taskId.value = idFromQuery
     await fetchTaskDetail(idFromQuery)
     await getOrderHistoryVersion(idFromQuery)
+    const activeDetailItemId= localStorage.getItem('activeBoilerDetailItemId')
+      if (activeDetailItemId){
+        await handleItemSelect(reportList.value.find( item=> item.id === activeDetailItemId))
+        localStorage.removeItem('activeBoilerDetailItemId')
+      }
   } else {
     taskInfo.value = null
     loading.value = false
@@ -641,6 +658,165 @@ const EquipBoilerFormRef = ref<InstanceType<typeof EquipBoilerForm>>()
 const handleViewEquipment = () => {
   EquipBoilerFormRef.value.open(taskOrderItem.value?.equipId)
 }
+
+const toTaskOrderDetail = () => {
+  router.push({
+    name: 'BoilerTaskOrderView',
+    query: {
+      id: taskOrderItem.value?.orderId,
+      type: 'checker'
+    }
+  })
+}
+
+const handleSyncReportData = async () => {
+
+  try {
+    const response = await BoilerTaskOrderApi.syncAllReportData({
+      refId: selectedItem.value.id,
+    })
+    if (response){
+      ElMessage.success('同步数据成功')
+      handleRefresh()
+    }else{
+      ElMessage.error('同步数据失败,请稍后重试')
+    }
+
+  } catch (error: any) {
+    console.error('同步数据失败:', error)
+    ElMessage.error('同步数据失败,请稍后重试')
+  }
+
+}
+
+//新加功能
+const {id: userId = ''} = userStore.getUser || {}
+
+const isMainChecker = () => {
+  const {mainCheckerUser} = taskOrderItem.value
+  const {id: mainCheckerUserId = ''} = mainCheckerUser || {}
+  return mainCheckerUserId === userId
+}
+const isShowOnlyMyItems = ref(false)
+const isShowConcludeItems = ref(false)
+
+const handleShowOnlyMyItems = (value) => {
+  isShowOnlyMyItems.value = value
+  getReportList()
+}
+const handleShowConcludeItemsItems = (value) => {
+  isShowConcludeItems.value = value
+  getReportList()
+}
+const getReportList = (activeReportId = '')=>{
+  // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
+  if(!isShowOnlyMyItems.value){
+    reportList.value = isShowConcludeItems.value ?  filteredReportList.value.map( item => item )?.filter(
+      (item) => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END) : filteredReportList.value.map( item => item )
+  } else {
+    const documentTypes = [
+      PressureReportType.WORKINSTRUCTION,
+      PressureReportType.INSPECTIONPLAN,
+      PressureReportType.MAINQUESTION
+    ]
+    reportList.value = filteredReportList.value.filter(v=> {
+      if(documentTypes.includes(v.reportType as number)){
+        return true
+      }
+      return v.checkUsers ? v.checkUsers?.some(member=> member?.id === userId) : false
+    })
+    if (isShowConcludeItems.value) {
+      reportList.value = reportList.value?.filter(item => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END)
+    }
+  }
+  // 智能选择:保持当前选中项目或选择第一个项目
+  if (reportList.value.length > 0) {
+    let targetItem: ReportItemVO | undefined = undefined
+
+    // 如果当前有选中项目,尝试在新列表中找到相同的项目
+    if (selectedItem.value) {
+      targetItem = reportList.value.find((item) => item.id === selectedItem.value?.id)
+    }
+
+    if(activeReportId) {
+      targetItem = reportList.value.find((item) => item.id === activeReportId)
+    }
+    // 如果当前选中项目不存在于新列表中,或者没有选中项目,选择第一个项目
+    if (!targetItem) {
+      targetItem = reportList.value[0]
+    }
+
+    selectedItem.value = targetItem as ReportItemVO
+  }
+
+}
+const filteredReportList = ref<ReportItemVO[]>([])
+const filteredConcludeReportList = ref<ReportItemVO[]>([])
+// 获取任务详情数据
+async function fetchTaskDetail(id: string, activeReportId = '') {
+  loading.value = true
+  try {
+    const response = await BoilerTaskOrderApi.getTaskOrderOrderItem(id)
+    taskInfo.value = response.taskOrder
+    checkUsers.value = response.checkUsers
+    taskOrderItem.value = response.taskOrderItem || {}
+    partList.value = response.partList
+    const showAllReport = route.query?.showAllReport   //如果是所有报告,就不根据检验员过滤
+
+    //暂不需要默认只看我的
+    //if(!showAllReport) {isShowOnlyMyItems.value =  !isMainChecker()}
+    isShowConcludeItems.value = false
+    // 过滤报告列表 - 只显示状态 >= CONFIRMED 的项目 已认领 400
+    filteredReportList.value = response.reportList.filter(
+      (item) => item.taskStatus >= PressureCheckerMyTaskStatus.CONFIRMED
+    ).filter(v=> {
+      // 如果是检验方案和指导书
+      if([600].includes(v.reportType as number)){
+        return [200].includes(v.status)
+      } else if( [500].includes(v.reportType as number)){
+        return false
+      }
+      // if([700].includes(v.reportType as number)){
+      //   return [200, 400].includes(v.status)
+      // }
+      return true
+    })//.reverse()
+    filteredConcludeReportList.value = response.reportList.filter(
+      (item) => item.taskStatus >= PressureCheckerMyTaskStatus.REPORT_END
+    ).filter(v=> {
+      // 如果是检验方案和指导书
+      if([600].includes(v.reportType as number)){
+        return [200].includes(v.status)
+      } else if( [500].includes(v.reportType as number)){
+        return false
+      }
+      // if([700].includes(v.reportType as number)){
+      //   return [200, 400].includes(v.status)
+      // }
+      return true
+    })//.reverse()
+    allReportList.value = response.reportList.filter(v=> {
+      // 如果是检验方案和指导书
+      if([600, 700].includes(v.reportType as number)){
+        return [200, 400].includes(v.status)
+      } else if([500].includes(v.reportType as number)){
+        return false
+      }
+      return true
+    })//.reverse()
+    // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
+    getReportList(activeReportId)
+
+  } catch (error) {
+    console.error('获取任务详情失败:', error)
+    ElMessage.error('获取任务详情失败')
+    taskInfo.value = {} as PipeTaskOrderDetailVO
+    reportList.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
 </script>
 
 <style lang="scss" scoped>

+ 1 - 1
yudao-ui-admin-vue3/src/views/pressure2/boilertaskorder/components/AddOrEditCheckItemForPart.vue

@@ -513,8 +513,8 @@ const handleConfirm = async () => {
       .map((orderItemId) => {
         return selectedCheckItem.value.map((x,index) => ({
           templateId: x.templateId,
-          connectId: x.connectId,
           fee: x.fee,
+          connectId: x.connectId,
           orderItemId
         }))
       })

+ 6 - 7
yudao-ui-admin-vue3/src/views/pressure2/dynamictb/DynamicTbForm.vue

@@ -79,7 +79,7 @@
         />-->
       </el-tab-pane>
       <el-tab-pane :label="showInfos.title" :name="showInfos.title" v-if="formType !== 'create' && connectInfos?.length">
-        <el-tabs v-model="activeSpreadTab" type="border-card" class="spread-tabs" style="height: 690px">
+        <el-tabs v-model="activeSpreadTab" type="border-card" class="spread-tabs">
           <el-tab-pane
             v-for="(item, index) in spreadDataList"
             :key="index"
@@ -89,7 +89,7 @@
           >
             <div class="spread-scrollbar-wrapper">
               <el-scrollbar>
-                  <SpreadViewer :initData="item.initData" ref="spreadRefs" />
+                <SpreadViewer :initData="item.initData" ref="spreadRefs" />
               </el-scrollbar>
             </div>
           </el-tab-pane>
@@ -110,15 +110,15 @@ import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
 import { DynamicTbApi, DynamicTbVO } from '@/api/pressure2/dynamictb'
 import { Close } from "@element-plus/icons-vue";
 //import SpreadView from "@/components/DynamicReport/index1.vue";
-import {SpreadEditor, SpreadViewer} from '@/components/DynamicReport/index';
+import {SpreadEditor, SpreadViewer} from '@/components/pressure2/DynamicReport/index';
 //import { DocumentEditor } from "@onlyoffice/document-editor-vue";
-import { DocEditor,DocEditorEvents } from '@/components/BwDocEditor'
+import { DocEditor,DocEditorEvents } from '@/components/pressure2/BwDocEditor'
 import FileUploadModal from './ImportFile.vue'
 import {ref } from "vue";
 import GC from "@grapecity-software/spread-sheets";
 import undo = GC.Spread.Sheets.Commands.undo;
 import {useUserStore} from "@/store/modules/user";
-import {InitParams} from "@/components/DynamicReport/SpreadInterface";
+import {InitParams} from "@/components/pressure2/DynamicReport/SpreadInterface";
 import {DynamicTbInsApi} from '@/api/pressure2/dynamictbins'
 import {BoilerConnectRecordReportApi} from "@/api/pressure2/boilerconnectrecordreport";
 
@@ -229,7 +229,7 @@ const initConnectInfos = async () => {
           if (spreadRefs.value && Array.isArray(spreadRefs.value)) {
             spreadRefs.value[index]?.reloadView()
           }
-        }, 600)
+        }, 400)
 
       } catch (error) {
         console.error(`加载模板 ${getTemplateName(item)} 失败:`, error)
@@ -715,6 +715,5 @@ const resetForm = () => {
   height: 100%;
   padding: 10px;
   box-sizing: border-box;
-  overflow: hidden;
 }
 </style>

+ 3 - 1
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/BoilerPlanScheduleDialog.vue

@@ -235,7 +235,7 @@
     <template #footer>
       <div class="flex justify-end">
         <el-button @click="handleCancel">取消</el-button>
-        <el-button type="primary" @click="handleConfirm">确定</el-button>
+        <el-button type="primary" @click="handleConfirm" :disabled="formData.inNoSchedule && formData.outNoSchedule && formData.preNoSchedule">确定</el-button>
       </div>
     </template>
 
@@ -747,7 +747,9 @@ const handleQueryCheckItemList = async (row?) => {
   } else {
     req = [...(row[0].inEquipIds?.split(',') || []), ...(row[0].outEquipIds?.split(',') || []), ...(row[0].preEquipIds?.split(',') || [])]
   }
+  if (req.length != 0){
   equipList.value = await EquipBoilerApi.getNameByIds(req)
+  }
   checkItemList.value = []
   const checkTypes = [
     PressureBoilerCheckType.IN,

+ 14 - 7
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/components/CheckerSelectBox.vue

@@ -102,17 +102,24 @@ const isTextOverflow = (text: string) => {
 
 // 判断复选框是否应该禁用
 const isCheckboxDisabled = (user: any) => {
-  // 如果已选中该用户,则不禁用(允许取消选择
-  if (selectedUsers.value.includes(user.id)) {
-    return false;
+  // 如果已达最大选择数量且未选中该用户,则禁用(max > 1 时
+  if (props.max > 1 && selectedUsers.value.length >= props.max) {
+    return !selectedUsers.value.includes(user.id)
   }
-  // 如果已达最大选择数量,则禁用未选中的项
-  return props.max > 0 && selectedUsers.value.length >= props.max;
+  // max <= 0 表示不限制,不禁用
+  return false
 }
 
-const handleChange = () => {
+const handleChange = (value?: any) => {
+  // 当最大选择数为 1 时,实现单选效果
+  if (props.max === 1 && selectedUsers.value.length > 1) {
+    // 保留最后一个选中的,移除之前的所有选项
+    const lastSelected = selectedUsers.value[selectedUsers.value.length - 1]
+    selectedUsers.value = [lastSelected]
+  }
+  
   emit('update:modelValue', selectedUsers.value)
-  emit('change',userList.value.filter(item => selectedUsers.value.includes(item.id)))
+  emit('change', userList.value.filter(item => selectedUsers.value.includes(item.id)))
 }
 
 // 监听 modelValue 变化

+ 46 - 6
yudao-ui-admin-vue3/src/views/pressure2/equipboilerscheduling/index.vue

@@ -185,6 +185,8 @@
       @selection-change="handleSelectionChange"
       @sort-change="handleSortChange"
       ref="tableRef"
+      :cell-style="getCellStyle"
+      border
     >
       <el-table-column v-if="source === 'pressure'" type="selection" width="30" />
       <el-table-column
@@ -214,7 +216,7 @@
         </template>
       </el-table-column>
       <el-table-column label="使用单位地址" align="center" prop="unitAddress" min-width="150" />
-      <!-- 内部检验 -->
+            <!-- 内部检验 -->
       <el-table-column
         label="内部检验"
         align="center"
@@ -224,12 +226,15 @@
       >
         <template #default="{ row }">
 <!--          <div class="check-number regular-check">{{ row.countIn }}</div>-->
-          <div v-if="row.nextInCheckDate !== null && row.countIn > 0" class="text-xs regular-check">
+          <div v-if="row.nextInCheckDate !== null" :class="['text-sm']">
            {{ dayjs(row.nextInCheckDate).format('YYYY-MM-DD') }}
           </div>
           <div v-else class="text-xs text-gray-500">
             -
           </div>
+          <div class="text-xs text-gray-400" v-if="hasPlanSchedule(row, 'in')">
+            {{ row.planInCheckDate ? `(已排时间:${dayjs(row.planInCheckDate).format('YYYY-MM-DD')})` : '' }}
+          </div>
         </template>
       </el-table-column>
       <!-- 外部检验 -->
@@ -243,14 +248,17 @@
         <template #default="{ row }">
 <!--          <div class="check-number year-check">{{ row.countOut }}</div>-->
           <div
-            v-if="row.nextOutCheckDate !== null && row.countOut > 0"
-            class="text-xs year-check"
+            v-if="row.nextOutCheckDate !== null"
+            :class="['text-sm']"
           >
             {{ dayjs(row.nextOutCheckDate).format('YYYY-MM-DD') }}
           </div>
           <div v-else class="text-xs text-gray-500">
             -
           </div>
+          <div class="text-xs text-gray-400" v-if="hasPlanSchedule(row, 'out')">
+            {{ row.planOutCheckDate ? `(已排时间:${dayjs(row.planOutCheckDate).format('YYYY-MM-DD')})` : '' }}
+          </div>
         </template>
       </el-table-column>
       <!-- 耐压检验 -->
@@ -264,16 +272,20 @@
         <template #default="{ row }">
           <!--          <div class="check-number expired-check">{{ row.countOut }}</div>-->
           <div
-            v-if="row.nextPressureCheckDate !== null && row.countPre > 0"
-            class="text-xs expired-check"
+            v-if="row.nextPressureCheckDate !== null"
+            :class="['text-sm']"
           >
             {{ dayjs(row.nextPressureCheckDate).format('YYYY-MM-DD') }}
           </div>
           <div v-else class="text-xs text-gray-500">
             -
           </div>
+          <div class="text-xs text-gray-400" v-if="hasPlanSchedule(row, 'pressure')">
+            {{ row.planPressureCheckDate ? `(已排时间:${dayjs(row.planPressureCheckDate).format('YYYY-MM-DD')})` : '' }}
+          </div>
         </template>
       </el-table-column>
+
       <!-- 设备注册代码 -->
       <el-table-column label="设备注册代码" align="center" prop="equipCode" min-width="140">
         <template #default="{ row }">
@@ -530,6 +542,34 @@ const handleSortChange = ({ prop, order }) => {
   })
 }
 
+/** 判断是否有已排期的时间 */
+const hasPlanSchedule = (row: EquipBoilerSchedulingVO, type: 'in' | 'out' | 'pressure') => {
+  if (type === 'in') {
+    return !!row.planInCheckDate
+  } else if (type === 'out') {
+    return !!row.planOutCheckDate
+  } else {
+    return !!row.planPressureCheckDate
+  }
+}
+
+/** 获取单元格样式 */
+const getCellStyle = ({ row, column, rowIndex, columnIndex }) => {
+  // 检查列是否为检验日期列
+  const checkColumns = ['nextInCheckDate', 'nextOutCheckDate', 'nextPressureCheckDate']
+  if (checkColumns.includes(column.property)) {
+    // 根据不同类型的检验判断是否有排期
+    if (column.property === 'nextInCheckDate' && row.planInCheckDate) {
+      return { background: '#FFFF99' }
+    } else if (column.property === 'nextOutCheckDate' && row.planOutCheckDate) {
+      return { background: '#FFFF99' }
+    } else if (column.property === 'nextPressureCheckDate' && row.planPressureCheckDate) {
+      return { background: '#FFFF99' }
+    }
+  }
+  return {}
+}
+
 /** 处理单条记录排期 */
 const handleSchedule = (row: EquipBoilerSchedulingVO) => {
   selectedRows.value = [row]

+ 36 - 7
yudao-ui-admin-vue3/src/views/pressure2/pipeReportCheck/index.vue

@@ -136,12 +136,12 @@
 
       <!-- 检验项目 -->
       <el-table-column label="检验项目" prop="reportDOList" min-width="200px">
-        <template #default="scope">
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -155,7 +155,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -186,7 +186,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -220,7 +220,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -238,6 +238,34 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #header>
+          <span class="table-header-content">
+            检验项目
+            <el-tooltip placement="bottom" effect="light" popper-class="status-help-tooltip">
+              <template #content>
+                <div class="status-help-content">
+                  <div class="help-title">状态说明</div>
+                  <div class="status-list">
+                    <div
+                      v-for="item in statusList"
+                      :key="item.status"
+                      class="status-item"
+                    >
+                      <div class="color-dot" :style="{ backgroundColor: item.color }"></div>
+                      <span class="status-label">{{ item.label }}</span>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              <el-icon class="help-icon">
+                <QuestionFilled/>
+              </el-icon>
+            </el-tooltip>
+          </span>
+        </template>
+        <template #default="scope">
+          <PipeReportList :row="scope.row"/>
         </template>
       </el-table-column>
       <el-table-column label="主报告状态" align="center" prop="taskStatus" min-width="150px">
@@ -344,7 +372,7 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from 'vue'
 import { formatArrayDate } from '@/utils/formatTime'
-import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
+import {ArrowDown, ArrowUp, QuestionFilled} from '@element-plus/icons-vue'
 import { PipeTaskOrderApi, PipeTaskOrderOrderItemVO } from '@/api/pressure2/pipetaskorder'
 import {
   PressurePipeCheckTypeMap,
@@ -361,6 +389,7 @@ import { getUserList } from '@/api/common/user'
 import { useUserStore } from '@/store/modules/user'
 import { cloneDeep } from 'lodash-es'
 import { useEmitt } from '@/hooks/web/useEmitt'
+import PipeReportList from "@/views/pressure2/pipeReportPreparationList/PipeReportList.vue";
 const userStore = useUserStore()
 const userInfo = computed(() => userStore.user)
 const route = useRoute()

+ 121 - 0
yudao-ui-admin-vue3/src/views/pressure2/pipeReportPreparationList/PipeReportList.vue

@@ -0,0 +1,121 @@
+<template>
+  <div class="report-list-container">
+    <span
+      v-for="item in row.reportDOList"
+      :key="item?.id"
+      class="report-item"
+    >
+      <span
+        class="color-dot"
+        :style="{ backgroundColor: getStatusColor(item.taskStatus) }"
+      ></span>
+      <span
+        class="report-name"
+        :style="{ color: getStatusColor(item.taskStatus) }"
+        @click="() => handleClick(row, item)"
+      >
+        {{ getReportName(item) }}
+      </span>
+      <span
+        v-if="item?.fee"
+        class="report-fee"
+        :style="{ color: getStatusColor(item.taskStatus) }"
+      >
+        ({{ item?.fee }})
+      </span>
+    </span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getStrDictOptions } from '@/utils/dict'
+import {useRouter} from "vue-router";
+
+interface ReportItem {
+  id: string
+  reportName: string
+  taskStatus: number
+  fee?: string | number
+  itemPartId?: string
+  [key: string]: any
+}
+
+const router = useRouter()
+const props = defineProps<{
+  row
+}>()
+
+const PartTypeList = getStrDictOptions(DICT_TYPE.PRESSURE2_BOILER_PART_TYPE)
+
+const getTypeName = (type: string) => {
+  const item = PartTypeList.find(item => item.value === type)
+  return item ? item.label : ''
+}
+
+const getReportName = (item: ReportItem) => {
+  if (!item.itemPartId || item.itemPartId === '0') {
+    return item.reportName
+  }
+  return `${item.reportName}(${getTypeName(item.itemPartId)})`
+}
+
+const getStatusColor = (status: number): string => {
+  const statusMap: Record<number, string> = {
+    100: '#5B9BD5',  // 待录入
+    400: '#70AD47',  // 记录录入
+    500: '#9973C2',  // 记录校核
+    520: '#FFC000',  // 报告编制
+    600: '#ED7D31',  // 报告审核
+    700: '#FF8DC7',  // 报告审批
+    800: '#303133'   // 报告办结
+  }
+  return statusMap[status] || '#A7D78B'
+}
+
+const handleClick = (row, item: ReportItem) => {
+  if (row.isClaim === false) {
+    return
+  }
+  localStorage.setItem('activePipeDetailItemId', item.id)
+  router.push({
+    name: 'PipeCheckerTaskDetail',
+    query: {id: row.id,orderId:row.orderId, type: 'PipeMyTask'},
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.report-list-container {
+  display: inline;
+  line-height: 1.8;
+}
+
+.report-item {
+  display: inline-block;
+  white-space: nowrap;
+  margin-right: 12px;
+
+  .color-dot {
+    display: inline-block;
+    width: 8px;
+    height: 8px;
+    border-radius: 50%;
+    vertical-align: middle;
+    margin-right: 4px;
+  }
+
+  .report-name {
+    cursor: pointer;
+    transition: opacity 0.2s;
+
+    &:hover {
+      opacity: 0.8;
+      text-decoration: underline;
+    }
+  }
+
+  .report-fee {
+    margin-left: 2px;
+  }
+}
+</style>

+ 47 - 8
yudao-ui-admin-vue3/src/views/pressure2/pipeReportPreparationList/index.vue

@@ -145,13 +145,13 @@
       </el-table-column>
 
       <!-- 检验项目 -->
-      <el-table-column label="检验项目" prop="reportDOList" min-width="200px">
-        <template #default="scope">
+      <el-table-column label="检验项目" prop="reportDOList" :min-width="200">
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -165,7 +165,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -196,7 +196,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -230,7 +230,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -248,6 +248,34 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #header>
+          <span class="table-header-content">
+            检验项目
+            <el-tooltip placement="bottom" effect="light" popper-class="status-help-tooltip">
+              <template #content>
+                <div class="status-help-content">
+                  <div class="help-title">状态说明</div>
+                  <div class="status-list">
+                    <div
+                      v-for="item in statusList"
+                      :key="item.status"
+                      class="status-item"
+                    >
+                      <div class="color-dot" :style="{ backgroundColor: item.color }"></div>
+                      <span class="status-label">{{ item.label }}</span>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              <el-icon class="help-icon">
+                <QuestionFilled/>
+              </el-icon>
+            </el-tooltip>
+          </span>
+        </template>
+        <template #default="scope">
+          <PipeReportList :row="scope.row"/>
         </template>
       </el-table-column>
       <el-table-column label="主报告状态" align="center" prop="taskStatus" min-width="150px">
@@ -354,7 +382,7 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from 'vue'
 import { formatArrayDate } from '@/utils/formatTime'
-import { ArrowDown, ArrowUp } from '@element-plus/icons-vue'
+import {ArrowDown, ArrowUp, QuestionFilled} from '@element-plus/icons-vue'
 import { PipeTaskOrderApi, PipeTaskOrderOrderItemVO } from '@/api/pressure2/pipetaskorder'
 import {
   PressurePipeCheckTypeMap,
@@ -371,6 +399,7 @@ import { getUserList } from '@/api/common/user'
 import { useUserStore } from '@/store/modules/user'
 import { cloneDeep } from 'lodash-es'
 import { useEmitt } from '@/hooks/web/useEmitt'
+import PipeReportList from "@/views/pressure2/pipeReportPreparationList/PipeReportList.vue";
 const userStore = useUserStore()
 const userInfo = computed(() => userStore.user)
 const route = useRoute()
@@ -393,6 +422,16 @@ const getTypeColor = (status: string | number) => {
   }
   return statusMap[status] || 'info'
 }
+
+const statusList = [
+  { status: PressureCheckerMyTaskStatus.CONFIRMED, label: '待录入', color: '#5B9BD5' },
+  { status: PressureCheckerMyTaskStatus.RECORD_INPUT, label: '记录录入', color: '#70AD47' },
+  { status: PressureCheckerMyTaskStatus.RECORD_CHECK, label: '记录校核', color: '#9973C2' },
+  { status: PressureCheckerMyTaskStatus.REPORT_INPUT, label: '报告编制', color: '#FFC000' },
+  { status: PressureCheckerMyTaskStatus.REPORT_AUDIT, label: '报告审核', color: '#ED7D31' },
+  { status: PressureCheckerMyTaskStatus.REPORT_APPROVE, label: '报告审批', color: '#FF8DC7' },
+  { status: PressureCheckerMyTaskStatus.REPORT_END, label: '报告办结', color: '#303133' }
+]
 /** 报告编制 列表 */
 defineOptions({ name: 'PipeReportPreparationList' })
 const isSearchExpanded = ref<boolean>(false)

+ 18 - 6
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/AuditCheckRecord.vue

@@ -178,8 +178,8 @@ import ReportListUploadModal from '@/views/pressure2/pipechecker/components/repo
   import SpreadDesigner from "@/components/SpreadDesigner/index.vue";
   import {editReport} from "@/utils/reportUtil";
   import {DynamicTbColApi} from "@/api/pressure2/dynamictbcol";
-  import {SpreadViewer} from "@/components/DynamicReport";
-  import {InitParams} from "@/components/DynamicReport/SpreadInterface";
+  import {SpreadViewer} from "@/components/pressure2/DynamicReport";
+  import {InitParams} from "@/components/pressure2/DynamicReport/SpreadInterface";
   const route = useRoute()
   const routeNameTypes = {
     'PipeCheckerRecordCheck': '记录校核审核',
@@ -378,10 +378,22 @@ import ReportListUploadModal from '@/views/pressure2/pipechecker/components/repo
 
     let res
     let apiParamsId;
-    if (props.useType === 'checkNotice'){
-      apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
-    }else{
-      apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+    // if (props.useType === 'checkNotice'){
+    //   apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.ids[0]
+    // }else{
+    //   apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+    // }
+    console.log(props.reportType)
+    switch(props.reportType) {
+      case PressureReportType['SUGGUESTION']:
+        apiParamsId = props.apiParams.reportId ? props.apiParams.reportId : props.apiParams.reportIds[0]
+        break
+      case PressureReportType['WORKINSTRUCTION']:
+      case PressureReportType['INSPECTIONPLAN']:
+      case PressureReportType['MAINQUESTION']:
+      default:
+        apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
+        break
     }
     //let apiParamsId = props.apiParams.id ? props.apiParams.id : props.apiParams.ids[0]
     switch(props.reportType) {

+ 448 - 100
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/InspectionItemList.vue

@@ -1,14 +1,49 @@
 <template>
   <div class="inspection-item-list">
+
+    <!-- 胶囊形状的 Tabs -->
+    <div class="capsule-tabs">
+      <el-badge
+        :value="inspectionItems.length"
+        :hidden="inspectionItems.length === 0"
+        :type="activeTab === 'inspection' ? 'primary' : 'danger'"
+        style="flex: 1"
+      >
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'inspection' }"
+          @click="activeTab = 'inspection'"
+        >
+          <span style="font-size: 12px">检验项目</span>
+        </div>
+      </el-badge>
+      <el-badge
+        :value="documentItems.length"
+        :hidden="documentItems.length === 0"
+        :type="activeTab === 'document' ? 'primary' : 'danger'"
+        style="flex: 1"
+      >
+        <div
+          class="tab-item"
+          :class="{ active: activeTab === 'document' }"
+          @click="activeTab = 'document'"
+        >
+          <span style="font-size: 12px">记录文件</span>
+        </div>
+      </el-badge>
+    </div>
+
     <div class="list-content">
-      <el-scrollbar always>
-      <div v-if="props.reportList.length === 0" class="empty-state">
-        <el-empty description="暂无检验项目" :image-size="120" />
+      <div v-if="currentTabItems.length === 0" class="empty-state">
+        <el-empty
+          :description="`暂无${activeTab === 'inspection' ? '检验项目' : '记录文件'}`"
+          :image-size="120"
+        />
       </div>
 
       <div v-else class="item-list">
         <div
-          v-for="item in props.reportList"
+          v-for="item in currentTabItems"
           :key="item.id"
           :class="getItemClass(item)"
           @click="handleItemClick(item)"
@@ -17,32 +52,65 @@
         >
           <div class="item-header">
             <div class="item-title-wrapper break-all">
-              <span
-                v-if="getReportTypeIcon(item.reportType)"
-                :class="getReportTypeIconClass(item.reportType)"
-              >
-                {{ getReportTypeIcon(item.reportType) }}
-              </span>
-              <span class="item-title">{{ item.reportName || '检验项目' }}({{ item.checkUsers.length ? item.checkUsers[0].nickname : '未分配' }})</span>
+              <div class="title-content">
+                <div
+                  v-if="getReportTypeIcon(item.reportType)"
+                  :class="getReportTypeIconClass(item.reportType)"
+                >
+                  {{ getReportTypeIcon(item.reportType) }}
+                </div>
+                <div class="item-title">
+                  <span
+                    v-if="activeTab == 'inspection'"
+                    :style="{color: getStatusBgColor(item.taskStatus) }"
+                  >
+                    <span>{{ item.reportName || '检验项目' }}</span>
+                    <span >({{ getCurrentStepName(item) }})</span>
+                  </span>
+                  <span v-else>
+                    {{ item.reportName || '检验项目' }}
+                  </span>
+                  <span style="color: red" v-if="isOtherReport(item) && getOtherStepName(item)">({{ getOtherStepName(item) }})</span>
+                  <span>&nbsp;&mdash;&nbsp;</span>
+                  <span v-if="activeTab == 'inspection'" :style="{color: getStatusBgColor(item.taskStatus) }">{{
+                      item.checkUsers.length ? item.checkUsers[0].nickname : '未分配'
+                    }}</span>
+                  <span v-else>{{ item.creatorUser ? item.creatorUser?.nickname : '' }}</span>
+                  <div
+                    class="text-[14px]"
+                    v-if="item.reportType === 300 && !item.instructionId && item.instructionTempId"
+                  >
+                    <el-button
+                      link
+                      :type="item.instructionId ? 'warning' : 'primary'"
+                      size="small"
+                      :disabled="isDisabledBtn(item)"
+                      @click.stop.prevent="() => handleCorrelationReport(item)"
+                    >
+                      {{ `(${item.instructionId ? '已' : '未'}关联操作指导书)` }}
+                    </el-button>
+                  </div>
+                </div>
+              </div>
             </div>
-            <div class="pl-[28px] text-[14px]" v-if="item.reportType === 300">
-              <el-button link :type="item.instructionId ? 'warning' : 'primary'" size="small" @click.stop.prevent="()=>handleCorrelationReport(item)">
-                {{  `(${item.instructionId ? '已' : '未'}关联操作指导书)` }}
+            <div v-if="activeTab == 'inspection'" class="fee-btn">
+              <el-button
+                :disabled="item?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END']"
+                link
+                type="primary"
+                @click.stop.prevent="() => handleInputCalcField(props.taskOrderItem, item)"
+              >
+                <span
+                  v-if="activeTab == 'inspection'"
+                >
+                  {{ getCheckItemFeeType(item) }}
+                </span>
+                <span v-else>{{ getCheckItemFeeType(item) }}</span>
               </el-button>
             </div>
-            <div class="pl-[28px] text-[12px]" v-if="item.pipeDetailName !== null" style="color: gray">
-              <span>对应管线:{{ item.pipeDetailName }}</span>
-            </div>
-            <div class="status-wrapper">
-              <el-tag :type="getStatusColor(item.taskStatus)" size="small">
-                {{ getReportStatusText(item) }}
-              </el-tag>
-              <el-button  :disabled="item?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END']" link type="primary" @click.stop.prevent="() => handleInputCalcField(props.taskOrderItem, item)">(费用:{{ getCheckItemFeeType(item) }})</el-button>
-            </div>
           </div>
         </div>
       </div>
-      </el-scrollbar>
     </div>
 
     <!-- 右键菜单 -->
@@ -87,6 +155,15 @@
         <span>修改检验员</span>
       </div>
       <div class="context-menu-divider"></div>
+      <div
+        class="context-menu-item"
+        :class="{ disabled: (contextMenuItem && !canSyncReport(contextMenuItem) || checkerIsLoginUserForSync) }"
+        @click="handleContextMenuCommand('syncAllReport')"
+      >
+        <el-icon><RefreshLeft /></el-icon>
+        <span>同步报表</span>
+      </div>
+      <div class="context-menu-divider"></div>
       <div 
         class="context-menu-item danger" 
         :class="{ disabled: (contextMenuItem && !canVoidItem(contextMenuItem) || checkerIsLoginUser) }"
@@ -124,7 +201,7 @@
 import { ref, computed, nextTick, onMounted, onUnmounted } from 'vue'
 import calcCheckItemFee from '@/views/pressure2/pipetaskorder/components/calcCheckItemFee.vue'
 
-import { User, Delete, ArrowUp, ArrowDown,Setting } from '@element-plus/icons-vue'
+import { User, Delete, ArrowUp, ArrowDown,Setting, RefreshLeft } from '@element-plus/icons-vue'
 import {ElMessage, ElMessageBox} from 'element-plus'
 import { 
   PressureCheckerMyTaskStatusMap,
@@ -137,7 +214,8 @@ import type { ReportItemVO, PipeTaskOrderOrderItemVO } from '@/api/pressure2/pip
 import { PipeTaskOrderApi } from '@/api/pressure2/pipetaskorder'
 import { is } from '@/utils/is'
 import { useUserStore } from '@/store/modules/user'
-import {DICT_TYPE, getStrDictOptions} from "@/utils/dict";
+import { useTaskProgress } from './useTaskProgress'
+import { cloneDeep } from 'lodash-es'
 
 interface Props {
   reportList: ReportItemVO[]
@@ -159,6 +237,7 @@ interface Emits {
   (e: 'set-pipe-detail', item: ReportItemVO): void
   (e: 'sort-report'): void
   (e: 'correlation-report', item: ReportItemVO): void
+  (e: 'sync-all-report', item: ReportItemVO)
 }
 
 
@@ -291,6 +370,16 @@ const canVoidItem = (item: ReportItemVO): boolean => {
   return [PressureReportType.MAINQUESTION, PressureReportType.INSPECTIONPLAN, PressureReportType.WORKINSTRUCTION].includes(item.reportType) || !forbiddenStatuses.includes(item.taskStatus)
 }
 
+// 判断是否允许同步报表数据
+const canSyncReport = (item: ReportItemVO): boolean => {
+  const forbiddenStatuses = [
+    PressureCheckerMyTaskStatus.REPORT_AUDIT, // 报告审核
+    PressureCheckerMyTaskStatus.REPORT_APPROVE, // 报告审批
+    PressureCheckerMyTaskStatus.REPORT_END // 报告办结
+  ]
+  return !forbiddenStatuses.includes(item.taskStatus)
+}
+
 // 判断当前检验员或主检人是否为登录用户,如果不是,则功能按钮disabled
 const checkerIsLoginUser = computed(() => {
   //记录校核后不允许修改
@@ -299,6 +388,13 @@ const checkerIsLoginUser = computed(() => {
   return !(checkerUserIds?.includes(userStore?.user?.id) || props.taskOrderItem?.mainCheckerUser?.id === userStore?.user?.id)
 })
 
+// 判断当前检验员或主检人是否为登录用户,如果不是,则功能按钮disabled
+const checkerIsLoginUserForSync = computed(() => {
+  //记录校核后不允许修改
+  const checkerUserIds = contextMenuItem.value?.checkUsers.map(checker => checker?.id)
+  return !(checkerUserIds?.includes(userStore?.user?.id) || props.taskOrderItem?.mainCheckerUser?.id === userStore?.user?.id)
+})
+
 // 判断是否可以上移
 const canMoveUp = (item: ReportItemVO): boolean => {
   const currentIndex = props.reportList.findIndex(report => report.id === item.id)
@@ -314,34 +410,64 @@ const canMoveDown = (item: ReportItemVO): boolean => {
 // 处理报告移动
 const handleMoveReport = async (item: ReportItemVO, direction: 'up' | 'down') => {
   try {
-    const currentIndex = props.reportList.findIndex(report => report.id === item.id)
+    const sortList = activeTab.value === 'inspection' ? inspectionItems.value : documentItems.value
+    const currentIndex = sortList.findIndex((report) => report.id === item.id)
     if (currentIndex === -1) return
-    
+
     // 创建新的排序数组
-    const sortedItems = [...props.reportList]
-    
+    const sortedItems = cloneDeep(props.reportList)
+    const prevReport = sortList[currentIndex - 1]
+    const currentReport = sortList[currentIndex]
+    const nextReport = sortList[currentIndex + 1]
+    const prevSort = prevReport?.sort || 0
+    const curSort = currentReport.sort
+    const nextSort = nextReport?.sort || 0
+
     if (direction === 'up' && currentIndex > 0) {
-      // 上移:与前一个交换位置
-      [sortedItems[currentIndex - 1], sortedItems[currentIndex]] = 
-      [sortedItems[currentIndex], sortedItems[currentIndex - 1]]
+      // 上移:与前一个交换位置, 修改sort字段值
+      sortedItems.forEach((item) => {
+        if(item.id === prevReport.id){
+          item.sort = curSort
+        } else if(item.id === currentReport.id){
+          item.sort = prevSort
+        }
+      })
+      // sortedItems[currentIndex].sort = sortedItems[currentIndex-1].sort
+      // sortedItems[currentIndex-1].sort = curSort
+      // ;[sortedItems[currentIndex - 1], sortedItems[currentIndex]] = [
+      //   sortedItems[currentIndex],
+      //   sortedItems[currentIndex - 1]
+      // ]
     } else if (direction === 'down' && currentIndex < sortedItems.length - 1) {
-      // 下移:与后一个交换位置
-      [sortedItems[currentIndex], sortedItems[currentIndex + 1]] = 
-      [sortedItems[currentIndex + 1], sortedItems[currentIndex]]
+      // 下移:与后一个交换位置, 修改sort字段值
+      sortedItems.forEach((item) => {
+        if(item.id === currentReport.id){
+          item.sort = nextSort
+        } else if(item.id === nextReport.id){
+          item.sort = curSort
+        }
+      })
+      // sortedItems[currentIndex].sort = sortedItems[currentIndex+1].sort
+      // sortedItems[currentIndex+1].sort = curSort
+      // ;[sortedItems[currentIndex], sortedItems[currentIndex + 1]] = [
+      //   sortedItems[currentIndex + 1],
+      //   sortedItems[currentIndex]
+      // ]
     } else {
       return // 无法移动
     }
-    
+
     // 构造API参数,重新分配sort值(从1开始)
-    const items = sortedItems.map((report, index) => ({
+    const newItems = sortedItems.sort((a, b) =>  (a.sort || 0) - (b.sort || 0))
+    const items = newItems.map((report, index) => ({
       id: report.id,
       sort: index + 1
     }))
-    
+
     // 调用API
     await PipeTaskOrderApi.sortReport({ items })
     ElMessage.success('排序已更新')
-    
+
     // 通知父组件刷新数据
     emit('sort-report')
   } catch (error) {
@@ -446,6 +572,10 @@ const handleContextMenuCommand = async (command: string) => {
       if(unref(checkerIsLoginUser)) return
       emit('set-pipe-detail', contextMenuItem.value)
       break
+    case 'syncAllReport':
+      if((contextMenuItem.value && !canSyncReport(contextMenuItem.value) || unref(checkerIsLoginUserForSync)) ) return
+      emit('sync-all-report', contextMenuItem.value)
+      break
   }
   
   contextMenuVisible.value = false
@@ -497,6 +627,89 @@ onUnmounted(() => {
   document.removeEventListener('click', handleDocumentClick)
   document.removeEventListener('contextmenu', handleDocumentContextMenu)
 })
+
+//新加
+// 使用任务进度逻辑
+const { getCurrentStepName } = useTaskProgress()
+
+// 检验项目类型:主报告(100)、子报告(200)、独审报告(300)、检验意见通知书(400)、分包项目(900)
+const inspectionTypes = [
+  PressureReportType.MAIN,
+  PressureReportType.SUB,
+  PressureReportType.SINGLE,
+  PressureReportType.SUGGUESTION,
+  PressureReportType.SUBCONTRACT
+]
+
+// 项目文件类型:操作指导书(700)、检验方案(600)、重大问题线索告知表(500)
+const documentTypes = [
+  PressureReportType.WORKINSTRUCTION,
+  PressureReportType.INSPECTIONPLAN,
+  PressureReportType.MAINQUESTION,
+]
+
+// 获取状态字体颜色
+const getStatusBgColor = (status: number): '#5B9BD5' | '#70AD47' | '#ED7D31' | '#9973C2' | '#FF8DC7' | '#FFC000' | '#A7D78B' | 'primary' | 'danger' => {
+  const statusMap: Record<number, '#5B9BD5' | '#70AD47' | '#FFC000'| '#9973C2' | '#ED7D31'| '#FF8DC7' | '#A7D78B' | 'primary' | 'danger'> = {
+    [PressureCheckerMyTaskStatus.CONFIRMED]: '#5B9BD5', //待录入
+    [PressureCheckerMyTaskStatus.RECORD_INPUT]: '#70AD47', //记录录入
+    [PressureCheckerMyTaskStatus.RECORD_CHECK]: '#9973C2', //记录校核
+    [PressureCheckerMyTaskStatus.REPORT_INPUT]: '#FFC000', //报告编制
+    [PressureCheckerMyTaskStatus.REPORT_AUDIT]: '#ED7D31', //报告审核
+    [PressureCheckerMyTaskStatus.REPORT_APPROVE]: '#FF8DC7', //报告审批
+    [PressureCheckerMyTaskStatus.REPORT_END]: '#303133' //报告办结
+  }
+  return statusMap[status] || '#A7D78B'
+}
+
+// 操作指导书名称
+const getWorkInstructionStepName = (item: ReportItemVO): string => {
+  switch (item.status) {
+    case 0:
+      return '待提交'
+    case 100:
+      return '待批准'
+    case 300:
+      return '退回'
+    default:
+      return ''
+  }
+}
+
+// Tabs 状态
+const activeTab = ref<'inspection' | 'document'>('inspection')
+
+// 根据类型分类数据
+const inspectionItems = computed(() => {
+  return props.reportList.filter((item) => inspectionTypes.includes(item.reportType))
+})
+
+const documentItems = computed(() => {
+  return props.reportList.filter((item) => documentTypes.includes(item.reportType))
+})
+
+// 当前激活的 tab 对应的数据
+const currentTabItems = computed(() => {
+  return activeTab.value === 'inspection' ? inspectionItems.value : documentItems.value
+})
+
+// 判断是否为其他报告类型
+const isOtherReport = (item: ReportItemVO): boolean => {
+  return [PressureReportType.WORKINSTRUCTION].includes(item.reportType)
+}
+
+// 获取其他报告类型的步骤名称
+const getOtherStepName = (item: ReportItemVO): string => {
+  if(PressureReportType.WORKINSTRUCTION == item.reportType) return getWorkInstructionStepName(item)
+  return getCurrentStepName(item)
+}
+
+// 独走报告关联操作指导书
+const isDisabledBtn = (item) => {
+  const checkerUserIds = item.checkUsers.map(checker => checker?.id)
+  return !(checkerUserIds?.includes(userStore?.user?.id))
+}
+
 </script>
 
 <style lang="scss" scoped>
@@ -504,122 +717,257 @@ onUnmounted(() => {
   height: 100%;
   display: flex;
   flex-direction: column;
-  
+
+  .capsule-tabs {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+    padding: 12px 6px;
+    background: #f5f7fa;
+    border-radius: 8px;
+    margin: 8px;
+
+    .tab-item {
+      flex: 1;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 6px;
+      padding: 10px 10px;
+      font-size: 12px;
+      background: white;
+      border-radius: 20px;
+      cursor: pointer;
+      transition: all 0.3s ease;
+      font-size: 14px;
+      color: #606266;
+      font-weight: 500;
+      border: 2px solid transparent;
+      user-select: none;
+
+      &:hover {
+        color: #409eff;
+        border-color: #409eff;
+      }
+
+      &.active {
+        background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+        color: white;
+        border-color: #409eff;
+        box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+
+        // 激活状态下的 badge 样式调整
+        :deep(.el-badge__content) {
+          background-color: white;
+          color: #409eff;
+          border: 1px solid rgba(255, 255, 255, 0.3);
+        }
+      }
+
+      // el-badge 样式优化
+      :deep(.el-badge) {
+        display: flex;
+        align-items: center;
+      }
+
+      :deep(.el-badge__content) {
+        transform: translateY(-50%) translateX(50%);
+        font-weight: bold;
+      }
+    }
+  }
+
   .list-content {
     flex: 1;
     overflow-y: auto;
-    
+
     .empty-state {
       height: 100%;
       display: flex;
       align-items: center;
       justify-content: center;
     }
-    
+
     .item-list {
       padding: 8px;
-      margin-right: 4px;
-      
+
       .list-item {
         border: 1px solid #e4e7ed;
         border-radius: 6px;
-        padding: 12px;
-        margin-bottom: 8px;
+        // padding: 8px 12px;
+        margin-bottom: 6px;
         cursor: pointer;
         transition: all 0.2s;
         background-color: white;
-        
+
         &:hover {
           border-color: #409eff;
           box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+
+          .fee-btn {
+            border-color: #409eff !important;
+          }
         }
-        
+
         &.selected {
           border-color: #409eff;
           background-color: #f0f9ff;
+
+          .fee-btn {
+            border-color: #409eff !important;
+          }
         }
-        
+
         .item-header {
           display: flex;
-          flex-direction: column;
-          
+          // flex-direction: column;
+          // gap: 8px;
+
           .item-title-wrapper {
-            flex: 1;
             display: flex;
-            justify-content: flex-start;
             align-items: center;
-            gap: 8px;
-            margin-right: 8px;
-          }
-          
-          .item-title {
-            font-weight: 500;
-            color: #303133;
-            font-size: 14px;
-          }
-          
-          .report-type-icon {
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            width: 20px;
-            height: 20px;
-            background-color: var(--el-color-primary);
-            color: white;
-            border-radius: 3px;
-            font-size: 12px;
-            font-weight: bold;
-            flex-shrink: 0;
-            
-            &.main {
-              background-color: var(--el-color-primary); // 主报告使用系统primary颜色
-            }
-            
-            &.single {
-              background-color: #e6a23c; // 独审报告使用橙色
+            flex-wrap: wrap;
+            gap: 6px;
+            line-height: 1.5;
+            padding: 8px 12px;
+            flex: 1;
+
+            .title-content {
+              display: flex;
+              gap: 6px;
+              // flex-wrap: wrap;
+              flex: 1;
+              min-width: 0;
             }
 
-            &.sub {
-              background-color: #91d5ff; // 子报告使用蓝色
+            .report-type-icon {
+              display: inline-flex;
+              align-items: center;
+              justify-content: center;
+              width: 20px;
+              height: 20px;
+              background-color: var(--el-color-primary);
+              color: white;
+              border-radius: 3px;
+              font-size: 12px;
+              font-weight: bold;
+              flex-shrink: 0;
+
+              &.main {
+                background-color: var(--el-color-primary);
+              }
+
+              &.single {
+                background-color: #e6a23c;
+              }
+
+              &.sub {
+                background-color: #91d5ff;
+              }
+
+              &.subcontract {
+                background-color: #afadf6;
+              }
+
+              &.work {
+                background-color: #67c23a;
+              }
+
+              &.zdwtxsgz {
+                background-color: #ff579b;
+              }
+
+              &.jyfa {
+                background-color: #d6e645;
+              }
+
+              &.plan {
+                background-color: #909399;
+              }
+
+              &.question {
+                background-color: #f56c6c;
+              }
             }
-            &.subcontract {
-              background-color: #afadf6; // 分包项目使用蓝色
+
+            .item-title {
+              // display: inline-flex;
+              // flex-wrap: wrap;
+              font-weight: 500;
+              color: #303133;
+              font-size: 14px;
+              flex: 1;
+
+              > span {
+                // display: inline-flex;
+                // align-items: center;
+              }
+
+              .el-button {
+                padding: 0 4px;
+                height: auto;
+                line-height: 1.5;
+                font-size: 14px;
+                vertical-align: baseline;
+              }
             }
           }
-          
+
+          .fee-btn {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            flex-basis: 60px;
+            border-left: 1px solid #e4e7ed;
+          }
+
+          .pl-\[28px\] {
+            padding-left: 28px;
+          }
+
           .status-wrapper {
-            max-width: 100%;
             display: flex;
-            align-items: flex-start;
+            align-items: center;
             flex-wrap: wrap;
             padding-left: 28px;
-            gap: 4px;
-            margin-top: 4px;
+            gap: 6px;
             box-sizing: border-box;
-          }
-          
 
+            .el-tag {
+              margin: 0;
+            }
+          }
         }
       }
     }
   }
 }
 
-// 响应式调整
 @media (max-width: 768px) {
-  .inspection-item-list {    
+  .inspection-item-list {
+    .capsule-tabs {
+      padding: 10px 12px;
+
+      .tab-item {
+        padding: 8px 16px;
+        font-size: 13px;
+      }
+    }
+
     .list-content .item-list .list-item {
       padding: 10px;
-      
+
       .item-header {
-        flex-wrap: wrap;
         gap: 6px;
+
+        .item-title-wrapper {
+          gap: 4px;
+        }
       }
     }
   }
 }
 </style>
-
 <style>
 /* 右键菜单全局样式 */
 .context-menu {
@@ -686,4 +1034,4 @@ onUnmounted(() => {
   background-color: #e4e7ed;
   margin: 6px 0;
 }
-</style> 
+</style>

+ 471 - 260
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/StatusOperationPanel.vue

@@ -12,11 +12,35 @@
 <!--          inactive-text="记录"-->
 <!--          @change="handleChangeShowReportPdf"-->
 <!--        />-->
-        <el-radio-group v-model="showReportPdfType" @change="handleChangeShowReportPdf">
-          <el-radio-button label="record">记录</el-radio-button>
-          <el-radio-button label="report">报告</el-radio-button>
-          <el-radio-button label="result" v-if="selectedItem?.reportType === 100">结论报告</el-radio-button>
-        </el-radio-group>
+<!--        <el-radio-group v-model="showReportPdfType" @change="handleChangeShowReportPdf">-->
+<!--          <el-radio-button label="record">记录</el-radio-button>-->
+<!--          <el-radio-button label="report">报告</el-radio-button>-->
+<!--          <el-radio-button label="result" v-if="selectedItem?.reportType === 100">结论报告</el-radio-button>-->
+<!--        </el-radio-group>-->
+        <div class="capsule-tabs">
+          <div
+            class="tab-item"
+            :class="{ active: showReportPdfType === 'record' }"
+            @click="handleChangeShowReportPdf('record')"
+          >
+            <span>记录</span>
+          </div>
+          <div
+            class="tab-item"
+            :class="{ active: showReportPdfType === 'report' }"
+            @click="handleChangeShowReportPdf('report')"
+          >
+            <span>报告</span>
+          </div>
+          <div
+            class="tab-item"
+            :class="{ active: showReportPdfType === 'result' }"
+            @click="handleChangeShowReportPdf('result')"
+            v-if="selectedItem?.reportType === 100"
+          >
+            <span>结论报告</span>
+          </div>
+        </div>
       </div>
       <div class="info-item" style="margin: 0 auto;">
         <span class="label">检验项目:</span>
@@ -94,22 +118,11 @@
           @modify-checker="handleModifyChecker"
         />
         <!-- <div class="pdf-panel" :style="{ maxWidth: !onlyShowPdf ? '1200px' : 'unset' }"> -->
-        <div class="pdf-panel" style="max-width: 1200px; margin: 0 auto;" >
+        <div ref="pdfPanelRef" class="pdf-panel" >
           <!-- PDF预览区域 -->
-<!--          <InlinePdfViewer-->
-<!--            class="!w-full"-->
-<!--            :refreshPdf="refreshPdf"-->
-<!--            v-if="refreshPdfFlag"-->
-<!--            :pdf-url="(selectedItem as any)?.prepareUrl || (selectedItem as any)?.reportUrl"-->
-<!--            :selected-item="selectedItem"-->
-<!--            :template-params="templateParams"-->
-<!--            :report-list="reportList"-->
-<!--            :handleUpload="handleUploadItem"-->
-<!--            :report-type="showReportPdfType"-->
-<!--          />-->
-          <el-scrollbar>
-            <SpreadViewer :initData="initData" ref="spreadRef"/>
-          </el-scrollbar>
+          <div class="!h-full" :style="{ width: pdfContentWidth + 'px'}">
+            <SpreadViewer :initData="initData" ref="spreadRef" isFullscreen  @saveSuccess="saveSuccessRecord"/>
+          </div>
         </div>
         <!--
           1、重大问题线索告知表
@@ -118,93 +131,93 @@
           以上报告类型不显示右侧栏目
         -->
         <template v-if="showCheckBook && getReportStatusEnd">
-          <div class="operation-panel">
-            <template v-if="[0, 2].includes(checkBookDetail.rectificationStatus)">
-              <div class="default-toolbar">
-                <el-button type="success" @click="() => handlePass(0)">整改通过</el-button>
-                <el-button type="success" @click="() => handlePass(2)">整改不通过</el-button>
+          <div class="right-panel-container">
+            <!-- 收缩展开按钮 -->
+            <div class="toggle-btn" @click="togglePanel" :class="{ 'collapsed': !isExpanded }">
+              <el-icon>
+                <Back v-if="!isExpanded" />
+                <Right v-else />
+              </el-icon>
+            </div>
+            <div class="operation-panel" :class="{ 'expanded': isExpanded, 'collapsed': !isExpanded }">
+              <div class="operation-inner custom-inner">
                 <template v-if="checkBookDetail.rectificationStatus === 2">
-                  <el-button type="primary" @click="() => handlePass(1)">回退</el-button>
+                  <div class="operation-item">
+                    <div class="item-header"> 回退原因 </div>
+                    <div class="item-content">
+                      <el-form
+                        ref="returnFormRef"
+                        :model="returnForm"
+                        :rules="returnFormRules"
+                        label-position="right"
+                        label-width="80px"
+                      >
+                        <el-form-item label="回退原因" prop="rejectionReason">
+                          <el-input
+                            v-model="returnForm.rejectionReason"
+                            type="textarea"
+                            :rows="5"
+                            maxlength="100"
+                            placeholder="请输入回退原因"
+                          />
+                        </el-form-item>
+                      </el-form>
+                    </div>
+                  </div>
                 </template>
-              </div>
-            </template>
-            <div class="operation-inner custom-inner">
-              <template v-if="checkBookDetail.rectificationStatus === 2">
-                <div class="operation-item">
-                  <div class="item-header"> 回退原因 </div>
+                <div class="operation-item1">
+                  <div class="item-header"> 处理人信息 </div>
                   <div class="item-content">
-                    <el-form
-                      ref="returnFormRef"
-                      :model="returnForm"
-                      :rules="returnFormRules"
-                      label-position="right"
-                      label-width="80px"
-                    >
-                      <el-form-item label="回退原因" prop="rejectionReason">
-                        <el-input
-                          v-model="returnForm.rejectionReason"
-                          type="textarea"
-                          :rows="5"
-                          maxlength="100"
-                          placeholder="请输入回退原因"
-                        />
-                      </el-form-item>
-                    </el-form>
-                  </div>
-                </div>
-              </template>
-              <div class="operation-item1">
-                <div class="item-header"> 处理人信息 </div>
-                <div class="item-content">
-                  <div class="item-content-item">
-                    <span class="item-content-item-label">联系人姓名:</span>
-                    <span class="item-content-item-value">{{ checkBookDetail?.recipient || '-' }}</span>
-                  </div>
-                  <div class="item-content-item">
-                    <span class="item-content-item-label">联系人电话:</span>
-                    <span class="item-content-item-value">{{ checkBookDetail?.recipientPhone || '-'}}</span>
-                  </div>
-                  <div class="item-content-item">
-                    <span class="item-content-item-label">当前状态:</span>
-                    <span class="item-content-item-value">{{
-                      rectificationStatusMap[checkBookDetail?.rectificationStatus] || '-'
-                    }}</span>
+                    <div class="item-content-item">
+                      <span class="item-content-item-label">联系人姓名:</span>
+                      <span class="item-content-item-value">{{ checkBookDetail?.recipient || '-' }}</span>
+                    </div>
+                    <div class="item-content-item">
+                      <span class="item-content-item-label">联系人电话:</span>
+                      <span class="item-content-item-value">{{ checkBookDetail?.recipientPhone || '-'}}</span>
+                    </div>
+                    <div class="item-content-item">
+                      <span class="item-content-item-label">当前状态:</span>
+                      <span class="item-content-item-value">{{
+                          rectificationStatusMap[checkBookDetail?.rectificationStatus] || '-'
+                        }}</span>
+                    </div>
                   </div>
                 </div>
-              </div>
-              <div class="operation-item1 videoAndImg">
-                <div class="item-header"> 整改材料 </div>
-                <div class="item-content1">
-                  <div class="item-content1-item">图片:</div>
-                  <div class="item-content1-img">
-                    <template
-                      v-for="(path, index) in checkBookDetail.rectificationImage?.split(',') || []"
-                      :key="index"
-                    >
-                    <div class="item-content1-img-item">
-                      <img class="img-item" :src="buildFileUrl(path)" alt="" @click="handlePreview(buildFileUrl(path), 'image')" />
-                      <div class="list-item-tool">
-                        <el-icon class="icon"  @click="() => handlePreview(buildFileUrl(path), 'image')"><View /></el-icon>
-                      </div>
+                <div class="operation-item1 videoAndImg">
+                  <div class="item-header"> 整改材料 </div>
+                  <div class="item-content1">
+                    <div class="item-content1-item">图片:</div>
+                    <div class="item-content1-img">
+                      <template
+                        v-for="(path, index) in checkBookDetail.rectificationImage?.split(',') || []"
+                        :key="index"
+                      >
+                        <div class="item-content1-img-item">
+                          <img class="img-item" :src="buildFileUrl(path)" alt="" @click="handlePreview(buildFileUrl(path), 'image')" />
+                          <div class="list-item-tool">
+                            <el-icon class="icon"  @click="() => handlePreview(buildFileUrl(path), 'image')"><View /></el-icon>
+                          </div>
+                        </div>
+                      </template>
                     </div>
-                    </template>
                   </div>
-                </div>
-                <div class="item-content1">
-                  <div class="item-content1-item">视频:</div>
-                  <div class="item-content1-img">
-                    <template
-                      v-for="(path, index) in checkBookDetail.rectificationVideo?.split(',') || []"
-                      :key="index"
-                    >
-                    <div class="item-content1-img-item">
-                      <video class="img-item" :src="buildFileUrl(path)" alt="" @click="handlePreview(buildFileUrl(path), 'video')"></video>
-                      <!-- 视频播放按钮 -->
-                      <div class="list-item-tool">
-                        <el-icon class="icon"  @click="() => handlePreview(buildFileUrl(path), 'video')"><View /></el-icon>
-                      </div>
+                  <div class="item-content1">
+                    <div class="item-content1-item">视频:</div>
+                    <div class="item-content1-img">
+                      <template
+                        v-for="(path, index) in checkBookDetail.rectificationVideo?.split(',') || []"
+                        :key="index"
+                      >
+                        <div class="item-content1-img-item">
+                          <video class="img-item" :src="buildFileUrl(path)" alt="" @click="handlePreview(buildFileUrl(path), 'video')"></video>
+                          <!-- 视频播放按钮 -->
+                          <div class="list-item-tool">
+                            <el-icon class="icon"  @click="() => handlePreview(buildFileUrl(path), 'video')"><View /></el-icon>
+                          </div>
+                        </div>
+                      </template>
                     </div>
-                    </template>
                   </div>
                 </div>
               </div>
@@ -212,158 +225,112 @@
           </div>
         </template>
         <template v-else>
-          <div class="operation-panel" v-if="!onlyShowPdf">
-            <div class="operation-btns">
-              <!-- 报告办结后,这部分按钮不再显示 -->
-              <template v-if="!getReportStatusEnd">
-                <!-- 根据报告类型和配置显示相应按钮 -->
-                <template
-                  v-if="
-                    selectedItem.reportType === PressureReportType.SINGLE &&
-                    'PipeCheckerTaskDetail' === routeName
-                  "
-                >
-                  <!-- 独审报告根据配置显示按钮 -->
-                  <el-button
-                    v-if="currentReportConfig?.isApproval && isCanSubmitToAudit"
-                    type="danger"
-                    @click="handleSubmitAudit"
-                    :disabled="checkerIsLoginUser"
-                  >
-                    提交报告审核
-                  </el-button>
-                  <!-- 没有审核只有审批 -->
-                  <el-button
-                    v-if="
-                      currentReportConfig?.isRatify &&
-                      !currentReportConfig?.isApproval &&
-                      isCanSubmitToAudit
-                    "
-                    type="danger"
-                    @click="handleApprovalSelectConfirm"
-                    :disabled="checkerIsLoginUser"
-                  >
-                    提交报告审批
-                  </el-button>
-                </template>
-                <!-- 非独审报告默认显示审核按钮 -->
-                <el-button
-                  v-if="
-                    selectedItem.reportType !== PressureReportType.SINGLE &&
-                    isCanSubmitToAudit &&
-                    'PipeCheckerTaskDetail' === routeName
-                  "
-                  type="danger"
-                  @click="handleSubmitAudit"
-                  :disabled="checkerIsLoginUser"
-                >
-                  {{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '提交审核' : '提交报告审核' }}
-                </el-button>
-                <el-button type="primary" plain v-if="isCanEditReport" @click="handleEditRreport" :disabled="checkerIsLoginUser"
-                >编制报告</el-button>
-                <el-button v-if="isCanSubmitRecheck" type="primary" @click="handleSelectVerfiyer" :disabled="checkerIsLoginUser"
-                >提交校核</el-button>
-                <el-button
-                  type="primary"
-                  v-if="isCanEditTestRecord"
-                  plain
-                  @click="handleEditSpreadRecord"
-                  :disabled="checkerIsLoginUser"
-                >{{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '编制意见书' : '填写记录' }}</el-button>
-                <el-button v-if="isCanSyncReportData" type="primary" @click="handleSyncReportData" :disabled="checkerIsLoginUser"
-                >同步报表</el-button>
-              </template>
-              <!-- 修改费用废弃 20250807 -->
-              <!-- <el-button
-                type="primary"
-                v-if="isCanEditRecordResult"
-                plain
-                @click="handleInputCheckConclusion"
-              >修改费用</el-button> -->
+          <div class="right-panel-container">
+            <!-- 收缩展开按钮 -->
+            <div class="toggle-btn" @click="togglePanel" :class="{ 'collapsed': !isExpanded }">
+              <el-icon>
+                <Back v-if="!isExpanded" />
+                <Right v-else />
+              </el-icon>
             </div>
-            <div class="operation-inner">
-              <!-- 流转记录 -->
-              <div class="operation-item">
-                <div class="item-header"> 流转记录 </div>
-                <div class="item-content">
-                  <el-empty
-                    v-if="!recordList.length"
-                    description="暂无流转记录"
-                    :image-size="120"
-                  />
-                  <div class="record-item" v-for="record in recordList" :key="record.id">
-                    <div class="record-item-title">{{
-                      PressureCheckerMyTaskStatusMap[record.process]
-                    }}</div>
-                    <div class="record-item-inner">
-                      <div class="content">
-                        <span>{{ record.createUser?.nickname||record.creator }}</span>
-                        <el-button
-                          :type="getRecordResultButtonType(record)"
-                          round
-                          size="small"
-                        >{{ formatRecordResult(record) }}</el-button>
-                        <span class="time">{{
-                          !record.createTime
-                            ? '-'
-                            : dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss')
-                        }}</span>
+            <div class="operation-panel" :class="{ 'expanded': isExpanded, 'collapsed': !isExpanded }" v-if="!onlyShowPdf">
+              <div class="operation-inner">
+                <!-- 流转记录 -->
+                <div class="operation-item">
+                  <div class="item-header"> 流转记录 </div>
+                  <div class="item-content">
+                    <el-empty
+                      v-if="!recordList.length"
+                      description="暂无流转记录"
+                      :image-size="120"
+                    />
+                    <div class="record-item" v-for="record in recordList" :key="record.id">
+                      <div class="record-item-title">{{ PressureReportType['SUGGUESTION'] === props.selectedItem?.reportType ? record.processName : PressureCheckerMyTaskStatusMap[record.process]
+
+                        }}</div>
+                      <div class="record-item-inner">
+                        <div class="content">
+                          <span>{{ record.createUser.nickname }}</span>
+                          <el-button
+                            :type="
+                              record.result === 100
+                                ? 'success'
+                                : record.result === 200
+                                  ? 'danger'
+                                  : 'default'
+                            "
+                            round
+                            size="small"
+                          >{{
+                              record.result === 100
+                                ? '通过'
+                                : record.result === 200
+                                  ? '拒绝'
+                                  : record.result || '-'
+                            }}</el-button
+                          >
+                          <span class="time">{{
+                              !record.createTime
+                                ? '-'
+                                : dayjs(record.createTime).format('YYYY-MM-DD HH:mm:ss')
+                            }}</span>
+                        </div>
+                        <p class="desc">
+                          <span>描述</span>
+                          {{ record.remark && record.remark.trim() !== '' ? record.remark : '-' }}
+                        </p>
                       </div>
-                      <p class="desc">
-                        <span>描述</span>
-                        {{ record.remark && record.remark.trim() !== '' ? record.remark : '-' }}
-                      </p>
                     </div>
                   </div>
                 </div>
-              </div>
-              <!-- 智能纠错 -->
-              <div class="operation-item">
-                <div class="item-header"> 智能纠错 </div>
-                <div class="item-content">
-                  <el-empty
-                    v-if="!checkInputList.length"
-                    description="暂无纠错内容"
-                    :image-size="120"
-                  />
-                  <div class="error-item" v-for="input in checkInputList" :key="input.code">
-                    <el-icon color="#E0534E" :size="20"><InfoFilled /></el-icon>
-                    {{ input.code }}:{{ input.name }}
+                <!-- 智能纠错 -->
+                <div class="operation-item">
+                  <div class="item-header"> 智能纠错 </div>
+                  <div class="item-content">
+                    <el-empty
+                      v-if="!checkInputList.length"
+                      description="暂无纠错内容"
+                      :image-size="120"
+                    />
+                    <div class="error-item" v-for="input in checkInputList" :key="input.code">
+                      <el-icon color="#E0534E" :size="20"><InfoFilled /></el-icon>
+                      {{ input.code }}:{{ input.name }}
+                    </div>
                   </div>
                 </div>
-              </div>
-              <!-- 历史版本 -->
-              <div class="operation-item" >
-                <div class="item-header"> 历史版本 </div>
-                <div class="item-content" v-if="historyList.length" >
-                  <!-- <template> -->
-                  <div class="history-item" v-for="item in historyList" :key="item.id">
-                    <div class="history-title">{{ item.versionNoStr || '-' }}号版本</div>
-                    <p
+                <!-- 历史版本 -->
+                <div class="operation-item">
+                  <div class="item-header"> 历史版本 </div>
+                  <div class="item-content" v-if="historyList.length">
+                    <!-- <template> -->
+                    <div class="history-item" v-for="item in historyList" :key="item.id">
+                      <div class="history-title">{{ item.versionNoStr || '-' }}号版本</div>
+                      <p
                       >修改原因:<span>{{ item.modifiedReason || '人工修改' }}</span></p
-                    >
-                    <div class="history-footer">
-                      <span class="name">{{ item.creatorName || '-' }}</span>
-                      <span class="time"
-                        >修改于{{
-                          dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss') || '-'
-                        }}</span
                       >
-                    </div>
-                    <div class="my-2">
-                      <el-button type="primary" link @click="handleShowVersionInfo(item)"
+                      <div class="history-footer">
+                        <span class="name">{{ item.creatorName || '-' }}</span>
+                        <span class="time"
+                        >修改于{{
+                            dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss') || '-'
+                          }}</span
+                        >
+                      </div>
+                      <div class="my-2">
+                        <el-button type="primary" link @click="handleShowVersionInfo(item)"
                         >查看详情</el-button
-                      >
-                      <el-button type="primary" link @click="restoreVersion(item)" :disabled="isCompleteInput"
+                        >
+                        <el-button v-if="!checkerIsLoginUser" type="primary" link @click="restoreVersion(item)"
                         >恢复版本</el-button
-                      >
+                        >
+                      </div>
                     </div>
+                    <!-- </template> -->
                   </div>
-                  <!-- </template> -->
+                  <template v-else>
+                    <el-empty description="暂无历史版本" :image-size="120" />
+                  </template>
                 </div>
-                <template v-else>
-                  <el-empty description="暂无历史版本" :image-size="120" />
-                </template>
               </div>
             </div>
           </div>
@@ -564,8 +531,12 @@
 <!--    />-->
 
     <!-- 检验录入-模板 -->
-    <CustomDialog v-model="showInlineEditCheckRecord" title="检验录入" :show-footer="false" fullscreen>
-      <SpreadViewer :initData="editData" ref="editSpreadRecordRef" @saveSuccess="saveSuccessRecord"/>
+    <CustomDialog v-model="showInlineEditCheckRecord" :show-footer="false" fullscreen :show-close="false">
+      <SpreadViewer :initData="editData" ref="editSpreadRecordRef" @saveSuccess="saveSuccessRecord" @close="handleClose">
+        <template #title>
+          <div style="font-size: 20px; ">检测录入</div>
+        </template>
+      </SpreadViewer>
     </CustomDialog>
 
     <!-- 报告编制 - 模板 -->
@@ -581,8 +552,12 @@
 <!--    />-->
 
     <!-- 报告编制 - 模板 -->
-    <CustomDialog v-model="showInlineReportEdit" title="报告编制" :show-footer="false" fullscreen>
-      <SpreadViewer :initData="editData" ref="editSpreadReportRef" @saveSuccess="saveSuccessReport"/>
+    <CustomDialog v-model="showInlineReportEdit" :show-footer="false" fullscreen  :show-close="false">
+      <SpreadViewer :initData="editData" ref="editSpreadReportRef" @saveSuccess="saveSuccessReport"  @close="handleClose">
+        <template #title>
+          <div style="font-size: 20px; ">报告编制</div>
+        </template>
+      </SpreadViewer>
     </CustomDialog>
 
     <!-- 修改主检人弹窗 -->
@@ -634,6 +609,77 @@
       @confirm="handleRefresh"
     />
   </div>
+
+  <!-- 新加 -->
+  <Teleport v-if="teleportBtnRef" to=".teleport-btn">
+    <div class="operation-btns">
+      <!-- 报告办结后,这部分按钮不再显示 -->
+      <template v-if="!getReportStatusEnd && selectedItem">
+        <!-- 根据报告类型和配置显示相应按钮 -->
+        <template
+          v-if="
+                    selectedItem.reportType === PressureReportType.SINGLE &&
+                    'PipeCheckerTaskDetail' === routeName
+                  "
+        >
+          <!-- 独审报告根据配置显示按钮 -->
+          <el-button
+            v-if="currentReportConfig?.isApproval && isCanSubmitToAudit"
+            type="danger"
+            @click="handleSubmitAudit"
+            :disabled="checkerIsLoginUser"
+            size="small"
+          >
+            提交报告审核
+          </el-button>
+          <!-- 没有审核只有审批 -->
+          <el-button
+            v-if="
+                      currentReportConfig?.isRatify &&
+                      !currentReportConfig?.isApproval &&
+                      isCanSubmitToAudit
+                    "
+            type="danger"
+            @click="handleApprovalSelectConfirm"
+            :disabled="checkerIsLoginUser"
+            size="small"
+          >
+            提交报告审批
+          </el-button>
+        </template>
+        <!-- 非独审报告默认显示审核按钮 -->
+        <el-button
+          v-if="
+                    selectedItem.reportType !== PressureReportType.SINGLE &&
+                    isCanSubmitToAudit &&
+                    'PipeCheckerTaskDetail' === routeName
+                  "
+          type="danger"
+          @click="handleSubmitAudit"
+          :disabled="checkerIsLoginUser"
+          size="small"
+        >
+          {{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '提交审核' : '提交报告审核' }}
+        </el-button>
+        <el-button type="primary" size="small" plain v-if="isCanEditReport" @click="handleEditRreport" :disabled="checkerIsLoginUser"
+        >编制报告</el-button>
+        <el-button v-if="isCanSubmitRecheck" type="primary" @click="handleSelectVerfiyer" :disabled="checkerIsLoginUser" size="small"
+        >提交校核</el-button>
+        <el-button
+          type="primary"
+          v-if="isCanEditTestRecord"
+          plain
+          @click="handleEditSpreadRecord"
+          :disabled="checkerIsLoginUser"
+          size="small"
+        >{{ selectedItem.reportType === PressureReportType['SUGGUESTION'] ? '编制意见书' : '填写记录' }}</el-button>
+<!--        <el-button v-if="isCanSyncReportData" type="primary" @click="handleSyncReportData" :disabled="checkerIsLoginUser" size="small"-->
+<!--        >同步报表</el-button>-->
+      </template>
+    </div>
+  </Teleport>
+
+
   <AssociationOperationManual 
     v-if="showAssociationOperationManual"
     :selectedItem="selectedItem"
@@ -650,7 +696,7 @@
 import SmartTable from '@/components/SmartTable/SmartTable'
 import {computed, defineAsyncComponent, ref, watch} from 'vue'
 import {useRoute} from 'vue-router'
-import {InfoFilled, View} from '@element-plus/icons-vue'
+import { InfoFilled, View, Back, Right, WarningFilled } from '@element-plus/icons-vue'
 import {dayjs, ElMessage, ElMessageBox} from 'element-plus'
 import * as UserApi from '@/api/system/user'
 import {
@@ -676,8 +722,9 @@ import _ from 'lodash'
 import {uploadFile} from '@/api/common/index'
 import ReportListUploadModal from './reportListUploadModal.vue'
 import AssociationOperationManual from './AssociationOperationManual.vue'
-import {SpreadViewer} from "@/components/DynamicReport";
-import {InitParams} from "@/components/DynamicReport/SpreadInterface";
+import {SpreadViewer} from "@/components/pressure2/DynamicReport";
+import {InitParams} from "@/components/pressure2/DynamicReport/SpreadInterface";
+import { cloneDeep, debounce } from 'lodash-es'
 
 const CustomDialog = defineAsyncComponent(() => import('@/components/CustomDialog/index.vue'))
 const AuditUserDialog = defineAsyncComponent(
@@ -692,6 +739,7 @@ interface Props {
   reportId?: string | null
   reportList?: ReportItemVO[]
   taskId: string
+  teleportBtnRef: Ref<HTMLDivElement | null>
 }
 
 interface Emits {
@@ -703,6 +751,7 @@ interface Emits {
   (e: 'cancel'): void
   (e: 'submit-ratify'): void
   (e: 'update:selected-item', item: ReportItemVO): void
+  (e: 'item-select', item: ReportItemVO): void
 }
 
 const props = defineProps<Props>()
@@ -760,7 +809,7 @@ const initData=ref<InitParams>(
     refId: '',
     refName:'',
     insId:'',
-    opType: 1, // 0:excel,1: pdf
+    opType: 0, // 0:excel,1: pdf
   });
 const reportInitData=ref<InitParams>(
   {
@@ -795,7 +844,8 @@ const getCanSubmitRecheckReport = computed(() => {
     const reportTypes = [PressureReportType['SUB'], PressureReportType['SINGLE']]
     const status = PressureTaskOrderTaskStatus['RECORD_INPUT'] // 记录录入状态
     // report.reportUrl 已经录入数据
-    if(report.taskStatus !== status || !report.reportUrl) return false
+    // if(report.taskStatus !== status || !report.reportUrl) return false
+    if(report.taskStatus !== status) return false
     else if(report.reportType === PressureReportType['MAIN'] && userStore.user.id === props.taskOrderItem?.mainCheckerUser?.id) return true
     else
     return reportTypes.includes(report.reportType) && (report.checkUsers?.[0]?.id === userStore.user.id || !report.checkUsers || !report.checkUsers.length)
@@ -842,6 +892,7 @@ const handleSelectVerfiyer = async () => {
   // 获取审核人\校核人配置信息
   let res = await UserApi.getApprovalDetail({})
   // 如果存在多份待提交校核的报告,执行批量校核弹窗
+  console.log('getCanSubmitRecheckReport', getCanSubmitRecheckReport,props.reportList)
   if(getCanSubmitRecheckReport.value.length > 1) {
     if(res && res.recheckUser) {
       batchRecheckForm.value.recheckUser = res.recheckUser
@@ -867,6 +918,11 @@ const handleBatchSubmitToRecheck = async () => {
     ElMessage.error('请完善表单数据!')
     return
   }
+
+  if (_.isEmpty(form.value.recheckUser)) {
+    return ElMessage.error('请选择校核人')
+  }
+
   // 待提交的数据
   const params = {
     reportList: getCanSubmitRecheckReport.value.filter((x: any) => batchRecheckForm.value.reportIds.includes(x?.id)).map((x: any) => ({
@@ -882,6 +938,7 @@ const handleBatchSubmitToRecheck = async () => {
   if (submitResult) {
     ElMessage.success('提交校核成功!')
     showBatchSubmitToRecheckDialog.value = false
+    selectNextItem(getCanSubmitRecheckReport.value.filter((x: any) => batchRecheckForm.value.reportIds.includes(x?.id)))
     // 这里可以做页面刷新
     emit('template-confirm')
   }
@@ -902,6 +959,7 @@ const handleAuditSelectConfirm = async () => {
     if (submitResult) {
       ElMessage.success('提交审核成功!')
       isShowAuditDialog.value = false
+      selectNextItem([props.selectedItem])
       // 这里可以做页面刷新
       emit('template-confirm')
     }
@@ -914,11 +972,30 @@ const handleAuditSelectConfirm = async () => {
     if (saveResult) {
       ElMessage.success('提交校核成功!')
       isShowAuditDialog.value = false
+      selectNextItem([props.selectedItem])
       // 这里可以做页面刷新
       emit('template-confirm')
     }
   }
 }
+/**
+ * 项目切换
+ */
+const selectNextItem = (items:ReportItemVO[]) => {
+  // 找到其他在记录录入或者报告编制状态的第一项目
+  const ids = items.map(item => item.id)
+  const inputReportList= props.reportList.filter(
+    item => {
+      return !ids.includes(item.id)
+    }
+  ).filter(item =>{
+    return item.taskStatus === PressureTaskOrderTaskStatus['RECORD_INPUT'] || item.taskStatus === PressureTaskOrderTaskStatus['REPORT_INPUT']
+  })
+  console.log(inputReportList)
+  if (inputReportList.length > 0){
+    emit('item-select', inputReportList[0])
+  }
+}
 
 
 // 审批人选择对话框状态
@@ -988,7 +1065,7 @@ const returnFormRules = ref({
 const returnFormRef = ref()
 
 const isCompleteInput = computed(() => {
-  return props.selectedItem?.taskStatus >= PressureCheckerMyTaskStatus.REPORT_AUDIT
+  return props.selectedItem?.taskStatus >= PressureCheckerMyTaskStatus.RECORD_CHECK
 })
 
 const isShowRecordOrReportBtn = computed(() => {
@@ -1066,8 +1143,8 @@ const isCanSubmitRecheck = computed(() => {
   else
     return (
       props.selectedItem &&
-      props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT'] &&
-      props.selectedItem.reportUrl
+      props.selectedItem.taskStatus === PressureCheckerMyTaskStatus['RECORD_INPUT']
+      // && props.selectedItem.reportUrl
     )
 })
 
@@ -1729,10 +1806,10 @@ const showAssociationOperationManual = ref(false)
 const handleShowAssociationOperationManual = () => {
   showAssociationOperationManual.value = true
 }
-onMounted(() => {
-  console.log(props)
-  //initPreview()
-})
+// onMounted(() => {
+//   console.log(props)
+//   //initPreview()
+// })
 
 const initPreview=()=>{
 
@@ -1744,7 +1821,7 @@ const initPreview=()=>{
 
   initData.value.templateId = templateId;
   initData.value.refId = refId;
-  initData.value.opType = 1;
+  initData.value.opType = (props.selectedItem.taskStatus == 510 || props.selectedItem.taskStatus >= 600 || (showReportPdfType.value === 'record' && props.selectedItem.taskStatus > 500) ||  props?.selectedItem?.checkUsers?.[0]?.id != userStore?.user?.id) ? 1 : 0;
 
   //spreadRef.value?.reloadView();
 
@@ -1795,6 +1872,18 @@ const saveSuccessRecord = async (data)=>{
 
 }
 
+const handleClose = () => {
+  ElMessageBox.confirm('是否关闭?', {
+    confirmButtonText: '确认',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(() => {
+    showInlineEditCheckRecord.value = false
+    showInlineReportEdit.value = false
+    handleRefresh()
+  })
+}
+
 const saveSuccessReport = async (data)=>{
 
   const dataJson = !data.dataSource ? '' : JSON.stringify(data.dataSource)
@@ -1821,9 +1910,87 @@ const handleRefreshData = () => {
 defineExpose({
   handleShowAssociationOperationManual
 })
+
+//新加
+// 添加控制展开/收缩状态的变量
+const isExpanded = ref(false);
+
+// 添加切换面板展开/收缩的方法
+const togglePanel = () => {
+  isExpanded.value = !isExpanded.value;
+
+  setTimeout((() => {
+    nextTick(handleWindowResize)
+  }), 300);
+};
+
+// 获取PDF宽度
+const pdfContentWidth = ref<number>(980)
+const pdfPanelRef = ref<HTMLDivElement>()
+const handleWindowResize = debounce(() => {
+  if(!pdfPanelRef.value) return
+  const width = pdfPanelRef.value?.clientWidth - 20
+  pdfContentWidth.value = width > 980 ? 980 : width
+
+  //重新加载适应宽度
+  spreadRef.value?.reloadView();
+
+}, 100)
+
+onMounted(() => {
+  handleWindowResize()
+  window.addEventListener('resize', handleWindowResize)
+})
+onUnmounted(() => {
+  window.removeEventListener('resize', handleWindowResize)
+})
+
 </script>
 
 <style lang="scss" scoped>
+.capsule-tabs {
+  display: inline-flex;
+  align-items: center;
+  gap: 2px;
+  padding: 2px;
+  border-radius: 20px;
+  margin-right: 12px;
+  flex-shrink: 0;
+
+  .tab-item {
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    padding: 4px 12px;
+    font-size: 12px;
+    background: white;
+    border-radius: 16px;
+    cursor: pointer;
+    transition: all 0.3s ease;
+    color: #606266;
+    font-weight: 500;
+    border: 1px solid transparent;
+    user-select: none;
+    white-space: nowrap;
+    min-width: 50px;
+
+    span {
+      font-size: 12px !important;
+    }
+
+    &:hover {
+      color: #409eff;
+      border-color: #409eff;
+    }
+
+    &.active {
+      background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+      color: white;
+      border-color: #409eff;
+      box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+    }
+  }
+}
 .container-panel {
   height: 100%;
   border: 1px solid var(--el-border-color);
@@ -1857,19 +2024,64 @@ defineExpose({
       overflow: hidden;
     }
 
+    .right-panel-container {
+      display: flex;
+      position: relative;
+
+      // 收缩展开按钮样式
+      .toggle-btn {
+        position: absolute;
+        left: -30px;
+        top: 50%;
+        transform: translateY(-50%);
+        width: 30px;
+        height: 60px;
+        background-color: #fff;
+        border: 1px solid var(--el-border-color);
+        border-radius: 10px 0 0 10px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        cursor: pointer;
+        z-index: 100;
+        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+        transition: all 0.3s ease;
+        &:hover {
+          background-color: #f5f5f5;
+        }
+        &.collapsed {
+          left: -18px;
+          // border-radius: 0 10px 10px 0;
+        }
+      }
+    }
+
     .operation-panel {
       flex-basis: 300px;
       padding: 10px 10px 10px 0;
       box-sizing: border-box;
-      display: flex;
-      flex-direction: column;
-      height: 100%;
+      transition: all 0.3s ease;
+
+      // 展开状态样式
+      &.expanded {
+        width: 300px;
+      }
+
+      // 收缩状态样式
+      &.collapsed {
+        width: 0;
+        padding: 0;
+        border: none;
+        .default-toolbar,
+        .operation-inner {
+          display: none;
+        }
+      }
 
       .operation-btns {
         display: flex;
         flex-wrap: wrap;
         gap: 12px;
-        flex-shrink: 0; // 不收缩
 
         .el-button {
           margin-bottom: 10px;
@@ -1879,8 +2091,7 @@ defineExpose({
     }
 
     .operation-inner {
-      flex: 1; // 占据剩余空间
-      overflow-y: auto;
+      height: 100%;
       padding: 7px 10px 20px;
       background-color: #fff;
       border: 1px solid var(--el-border-color);

+ 11 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/useTaskProgress.ts

@@ -225,11 +225,21 @@ export const useTaskProgress = () => {
     return rollbackStages
   }
 
+  // 获取当前节点对应的名称
+  const getCurrentStepName = (selectedItem: ReportItemVO | null): string => {
+    if (!selectedItem) return ''
+    const steps = getStepsByType(selectedItem)
+    const currentStepIndex = getCurrentStep(selectedItem)
+    const i = selectedItem.taskStatus === PressureCheckerMyTaskStatus.REPORT_END ? currentStepIndex - 1 : currentStepIndex
+    return steps[i]?.title || ''
+  }
+
   return {
     getStepsByType,
     getCurrentStep,
     getStepStatus,
     getRollbackStages,
-    checkNeedAuditApproval
+    checkNeedAuditApproval,
+    getCurrentStepName
   }
 } 

+ 232 - 18
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/myTask.vue

@@ -231,8 +231,16 @@
 
   <!-- 列表 -->
   <ContentWrap>
-    <el-table v-loading="loading" :data="list" :stripe="true" ref="MyTaskTableListRef">
+    <el-table v-loading="loading" :data="list" :stripe="true" ref="MyTaskTableListRef"
+              border
+              class-name="cursor-pointer" @row-dblclick="handleRowDblclick">
       <el-table-column type="selection" align="center" width="55"/>
+      <el-table-column
+        label="剩余期限 (工作日)"
+        align="center"
+        prop="remainingDays"
+        min-width="140px"
+      />
       <el-table-column label="任务单号" align="center" prop="orderNo" min-width="150px"/>
       <el-table-column label="工程号" align="center" prop="projectNoList" min-width="200px">
         <template #default="{ row }">
@@ -251,13 +259,13 @@
       </el-table-column>
 
       <!-- 检验项目 -->
-      <el-table-column label="检验项目" align="center" prop="reportDOList" min-width="200px">
-        <template #default="scope">
+      <el-table-column align="center" prop="reportDOList" :min-width="200">
+<!--        <template #default="scope">
           <div v-if="scope.row.reportDOList && scope.row.reportDOList.length > 0">
             <div class="reportDOList-item">
-              <!-- 数据大于2条时,使用展开收起功能 -->
+              &lt;!&ndash; 数据大于2条时,使用展开收起功能 &ndash;&gt;
               <div v-if="scope.row.reportDOList.length > 2">
-                <!-- 始终显示的前2条数据 -->
+                &lt;!&ndash; 始终显示的前2条数据 &ndash;&gt;
                 <div
                   v-for="(item, index) in scope.row.reportDOList.slice(0, 2)"
                   :key="item?.id || index"
@@ -271,7 +279,7 @@
                   </div>
                 </div>
 
-                <!-- 可展开的剩余数据 -->
+                &lt;!&ndash; 可展开的剩余数据 &ndash;&gt;
                 <div
                   class="expandable-content"
                   :style="{
@@ -302,7 +310,7 @@
                   </div>
                 </div>
 
-                <!-- 展开收起按钮 -->
+                &lt;!&ndash; 展开收起按钮 &ndash;&gt;
                 <div class="expand-control" style="text-align: center; margin-top: 8px">
                   <el-button
                     size="small"
@@ -336,7 +344,7 @@
                 </div>
               </div>
 
-              <!-- 数据小于等于2条时,直接显示所有数据 -->
+              &lt;!&ndash; 数据小于等于2条时,直接显示所有数据 &ndash;&gt;
               <div v-else>
                 <div
                   v-for="(item, index) in scope.row.reportDOList"
@@ -354,6 +362,34 @@
             </div>
           </div>
           <div v-else class="empty-data">-</div>
+        </template>-->
+        <template #header>
+          <span class="table-header-content">
+            检验项目
+            <el-tooltip placement="bottom" effect="light" popper-class="status-help-tooltip">
+              <template #content>
+                <div class="status-help-content">
+                  <div class="help-title">状态说明</div>
+                  <div class="status-list">
+                    <div
+                      v-for="item in statusList"
+                      :key="item.status"
+                      class="status-item"
+                    >
+                      <div class="color-dot" :style="{ backgroundColor: item.color }"></div>
+                      <span class="status-label">{{ item.label }}</span>
+                    </div>
+                  </div>
+                </div>
+              </template>
+              <el-icon class="help-icon">
+                <QuestionFilled/>
+              </el-icon>
+            </el-tooltip>
+          </span>
+        </template>
+        <template #default="scope">
+          <PipeReportList :row="scope.row"/>
         </template>
       </el-table-column>
 
@@ -373,12 +409,6 @@
           </el-tag>
         </template>
       </el-table-column>
-      <el-table-column
-        label="剩余期限 (工作日)"
-        align="center"
-        prop="remainingDays"
-        min-width="140px"
-      />
       <el-table-column label="检验时间" align="center" prop="checkDate" min-width="120px">
         <template #default="scope">
           {{ formatArrayDate(scope.row.checkDate) }}
@@ -447,6 +477,13 @@
               @click="handleCheckInput(scope.row.id,scope.row.orderId)">
               检验录入
             </el-button>
+            <el-button
+              link
+              type="primary"
+              @click="printProject(scope.row.reportDOList)"
+            >
+              打印项目
+            </el-button>
 <!--            <el-button-->
 <!--                v-if="scope.row.taskStatus == 400 && scope.row.isClaim == true && scope.row.mainCheckerUser && userStore.user.id === scope.row.mainCheckerUser.id"-->
 <!--                link-->
@@ -507,7 +544,47 @@
       @on-page-no-change="onPageNoChange"
     />
   </CustomDialog>
-
+  <CustomDialog
+    v-model="printProjectDialogVisible"
+    :close-on-click-modal="true"
+  >
+    <el-table
+      :data="printProjectList"
+      style="width: 100%"
+      @selection-change="handleSelectionChange"
+    >
+      <el-table-column
+        type="selection"
+        align="center"
+      />
+      <el-table-column
+        label="序号"
+        width="60"
+        align="center"
+        type="index"
+      />
+      <el-table-column
+        label="项目"
+        align="center"
+        prop="reportName"
+      />
+      <el-table-column
+        label="项目状态"
+        align="center"
+        prop="taskStatus"
+      >
+        <template #default="scope">
+          <el-tag :type="getTypeColor(scope.row.taskStatus)">
+            {{ PressureTaskOrderTaskStatusMap[scope.row.taskStatus] }}
+          </el-tag>
+        </template>
+      </el-table-column>
+    </el-table>
+    <template #footer>
+      <el-button type="success" @click="handlePrint">打印</el-button>
+      <el-button type="danger" @click="handleDownload">下载</el-button>
+    </template>
+  </CustomDialog>
 </template>
 
 <script setup lang="ts">
@@ -516,19 +593,22 @@ import {formatArrayDate} from '@/utils/formatTime'
 //import SettingDialog from './components/SettingDialog.vue'
 import {PipeTaskOrderApi, PipeTaskOrderOrderItemVO} from '@/api/pressure2/pipetaskorder'
 import {
+  PressureCheckerMyTaskStatus,
   PressureCheckerMyTaskStatusMap, PressurePipeCheckTypeMap,
   PressureTaskOrderTaskStatus, PressureTaskOrderTaskStatusMap
 } from '@/utils/constants'
 import {ElMessageBox, ElMessage} from 'element-plus'
 import {useRoute, useRouter} from 'vue-router'
-import {ArrowDown, ArrowUp} from "@element-plus/icons-vue";
+import {ArrowDown, ArrowUp, QuestionFilled} from "@element-plus/icons-vue";
 import { useUserStore } from '@/store/modules/user'
 import {useEmitt} from "@/hooks/web/useEmitt";
 import endCheckForm from './components/endCheckForm.vue'
 import SmartTable from "@/components/SmartTable/SmartTable";
 import { getUserList } from '@/api/common/user'
 import SettingDialog from "@/views/pressure/checker/components/SettingDialog.vue";
-
+import {PipeInputApi} from "@/api/pressure2/pipeInput";
+import 'https://printjs-4de6.kxcdn.com/print.min.js'
+import PipeReportList from "@/views/pressure2/pipeReportPreparationList/PipeReportList.vue";
 const { emitter } = useEmitt()
 const router = useRouter()
 const route = useRoute()
@@ -554,6 +634,16 @@ const getTypeColor = (status: string | number) => {
   return statusMap[status] || 'info'
 }
 
+const statusList = [
+  { status: PressureCheckerMyTaskStatus.CONFIRMED, label: '待录入', color: '#5B9BD5' },
+  { status: PressureCheckerMyTaskStatus.RECORD_INPUT, label: '记录录入', color: '#70AD47' },
+  { status: PressureCheckerMyTaskStatus.RECORD_CHECK, label: '记录校核', color: '#9973C2' },
+  { status: PressureCheckerMyTaskStatus.REPORT_INPUT, label: '报告编制', color: '#FFC000' },
+  { status: PressureCheckerMyTaskStatus.REPORT_AUDIT, label: '报告审核', color: '#ED7D31' },
+  { status: PressureCheckerMyTaskStatus.REPORT_APPROVE, label: '报告审批', color: '#FF8DC7' },
+  { status: PressureCheckerMyTaskStatus.REPORT_END, label: '报告办结', color: '#303133' }
+]
+
 /** 承压任务单 列表 */
 defineOptions({name: 'PipeMyTask'})
 
@@ -641,12 +731,136 @@ const handleFinishCheck = (id: string,endCheckDate: any) => {
   endCheckFormRef.value.open(id,endCheckDate)
 }
 
+const printProjectDialogVisible = ref(false)
+const printProjectList = ref([])
+const selectionProject = ref([])
+const printProject = (reportDOList) => {
+  printProjectDialogVisible.value = true
+  printProjectList.value = reportDOList
+}
+
+const handleSelectionChange = (selection) => {
+  // 根据表格的序号重新排序 selection
+  const sortedSelection = selection.map(item => {
+    // 在原始数据中查找该项的索引
+    const index = printProjectList.value.findIndex(v => v.id === item.id)
+    return {
+      ...item,
+      _index: index // 临时存储原始索引
+    }
+  })
+  
+  // 按照索引排序
+  sortedSelection.sort((a, b) => a._index - b._index)
+  
+  // 移除临时索引字段
+  selectionProject.value = sortedSelection.map(({ _index, ...rest }) => rest)
+}
+
+const handleDownload = async () => {
+  let data: any[] = []
+  console.log(selectionProject)
+  selectionProject.value.forEach(
+    (item) => {
+      data.push({
+        taskStatus: item.taskStatus,
+        resultTemplateId: item.resultTemplateId,
+        templateId: item.templateId,
+        reportTemplateId: item.reportTemplateId,
+        reportUrl: item.reportUrl,
+        id: item.id,
+      })
+    }
+  )
+  
+  try {
+    // 调用 API 获取 ZIP blob
+    const zipBlob = await PipeInputApi.handleDownload(data)
+    
+    if (zipBlob) {
+      // 创建临时 URL
+      const zipUrl = URL.createObjectURL(zipBlob)
+      
+      // 创建隐藏的 a 标签触发下载
+      const link = document.createElement('a')
+      link.href = zipUrl
+      link.download = '管道检测录入报告.zip'
+      document.body.appendChild(link)
+      link.click()
+      
+      // 清理 DOM 和 URL
+      document.body.removeChild(link)
+      URL.revokeObjectURL(zipUrl)
+      
+      ElMessage.success('下载成功')
+    } else {
+      ElMessage.error('获取下载文件失败')
+    }
+  } catch (error) {
+    console.error('下载失败:', error)
+    ElMessage.error('下载失败')
+  }
+}
+
+const handlePrint = async () => {
+  let data: any[] = []
+  selectionProject.value.forEach(
+    (item) => {
+      data.push({
+        taskStatus: item.taskStatus,
+        resultTemplateId: item.resultTemplateId,
+        templateId: item.templateId,
+        reportTemplateId: item.reportTemplateId,
+        reportUrl: item.reportUrl,
+        id: item.id,
+      })
+    }
+  )
+  let loadingInstance = ElLoading.service({
+    lock: true,
+    text: '正在打印...',
+    spinner: 'el-icon-loading',
+  })
+  try {
+    // 调用 API 获取 PDF blob
+    const pdfBlob = await PipeInputApi.handlePrint(data)
+    
+    if (pdfBlob) {
+      // 创建临时 URL
+      const pdfUrl = URL.createObjectURL(pdfBlob)
+      
+      // 使用 printJS 打印
+      printJS({
+        printable: pdfUrl,
+        type: 'pdf',
+        showModal: false,
+        onPrintDialogClose: () => {
+          // 打印对话框关闭后释放 URL
+          URL.revokeObjectURL(pdfUrl)
+        }
+      })
+    } else {
+      ElMessage.error('获取打印文件失败')
+    }
+  } catch (error) {
+    console.error('打印失败:', error)
+    ElMessage.error('打印失败')
+  }finally {
+    loadingInstance.close()
+  }
+}
+
 /** 修改成功处理 */
 const handleScheduleSuccess = () => {
   //通知父组件刷新数据
   getList()
 }
-
+const handleRowDblclick = (row: Record<string, any>) => {
+  if (row.isClaim === false) {
+    return
+  }
+  router.push({ name: 'PipeCheckerTaskDetail', query: { id: row.id,orderId:row.orderId, type: 'PipeReportPreparationList' } })
+}
 
 const handleClaim = (id: string) => {
   ElMessageBox.confirm('是否确认认领?', '提示', {

+ 1 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/recordCheck.vue

@@ -83,7 +83,7 @@
           v-model="queryParams.checkUserStrIds"
           readonly
           clearable
-          placeholder="请选择校核人"
+          placeholder="请选择检验员"
           multiple
           popper-class="user-select-popper"
           @click.stop.prevent="() => handleOpenUserDialog(recheckStrIdsOpts, 'checkUserStrIds')"

+ 184 - 11
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/taskDetail.vue

@@ -2,15 +2,20 @@
   <!-- 我的任务详情 -->
   <div class="task-detail-layout">
     <div class="detail-header">
-      任务详情
-      <el-button class="ml-15px" type="primary" size="small" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
-<!--      <el-button class="ml-15px" type="primary" size="small" v-if="showGenerateReport" plain @click="() => handleGenerateReportPdf()">-->
-<!--        出具报告PDF</el-button>-->
+      <div class="flex items-center pl-14px flex-[0_0_450px]">
+        任务详情
+        <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowOnlyMyItems" size="small" @change="handleShowOnlyMyItems">只看我</el-checkbox>
+        <el-checkbox class="flex-1 flex items-center justify-center" v-model="isShowConcludeItems" size="small" @change="handleShowConcludeItemsItems">报告未办结</el-checkbox>
+        <el-button class="ml-15px" type="primary" size="small" plain @click="() => handleViewEquipment()">查看设备档案</el-button>
+        <el-button class="ml-15px" type="primary" size="small" plain @click="() => toTaskOrderDetail()">任务单</el-button>
+      </div>
+      <el-divider direction="vertical" class="mx-10px" />
+      <div id="teleport-btn" ref="teleportBtnRef" class="teleport-btn"></div>
       <div class="detail-header-back"><el-button type="default" plain @click="() => handleBack()">返回</el-button></div>
     </div>
-    <el-row class="task-detail-container">
+    <div class="task-detail-container flex">
       <!-- 左侧:检验项目列表 -->
-      <el-col :span="4" class="left-panel">
+      <div class="left-panel flex-[0_0_450px]">
         <ContentWrap title="检验项目清单" class="h-full flex flex-col" :bodyStyle="{padding: '10px', flex: 1, overflow: 'hidden'}">
           <template #header>
             <el-button @click="handleAddItem" type="primary" size="small" :disabled="taskInfo?.taskStatus === PressureTaskOrderTaskStatus['REPORT_END'] || canAddReportItem">添加检验项目</el-button>
@@ -28,14 +33,15 @@
             @modify-checker="handleModifyCheckerForItem"
             @void-item="handleVoidItem"
             @set-pipe-detail="setPipeDetail"
+            @sync-all-report="handleSyncReportData"
             @sort-report="handleRefresh"
             @refresh="handleRefresh"
             @correlation-report="handleCorrelationReport"
             @template-confirm="handleTemplateConfirm"
           />
         </ContentWrap>
-      </el-col>
-      <el-col :span="20" class="right-panel">
+      </div>
+      <div class="right-panel flex-1 overflow-hidden">
         <StatusOperationPanel
           ref="statusOperationPanelRef"
           v-model:selected-item="selectedItem"
@@ -44,12 +50,14 @@
           :report-list="reportList"
           :history-list="historyList"
           :taskId="taskId"
+          :teleportBtnRef="teleportBtnRef"
           @refresh="handleRefresh"
           @modify-checker="handleModifyChecker"
           @template-confirm="handleTemplateConfirm"
+          @item-select="handleItemSelect"
         />
-      </el-col>
-    </el-row>
+      </div>
+    </div>
   </div>
 
   <AddOrEditCheckItemForEquipment
@@ -161,6 +169,8 @@ const router = useRouter()
 const userStore = useUserStore()
 const { emitter } = useEmitt()
 
+const teleportBtnRef = ref<HTMLDivElement>()
+
 // 基础数据
 const taskId = ref<string | null>(null)
 const orderId = ref<string | null>(null)
@@ -288,7 +298,7 @@ const handleCloseNewInspectionItem = () => {
 }
 
 // 获取任务详情数据
-async function fetchTaskDetail(id: string) {
+async function fetchTaskDetail11(id: string) {
   loading.value = true
   try {
     //debugger;
@@ -577,6 +587,11 @@ const handleVoidItem = async (item: ReportItemVO) => {
     }
     await fetchTaskDetail(idFromQuery)
     await getOrderHistoryVersion(idFromQuery)
+    const activeDetailItemId= localStorage.getItem('activePipeDetailItemId')
+    if (activeDetailItemId){
+      await handleItemSelect(reportList.value.find( item=> item.id === activeDetailItemId))
+      localStorage.removeItem('activePipeDetailItemId')
+    }
   } else {
     taskInfo.value = null
     loading.value = false
@@ -602,6 +617,164 @@ const EquipPipeFormRef = ref<InstanceType<typeof EquipPipeForm>>()
 const handleViewEquipment = () => {
   EquipPipeFormRef.value.open(orderId.value)
 }
+
+const toTaskOrderDetail = () => {
+  router.push({
+    name: 'PipeTaskOrderView',
+    query: {
+      id: orderId.value,
+      type: 'checker'
+    }
+  })
+}
+
+const handleSyncReportData = async () => {
+
+  try {
+    const response = await PipeTaskOrderApi.syncAllReportData({
+      refId: selectedItem.value.id,
+    })
+    if (response){
+      ElMessage.success('同步数据成功')
+      handleRefresh()
+    }else{
+      ElMessage.error('同步数据失败,请稍后重试')
+    }
+
+  } catch (error: any) {
+    console.error('同步数据失败:', error)
+    ElMessage.error('同步数据失败,请稍后重试')
+  }
+
+}
+
+//新加功能
+const {id: userId = ''} = userStore.getUser || {}
+
+const isMainChecker = () => {
+  const {mainCheckerUser} = taskOrderItem.value
+  const {id: mainCheckerUserId = ''} = mainCheckerUser || {}
+  return mainCheckerUserId === userId
+}
+const isShowOnlyMyItems = ref(false)
+const isShowConcludeItems = ref(false)
+
+const handleShowOnlyMyItems = (value) => {
+  isShowOnlyMyItems.value = value
+  getReportList()
+}
+const handleShowConcludeItemsItems = (value) => {
+  isShowConcludeItems.value = value
+  getReportList()
+}
+const getReportList = (activeReportId = '')=>{
+  // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
+  if(!isShowOnlyMyItems.value){
+    reportList.value = isShowConcludeItems.value ?  filteredReportList.value.map( item => item )?.filter(
+      (item) => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END) : filteredReportList.value.map( item => item )
+  } else {
+    const documentTypes = [
+      PressureReportType.WORKINSTRUCTION,
+      PressureReportType.INSPECTIONPLAN,
+      PressureReportType.MAINQUESTION
+    ]
+    reportList.value = filteredReportList.value.filter(v=> {
+      if(documentTypes.includes(v.reportType as number)){
+        return true
+      }
+      return v.checkUsers ? v.checkUsers?.some(member=> member?.id === userId) : false
+    })
+    if (isShowConcludeItems.value) {
+      reportList.value = reportList.value?.filter(item => item.taskStatus != PressureCheckerMyTaskStatus.REPORT_END)
+    }
+  }
+  // 智能选择:保持当前选中项目或选择第一个项目
+  if (reportList.value.length > 0) {
+    let targetItem: ReportItemVO | undefined = undefined
+
+    // 如果当前有选中项目,尝试在新列表中找到相同的项目
+    if (selectedItem.value) {
+      targetItem = reportList.value.find((item) => item.id === selectedItem.value?.id)
+    }
+
+    if(activeReportId) {
+      targetItem = reportList.value.find((item) => item.id === activeReportId)
+    }
+    // 如果当前选中项目不存在于新列表中,或者没有选中项目,选择第一个项目
+    if (!targetItem) {
+      targetItem = reportList.value[0]
+    }
+
+    selectedItem.value = targetItem as ReportItemVO
+  }
+
+}
+const filteredReportList = ref<ReportItemVO[]>([])
+const filteredConcludeReportList = ref<ReportItemVO[]>([])
+// 获取任务详情数据
+async function fetchTaskDetail(id: string, activeReportId = '') {
+  loading.value = true
+  try {
+    const response = await PipeTaskOrderApi.getTaskOrderOrderItem(id)
+    taskInfo.value = response.taskOrder
+    checkUsers.value = response.checkUsers
+    taskOrderItem.value = response.taskOrderItem || {}
+    const showAllReport = route.query?.showAllReport   //如果是所有报告,就不根据检验员过滤
+
+    //暂不需要默认只看我的
+    //if(!showAllReport) {isShowOnlyMyItems.value =  !isMainChecker()}
+    isShowConcludeItems.value = false
+    // 过滤报告列表 - 只显示状态 >= CONFIRMED 的项目 已认领 400
+    filteredReportList.value = response.reportList.filter(
+      (item) => item.taskStatus >= PressureCheckerMyTaskStatus.CONFIRMED
+    ).filter(v=> {
+      // 如果是检验方案和指导书
+      if([600].includes(v.reportType as number)){
+        return [200].includes(v.status)
+      } else if( [500].includes(v.reportType as number)){
+        return false
+      }
+      // if([700].includes(v.reportType as number)){
+      //   return [200, 400].includes(v.status)
+      // }
+      return true
+    })//.reverse()
+    filteredConcludeReportList.value = response.reportList.filter(
+      (item) => item.taskStatus >= PressureCheckerMyTaskStatus.REPORT_END
+    ).filter(v=> {
+      // 如果是检验方案和指导书
+      if([600].includes(v.reportType as number)){
+        return [200].includes(v.status)
+      } else if( [500].includes(v.reportType as number)){
+        return false
+      }
+      // if([700].includes(v.reportType as number)){
+      //   return [200, 400].includes(v.status)
+      // }
+      return true
+    })//.reverse()
+    allReportList.value = response.reportList.filter(v=> {
+      // 如果是检验方案和指导书
+      if([600, 700].includes(v.reportType as number)){
+        return [200, 400].includes(v.status)
+      } else if([500].includes(v.reportType as number)){
+        return false
+      }
+      return true
+    })//.reverse()
+    // 当前登录人是主检人,显示所有报告,如果不是则需要根据检验员过滤对应的报告类型
+    getReportList(activeReportId)
+
+  } catch (error) {
+    console.error('获取任务详情失败:', error)
+    ElMessage.error('获取任务详情失败')
+    taskInfo.value = {} as PipeTaskOrderDetailVO
+    reportList.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
 </script>
 
 <style lang="scss" scoped>

+ 4 - 2
yudao-ui-admin-vue3/src/views/pressure2/pipeequipment/components/PipeEquipmentDetailList.vue

@@ -50,8 +50,10 @@
           <dict-tag :type="DICT_TYPE.SYSTEM_EQUIP_BOILER_STATUS" :value="scope.row.useStatus" />
         </template>
       </el-table-column>
-      <el-table-column label="法定-安全状况等级" align="center" prop="legalSafetyStatusLevel" min-width="130" />
-      <el-table-column label="年检-安全状况等级" align="center" prop="yearSafetyStatusLevel" min-width="130" />
+      <el-table-column label="法定-安全状况等级" align="center" prop="legalSafetyStatusLevel" min-width="150" />
+      <el-table-column label="年检-安全状况等级" align="center" prop="yearSafetyStatusLevel" min-width="150" />
+      <el-table-column label="上次全面检验日期" align="center" prop="lastalldate" min-width="140" />
+      <el-table-column label="上次年检日期" align="center" prop="lastonlinedate" min-width="130" />
       <el-table-column label="法定-上次检验检查结论" align="center" prop="legalInspectionConclusion" min-width="130" />
       <el-table-column label="年检-上次检验检查结论" align="center" prop="yearInspectionConclusion" min-width="130" />
       <el-table-column label="操作" align="center" fixed="right" width="120">

+ 1 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipePlanScheduleEquipDialog.vue

@@ -288,7 +288,7 @@
 import type { PropType } from 'vue'
 import { FormInstance } from 'element-plus'
 import { useMessage } from '@/hooks/web/useMessage'
-import {formatArrayDate, formatDate, formatDate1} from '@/utils/formatTime'
+import {formatArrayDate, formatDate, formatDate1} from '@/utils/pressure2/formatTime.ts'
 import {
   PressurePipeCheckType,
   PressurePipeCheckTypeMap,

+ 1 - 0
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/components/PipelineDetailList.vue

@@ -4,6 +4,7 @@
               @expand-change="handleExpandChange"
               @selection-change="(selection) => handleSelectionChange(selection)"
               class="outer-table"
+              border
     >
             <el-table-column type="selection" width="50"/>
       <el-table-column type="expand">

+ 79 - 79
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/detail.vue

@@ -24,25 +24,25 @@
       <div class="section-title">
         <span>设备信息</span>
       </div>
-
+      
       <!-- 搜索工作栏 -->
       <ContentWrap>
         <el-form
-          :model="queryParams"
-          ref="queryFormRef"
-          :inline="true"
-          label-width="80px"
+        :model="queryParams"
+        ref="queryFormRef"
+        :inline="true"
+        label-width="80px"
         >
           <!-- 基本信息查询部分 -->
           <div class="flex flex-wrap items-start gap-x-2">
             <el-form-item label="设备代码" prop="equipCode">
-              <el-input
+            <el-input
                 v-model="queryParams.equipCode"
                 placeholder="请输入设备注册代码"
                 clearable
                 @keyup.enter="handleQuery"
                 class="!w-240px"
-              />
+            />
             </el-form-item>
 
             <el-form-item label="出厂编号" prop="equipCode">
@@ -56,12 +56,12 @@
             </el-form-item>
 
             <el-form-item label="部门" prop="relateDepartment">
-              <DeptSelect
+            <DeptSelect
                 v-model="queryParams.relateDepartment"
                 placeholder="请选择部门"
                 clearable
                 class="!w-210px"
-              />
+            />
             </el-form-item>
 
             <!-- <el-form-item label="运行状态" prop="status">
@@ -86,27 +86,27 @@
             <el-form-item label="临检时间" prop="nextDate">
               <div class="flex items-center gap-x-2">
                 <el-select v-model="datePickerType" class="!w-[90px]">
-                  <el-option label="时间段" value="daterange" />
-                  <el-option label="月份" value="month" />
+                <el-option label="时间段" value="daterange" />
+                <el-option label="月份" value="month" />
                 </el-select>
                 <el-date-picker
-                  v-if="datePickerType === 'daterange'"
-                  v-model="daterange"
-                  type="daterange"
-                  value-format="YYYY-MM-DD"
-                  start-placeholder="开始日期"
-                  end-placeholder="结束日期"
-                  class="!w-[220px]"
-                  @change="handleDateChange"
+                v-if="datePickerType === 'daterange'"
+                v-model="daterange"
+                type="daterange"
+                value-format="YYYY-MM-DD"
+                start-placeholder="开始日期"
+                end-placeholder="结束日期"
+                class="!w-[220px]"
+                @change="handleDateChange"
                 />
                 <el-date-picker
-                  v-else
-                  v-model="month"
-                  type="month"
-                  value-format="YYYY-MM"
-                  placeholder="选择月份"
-                  class="!w-[140px]"
-                  @change="handleMonthChange"
+                v-else
+                v-model="month"
+                type="month"
+                value-format="YYYY-MM"
+                placeholder="选择月份"
+                class="!w-[140px]"
+                @change="handleMonthChange"
                 />
               </div>
             </el-form-item>
@@ -116,9 +116,9 @@
           <!-- 区域和时间查询部分 -->
           <div class="flex flex-wrap items-start gap-x-2">
             <el-form-item label="区域" prop="equipDistrict">
-              <AreaSelect
-                v-model="queryParams.equipDistrict"
-                placeholder="请选择区域"
+              <AreaSelect 
+                v-model="queryParams.equipDistrict" 
+                placeholder="请选择区域" 
                 class="!w-[240px]"
                 multiple
                 collapse-tags
@@ -131,17 +131,17 @@
             </el-form-item>
 
             <el-form-item label="街道" prop="equipStreet">
-              <StreetSelect
-                v-model="queryParams.equipStreet"
-                :district-ids="queryParams.equipDistrict"
-                placeholder="请选择街道"
-                class="!w-[240px]"
-                multiple
-                collapse-tags
-                collapse-tags-tooltip
-                :clearable="true"
-                @clear="handleStreetClear"
-                @change="handleStreetChange"
+              <StreetSelect 
+                  v-model="queryParams.equipStreet" 
+                  :district-ids="queryParams.equipDistrict"
+                  placeholder="请选择街道" 
+                  class="!w-[240px]"
+                  multiple
+                  collapse-tags
+                  collapse-tags-tooltip
+                  :clearable="true"
+                  @clear="handleStreetClear"
+                  @change="handleStreetChange"
               />
             </el-form-item>
           </div>
@@ -150,10 +150,10 @@
           <div class="">
             <el-form-item label="管道归类" prop="typeList" class="mb-2">
               <div class="flex items-center flex-1">
-                <el-checkbox-group
-                  v-model="queryParams.typeList"
-                  class="flex flex-wrap gap-2"
-                  @change="handleTypeListChange"
+                <el-checkbox-group 
+                v-model="queryParams.typeList" 
+                class="flex flex-wrap gap-2"
+                @change="handleTypeListChange"
                 >
                   <el-checkbox
                     v-for="dict in containerTypeOptions"
@@ -168,24 +168,24 @@
 
           <!-- 操作按钮 -->
           <div class="flex justify-center">
-            <el-form-item class="!mb-0">
+              <el-form-item class="!mb-0">
               <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
               <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
-              <el-button
-                type="primary"
-                @click="handleBatchSchedule"
-                :disabled="selectedRows.length === 0"
-              >
-                <Icon icon="ep:calendar" class="mr-5px" /> 批量排期
-              </el-button>
-              <el-button
-                type="primary"
-                @click="handleBatchConcat"
-                :disabled="selectedRows.length === 0"
+              <el-button 
+                  type="primary" 
+                  @click="handleBatchSchedule" 
+                  :disabled="selectedRows.length === 0"
               >
-                <Icon icon="ep:edit" class="mr-5px" /> 批量修改约检联系人
+                  <Icon icon="ep:calendar" class="mr-5px" /> 批量排期
               </el-button>
-            </el-form-item>
+                <el-button
+                  type="primary"
+                  @click="handleBatchConcat"
+                  :disabled="selectedRows.length === 0"
+                >
+                  <Icon icon="ep:edit" class="mr-5px" /> 批量修改约检联系人
+                </el-button>
+              </el-form-item>
           </div>
         </el-form>
       </ContentWrap>
@@ -282,13 +282,13 @@
               </div>
             </template>
           </el-table-column>
-          <!--          <el-table-column-->
-          <!--            label="使用证编号"-->
-          <!--            align="center"-->
-          <!--            prop="useNo"-->
-          <!--            min-width="120"-->
-          <!--            show-overflow-tooltip-->
-          <!--          />-->
+<!--          <el-table-column-->
+<!--            label="使用证编号"-->
+<!--            align="center"-->
+<!--            prop="useNo"-->
+<!--            min-width="120"-->
+<!--            show-overflow-tooltip-->
+<!--          />-->
           <el-table-column label="工程名称"
                            align="center"
                            prop="projectName"
@@ -554,7 +554,7 @@ const open = async (row: EquipBoilerSchedulingVO, indexQueryParams?: any) => {
 
     // 同步容器归类选中状态
     if (indexQueryParams.typeList?.length > 0) {
-      selectedTypeList.value = containerTypeOptions.filter(dict =>
+      selectedTypeList.value = containerTypeOptions.filter(dict => 
         indexQueryParams.typeList.includes(dict.value)
       )
     }
@@ -609,7 +609,7 @@ const handleQuery = async () => {
     const res = await EquipPipeSchedulingApi.getPlanSchedulingPipesList(params)
     list.value = res.list
     total.value = res.total
-
+      
   } finally {
     loading.value = false
   }
@@ -618,13 +618,13 @@ const handleQuery = async () => {
 /** 重置按钮操作 */
 const resetQuery = () => {
   queryFormRef.value.resetFields()
-
+  
   // 重置页码
   queryParams.value.pageNo = 1
-
+  
   // 清空选中的容器归类对象
   selectedTypeList.value = []
-
+  
   // 重新查询
   handleQuery()
 }
@@ -734,7 +734,7 @@ const handleSortChange = ({ prop, order }) => {
   list.value.sort((a, b) => {
     const aValue = a[prop] || ''
     const bValue = b[prop] || ''
-
+    
     if (order === 'ascending') {
       return aValue > bValue ? 1 : -1
     } else {
@@ -748,7 +748,7 @@ const handleAreaChange = (areas: number[]) => {
   // 获取移除的区域(通过比较之前的区域列表和现在的区域列表)
   const prevAreas = Array.from(areaStreetMap.value.keys())
   const removedAreas = prevAreas.filter(area => !areas.includes(area))
-
+  
   // 移除取消选择的区域下的街道
   if (removedAreas.length > 0) {
     const currentStreets = queryParams.value.equipStreet || []
@@ -789,11 +789,11 @@ const handleStreetClear = () => {
 /** 处理日期变化 */
 const handleDateChange = (val: [string, string] | null) => {
   daterange.value = val || []
-  queryParams.value.nextDate = val
+  queryParams.value.nextDate = val 
     ? [
-      dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
-      dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
-    ]
+        dayjs(val[0]).startOf('day').format('YYYY-MM-DD HH:mm:ss'),
+        dayjs(val[1]).endOf('day').format('YYYY-MM-DD HH:mm:ss')
+      ] 
     : []
 }
 
@@ -803,7 +803,7 @@ const handleMonthChange = (val: string | null) => {
     queryParams.value.nextDate = []
     return
   }
-
+  
   const date = dayjs(val)
   queryParams.value.nextDate = [
     date.startOf('month').format('YYYY-MM-DD HH:mm:ss'),
@@ -839,7 +839,7 @@ defineExpose({
   padding: 16px;
   border: 1px solid #EBEEF5;
   border-radius: 4px;
-
+  
   .section-title {
     margin: -16px -16px 16px -16px;
     padding: 8px 16px;
@@ -853,7 +853,7 @@ defineExpose({
 
 .schedule-link {
   color: var(--el-color-primary);
-
+  
   &:hover {
     color: var(--el-color-primary-light-3);
   }

+ 2 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipescheduling/index.vue

@@ -178,6 +178,7 @@
       @sort-change="handleSortChange"
       @expand-change="handleExpandChange"
       ref="mainTableRef"
+      border
     >
       <el-table-column type="expand">
         <template #default="props">
@@ -579,7 +580,7 @@ const handleEdit = (row: EquipPipeSchedulingVO) => {
     nextDate: queryParams.value.nextDate
   }
 
-  //console.log("currentQueryParams",currentQueryParams)
+  //console.log(currentQueryParams)
   detailDialogRef.value?.open(row, currentQueryParams)
 }
 

+ 1 - 1
yudao-ui-admin-vue3/src/views/pressure2/pipetaskorder/components/AddOrEditCheckItemForEquipment.vue

@@ -85,7 +85,7 @@
                       :precision="0"
                       :step="1"
                       size="small"
-                      style="width: 80px; margin-left: 8px;"
+                      style="width: 120px; margin-left: 8px;"
                       controls-position="right"
                     />
                     <template

+ 1 - 2
yudao-ui-admin-vue3/src/views/pressure2/planNew/components/PlanScheduleEquipBoilerDialog.vue

@@ -348,8 +348,7 @@ import {ElMessageBox, FormInstance} from 'element-plus'
 import { useMessage } from '@/hooks/web/useMessage'
 import CheckerSelect from '@/views/pressure2/equipboilerscheduling/components/CheckerSelect.vue'
 import { PlanSchedulingEquipVO, PlanSchedulingApi } from '@/api/pressure/planScheduling'
-import {formatArrayDate, formatDate} from '@/utils/formatTime'
-import {formatDate1} from '@/api/pressure2/comm/formatTime'
+import {formatArrayDate, formatDate, formatDate1} from '@/utils/formatTime'
 import {
   PressureBoilerCheckType,
   PressureBoilerCheckTypeMap,

+ 1 - 2
yudao-ui-admin-vue3/src/views/pressure2/planNew/components/PlanScheduleEquipPipeDialog.vue

@@ -300,8 +300,7 @@
 import type { PropType } from 'vue'
 import { FormInstance } from 'element-plus'
 import { useMessage } from '@/hooks/web/useMessage'
-import {formatArrayDate, formatDate} from '@/utils/formatTime'
-import {formatDate1} from '@/api/pressure2/comm/formatTime'
+import {formatArrayDate, formatDate, formatDate1} from '@/utils/pressure2/formatTime'
 import {
   PressurePipeCheckType,
   PressurePipeCheckTypeMap,

+ 1 - 0
yudao-ui-admin-vue3/src/views/pressure2/planNew/pipeDetail.vue

@@ -637,6 +637,7 @@ const getQueryParams = () => {
     equipStreet: queryParams.value.equipStreet,
     pipeCategory: queryParams.value.typeList?.length === 1 ? queryParams.value.typeList[0] : undefined,
     pipeCategorys: queryParams.value.typeList || [],
+    typeList: queryParams.value.typeList || [],
     nextDate: (queryParams.value.nextDate || []).map((item) => dayjs(item).valueOf()),
     useStatus: queryParams.value.useStatus ? [+queryParams.value.useStatus] : []
   })