فهرست منبع

refactor(pressure2): 优化报告预览流程,统一报表组件使用

1. 新增reportTemplateId字段用于关联生成的报告模板
2. 移除冗余的instructionId/instructionTempId判断逻辑
3. 替换原生PDF组件为SpreadViewer统一预览组件
4. 新增预生成PDF直接预览逻辑,优化加载流程
5. 重构关联操作指导书页面,简化尺寸计算逻辑
徐展城 2 هفته پیش
والد
کامیت
2e686560d9

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

@@ -132,6 +132,7 @@ export interface ReportItemVO {
   remark: string // 备注
   checkDate: string // 检验日期
   templateId: string // 模板ID
+  reportTemplateId: string // 生成的报告模板ID
   auditor: UserVO
   reportType?: number // 报告类型
   reportUrl?: string // 报告URL

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

@@ -102,6 +102,7 @@ export interface ReportItemVO {
   remark: string // 备注
   checkDate: string // 检验日期
   templateId: string // 模板ID
+  reportTemplateId: string // 生成的报告模板ID
   auditor: UserVO
   reportType?: number // 报告类型
   reportUrl?: string // 报告URL

+ 2 - 0
yudao-ui-admin-vue3/src/components/DynamicReport/SpreadInterface.ts

@@ -10,4 +10,6 @@ export interface InitParams{
   opType:number; // 0:excel输入,1:pdf查看
   dataSource?:string; // 默认数据源
   manualUrl?:string; //需要拼接的附件路径
+  enterPdfUrl?:string; // 记录模式下预生成的 PDF 地址,存在时跳过表格渲染直接预览
+  issueUrl?:string; // 报告模式下预生成的 PDF 地址,存在时跳过表格渲染直接预览
 }

+ 16 - 1
yudao-ui-admin-vue3/src/components/DynamicReport/SpreadViewer.vue

@@ -205,12 +205,27 @@ const fetchTemplateData = async () => {
   return;
 }
 const initPreview = async () => {
-  const { refId, opType, templateId } = props.initData;
+  const { refId, opType, templateId, enterPdfUrl, issueUrl } = props.initData;
   
   if (!refId) {
     return false;
   }
   
+  // 有预生成的 PDF URL 时,跳过表格渲染直接预览 PDF
+  const directPdfUrl = (enterPdfUrl || issueUrl || '').trim();
+  if (opType === 1 && directPdfUrl) {
+    loading.value = false;
+    showSpread.value = false;
+    showPdf.value = true;
+    recordSource.value = buildFileUrl(directPdfUrl);
+    pdfTimestamp.value = Date.now();
+    console.log("使用url")
+    setTimeout(() => {
+      calculatePdfSize()
+    }, 100)
+    return;
+  }
+  
   loading.value = true;
   
   if (opType === 0) {

+ 49 - 112
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/AssociationOperationManual.vue

@@ -16,20 +16,14 @@
       </div>
       <div class="flex p-[10px] flex-1 gap-[20px] overflow-hidden" v-loading="btnLoading">
         <div class="pdf-viewer-content flex-1 overflow-y-auto">
-          <div ref="pdfViewer" style="max-width: 1200px; margin: 0 auto">
-            <!-- PDF查看区域 -->
-            <VuePdfEmbed
-              v-if="recordSource"
-              :height="pdfViewerHeight"
-              :width="pdfContentWidth"
-              :source="recordSource"
-              :text-layer="false"
-              :annotation-layer="false"
-              @rendered="handlePdfRendered"
-            />
-            <div v-else class="no-record mt-[35%]">
-              <el-empty description="暂无记录文件" :image-size="180" />
-            </div>
+          <SpreadViewer
+            v-if="spreadInitData.templateId"
+            :initData="spreadInitData"
+            ref="spreadRef"
+            isFullscreen
+          />
+          <div v-else class="no-record mt-[35%]">
+            <el-empty description="暂无记录文件" :image-size="180" />
           </div>
         </div>
         <div class="flex-basis-[300px] flex flex-col overflow-hidden">
@@ -61,14 +55,12 @@
 </template>
 
 <script setup lang="ts">
-import VuePdfEmbed from 'vue-pdf-embed'
-import 'vue-pdf-embed/dist/styles/annotationLayer.css'
-import 'vue-pdf-embed/dist/styles/textLayer.css'
 import { BoilerTaskOrderApi, ReportItemVO } from '@/api/pressure2/boilertaskorder'
 import { PressureReportType } from '@/utils/constants'
 import { CloseBold } from '@element-plus/icons-vue'
 import { useRoute } from 'vue-router'
-import { getPressureReportTemplateInfo } from '@/api/pressure2/reportTemplate'
+import { SpreadViewer } from '@/components/DynamicReport'
+import { InitParams } from '@/components/DynamicReport/SpreadInterface'
 
 const props = defineProps({
   selectedItem: {
@@ -90,27 +82,12 @@ const uploadBtnText = computed(() => {
   return route.query.type === 'MyTask' ? '附件上传' : '查看附件'
 })
 
-// PDF相关状态
-const pdfLoading = ref(true)
-const recordSource = ref('')
-const pdfViewer = ref()
-const pdfContentWidth = ref(800)
-const pdfViewerHeight = ref(600)
-
-// 动态计算PDF查看器尺寸
-const calculatePdfSize = () => {
-  pdfContentWidth.value = pdfViewer.value.clientWidth - 32
-  pdfViewerHeight.value = pdfViewer.value.clientHeight
-}
-
-// 监听窗口大小变化
-onMounted(() => {
-  calculatePdfSize()
-  window.addEventListener('resize', calculatePdfSize)
-})
-
-onUnmounted(() => {
-  window.removeEventListener('resize', calculatePdfSize)
+const spreadRef = ref()
+const spreadInitData = ref<InitParams>({
+  templateId: '',
+  refId: '',
+  refName: '',
+  opType: 1
 })
 
 const currentReport = ref<ReportItemVO | Recordable>({})
@@ -119,26 +96,16 @@ const reportData = ref<Recordable[]>([])
 const setCurrentReport = (item) => {
   if (item.id === currentReport.value.id) return
   currentReport.value = item
-  getPdfUrl(item)
-}
-
-const getPdfUrl = (item) => {
-  btnLoading.value = true
-  const { id, taskStatus } = item
-  BoilerTaskOrderApi.getReportPreview({
-    reportId: id,
-    type: !taskStatus || taskStatus < 510 ? 100 : 300,
-    fileType: 200
+  console.log('[关联操作指导书] 切换预览报告:', { id: item.id, reportName: item.reportName, templateId: item.templateId })
+  spreadInitData.value = {
+    templateId: item.templateId,
+    refId: item.id,
+    refName: item.reportName,
+    opType: 1
+  }
+  nextTick(() => {
+    spreadRef.value?.reloadView()
   })
-    .then((pdfBlob) => {
-      recordSource.value = URL.createObjectURL(pdfBlob)
-      nextTick(() => {
-        calculatePdfSize()
-      })
-    })
-    .finally(() => {
-      btnLoading.value = false
-    })
 }
 const btnLoading = ref(false)
 const handleConfirm = () => {
@@ -146,30 +113,14 @@ const handleConfirm = () => {
     ElMessage.error('请选择操作指导书')
     return
   }
-  const { id } = currentReport.value
   btnLoading.value = true
-  BoilerTaskOrderApi.getReportTemplateJson({
-    reportId: id,
-    equipCode: props.equipCode,
-    type: 100,
-    fileType: 100
+  BoilerTaskOrderApi.reportOperationInstruction({
+    id: props.selectedItem.id,
+    instructionId: currentReport.value.id
   })
-    .then((templateInitJSON) => {
-      console.log(templateInitJSON)
-      const params = {
-        id: props.selectedItem.id, //-- 独走报告id
-        instructionId: currentReport.value.id, //-- 操作指导书报告id
-        instructionPrepareJson: templateInitJSON //-- 操作指导书数据json
-      }
-
-      BoilerTaskOrderApi.reportOperationInstruction(params)
-        .then((res) => {
-          ElMessage.success('关联操作指导书成功')
-          handleBack()
-        })
-        .finally(() => {
-          btnLoading.value = false
-        })
+    .then((res) => {
+      ElMessage.success('关联操作指导书成功')
+      handleBack()
     })
     .finally(() => {
       btnLoading.value = false
@@ -180,48 +131,34 @@ const handleBack = () => {
   emit('close')
 }
 
-const handlePdfRendered = () => {
-  pdfLoading.value = false
-}
-
-const getTemplateRelateFunction = () => {
-  if (!props.selectedItem.templateId) return Promise.resolve({})
-  return getPressureReportTemplateInfo({ id: props.selectedItem.templateId })
-}
-
 onMounted(() => {
-  getTemplateRelateFunction().then((templateInfo) => {
-    const { instructionTempId } = templateInfo || {}
-    if (!instructionTempId) return
-    const instructionTempIdList = instructionTempId.split(',')
-    const instructionId = props.selectedItem.instructionId
-    reportData.value = props.reportList
-      .filter((item) => item.reportType === PressureReportType.WORKINSTRUCTION)
-      .filter((item) => instructionTempIdList.includes(item.templateId))
-      .map((item) => ({ ...item, checked: false }))
-    const selectedReport = reportData.value.find((item) => item.id === instructionId)
-    if (selectedReport) setCurrentReport(selectedReport)
-  })
+  console.log('[关联操作指导书] 组件挂载,reportList 长度:', props.reportList?.length)
+  reportData.value = props.reportList
+    .filter((item) => item.reportType === PressureReportType.WORKINSTRUCTION)
+    .map((item) => ({ ...item, checked: false }))
+  console.log('[关联操作指导书] 筛选后 reportData 长度:', reportData.value.length,
+    reportData.value.map(i => ({ id: i.id, reportName: i.reportName, templateId: i.templateId })))
+
+  const instructionId = props.selectedItem.instructionId
+  const selectedReport = reportData.value.find((item) => item.id === instructionId)
+  console.log('[关联操作指导书] 根据 instructionId 查找已关联报告:', { instructionId, found: !!selectedReport })
+  if (selectedReport) setCurrentReport(selectedReport)
 })
 </script>
 
 <style lang="scss" scoped>
 .container-view {
-  position: absolute;
-  top: calc(var(--app-content-padding) * -1);
-  left: calc(var(--app-content-padding) * -1);
-  padding: var(--app-content-padding);
-  padding-bottom: 0;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 10px 14px 0;
   box-sizing: border-box;
   background-color: #fff;
-  width: calc(100% + var(--app-content-padding) * 2);
-  height: calc(
-    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) +
-      var(--app-content-padding)
-  );
   overflow: hidden;
   border: 1px solid var(--el-border-color);
-  z-index: 1010;
+  z-index: 9999;
 
   &-header {
     height: 42px;

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

@@ -125,7 +125,7 @@
                     </el-icon>
                     <div
                       class="text-[14px]"
-                      v-if="item.reportType === 300 && !item.instructionId && item.instructionTempId"
+                      v-if="item.reportType === 300"
                     >
                       <el-button
                         link

+ 9 - 0
yudao-ui-admin-vue3/src/views/pressure2/boilerchecker/components/StatusOperationPanel.vue

@@ -2433,6 +2433,15 @@ const initPreview=()=>{
     initData.value.manualUrl = '';
   }
 
+  // 记录模式使用 enterPdfUrl,报告模式使用 issueUrl,有值时跳过表格渲染直接预览 PDF
+  if (showReportPdfType.value === 'record') {
+    initData.value.enterPdfUrl = props.selectedItem?.enterPdfUrl || '';
+    initData.value.issueUrl = '';
+  } else {
+    initData.value.enterPdfUrl = '';
+    initData.value.issueUrl = props.selectedItem?.issueUrl || '';
+  }
+
 
   initPreviewTimer.value = setTimeout(()=>{
     initPreviewTimer.value = null

+ 49 - 112
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/AssociationOperationManual.vue

@@ -16,20 +16,14 @@
       </div>
       <div class="flex p-[10px] flex-1 gap-[20px] overflow-hidden" v-loading="btnLoading">
         <div class="pdf-viewer-content flex-1 overflow-y-auto">
-          <div ref="pdfViewer" style="max-width: 1200px; margin: 0 auto">
-            <!-- PDF查看区域 -->
-            <VuePdfEmbed
-              v-if="recordSource"
-              :height="pdfViewerHeight"
-              :width="pdfContentWidth"
-              :source="recordSource"
-              :text-layer="false"
-              :annotation-layer="false"
-              @rendered="handlePdfRendered"
-            />
-            <div v-else class="no-record mt-[35%]">
-              <el-empty description="暂无记录文件" :image-size="180" />
-            </div>
+          <SpreadViewer
+            v-if="spreadInitData.templateId"
+            :initData="spreadInitData"
+            ref="spreadRef"
+            isFullscreen
+          />
+          <div v-else class="no-record mt-[35%]">
+            <el-empty description="暂无记录文件" :image-size="180" />
           </div>
         </div>
         <div class="flex-basis-[300px] flex flex-col overflow-hidden">
@@ -61,14 +55,12 @@
 </template>
 
 <script setup lang="ts">
-import VuePdfEmbed from 'vue-pdf-embed'
-import 'vue-pdf-embed/dist/styles/annotationLayer.css'
-import 'vue-pdf-embed/dist/styles/textLayer.css'
 import { PipeTaskOrderApi, ReportItemVO } from '@/api/pressure2/pipetaskorder'
 import { PressureReportType } from '@/utils/constants'
 import { CloseBold } from '@element-plus/icons-vue'
 import { useRoute } from 'vue-router'
-import { getPressureReportTemplateInfo } from '@/api/pressure2/reportTemplate'
+import { SpreadViewer } from '@/components/DynamicReport'
+import { InitParams } from '@/components/DynamicReport/SpreadInterface'
 
 const props = defineProps({
   selectedItem: {
@@ -90,27 +82,12 @@ const uploadBtnText = computed(() => {
   return route.query.type === 'MyTask' ? '附件上传' : '查看附件'
 })
 
-// PDF相关状态
-const pdfLoading = ref(true)
-const recordSource = ref('')
-const pdfViewer = ref()
-const pdfContentWidth = ref(800)
-const pdfViewerHeight = ref(600)
-
-// 动态计算PDF查看器尺寸
-const calculatePdfSize = () => {
-  pdfContentWidth.value = pdfViewer.value.clientWidth - 32
-  pdfViewerHeight.value = pdfViewer.value.clientHeight
-}
-
-// 监听窗口大小变化
-onMounted(() => {
-  calculatePdfSize()
-  window.addEventListener('resize', calculatePdfSize)
-})
-
-onUnmounted(() => {
-  window.removeEventListener('resize', calculatePdfSize)
+const spreadRef = ref()
+const spreadInitData = ref<InitParams>({
+  templateId: '',
+  refId: '',
+  refName: '',
+  opType: 1
 })
 
 const currentReport = ref<ReportItemVO | Recordable>({})
@@ -119,26 +96,16 @@ const reportData = ref<Recordable[]>([])
 const setCurrentReport = (item) => {
   if (item.id === currentReport.value.id) return
   currentReport.value = item
-  getPdfUrl(item)
-}
-
-const getPdfUrl = (item) => {
-  btnLoading.value = true
-  const { id, taskStatus } = item
-  PipeTaskOrderApi.getReportPreview({
-    reportId: id,
-    type: !taskStatus || taskStatus < 510 ? 100 : 300,
-    fileType: 200
+  console.log('[关联操作指导书] 切换预览报告:', { id: item.id, reportName: item.reportName, templateId: item.templateId })
+  spreadInitData.value = {
+    templateId: item.templateId,
+    refId: item.id,
+    refName: item.reportName,
+    opType: 1
+  }
+  nextTick(() => {
+    spreadRef.value?.reloadView()
   })
-    .then((pdfBlob) => {
-      recordSource.value = URL.createObjectURL(pdfBlob)
-      nextTick(() => {
-        calculatePdfSize()
-      })
-    })
-    .finally(() => {
-      btnLoading.value = false
-    })
 }
 const btnLoading = ref(false)
 const handleConfirm = () => {
@@ -146,30 +113,14 @@ const handleConfirm = () => {
     ElMessage.error('请选择操作指导书')
     return
   }
-  const { id } = currentReport.value
   btnLoading.value = true
-  PipeTaskOrderApi.getReportTemplateJson({
-    reportId: id,
-    equipCode: props.equipCode,
-    type: 100,
-    fileType: 100
+  PipeTaskOrderApi.reportOperationInstruction({
+    id: props.selectedItem.id,
+    instructionId: currentReport.value.id
   })
-    .then((templateInitJSON) => {
-      console.log(templateInitJSON)
-      const params = {
-        id: props.selectedItem.id, //-- 独走报告id
-        instructionId: currentReport.value.id, //-- 操作指导书报告id
-        instructionPrepareJson: templateInitJSON //-- 操作指导书数据json
-      }
-
-      PipeTaskOrderApi.reportOperationInstruction(params)
-        .then((res) => {
-          ElMessage.success('关联操作指导书成功')
-          handleBack()
-        })
-        .finally(() => {
-          btnLoading.value = false
-        })
+    .then((res) => {
+      ElMessage.success('关联操作指导书成功')
+      handleBack()
     })
     .finally(() => {
       btnLoading.value = false
@@ -180,48 +131,34 @@ const handleBack = () => {
   emit('close')
 }
 
-const handlePdfRendered = () => {
-  pdfLoading.value = false
-}
-
-const getTemplateRelateFunction = () => {
-  if (!props.selectedItem.templateId) return Promise.resolve({})
-  return getPressureReportTemplateInfo({ id: props.selectedItem.templateId })
-}
-
 onMounted(() => {
-  getTemplateRelateFunction().then((templateInfo) => {
-    const { instructionTempId } = templateInfo || {}
-    if (!instructionTempId) return
-    const instructionTempIdList = instructionTempId.split(',')
-    const instructionId = props.selectedItem.instructionId
-    reportData.value = props.reportList
-      .filter((item) => item.reportType === PressureReportType.WORKINSTRUCTION)
-      .filter((item) => instructionTempIdList.includes(item.templateId))
-      .map((item) => ({ ...item, checked: false }))
-    const selectedReport = reportData.value.find((item) => item.id === instructionId)
-    if (selectedReport) setCurrentReport(selectedReport)
-  })
+  console.log('[关联操作指导书] 组件挂载,reportList 长度:', props.reportList?.length)
+  reportData.value = props.reportList
+    .filter((item) => item.reportType === PressureReportType.WORKINSTRUCTION)
+    .map((item) => ({ ...item, checked: false }))
+  console.log('[关联操作指导书] 筛选后 reportData 长度:', reportData.value.length,
+    reportData.value.map(i => ({ id: i.id, reportName: i.reportName, templateId: i.templateId })))
+
+  const instructionId = props.selectedItem.instructionId
+  const selectedReport = reportData.value.find((item) => item.id === instructionId)
+  console.log('[关联操作指导书] 根据 instructionId 查找已关联报告:', { instructionId, found: !!selectedReport })
+  if (selectedReport) setCurrentReport(selectedReport)
 })
 </script>
 
 <style lang="scss" scoped>
 .container-view {
-  position: absolute;
-  top: calc(var(--app-content-padding) * -1);
-  left: calc(var(--app-content-padding) * -1);
-  padding: var(--app-content-padding);
-  padding-bottom: 0;
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  padding: 10px 14px 0;
   box-sizing: border-box;
   background-color: #fff;
-  width: calc(100% + var(--app-content-padding) * 2);
-  height: calc(
-    100vh - var(--top-tool-height) - var(--tags-view-height) - var(--app-footer-height) +
-      var(--app-content-padding)
-  );
   overflow: hidden;
   border: 1px solid var(--el-border-color);
-  z-index: 1010;
+  z-index: 9999;
 
   &-header {
     height: 42px;

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

@@ -84,7 +84,7 @@
                   </el-icon>
                   <div
                     class="text-[14px]"
-                    v-if="item.reportType === 300 && !item.instructionId && item.instructionTempId"
+                    v-if="item.reportType == 300"
                   >
                     <el-button
                       link

+ 9 - 0
yudao-ui-admin-vue3/src/views/pressure2/pipechecker/components/StatusOperationPanel.vue

@@ -2322,6 +2322,15 @@ const initPreview=()=>{
     initData.value.manualUrl = '';
   }
 
+  // 记录模式使用 enterPdfUrl,报告模式使用 issueUrl,有值时跳过表格渲染直接预览 PDF
+  if (showReportPdfType.value === 'record') {
+    initData.value.enterPdfUrl = props.selectedItem?.enterPdfUrl || '';
+    initData.value.issueUrl = '';
+  } else {
+    initData.value.enterPdfUrl = '';
+    initData.value.issueUrl = props.selectedItem?.issueUrl || '';
+  }
+
   //spreadRef.value?.reloadView();
 
   initPreviewTimer.value = setTimeout(()=>{