Bladeren bron

完善检验方案相关处理

yangguanjin 5 dagen geleden
bovenliggende
commit
107b31c517

+ 22 - 2
src/api/ApiRouter/taskOrder.ts

@@ -33,7 +33,8 @@ import {
   getPipeOrderForm,
   addPipeMajorIssues,
   batchSuspendPipeOrderItem,
-  cancelPipeInSpectProject
+  cancelPipeInSpectProject,
+  submitPipeOpinionNoticeApproval,
 } from '@/api/pipe/pipeTaskOrder'
 import {
   getBoilerTaskConfirmPage,
@@ -52,7 +53,8 @@ import {
   getBoilerOrderForm,
   addBoilerMajorIssues,
   batchSuspendBoilerOrderItem,
-  cancelBoilerInSpectProject
+  cancelBoilerInSpectProject,
+  submitBoilerOpinionNoticeApproval,
 } from '@/api/boiler/boilerTaskOrder'
 
 type Adapter = {
@@ -81,6 +83,7 @@ export enum TaskOrderFuncName {
   AddMajorIssues,
   BatchSuspendEquip,
   BatchCancelReport,
+  SubmitOpinionNoticeApproval,
 }
 
 // 接口注册表(按接口、设备类型调用对应接口)
@@ -419,6 +422,23 @@ const map = {
       reqFunction: cancelBoilerInSpectProject,
       outputAdapter: null,
     },
+  },
+  [TaskOrderFuncName.SubmitOpinionNoticeApproval]: {
+    [EquipmentType.BOILER]: {
+      inputAdapter: null,
+      reqFunction: submitBoilerOpinionNoticeApproval,
+      outputAdapter: null,
+    },
+    [EquipmentType.PIPE]: {
+      inputAdapter: null,
+      reqFunction: submitPipeOpinionNoticeApproval,
+      outputAdapter: null,
+    },
+    [EquipmentType.CONTAINER]: {
+      inputAdapter: null,
+      reqFunction: submitBoilerOpinionNoticeApproval,
+      outputAdapter: null,
+    },
   }
 }
 

+ 5 - 1
src/api/boiler/boilerTaskOrder.ts

@@ -39,7 +39,6 @@ export const confirmBoilerEquipmentBatchClaim = (data: { orderItemIdList: string
   return httpPost('/pressure2/boiler-task-order/order-item/batchClaim', data)
 }
 
-
 // 取消设备认领
 export const cancelBoilerEquipmentClaim = (data: { id: string }) => {
   return httpPost('/pressure2/boiler-task-order/order-item/cancelClaim', data)
@@ -85,6 +84,11 @@ export const submitBoilerReport = (data: any) => {
   return httpPUT('/pressure2/boiler-task-order/order-item/report/prepare/submit', data)
 }
 
+// 检验意见通知书: 发起审核
+export const submitBoilerOpinionNoticeApproval = (data: any) => {
+  return httpPUT(`/pressure2/boiler-task-order/order-item/initiateApproval`, data)
+}
+
 // 查询任务单详情,审核列表
 export const getBoilerMajorIssuesAuditList = (params: any) => {
   return httpGet('/pressure2/boiler-task-order/order-item/major-issues/page', params)

+ 20 - 0
src/api/index.ts

@@ -1,4 +1,5 @@
 import { httpGet } from '@/utils/http'
+import { getEnvBaseUrl } from '@/utils/index'
 
 /**
  * 设备查询:列表
@@ -64,3 +65,22 @@ export const getPressure2ReportTemplateDetail = (params: any) => {
 export const getReportTemplateList = (params: any) => {
   return httpGet('/pressure/report-template/page', params)
 }
+
+export const uploadFile = (data: FormData) => {
+  let requestUrl = '/infra/file/upload'
+  if (JSON.parse(import.meta.env.VITE_APP_PROXY) && import.meta.env.MODE === 'development') {
+    requestUrl = import.meta.env.VITE_APP_PROXY_PREFIX + requestUrl
+  } else {
+    requestUrl = getEnvBaseUrl() + requestUrl
+  }
+  const token = uni.getStorageSync('APP_ACCESS_TOKEN')
+  const headers: Record<string, string> = {}
+  if (token) {
+    headers.Authorization = `Bearer ${token}`
+  }
+  return fetch(requestUrl, {
+    method: 'POST',
+    headers,
+    body: data,
+  }).then(res => res.json())
+}

+ 4 - 0
src/api/pipe/pipeTaskOrder.ts

@@ -80,6 +80,10 @@ export const submitPipeReport = (data: any) => {
   return httpPUT('/pressure2/pipe-task-order/order-item/report/prepare/submit', data)
 }
 
+export const submitPipeOpinionNoticeApproval = (data: any) => {
+  return httpPUT(`/pressure2/pipe-task-order/order-item/initiateApproval`, data)
+}
+
 // 查询任务单详情,审核列表
 export const getPipeMajorIssuesAuditList = (params: any) => {
   return httpGet('/pressure2/pipe-task-order/order-item/major-issues/page', params)

+ 10 - 0
src/api/system/user.ts

@@ -0,0 +1,10 @@
+import { httpGet } from '@/utils/http'
+
+
+/**
+ * roleCode: Pipeline Director 管道主任 Minister_Pipelines 管道部长
+ * roleCode: Boiler Director 锅炉主任  Boiler_Department_Head 锅炉部长
+ */
+export const getAuditList = (params: any) => {
+  return httpGet('/system/user/get-by-role', params)
+}

+ 3 - 1
src/api/task.ts

@@ -424,7 +424,9 @@ export const getFilterReport = (reportList: any[] | undefined, equipCode?: strin
       item.reportType === PressureReportType.FINITE_SPACE
     ) {
       if ([PressureReportType.INSPECTIONPLAN].includes(item.reportType as number)) {
-        if ([200].includes(item.status)) {
+        // 蚁群原版是只显示已通过检验方案
+        // 显示退回以外的检验方案(未提交则作为提交审核的入口,要配套上传文件功能)
+        if (![300].includes(item.status)) {
           otherReportList.push({ ...item, equipCode })
         }
       } else {

+ 237 - 0
src/components/BackendPDFViewer/inspectionPlanPDFViewer.vue

@@ -0,0 +1,237 @@
+<template>
+  <view class="backend-pdf-viewer">
+    <view v-if="loading" class="pdf-loading">
+      <wd-loading />
+      <text class="loading-text">PDF加载中...</text>
+    </view>
+    <view v-else-if="errorMsg" class="pdf-error">
+      <text class="error-text">{{ errorMsg }}</text>
+    </view>
+    <PDFViewer
+      v-show="!loading && !errorMsg"
+      ref="pdfViewerRef"
+      :source="pdfSource"
+      :viewer-height="viewerHeight"
+      @loaded="handlePDFLoaded"
+      @error="handlePDFError"
+    />
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+import { getEnvBaseUrl } from '@/utils/index'
+import PDFViewer from '@/components/PDFViewer/index.vue'
+
+interface Props {
+  refId?: string | number
+  templateId?: string | number
+  viewerHeight?: string
+  manualUrl?: string
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  refId: '',
+  templateId: '',
+  manualUrl: '',
+  viewerHeight: '95vh',
+})
+
+const emit = defineEmits<{
+  (e: 'pdf-loaded', data: ArrayBuffer): void
+  (e: 'error', message: string): void
+}>()
+
+const pdfViewerRef = ref<any>(null)
+const pdfSource = ref<ArrayBuffer | Blob | null>(null)
+
+const loading = ref(false)
+const errorMsg = ref('')
+
+const fetchPDF = async () => {
+  if (!props.refId || !props.templateId) {
+    pdfSource.value = null
+    loading.value = false
+    return
+  }
+
+  loading.value = true
+  errorMsg.value = ''
+
+  try {
+    const excelBlob = await fetchExcel(props.refId, props.templateId)
+    const fd = new FormData()
+    fd.append('file', excelBlob)
+    fd.append('manualUrl', props.manualUrl)
+    const pdfData = await convertToPDF(fd)
+    if (pdfData) {
+      pdfSource.value = pdfData
+      emit('pdf-loaded', pdfData)
+    }
+  } catch (error) {
+    console.error('[BackendPDFViewer] PDF获取失败:', error)
+    errorMsg.value = 'PDF获取失败'
+    loading.value = false
+    emit('error', 'PDF获取失败')
+  }
+}
+
+const handlePDFLoaded = () => {
+  loading.value = false
+}
+
+const handlePDFError = (message: string) => {
+  errorMsg.value = message
+  loading.value = false
+  emit('error', message)
+}
+
+const fetchExcel = async (refId: string | number, templateId: string | number): Promise<Blob> => {
+  // #ifdef H5
+  return fetchExcelForH5(refId, templateId)
+  // #endif
+}
+
+const fetchExcelForH5 = async (refId: string | number, templateId: string | number): Promise<Blob> => {
+  const apiPath = '/pressure2/excel/excel'
+  let requestUrl = apiPath
+  if (JSON.parse(import.meta.env.VITE_APP_PROXY) && import.meta.env.MODE === 'development') {
+    requestUrl = import.meta.env.VITE_APP_PROXY_PREFIX + apiPath
+  } else {
+    requestUrl = getEnvBaseUrl() + apiPath
+  }
+
+  const token = uni.getStorageSync('APP_ACCESS_TOKEN')
+  const headers: Record<string, string> = { 'Content-Type': 'application/json' }
+  if (token) {
+    headers.Authorization = `Bearer ${token}`
+  }
+
+  const response = await fetch(requestUrl, {
+    method: 'POST',
+    headers,
+    body: JSON.stringify({ refId, templateId }),
+  })
+
+  if (!response.ok) {
+    throw new Error(`获取Excel失败, status ${response.status}`)
+  }
+
+  return await response.blob()
+}
+
+const convertToPDF = (formData: FormData): Promise<ArrayBuffer> => {
+  let apiPath = '/pressure2/standard-file/getPDFByInspection'
+  if (!props.manualUrl || props.manualUrl === '') {
+    apiPath = '/pressure2/standard-file/getPdf'
+  }
+  // #ifdef H5
+  return getPDFForH5(apiPath, formData)
+  // #endif
+}
+
+const getPDFForH5 = async (url: string, formData: FormData): Promise<ArrayBuffer> => {
+  let requestUrl = url
+  if (JSON.parse(import.meta.env.VITE_APP_PROXY) && import.meta.env.MODE === 'development') {
+    requestUrl = import.meta.env.VITE_APP_PROXY_PREFIX + url
+  } else {
+    requestUrl = getEnvBaseUrl() + url
+  }
+
+  const token = uni.getStorageSync('APP_ACCESS_TOKEN')
+  const headers: Record<string, string> = {}
+  if (token) {
+    headers.Authorization = `Bearer ${token}`
+  }
+
+  const response = await fetch(requestUrl, {
+    method: 'POST',
+    headers,
+    body: formData,
+  })
+
+  if (!response.ok) {
+    throw new Error(`PDF转换失败, status ${response.status}`)
+  }
+
+  return await response.arrayBuffer()
+}
+
+const downloadFileAsBase64 = (fileUrl: string): Promise<string> => {
+  return new Promise((resolve, reject) => {
+    uni.request({
+      url: fileUrl,
+      method: 'GET',
+      responseType: 'arraybuffer',
+      success: (res) => {
+        if (res.statusCode === 200) {
+          const arrayBuffer = res.data as ArrayBuffer
+          const uint8Array = new Uint8Array(arrayBuffer)
+          const binaryString = uint8Array.reduce((data, byte) => {
+            return data + String.fromCharCode(byte)
+          }, '')
+          const base64 = btoa(binaryString)
+          resolve(base64)
+        } else {
+          reject(new Error(`Request failed with status ${res.statusCode}`))
+        }
+      },
+      fail: (err) => {
+        reject(err)
+      },
+    })
+  })
+}
+
+watch(
+  () => [props.refId, props.templateId],
+  () => {
+    fetchPDF()
+  },
+  { immediate: true },
+)
+
+defineExpose({
+  downloadFileAsBase64,
+  pdfViewerRef,
+  pdfSource,
+})
+</script>
+
+<style lang="scss" scoped>
+.backend-pdf-viewer {
+  width: 100%;
+}
+
+.pdf-loading {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12px;
+  width: 100%;
+  height: 300px;
+  background-color: #f5f5f5;
+  border-radius: 8px;
+
+  .loading-text {
+    font-size: 14px;
+    color: #999;
+  }
+}
+
+.pdf-error {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 100%;
+  height: 200px;
+  background-color: #fff5f5;
+  border-radius: 8px;
+
+  .error-text {
+    font-size: 14px;
+    color: #f56c6c;
+  }
+}
+</style>

+ 25 - 15
src/components/SpreadDesigner/spreadDesignerGeneric.vue

@@ -1,20 +1,22 @@
 <template>
   <view class="spread-designer-generic">
-    <NavBar>
-      <template #title>
-        <text class="nav-title">{{ navTitle }}</text>
-      </template>
-      <template #right>
-        <button
-          v-for="(button, index) in navButtons"
-          :key="index"
-          :class="['nav-btn', button.className]"
-          @click="handleNavButtonClick(button)"
-        >
-          {{ button.text }}
-        </button>
-      </template>
-    </NavBar>
+    <slot name="navBar">
+      <NavBar>
+        <template #title>
+          <text class="nav-title">{{ navTitle }}</text>
+        </template>
+        <template #right>
+          <button
+            v-for="(button, index) in navButtons"
+            :key="index"
+            :class="['nav-btn', button.className]"
+            @click="handleNavButtonClick(button)"
+          >
+            {{ button.text }}
+          </button>
+        </template>
+      </NavBar>
+    </slot>
 
     <view class="main-container">
       <Designer
@@ -1089,11 +1091,18 @@ watch(
   },
 )
 
+function getSpreadDataSource() {
+  if (!designer.value) return {}
+  return getDataSource(designer.value)
+}
+
 defineExpose({
   onWebViewResize,
   updateDataSource,
   handleCameraResult,
   setKeyboardHeight,
+  handleNavButtonClick,
+  getSpreadDataSource,
 })
 </script>
 
@@ -1164,6 +1173,7 @@ defineExpose({
 }
 
 .nav-btn {
+  margin: 0 4px;
   padding: 0 12px;
   font-size: 12px;
   color: #495057;

+ 10 - 0
src/pages.json

@@ -314,6 +314,16 @@
         "navigationStyle": "custom"
       }
     },
+    {
+      "path": "pages/equipment/detail/inspectionPlanPreviewer",
+      "type": "page",
+      "layout": "default",
+      "style": {
+        "navigationBarTitleText": "操作指导书批准",
+        "navigationStyle": "custom",
+        "disableScroll": true
+      }
+    },
     {
       "path": "pages/equipment/detail/mainQuestionPreviewer",
       "type": "page",

+ 440 - 6
src/pages/editor/inspectionPlanEditor.vue

@@ -13,22 +13,90 @@
 <template>
   <div>
     <SpreadDesignerGeneric
+      ref="designerRef"
       :businessConfig="businessConfig"
       :templateData="templateData"
       :templateBlob="templateBlob"
       @save="handleSave"
       @cancel="handleCancel"
-    />
+      @customAction="handleCustomAction"
+    >
+      <template #navBar>
+        <ReportNavBar>
+          <template #right>
+            <view class="nav-btn-group">
+              <view
+                class="nav-btn"
+                @click="designerRef?.handleNavButtonClick({ action: 'cancel' })"
+              >
+                取消
+              </view>
+              <view
+                class="nav-btn primary"
+                @click="designerRef?.handleNavButtonClick({ action: 'save' })"
+              >
+                保存
+              </view>
+              <view
+                class="nav-btn upload-btn"
+                @click="designerRef?.handleNavButtonClick({ action: 'upload' })"
+              >
+                方案上传
+              </view>
+              <view
+                class="nav-btn submit-btn"
+                @click="designerRef?.handleNavButtonClick({ action: 'submit' })"
+              >
+                提交审核
+              </view>
+            </view>
+          </template>
+        </ReportNavBar>
+      </template>
+    </SpreadDesignerGeneric>
+
+    <!-- 审核人选择弹窗 -->
+    <view v-if="auditorVisible" class="auditor-overlay" @click="auditorVisible = false">
+      <view class="auditor-dialog" @click.stop>
+        <view class="auditor-title">选择审核人</view>
+        <scroll-view scroll-y class="auditor-list">
+          <view
+            v-for="item in auditorList"
+            :key="item.id"
+            :class="['auditor-item', { active: selectedAuditorId === item.id }]"
+            @click="selectedAuditorId = item.id"
+          >
+            <text class="auditor-name">{{ item.nickname || item.userName }}</text>
+            <view v-if="selectedAuditorId === item.id" class="auditor-check">&#10003;</view>
+          </view>
+          <view v-if="auditorLoading" class="auditor-loading">加载中...</view>
+          <view v-if="!auditorLoading && auditorList.length === 0" class="auditor-empty">
+            暂无可选审核人
+          </view>
+        </scroll-view>
+        <view class="auditor-footer">
+          <view class="auditor-btn cancel" @click="auditorVisible = false">取消</view>
+          <view class="auditor-btn confirm" @click="confirmSubmit">确定</view>
+        </view>
+      </view>
+    </view>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, onUnmounted } from 'vue'
 import SpreadDesignerGeneric from '@/components/SpreadDesigner/spreadDesignerGeneric.vue'
+import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
 import { onLoad } from '@dcloudio/uni-app'
 import { buildFileUrl } from '@/utils/index'
-import { getStandardTemplate } from '@/api/index'
+import { getStandardTemplate, uploadFile } from '@/api/index'
+import { getAuditList } from '@/api/system/user'
 import { getDynamicTbVal, saveDynamicTbVal } from '@/api/task'
+import { useConfigStore } from '@/store/config'
+import { useUserStore } from '@/store'
+import { EquipmentType, PressureReportType } from '@/utils/dictMap'
+import { requestFunc, TaskOrderFuncName } from '@/api/ApiRouter/taskOrder'
+import dayjs from 'dayjs'
 
 const businessConfig = ref({
   businessType: 'JYFA',
@@ -40,21 +108,47 @@ const businessConfig = ref({
     saveButtonText: '保存',
     cancelButtonText: '取消',
     showAdditionalToolbar: true,
-    customButtons: [],
+    customButtons: [
+      {
+        text: '方案上传',
+        class: 'upload-btn',
+        action: 'upload',
+      },
+      {
+        text: '提交审核',
+        class: 'submit-btn',
+        action: 'submit',
+      },
+    ],
   },
 })
 const templateBlob = ref<string>('')
 const templateData = ref<any>({})
+const equipType = useConfigStore().getEquipType()
+const userStore = useUserStore()
+const userInfo = userStore.userInfo
 
 let templateId: string = ''
 let refId: string = ''
+let manualUrl: string = ''
 
 onLoad((options: any) => {
   templateId = options.templateId
   refId = options.refId
+  manualUrl = options.manualUrl || ''
+  // 注册RN文件选择回调
+  if (isRNEnv()) {
+    registerRNFileCallback()
+  }
   init()
 })
 
+onUnmounted(() => {
+  if ((window as any).onFileSelectedFromRN) {
+    delete (window as any).onFileSelectedFromRN
+  }
+})
+
 const instId = ref<string>('')
 const init = async () => {
   uni.hideLoading()
@@ -121,9 +215,9 @@ const downloadFileAsBase64 = (fileUrl: string): Promise<string> => {
 const handleSave = async (data: any) => {
   const result = await saveDynamicTbVal({ params: data.dataJSON, instId: instId.value })
   if (result?.code === 0 && result?.data) {
-    uni.showToast({ title: '保存成功', icon: 'success' })
+    uni.showToast({ title: '保存报表成功', icon: 'success' })
   } else {
-    const msg = result?.msg || '保存失败'
+    const msg = result?.msg || '保存报表失败'
     uni.showToast({ title: msg, icon: 'error' })
   }
 }
@@ -131,4 +225,344 @@ const handleSave = async (data: any) => {
 const handleCancel = () => {
   uni.navigateBack()
 }
+
+const designerRef = ref<any>(null)
+
+// 检测RN WebView环境
+const isRNEnv = () => {
+  return !!(window as any).ReactNativeWebView
+}
+
+// 注册RN文件选择回调
+const registerRNFileCallback = () => {
+  ;(window as any).onFileSelectedFromRN = handleFileFromRN
+}
+
+// 暂存待上传的文件
+const pendingFile = ref<{ base64: string; name: string; type: string; size: number } | null>(null)
+
+// 审核人选择相关
+const auditorVisible = ref(false)
+const auditorList = ref<any[]>([])
+const auditorLoading = ref(false)
+const selectedAuditorId = ref<string>('')
+
+// 处理RN回传的文件数据
+const handleFileFromRN = async (fileData: any) => {
+  if (fileData.canceled) return
+  if (fileData.error) {
+    uni.showToast({ title: fileData.error, icon: 'none' })
+    return
+  }
+  storeFile(fileData.base64, fileData.name, fileData.type, fileData.size)
+}
+
+// 暂存文件到内存,已存在则弹窗确认覆盖
+const storeFile = (base64: string, name: string, mimeType: string, size: number) => {
+  if (pendingFile.value) {
+    uni.showModal({
+      title: '提示',
+      content: `当前已选择文件「${pendingFile.value.name}」,是否覆盖为「${name}」?`,
+      confirmText: '覆盖',
+      cancelText: '取消',
+      success: (res) => {
+        if (res.confirm) {
+          pendingFile.value = { base64, name, type: mimeType, size }
+          uni.showToast({ title: '文件已更新', icon: 'success' })
+        }
+      },
+    })
+  } else {
+    pendingFile.value = { base64, name, type: mimeType, size }
+    uni.showToast({ title: '文件已选择', icon: 'success' })
+  }
+}
+
+// 执行文件上传,返回文件URL
+const doUpload = async (): Promise<string | null> => {
+  if (!pendingFile.value) return null
+  const { base64, name, type: mimeType } = pendingFile.value
+  try {
+    uni.showLoading({ title: '正在上传...' })
+    const byteString = atob(base64)
+    const ab = new ArrayBuffer(byteString.length)
+    const ia = new Uint8Array(ab)
+    for (let i = 0; i < byteString.length; i++) {
+      ia[i] = byteString.charCodeAt(i)
+    }
+    const blob = new Blob([ab], { type: mimeType || 'application/octet-stream' })
+    const file = new File([blob], name, { type: mimeType || 'application/octet-stream' })
+
+    const formData = new FormData()
+    formData.append('file', file)
+    const result = await uploadFile(formData)
+
+    uni.hideLoading()
+
+    if (result?.code === 0 && result.data) {
+      return result.data as string
+    } else {
+      uni.showToast({ title: result?.msg || '上传失败', icon: 'none' })
+      return null
+    }
+  } catch (error) {
+    uni.hideLoading()
+    console.error('上传文件异常:', error)
+    uni.showToast({ title: '上传失败', icon: 'none' })
+    return null
+  }
+}
+
+// 选择文件(RN环境)
+const pickFileRN = () => {
+  ;(window as any).ReactNativeWebView.postMessage(
+    JSON.stringify({
+      type: 'selectFile',
+      allowedTypes: [
+        'application/pdf',
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
+      ],
+      multiple: false,
+    }),
+  )
+}
+
+// 选择文件(非RN环境)
+const pickFileH5 = () => {
+  const input = document.createElement('input')
+  input.type = 'file'
+  input.accept = '.pdf,.docx'
+  input.style.display = 'none'
+  input.onchange = async (e: Event) => {
+    const file = (e.target as HTMLInputElement).files?.[0]
+    if (!file) return
+    const reader = new FileReader()
+    reader.onload = async () => {
+      const base64 = (reader.result as string).split(',')[1]
+      storeFile(base64, file.name, file.type, file.size)
+    }
+    reader.readAsDataURL(file)
+    document.body.removeChild(input)
+  }
+  document.body.appendChild(input)
+  input.click()
+}
+
+// 打开审核人选择弹窗
+const openAuditorDialog = async () => {
+  auditorVisible.value = true
+  auditorLoading.value = true
+  auditorList.value = []
+  selectedAuditorId.value = ''
+  try {
+    const roleCode = equipType === EquipmentType.BOILER ? 'Boiler Director' : 'Pipeline Director'
+    const res = await getAuditList({ roleCode })
+    auditorList.value = (res as any)?.data?.list || []
+  } catch (e) {
+    console.error('获取审核人列表失败:', e)
+    uni.showToast({ title: '获取审核人列表失败', icon: 'none' })
+  } finally {
+    auditorLoading.value = false
+  }
+}
+
+// 确认提交:上传文件后调用提交接口
+const confirmSubmit = async () => {
+  if (!selectedAuditorId.value) {
+    uni.showToast({ title: '请选择审核人', icon: 'none' })
+    return
+  }
+  auditorVisible.value = false
+  const fileUrl = await doUpload()
+  if (!fileUrl) {
+    uni.showToast({ title: '请先上传检验方案文件', icon: 'none' })
+    return
+  }
+  try {
+    const dataSource = designerRef.value?.getSpreadDataSource()
+    if (dataSource) {
+      const saveResult = await saveDynamicTbVal({ params: dataSource, instId: instId.value })
+      if (saveResult?.code !== 0) {
+        throw new Error(saveResult?.msg || '保存报表失败,请重试')
+      }
+    }
+    const reqData = {
+      id: refId,
+      reportType: PressureReportType.INSPECTIONPLAN,
+      prepareJson: JSON.stringify({
+        prepareName: uni.getStorageSync('userInfo')?.name || '',
+        prepareDate: dayjs().format('YYYY年MM月DD日'),
+      }),
+      auditUserIds: [selectedAuditorId.value],
+      approveUserIds: [],
+      manualUrl: fileUrl,
+    }
+    uni.showLoading({ title: '提交审核中...', mask: true })
+    // 调用提交审核接口
+    const res = await requestFunc(TaskOrderFuncName.SubmitOpinionNoticeApproval, equipType, reqData)
+    if (res?.code === 0) {
+      uni.showToast({ title: '提交审核成功', icon: 'success' })
+    } else {
+      throw new Error(res?.msg || '提交审核失败,请重试')
+    }
+  } catch (error) {
+    console.error('提交审核异常:', error)
+    uni.showToast({ title: error.message || '提交审核失败', icon: 'none' })
+  } finally {
+    uni.navigateBack()
+    uni.hideLoading()
+  }
+}
+
+const handleCustomAction = ({ action, data }) => {
+  switch (action) {
+    case 'upload':
+      isRNEnv() ? pickFileRN() : pickFileH5()
+      break
+    case 'submit':
+      if (!pendingFile.value) {
+        uni.showToast({ title: '请先上传检验方案文件', icon: 'none' })
+        break
+      }
+      uni.showModal({
+        title: '提交审核',
+        content: `检验方案文件目前为「${pendingFile.value.name}」,确认提交审核?`,
+        confirmText: '确定',
+        cancelText: '取消',
+        success: async (res) => {
+          if (!res.confirm) return
+          await openAuditorDialog()
+        },
+      })
+      break
+    default:
+      break
+  }
+}
 </script>
+
+<style scoped>
+.nav-btn-group {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+}
+
+.nav-btn {
+  padding: 0 12px;
+  font-size: 12px;
+  line-height: 28px;
+  color: #495057;
+  white-space: nowrap;
+  background-color: #fff;
+  border: 1px solid #dee2e6;
+  border-radius: 4px;
+}
+
+.nav-btn.primary {
+  color: white;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.nav-btn.upload-btn {
+  color: white;
+  background-color: #e6a23c;
+  border-color: #e6a23c;
+}
+
+.nav-btn.submit-btn {
+  color: white;
+  background-color: #67c23a;
+  border-color: #67c23a;
+}
+
+/* 审核人弹窗 */
+.auditor-overlay {
+  position: fixed;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 10001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: rgba(0, 0, 0, 0.5);
+}
+
+.auditor-dialog {
+  width: 80%;
+  max-height: 70vh;
+  overflow: hidden;
+  background: #fff;
+  border-radius: 12px;
+}
+
+.auditor-title {
+  padding: 16px;
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  text-align: center;
+  border-bottom: 1px solid #eee;
+}
+
+.auditor-list {
+  max-height: 50vh;
+  padding: 8px 0;
+}
+
+.auditor-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  cursor: pointer;
+}
+
+.auditor-item.active {
+  background-color: #ecf5ff;
+}
+
+.auditor-name {
+  font-size: 14px;
+  color: #333;
+}
+
+.auditor-check {
+  font-size: 16px;
+  color: #007bff;
+}
+
+.auditor-loading,
+.auditor-empty {
+  padding: 20px;
+  font-size: 14px;
+  color: #999;
+  text-align: center;
+}
+
+.auditor-footer {
+  display: flex;
+  border-top: 1px solid #eee;
+}
+
+.auditor-btn {
+  flex: 1;
+  padding: 14px 0;
+  font-size: 15px;
+  text-align: center;
+  cursor: pointer;
+}
+
+.auditor-btn.cancel {
+  color: #666;
+  border-right: 1px solid #eee;
+}
+
+.auditor-btn.confirm {
+  font-weight: 600;
+  color: #007bff;
+}
+</style>

+ 19 - 3
src/pages/equipment/detail/components/OtherReport.vue

@@ -282,6 +282,9 @@ const showStatusTag = (item: ReportItem): boolean => {
     [PressureReportType.WORKINSTRUCTION].includes(item.reportType) &&
     item.status != null &&
     item.status !== 200
+  ) || (
+    item.reportType === PressureReportType.INSPECTIONPLAN &&
+    item.status !== 300
   )
 }
 
@@ -299,6 +302,19 @@ const getStatusText = (item: ReportItem): string => {
       default:
         return ''
     }
+  } else if (item.reportType === PressureReportType.INSPECTIONPLAN) {
+    switch (item.status) {
+      case 0:
+        return '待提交'
+      case 100:
+        return '审核中'
+      case 200:
+        return '已通过'
+      case 300:
+        return '退回'
+      default:
+        return ''
+    }
   }
   return ''
 }
@@ -308,7 +324,7 @@ const canEdit = (item: ReportItem): boolean => {
   if (reportType === PressureReportType.WORKINSTRUCTION) {
     return [0, 200, 300].includes(status)
   } else if (reportType === PressureReportType.INSPECTIONPLAN) {
-    return true
+    return [0].includes(status)
   }
   return false
 }
@@ -327,7 +343,7 @@ const handleEdit = (item: ReportItem) => {
       break
     case PressureReportType.INSPECTIONPLAN:
       uni.navigateTo({
-        url: `/pages/editor/inspectionPlanEditor?templateId=${item?.templateId}&refId=${item?.id}`,
+        url: `/pages/editor/inspectionPlanEditor?templateId=${item?.templateId}&refId=${item?.id}&manualUrl=${encodeURIComponent(item.manualUrl || '')}`,
       })
       break
     default:
@@ -350,7 +366,7 @@ const handleViewDetail = async (item: ReportItem) => {
       break
     case PressureReportType.INSPECTIONPLAN:
       uni.navigateTo({
-        url: `/pages/equipment/detail/inspectionPlanPreviewer?templateId=${item?.templateId}&id=${item?.id}`,
+        url: `/pages/equipment/detail/inspectionPlanPreviewer?templateId=${item?.templateId}&id=${item?.id}&manualUrl=${encodeURIComponent(item.manualUrl || '')}`,
       })
       break
     default:

+ 173 - 0
src/pages/equipment/detail/inspectionPlanPreviewer.vue

@@ -0,0 +1,173 @@
+<route lang="json5" type="page">
+{
+  layout: 'default',
+  style: {
+    navigationBarTitleText: '操作指导书批准',
+    navigationStyle: 'custom',
+    disableScroll: true,
+  },
+}
+</route>
+
+<template>
+  <view class="preview-container">
+    <ReportNavBar />
+
+    <BackendPDFViewer
+      ref="backendPdfViewerRef"
+      :refId="refId"
+      :templateId="templateId"
+      :manualUrl="manualUrl"
+    />
+
+  </view>
+</template>
+
+<script lang="ts" setup>
+import { ref, watch } from 'vue'
+import { onLoad } from '@dcloudio/uni-app'
+import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
+import BackendPDFViewer from '@/components/BackendPDFViewer/inspectionPlanPDFViewer.vue'
+
+defineOptions({
+  name: 'WorkInstructionPreviewer',
+})
+
+const id = ref('')
+const templateIdFromRoute = ref('')
+
+const templateId = ref('')
+const refId = ref('')
+const manualUrl = ref('')
+
+onLoad((options: any) => {
+  id.value = options?.id || ''
+  templateIdFromRoute.value = options?.templateId || ''
+  manualUrl.value = decodeURIComponent(options?.manualUrl || '')
+})
+
+watch(id, (newVal) => {
+  if (newVal) {
+    loadPreviewData()
+  }
+})
+
+const loadPreviewData = async () => {
+  templateId.value = templateIdFromRoute.value
+  refId.value = id.value
+}
+
+</script>
+
+<style lang="scss" scoped>
+.preview-container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background-color: #f5f5f5;
+}
+
+.nav-btn {
+  padding: 4px 8px;
+  margin: 0 0 0 4px !important;
+  font-size: 12px;
+  line-height: 1.4;
+  color: #fff;
+  border: none;
+  border-radius: 3px;
+}
+
+.btn-orange {
+  background-color: #f5a623;
+}
+
+.btn-blue {
+  background-color: #2f8eff;
+}
+
+.popup-body {
+  width: 80vw;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 2vh 0 0 0;
+}
+
+.center-text {
+  font-size: 14px;
+  color: rgb(51, 51, 51);
+}
+
+.input-area {
+  flex: 1;
+  height: 80px;
+  border-radius: 6px;
+  border: 1px solid rgb(187, 187, 187);
+  padding: 8px;
+  font-size: 14px;
+}
+
+.popup-bottom-box {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 0 20px;
+}
+
+.bottom-btn {
+  flex: 1;
+  height: 40px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  border-radius: 4px;
+  background-color: rgb(47, 142, 255);
+  color: rgb(241, 247, 255);
+  font-size: 15px;
+  line-height: 40px;
+  margin: 0;
+}
+
+.bottom-btn::after {
+  border: none;
+}
+
+.white-btn {
+  border: 1px solid rgb(187, 187, 187);
+  background-color: rgb(255, 255, 255);
+  color: rgb(51, 51, 51);
+  margin-right: 20px;
+}
+
+.row {
+  display: flex;
+  flex-direction: row;
+  align-items: flex-start;
+  padding: 0 20px;
+  margin-bottom: 2vh;
+}
+
+.picker-value {
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+}
+.confirm-title {
+  font-size: 18px;
+  font-weight: bold;
+  text-align: center;
+  color: rgb(51, 51, 51);
+  margin-bottom: 20px;
+}
+
+.confirm-content {
+  font-size: 14px;
+  text-align: center;
+  color: rgb(102, 102, 102);
+  margin-bottom: 30px;
+  padding: 0 20px;
+}
+</style>

+ 1 - 1
src/pages/inspectionPlanAudit/list/InspectionPlanAuditList.vue

@@ -141,7 +141,7 @@ const handleRightRefresh = (value: string | number | boolean) => {
 // 跳转操作
 const pushAction = (item: any) => {
   uni.navigateTo({
-    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}`,
+    url: `/pages/inspectionPlanAudit/preViewReport/index?id=${item.id}&reportName=${item.reportName}&templateId=${item.templateId}&manualUrl=${encodeURIComponent(item.manualUrl)}`,
   })
 }
 

+ 4 - 1
src/pages/inspectionPlanAudit/preViewReport/index.vue

@@ -22,6 +22,7 @@
       ref="backendPdfViewerRef"
       :refId="refId"
       :templateId="templateId"
+      :manualUrl="manualUrl"
     />
 
     <wd-popup position="center" v-model="showGoBack">
@@ -55,7 +56,7 @@ import { ref, watch } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import ReportNavBar from '@/components/NavBar/ReportNavBar.vue'
 import { useToast } from 'wot-design-uni'
-import BackendPDFViewer from '@/components/BackendPDFViewer/index.vue'
+import BackendPDFViewer from '@/components/BackendPDFViewer/inspectionPlanPDFViewer.vue'
 
 import { useConfigStore } from '@/store/config'
 
@@ -73,9 +74,11 @@ const equipType = useConfigStore().getEquipType()
 const backendPdfViewerRef = ref<any>(null)
 const templateId = ref('')
 const refId = ref('')
+const manualUrl = ref('')
 
 onLoad((options: any) => {
   id.value = options?.id || ''
+  manualUrl.value = decodeURIComponent(options?.manualUrl || '' || '')
   templateIdFromRoute.value = options?.templateId || ''
 })
 

+ 4 - 3
src/pages/sign/index.vue

@@ -162,7 +162,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, onMounted, nextTick } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { onLoad } from '@dcloudio/uni-app'
 import SignatureCanvas from '@/components/Signature/SignatureCanvas.vue'
 import BackendPDFViewer from '@/components/BackendPDFViewer/index.vue'
@@ -171,7 +171,8 @@ import NavBar from '@/components/NavBar/NavBar.vue'
 import { useUserStore } from '@/store/user'
 import { useConfigStore } from '@/store/config'
 
-import { getGcConfig, getTaskOrderImg, pushPressure2TaskOrder, uploadSignImage } from '@/api/sign'
+import { pushPressure2TaskOrder } from '@/api/sign'
+import { uploadFile } from '@/api/index'
 import { getTaskOrderReport } from '@/api/orderReport'
 import { SignFuncName, requestFunc } from '@/api/ApiRouter/sign'
 import { TaskOrderFuncName, requestFunc as taskOrderRequestFunc } from '@/api/ApiRouter/taskOrder'
@@ -440,7 +441,7 @@ const uploadSignature = async (base64Data: string): Promise<string> => {
     }
     const fd = new FormData()
     fd.append('file', file)
-    const resp: any = await uploadSignImage(fd)
+    const resp: any = await uploadFile(fd)
     if (resp?.code === 0 && resp?.data) {
       return resp.data
     }

+ 55 - 5
src/pages/taskOnline/TaskOnlineEquipmentList.vue

@@ -228,8 +228,22 @@
     <view v-if="showInspectionplanPopup" class="popup-mask" @click="closeInspectionplanPopup">
       <view class="popup-content inspectionplan-popup" @click.stop>
         <text class="popup-title">添加检验方案</text>
+        <view class="tab-row">
+          <view
+            :class="['tab-item', { active: inspectionPlanType === 'commPlan' }]"
+            @click="inspectionPlanType = 'commPlan'"
+          >
+            通用
+          </view>
+          <view
+            :class="['tab-item', { active: inspectionPlanType === 'selfPlan' }]"
+            @click="inspectionPlanType = 'selfPlan'"
+          >
+            自编
+          </view>
+        </view>
         <view class="form-item">
-          <text class="form-label">模板封面</text>
+          <text class="form-label">{{ inspectionPlanType === 'commPlan' ? '检验方案' : '模板封面' }}</text>
           <wd-picker
             v-model="selectedTemplateId"
             :columns="templateListColumn"
@@ -284,13 +298,12 @@
 <script lang="ts" setup>
 import { ref, computed } from 'vue'
 import { onLoad, onShow } from '@dcloudio/uni-app'
-import { batchClaim, pressure2NotVerifyPageApi } from '@/api/task'
+import { pressure2NotVerifyPageApi } from '@/api/task'
 import { useUserStore } from '@/store/user'
 import { useConfigStore } from '@/store/config'
 import {
   PressureCheckerMyTaskStatus,
   PressureCheckerMyTaskStatusMap,
-  EquipmentType,
   getStrDictOptions,
 } from '@/utils/dictMap'
 import dayjs from 'dayjs'
@@ -345,12 +358,26 @@ const refuseCategoryColumns = computed(() => {
 })
 
 const showInspectionplanPopup = ref(false)
+const inspectionPlanType = ref('commPlan')
 const inspectionplanName = ref('')
 const selectedTemplate = ref<any>(null)
 const selectedTemplateId = ref('')
 const templateList = ref<any[]>([])
+const optionsResult = ref<any[]>([])
+const filteredTemplateList = computed(() => {
+  if (inspectionPlanType.value === 'commPlan') {
+    return optionsResult.value.filter(
+      (item) => item.reportType === 900 && (item.pjType === 3 || item.pjType === 5),
+    )
+  } else if (inspectionPlanType.value === 'selfPlan') {
+    return optionsResult.value.filter(
+      (item) => item.reportType === 500 && item.pjType === 5,
+    )
+  }
+  return []
+})
 const templateListColumn = computed(() => [
-  templateList.value.map((item) => ({ label: item.tbName, value: item.id })),
+  filteredTemplateList.value.map((item) => ({ label: item.tbName, value: item.id })),
 ])
 
 const showUpdateContactPopup = ref(false)
@@ -698,6 +725,7 @@ const showAddInspectionplanPopup = async () => {
   }
   showMoreOperate.value = false
   showInspectionplanPopup.value = true
+  inspectionPlanType.value = 'commPlan'
   inspectionplanName.value = ''
   selectedTemplate.value = null
   try {
@@ -708,7 +736,7 @@ const showAddInspectionplanPopup = async () => {
       pageNo: 1,
       pageSize: 9999,
     })
-    templateList.value = result?.data || []
+    optionsResult.value = result?.data || []
   } catch (error) {
     console.error('获取模板列表失败:', error)
   }
@@ -1223,6 +1251,28 @@ const handleCalcTotalFee = (reportDOList: any) => {
   text-align: center;
 }
 
+.tab-row {
+  display: flex;
+  margin-bottom: 12px;
+  border-radius: 4px;
+  overflow: hidden;
+  border: 1px solid #2f8eff;
+}
+
+.tab-item {
+  flex: 1;
+  padding: 8px 0;
+  font-size: 14px;
+  color: #2f8eff;
+  text-align: center;
+  background-color: #fff;
+}
+
+.tab-item.active {
+  color: #fff;
+  background-color: #2f8eff;
+}
+
 .suspend-textarea {
   box-sizing: border-box;
   width: 100%;

+ 103 - 22
src/pages/taskOnline/TaskOnlinePipeEquipmentList.vue

@@ -45,7 +45,9 @@
                 </view>
                 <view class="cell-top-left" @click="handleSelectPipeSet(pipeSet)">
                   <text class="equip-name">{{ pipeSet.equipName }}({{ pipeSet.projectNo }})</text>
-                  <text class="main-checker">主检人:{{ pipeSet?.mainCheckerUser?.nickname || '' }}</text>
+                  <text class="main-checker">
+                    主检人:{{ pipeSet?.mainCheckerUser?.nickname || '' }}
+                  </text>
                   <view
                     class="status-tag"
                     :class="
@@ -60,7 +62,11 @@
                   </view>
                 </view>
                 <view class="expand-arrow" @click.stop="toggleCollapse(pipeSet.id)">
-                  <text class="arrow-icon" :class="{ 'arrow-expanded': expandedNames.includes(pipeSet.id) }">▶</text>
+                  <image
+                    class="arrow-icon"
+                    :class="{ 'arrow-expanded': expandedNames.includes(pipeSet.id) }"
+                    :src="iconMap.ArrowRight"
+                  />
                 </view>
               </view>
             </view>
@@ -76,7 +82,10 @@
               :key="childPipe.id"
               class="child-pipe-cell"
             >
-              <view class="child-pipe-top" @click="handleSelectChildPipe(childPipe, pipeSet.id, pipeSet.mainID)">
+              <view
+                class="child-pipe-top"
+                @click="handleSelectChildPipe(childPipe, pipeSet.id, pipeSet.mainID)"
+              >
                 <view class="row">
                   <view v-if="orderId" class="checkbox-wrapper">
                     <view class="checkbox" :class="{ checked: selectedChildPipeMap[childPipe.id] }">
@@ -89,12 +98,17 @@
                     </view>
                   </view>
                   <view class="cell-top-left">
-                    <text class="child-pipe-name">{{ childPipe.equipName || childPipe.pipeName || '' }}({{ childPipe.pipeNo }})</text>
+                    <text class="child-pipe-name">
+                      {{ childPipe.equipName || childPipe.pipeName || '' }}({{ childPipe.pipeNo }})
+                    </text>
                   </view>
                 </view>
               </view>
 
-              <view class="child-pipe-info" @click="handleRouteToEquipmentDetail(childPipe, 'EQUIPMENT')">
+              <view
+                class="child-pipe-info"
+                @click="handleRouteToEquipmentDetail(childPipe, 'EQUIPMENT')"
+              >
                 <view class="info-box">
                   <text class="info-label">
                     设备注册代码:
@@ -231,9 +245,26 @@
     <view v-if="showInspectionplanPopup" class="popup-mask" @click="closeInspectionplanPopup">
       <view class="popup-content inspectionplan-popup" @click.stop>
         <text class="popup-title">添加检验方案</text>
+        <view class="tab-row">
+          <view
+            :class="['tab-item', { active: inspectionPlanType === 'commPlan' }]"
+            @click="inspectionPlanType = 'commPlan'"
+          >
+            通用
+          </view>
+          <view
+            :class="['tab-item', { active: inspectionPlanType === 'selfPlan' }]"
+            @click="inspectionPlanType = 'selfPlan'"
+          >
+            自编
+          </view>
+        </view>
         <view class="form-item">
-          <text class="form-label">模板封面</text>
+          <text class="form-label">
+            {{ inspectionPlanType === 'commPlan' ? '检验方案' : '模板封面' }}
+          </text>
           <wd-picker
+            custom-style="background-color: #f5f5f5;border: #d3d2d2 solid 1px;border-radius: 4px;"
             v-model="selectedTemplateId"
             :columns="templateListColumn"
             @confirm="onTemplateChange"
@@ -296,16 +327,19 @@ import {
   EquipmentType,
   getStrDictOptions,
 } from '@/utils/dictMap'
+import iconMap from '@/utils/imagesMap'
 import dayjs from 'dayjs'
 import UpdateSafetyManagerPopup from '@/pages/unClaim/components/UpdateSafetyManagerPopup.vue'
 import { TaskOrderFuncName, requestFunc } from '@/api/ApiRouter/taskOrder'
-import { getPipeDetailByOrderItemId, getPipeTaskItemListByOrderId, addInspectProject } from '@/api/pipe/pipeTaskOrder'
+import {
+  getPipeDetailByOrderItemId,
+  getPipeTaskItemListByOrderId,
+  addInspectProject,
+} from '@/api/pipe/pipeTaskOrder'
 import { updateEquipPipeSafetyManager } from '@/api/pipe/pipeEquip'
 import NavBar from '@/components/NavBar/NavBar.vue'
 import PipeCheckProject from '@/pages/taskOnline/components/PipeCheckProject.vue'
 
-
-
 interface PopupData {
   text: string
   isClaim: boolean
@@ -343,16 +377,31 @@ const showSuspendPopup = ref(false)
 const suspendReason = ref('')
 const suspendReasonDict = ref('')
 const refuseCategoryColumns = computed(() => {
-  return getStrDictOptions('refuseInspectedCategory').map((item: any) => ({ label: item.label, value: item.value }))
+  return getStrDictOptions('refuseInspectedCategory').map((item: any) => ({
+    label: item.label,
+    value: item.value,
+  }))
 })
 
 const showInspectionplanPopup = ref(false)
+const inspectionPlanType = ref('commPlan')
 const inspectionplanName = ref('')
 const selectedTemplate = ref<any>(null)
 const selectedTemplateId = ref('')
 const templateList = ref<any[]>([])
+const optionsResult = ref<any[]>([])
+const filteredTemplateList = computed(() => {
+  if (inspectionPlanType.value === 'commPlan') {
+    return optionsResult.value.filter(
+      (item) => item.reportType === 900 && (item.pjType === 3 || item.pjType === 5),
+    )
+  } else if (inspectionPlanType.value === 'selfPlan') {
+    return optionsResult.value.filter((item) => item.reportType === 500 && item.pjType === 5)
+  }
+  return []
+})
 const templateListColumn = computed(() => [
-  templateList.value.map((item) => ({ label: item.tbName, value: item.id })),
+  filteredTemplateList.value.map((item) => ({ label: item.tbName, value: item.id })),
 ])
 
 const showUpdateContactPopup = ref(false)
@@ -506,7 +555,6 @@ const initSelect = () => {
   updateOperateStatus()
 }
 
-
 const showCheckProjectPopup = async () => {
   if (selectedChildPipes.value.length) {
     return uni.showToast({ title: '选中管道设备时不可添加项目', icon: 'error' })
@@ -566,7 +614,7 @@ const handleCheckProjectConfirm = async (itemList: any[]) => {
         fee: project.fee,
         templateId: project.templateId,
         type: project.type,
-        orderItemId: item.mainID
+        orderItemId: item.mainID,
       })
     })
   })
@@ -626,7 +674,11 @@ const suspendCheck = async (flag: number) => {
       reasonDict: suspendReasonDict.value,
       flag,
     }
-    const result = await requestFunc(TaskOrderFuncName.BatchSuspendEquip, EquipmentType.PIPE, reqData)
+    const result = await requestFunc(
+      TaskOrderFuncName.BatchSuspendEquip,
+      EquipmentType.PIPE,
+      reqData,
+    )
     uni.hideLoading()
     if (result?.code === 0) {
       initSelect()
@@ -657,7 +709,9 @@ const createInform = async () => {
   }
 
   const selectedPipeSet = selectedPipeSets.value[0]
-  const majorIssue = selectedPipeSet.reportRespVOList?.find((item: any) => item.reportType == PressureReportType.MAINQUESTION)
+  const majorIssue = selectedPipeSet.reportRespVOList?.find(
+    (item: any) => item.reportType == PressureReportType.MAINQUESTION,
+  )
   if (majorIssue) {
     return uni.showToast({ title: '该管道工程已添加了重大问题线索', icon: 'error' })
   }
@@ -683,7 +737,11 @@ const createInform = async () => {
       prepareName: userInfo.value?.nickname || '',
       templateId: templateId,
     }
-    const addMajorIssueResp = await requestFunc(TaskOrderFuncName.AddMajorIssues, EquipmentType.PIPE, reqData)
+    const addMajorIssueResp = await requestFunc(
+      TaskOrderFuncName.AddMajorIssues,
+      EquipmentType.PIPE,
+      reqData,
+    )
     if (addMajorIssueResp?.code !== 0) {
       return uni.showToast({ title: addMajorIssueResp?.msg || '操作失败', icon: 'error' })
     }
@@ -708,6 +766,7 @@ const showAddInspectionplanPopup = async () => {
   }
   showMoreOperate.value = false
   showInspectionplanPopup.value = true
+  inspectionPlanType.value = 'commPlan'
   inspectionplanName.value = ''
   selectedTemplate.value = null
   try {
@@ -718,7 +777,7 @@ const showAddInspectionplanPopup = async () => {
       pageNo: 1,
       pageSize: 9999,
     })
-    templateList.value = result?.data || []
+    optionsResult.value = result?.data || []
   } catch (error) {
     console.error('获取模板列表失败:', error)
   }
@@ -841,10 +900,8 @@ const handleRouteToEquipmentDetail = (item: any, pageType: string) => {
 }
 
 .arrow-icon {
-  font-size: 12px;
-  color: #999;
-  transition: transform 0.3s ease;
-  transform: rotate(0deg);
+  width: 21px;
+  height: 21px;
 }
 
 .arrow-icon.arrow-expanded {
@@ -1234,6 +1291,28 @@ const handleRouteToEquipmentDetail = (item: any, pageType: string) => {
   text-align: center;
 }
 
+.tab-row {
+  display: flex;
+  margin-bottom: 12px;
+  border-radius: 4px;
+  overflow: hidden;
+  border: 1px solid #2f8eff;
+}
+
+.tab-item {
+  flex: 1;
+  padding: 8px 0;
+  font-size: 14px;
+  color: #2f8eff;
+  text-align: center;
+  background-color: #fff;
+}
+
+.tab-item.active {
+  color: #fff;
+  background-color: #2f8eff;
+}
+
 .suspend-textarea {
   box-sizing: border-box;
   width: 100%;
@@ -1258,10 +1337,12 @@ const handleRouteToEquipmentDetail = (item: any, pageType: string) => {
 }
 
 .form-input {
-  padding: 0px;
+  height: 46px;
+  padding: 0 0 0 15px;
   font-size: 14px;
   background-color: #f5f5f5;
   border-radius: 4px;
+  border: #d3d2d2 solid 1px;
 }
 
 ::deep(.wd-collapse-item) {

+ 1 - 0
src/types/uni-pages.d.ts

@@ -30,6 +30,7 @@ interface NavigateToOptions {
        "/pages/uploadFile/UploadFile" |
        "/pages/user/index" |
        "/pages/equipment/detail/equipmentDetail" |
+       "/pages/equipment/detail/inspectionPlanPreviewer" |
        "/pages/equipment/detail/mainQuestionPreviewer" |
        "/pages/equipment/detail/workInstructionPreviewer" |
        "/pages/inspectionApproval/list/inspectionApprovalList" |

+ 0 - 118
src/utils/http.ts

@@ -1,8 +1,6 @@
 import { CustomRequestOptions } from '@/interceptors/request'
 import signMd5Utils from '@/utils/signMd5Utils'
 import { isH5 } from '@/utils/platform'
-import { getEnvBaseUrl } from '@/utils/index'
-import { buildURL, buildFullPath } from '@/utils/helpers/buildURL'
 
 export const http = <T>(options: CustomRequestOptions) => {
   // 1. 返回 Promise 对象
@@ -151,123 +149,7 @@ export const httpDelete = <T>(
   })
 }
 
-const requestComFun = (response) => {
-  return response
-}
-
-const requestComFail = (response) => {
-  return response
-}
-/**
- * 上传 请求
- * @param url 后台地址
- * @param query 请求 query 参数
- * @returns
- */
-export const httpUpload = <T>(
-  url: string,
-  {
-    // #ifdef APP-PLUS || H5
-    files,
-    // #endif
-    filePath,
-    name,
-    // #ifdef H5
-    file,
-    // #endif
-    header = {},
-    formData = {},
-    custom = {},
-    params = {},
-    getTask,
-  }: any,
-) => {
-  return new Promise((resolve, reject) => {
-    const next = true
-    // 从本地存储获取 token
-    const token = uni.getStorageSync('APP_ACCESS_TOKEN')
-    const globalHeader = {
-      'X-Access-Token': token || '',
-      'X-Tenant-Id': 1,
-      'X-TIMESTAMP': signMd5Utils.getTimestamp(),
-    }
-    const pubConfig = {
-      baseUrl: getEnvBaseUrl(),
-      url,
-      filePath,
-      method: 'UPLOAD',
-      name,
-      header: { ...globalHeader, ...header },
-      formData,
-      params,
-      custom: { ...custom },
-      getTask,
-    } as any
-    // #ifdef APP-PLUS || H5
-    if (files) {
-      pubConfig.files = files
-    }
-    // #endif
-    // #ifdef H5
-    if (file) {
-      pubConfig.file = file
-    }
-    // #endif
-    const requestBeforeFun = (config) => {
-      return config
-    }
-    // 校验状态码
-    const validateStatus = (statusCode) => {
-      return statusCode === 200
-    }
-    const handleRe = { ...pubConfig }
-    const _config = {
-      url: buildURL(buildFullPath(handleRe.baseUrl, handleRe.url), handleRe.params),
-      filePath: handleRe.filePath,
-      name: handleRe.name,
-      header: handleRe.header,
-      formData: handleRe.formData,
-      complete: (response) => {
-        response.config = handleRe
-        try {
-          // 对可能字符串不是 json 的情况容错
-          if (typeof response.data === 'string') {
-            response.data = JSON.parse(response.data)
-          }
-        } catch (e) {}
-        if (validateStatus(response.statusCode)) {
-          // 成功
-          response = requestComFun(response)
-          resolve(response)
-        } else {
-          response = requestComFail(response)
-          reject(response)
-        }
-      },
-    } as any
-
-    // #ifdef APP-PLUS || H5
-    if (handleRe.files) {
-      _config.files = handleRe.files
-    }
-    // #endif
-
-    // #ifdef H5
-    if (handleRe.file) {
-      _config.file = handleRe.file
-    }
-    // #endif
-
-    if (!next) return
-    const requestTask = uni.uploadFile(_config)
-    if (handleRe.getTask) {
-      handleRe.getTask(requestTask, handleRe)
-    }
-  })
-}
-
 http.get = httpGet
 http.post = httpPost
 http.put = httpPUT
 http.delete = httpDelete
-http.upload = httpUpload

+ 1 - 1
src/utils/imagesMap.ts

@@ -25,7 +25,7 @@ import Add from '@/static/icons/add.png'
 import Delete from '@/static/icons/delete.png'
 import ZoomIn from '@/static/icons/zoom+.png'
 import ZoomOut from '@/static/icons/zoom-.png'
-import ArrowRight from '@/static/icons/arrow-right-.png'
+import ArrowRight from '@/static/icons/arrow-right-bold.png'
 
 // 创建映射表
 export default {