Просмотр исходного кода

试验员签名逻辑;自动续页逻辑

yangguanjin 1 неделя назад
Родитель
Сommit
c9bdd1b53b

+ 4 - 0
src/api/task.ts

@@ -61,6 +61,10 @@ export const getReportTemplateJSONData = (params: any) => {
   return httpGet('/pressure/report-template/report/json', params)
 }
 
+export const getDynamicTb = (params: any) => {
+  return httpGet('/pressure2/dynamic-tb/get', params)
+}
+
 export const getDynamicTbVal = (params: any) => {
   return httpPost('/pressure2/dynamic-tb-val/find/ref', params)
 }

+ 257 - 5
src/components/SpreadDesigner/spreadDesigner.vue

@@ -49,6 +49,7 @@
           >
             特殊符号
           </button>
+          <!--<button class="generate-page-btn" @click="handleGeneratePage">生成续页</button>-->
           <button
             id="prevCellBtn"
             class="prev-cell-btn"
@@ -152,6 +153,10 @@ const props = defineProps({
     type: String,
     default: '',
   },
+  copyConfig: {
+    type: Object,
+    default: null,
+  },
 })
 
 const emit = defineEmits([
@@ -300,6 +305,7 @@ function formatterOADateNumber(input) {
   return OADate !== null ? new Date(Math.round(OADate - 25569) * 86400000) : null
 }
 
+// 只过滤出当前sheet页需要的字段
 function getSheetBindingPathData(sheet, dataSource) {
   const boundFields = new Set()
   const tableMap = new Map()
@@ -552,6 +558,7 @@ function getDefaultSchema(designerInstance) {
   )
 }
 
+let dataCount = 0
 function setDataSource(designerInstance, schemaData, dataSourceValues) {
   dataSourceValues =
     !is(dataSourceValues, 'Object') && is(dataSourceValues, 'String')
@@ -566,6 +573,18 @@ function setDataSource(designerInstance, schemaData, dataSourceValues) {
   const resultDataSource = deepMergeSchemaValue(formatterSource, dataSourceValues)
 
   const spreadInstance = designerInstance.getWorkbook()
+  const {
+    dataSource: continuePageProceedDataSource,
+    sheetToRecordRowCount,
+    totalDataCount,
+  } = processContinueDataSource(spreadInstance, resultDataSource)
+  dataCount = totalDataCount
+  processContinuePage(
+    spreadInstance,
+    continuePageProceedDataSource,
+    sheetToRecordRowCount,
+    totalDataCount,
+  )
   registerCellValuesChangeEventHandlerForEverySheet(spreadInstance.sheets)
   for (const sheet of spreadInstance.sheets) {
     const tables = sheet.tables?.all() || []
@@ -573,8 +592,9 @@ function setDataSource(designerInstance, schemaData, dataSourceValues) {
       const table = tables[i]
       table.expandBoundRows(true)
     }
-    const filterResultDataSource = getSheetBindingPathData(sheet, resultDataSource)
+    const filterResultDataSource = getSheetBindingPathData(sheet, continuePageProceedDataSource)
     const dataSource = new GC.Spread.Sheets.Bindings.CellBindingSource(filterResultDataSource)
+    console.log('@@@every sheet datasource.......', dataSource)
     sheet.setDataSource(dataSource)
   }
 }
@@ -601,6 +621,200 @@ function getDataSource(designerInstance) {
   return dataSource
 }
 
+function processContinueDataSource(spreadInstance, dataSource) {
+  if (!props.copyConfig?.isContinuePage) {
+    return dataSource
+  }
+  console.log('@@@@@datasource', dataSource)
+  dataSource = { ...dataSource }
+  const copyConfig = props.copyConfig
+  const continuePageSheetName = copyConfig.sheetName
+  console.log('@@@@copyconfig....', copyConfig)
+  // 数据扁平化
+  // 0. 收集所有sheet页中的bindingPath字段集合,根据sheet页分组
+  const sheetToFieldListMap = {}
+  const fieldListToSheetNameMap = new Map()
+  for (const sheet of spreadInstance.sheets) {
+    const boundFields = new Set()
+    for (let row = 0; row < sheet.getRowCount(); row++) {
+      for (let col = 0; col < sheet.getColumnCount(); col++) {
+        const bindingPath = sheet.getBindingPath(row, col)
+        if (bindingPath) {
+          boundFields.add(bindingPath)
+        }
+      }
+    }
+    const currentSheetName = sheet.name()
+    sheetToFieldListMap[currentSheetName] = boundFields
+    fieldListToSheetNameMap.set(boundFields, currentSheetName)
+  }
+  console.log('@@@@sheetToFieldListMap....', sheetToFieldListMap)
+
+  // 每个 sheet 的数据行数
+  const sheetToRecordRowCount = {}
+  for (let sheetName in sheetToFieldListMap) {
+    const fieldList = [...sheetToFieldListMap[sheetName]]
+    let maxSuffixNum = 0
+    let minSuffixNum = -1
+    for (let i = 0; i < fieldList.length; i++) {
+      const field = fieldList[i]
+      const suffixNumStr = field.split('_')[1]
+      if (suffixNumStr == null || suffixNumStr == '') {
+        continue
+      }
+      const suffixNum = Number(suffixNumStr)
+      maxSuffixNum = suffixNum > maxSuffixNum ? suffixNum : maxSuffixNum
+      if (minSuffixNum === -1) {
+        minSuffixNum = suffixNum
+      } else {
+        minSuffixNum = suffixNum < minSuffixNum ? suffixNum : minSuffixNum
+      }
+    }
+
+    sheetToRecordRowCount[sheetName] = maxSuffixNum - minSuffixNum + 1
+  }
+  console.log('@@@@sheetToRecordRowCount.....', sheetToRecordRowCount)
+
+  // 1. 对于字段集合,取用copyConfig的sheetName对应sheet,识别 {filed}_{++num} 模式的字段,取出 {field} 部分组成的数组
+  const continuePageFieldSet = sheetToFieldListMap[continuePageSheetName]
+  console.log('@@@copyConfigContinuePageFieldSet...', continuePageFieldSet)
+
+  const prefixFieldSet = new Set()
+  const pattern = /^(.+)_(\d+)$/
+  for (const continuePageField of continuePageFieldSet) {
+    if (!continuePageField.match(pattern)) {
+      continue
+    }
+    prefixFieldSet.add(continuePageField.split('_')[0])
+  }
+  console.log('@@@prefixFieldSet.....', prefixFieldSet)
+
+  // 2. 遍历 {field}数组,取出 {field}_1 的数据,按逗号切割出数组,根据数组下标设置遍历,实现数据扁平处理
+  //    1. 遍历过程中,对dataSource的 {field}_{i+1} 设置值
+  //    2. 仅当{field}数组为0时,判断 {field}_{i+1} 在哪一个sheet,给sheet分页对应的recordCount加一
+  let totalDataCount = 0
+  let firstFlag = true
+  for (const prefixField of prefixFieldSet) {
+    const columnDataSource = dataSource[prefixField + '_1']
+    if (columnDataSource == null) {
+      break
+    }
+    if (firstFlag) {
+      totalDataCount = columnDataSource.length
+      firstFlag = false
+    } else {
+      if (totalDataCount !== columnDataSource.length) {
+        console.error('续页处理的数据,格式错误,字段为: ', prefixField)
+        continue
+      }
+    }
+    for (let i = 0; i < columnDataSource.length; i++) {
+      const cellData = columnDataSource[i]
+      const dataSourceKey = `${prefixField}_${i + 1}`
+      dataSource[dataSourceKey] = cellData
+    }
+  }
+  console.log('@@@flatDataSource.....', dataSource)
+
+  return { dataSource, sheetToRecordRowCount, totalDataCount }
+}
+
+// 续页生成,超出一览表(第一页),把续页(第二页)从隐藏改成显示,超出续页(第二页),拷贝续页,copyRange范围内的bindingPath做递增修改
+function processContinuePage(spreadInstance, dataSource, sheetToRecordRowCount, totalDataCount) {
+  if (!props.copyConfig?.isContinuePage) {
+    return dataSource
+  }
+  console.log('@@@@@datasource', dataSource, '@@@totalDataCount', totalDataCount)
+  dataSource = { ...dataSource }
+  const copyConfig = props.copyConfig
+  const copyRange = copyConfig.copyRange
+  const col = parseInt(copyRange.topLeft.split(',')[0])
+  const row = parseInt(copyRange.topLeft.split(',')[1])
+  const colCount = parseInt(copyRange.topRight.split(',')[0]) - col + 1
+  let rowCount = parseInt(copyRange.bottomLeft.split(',')[1]) - row + 1
+  const isHiddenContinuePage = copyConfig.hidden
+  const continuePageSheetName = copyConfig.sheetName
+
+  // 转换sheetName为对应数组下标,数组元素为对应页的数据行数量
+  const sheetIdxToRecordCountArray = new Array(spreadInstance.sheets.length).fill(0)
+  for (const sheet of spreadInstance.sheets) {
+    const sheetName = sheet.name()
+    const sheetIdx = spreadInstance.getSheetIndex(sheetName)
+    sheetIdxToRecordCountArray[sheetIdx] = sheetToRecordRowCount[sheetName]
+  }
+  console.log('@@@sheetIdxToRecordCountArray', sheetIdxToRecordCountArray)
+
+  // 一览表
+  let actualCursor = sheetIdxToRecordCountArray[0]
+  if (totalDataCount <= actualCursor) {
+    if (isHiddenContinuePage) {
+      spreadInstance.getSheet(1).visible(false)
+    }
+    return
+  }
+
+  // 一览表+续页表
+  actualCursor = actualCursor + rowCount
+  if (totalDataCount > sheetIdxToRecordCountArray[0] && totalDataCount <= actualCursor) {
+    spreadInstance.getSheet(1).visible(true)
+    return
+  }
+  spreadInstance.getSheet(1).visible(true)
+
+  let continuePageSuffixNum = 2
+  let copyPageIdx = 2
+  // 一览表+续页表+续页行为
+  while (totalDataCount > actualCursor) {
+    const newSheetName = `${continuePageSheetName}_${continuePageSuffixNum}`
+    spreadInstance.commandManager().execute({
+      cmd: 'copySheet',
+      sheetName: continuePageSheetName,
+      targetIndex: copyPageIdx,
+      newName: newSheetName,
+      includeBindingSource: true,
+    })
+
+    const currentCopiedSheet = spreadInstance.getSheet(copyPageIdx)
+    alterContinuePageBindingPath(currentCopiedSheet, actualCursor + 1, col, row, rowCount, colCount)
+
+    actualCursor += rowCount
+    continuePageSuffixNum += 1
+    copyPageIdx += 1
+  }
+
+  const sheetToFieldListMap = {}
+  for (const sheet of spreadInstance.sheets) {
+    const boundFields = new Set()
+    for (let row = 0; row < sheet.getRowCount(); row++) {
+      for (let col = 0; col < sheet.getColumnCount(); col++) {
+        const bindingPath = sheet.getBindingPath(row, col)
+        if (bindingPath) {
+          boundFields.add(bindingPath)
+        }
+      }
+    }
+    const currentSheetName = sheet.name()
+    sheetToFieldListMap[currentSheetName] = boundFields
+  }
+
+  console.log('@@@validateFieldMap......', sheetToFieldListMap)
+}
+
+function alterContinuePageBindingPath(sheet, startNum, startCol, startRow, rowCount, columnCount) {
+  for (let r = startRow; r < startRow + rowCount; r++) {
+    for (let c = startCol; c < startCol + columnCount; c++) {
+      const cellBindingPath = sheet.getBindingPath(r, c)
+      if (cellBindingPath == null) {
+        continue
+      }
+      const bindingPrefix = cellBindingPath.split('_')[0]
+      const newBindingPath = bindingPrefix + '_' + startNum
+      sheet.setBindingPath(r, c, newBindingPath)
+    }
+    startNum += 1
+  }
+}
+
 function getImages(designerInstance) {
   const spreadInstance = designerInstance.getWorkbook()
   const images = []
@@ -1047,7 +1261,7 @@ function openAndEditRecordFn() {
 
     spreadInstance.open(
       blob,
-      () => {
+      async () => {
         const activedSheet = spreadInstance.getActiveSheet()
         initDesignerSheetConfig(activedSheet)
 
@@ -1112,6 +1326,13 @@ function initSpreadInputEvents(spreadInstance) {
   console.log('悬浮输入框事件已绑定')
 }
 
+function handleDataJson(dataJson) {
+  if (!props.copyConfig?.isContinuePage) {
+    return dataJson
+  }
+  
+}
+
 function saveRecord() {
   if (!designer.value) return
 
@@ -1124,6 +1345,8 @@ function saveRecord() {
     const schemaJSON = getDefaultSchema(designer.value)
     const images = getImages(designer.value)
 
+
+
     emit('save', {
       blob: base64,
       instId: props.checkItemData.instId,
@@ -1201,7 +1424,14 @@ function getTargetPictureCell() {
     col = selection?.col
   }
 
-  if (row === null || row === undefined || col === null || col === undefined || row < 0 || col < 0) {
+  if (
+    row === null ||
+    row === undefined ||
+    col === null ||
+    col === undefined ||
+    row < 0 ||
+    col < 0
+  ) {
     return null
   }
 
@@ -1209,7 +1439,7 @@ function getTargetPictureCell() {
 }
 
 function handleTakePhotoData(photoData) {
-  console.log("处理图片。。。。。")
+  console.log('处理图片。。。。。')
   const targetCell = getTargetPictureCell()
   if (!targetCell) {
     showDialog('请先选择需要插入图片的单元格')
@@ -1338,7 +1568,7 @@ function updateCheckItemData(newData) {
 
     spreadInstance.open(
       blob,
-      () => {
+      async () => {
         const activedSheet = spreadInstance.getActiveSheet()
         initDesignerSheetConfig(activedSheet)
 
@@ -1582,6 +1812,28 @@ defineExpose({
   opacity: 0.7;
 }
 
+.generate-page-btn {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 60px;
+  height: 35px;
+  padding: 6px 12px;
+  font-size: 12px;
+  font-weight: 500;
+  color: white;
+  cursor: pointer;
+  background: #6f42c1;
+  border: none;
+  border-radius: 4px;
+  transition: all 0.2s ease;
+}
+
+.generate-page-btn:hover {
+  background: #5a32a3;
+  transform: translateY(-1px);
+}
+
 .floating-input-wrapper {
   position: relative;
   width: 100%;

+ 25 - 1
src/pages/editor/equipCheckRecordEditor.vue

@@ -19,6 +19,7 @@
       :reportList="reportList"
       :taskOrderItem="taskOrderItem"
       :templateBlob="templateBlob"
+      :copyConfig="copyConfig"
       @save="handleSave"
       @cancel="handleCancel"
       @close="handleCancel"
@@ -32,7 +33,12 @@
 import { ref, onUnmounted } from 'vue'
 import SpreadDesigner from '@/components/SpreadDesigner/spreadDesigner.vue'
 import { onLoad } from '@dcloudio/uni-app'
-import { getCheckerEquipmentDetailById, getDynamicTbVal, saveDynamicTbVal } from '@/api/task'
+import {
+  getCheckerEquipmentDetailById,
+  getDynamicTb,
+  getDynamicTbVal,
+  saveDynamicTbVal,
+} from '@/api/task'
 import { getStandardTemplate, uploadFile } from '@/api/index'
 import { getEnvBaseUrl } from '@/utils/index'
 import { PressureCheckerMyTaskStatus } from '@/utils/dictMap'
@@ -43,6 +49,7 @@ const reportList = ref<any[]>([])
 const taskOrderItem = ref<any>(null)
 const templateBlob = ref<string>('')
 const skipEntryKeys = ref<string[]>([])
+const copyConfig = ref<any>(null)
 
 let orderItemId = ''
 let checkItemId = ''
@@ -52,10 +59,27 @@ let equipCode = ''
 let reportUrlParam = ''
 
 const init = async () => {
+  await fetchCopyConfig()
   await handleGetCheckItemDetail(checkItemId, templateId)
   await getDetail()
 }
 
+/**
+ * 获取续页配置
+ * TODO: 后续对接后端接口获取 copyConfig
+ * 当前暂时返回 null,后续替换为实际 API 调用
+ * 接口参考: DynamicTbApi.getDynamicTb(templateId) → data.copyConfig
+ */
+const fetchCopyConfig = async () => {
+  // TODO: 调用后端接口获取续页配置
+  const resp = await getDynamicTb({id: templateId})
+  if (resp.data?.copyConfig) {
+    copyConfig.value = resp.data.copyConfig
+  }
+  console.log("copyConfig.....", copyConfig.value)
+  // copyConfig.value = null
+}
+
 const handleGetCheckItemDetail = async (
   reportId: string | undefined,
   newTemplateId: string | undefined,

+ 2 - 2
src/pages/equipment/detail/components/BoilerInspectProject.vue

@@ -859,7 +859,7 @@ const shouldShowFeeInput = (taskStatus?: number): boolean => {
 }
 
 const shouldShowSign = (item: CheckItem): boolean => {
-  return item.isSignFunction === 1 && item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT
+  return item.taskStatus === PressureCheckerMyTaskStatus.RECORD_INPUT && item.recordNeedSign === '1'
 }
 
 const shouldShowAttachmentUpload = (taskStatus?: number): boolean => {
@@ -1136,7 +1136,7 @@ const handleSign = (item: CheckItem) => {
   const { unitContact, unitPhone } = props.taskOrder || {}
 
   uni.navigateTo({
-    url: `/pages/sign-report/index?reportId=${item.id}&pushName=${unitContact}&pushPhone=${unitPhone}`,
+    url: `/pages/sign/index?orderId=${props.orderId}&reportId=${item.id}&templateId=${item.templateId}&type=RECORD_TESTER&pushName=${unitContact}&pushPhone=${unitPhone}`,
   })
 }
 

+ 47 - 38
src/pages/sign/index.vue

@@ -180,11 +180,13 @@ import {
   requestFunc as SecurityRequestFunc,
   SecurityCheckFuncName,
 } from '@/api/ApiRouter/taskOrderSecurityCheck'
+import { getDynamicTbVal, saveDynamicTbVal } from '@/api/task'
+
 import { EquipmentType } from '@/utils/dictMap'
 
 const equipType = useConfigStore().getEquipType()
 const title = ref('')
-const routeType = ref<'FWD' | 'JYRS' | 'AQJC' | 'ZXXX'>()
+const routeType = ref<'FWD' | 'JYRS' | 'AQJC' | 'ZXXX' | 'RECORD_TESTER'>()
 const orderId = ref('')
 const orderItemId = ref('')
 const securityCheckId = ref('')
@@ -227,6 +229,7 @@ const titleTextMap: Record<string, string> = {
   JYRS: '检验结果告知',
   AQJC: '安全检查记录',
   ZXXX: '重大问题线索告知',
+  RECORD_TESTER: '检验记录签名',
 }
 
 const businessTypeMap: Record<string, number> = {
@@ -234,6 +237,7 @@ const businessTypeMap: Record<string, number> = {
   JYRS: 200,
   AQJC: 300,
   ZXXX: 400,
+  RECORD_TESTER: 500,
 }
 
 const signButtonTextMap: Record<string, string> = {
@@ -241,6 +245,7 @@ const signButtonTextMap: Record<string, string> = {
   JYRS: '客户代表签名',
   AQJC: '受检单位签名',
   ZXXX: '受检单位签名',
+  RECORD_TESTER: '试验员签名',
 }
 
 const confirmTextMap: Record<string, string> = {
@@ -248,6 +253,7 @@ const confirmTextMap: Record<string, string> = {
   JYRS: '是否确认客户代表签名?',
   AQJC: '是否确认受检单位签名?',
   ZXXX: '是否提交审核?',
+  RECORD_TESTER: '是否确认试验员签名?',
 }
 
 const signButtonText = computed(() => {
@@ -278,6 +284,8 @@ onLoad((options) => {
 })
 
 const orderReportId = ref('')
+const reportData = ref()
+const reportDataInstId = ref()
 const getPreviewData = async () => {
   if (!routeType.value) {
     uni.showToast({ title: '必须选择签字文件类型', icon: 'error' })
@@ -288,6 +296,7 @@ const getPreviewData = async () => {
   })
   const orderDetail = orderDetailResp.data
   checkSignStatus(orderDetail)
+
   switch (routeType.value) {
     case 'FWD':
       const orderReportResp = await getTaskOrderReport({ taskOrderId: orderId.value })
@@ -334,6 +343,23 @@ const getPreviewData = async () => {
       templateId.value = defaultTemplateResp.data?.templateId
       refId.value = securityCheckId.value
       break
+    case 'RECORD_TESTER':
+      const dynamicTbResp = await getDynamicTbVal({
+        refId: reportId.value,
+      })
+      const dynamicTbInst = dynamicTbResp.data?.dynamicTbValRespVOList
+      reportDataInstId.value = dynamicTbResp.data?.dynamicTbInsRespVO?.id
+      const dynamicData = {}
+      for (const dataInst of dynamicTbInst) {
+        const key = dataInst.colCode
+        const val = dataInst.valValue
+        dynamicData[key] = val
+      }
+      reportData.value = dynamicData
+      setSignStatus(dynamicData)
+      // console.log("@@reportData....", reportData.value)
+      refId.value = reportId.value
+      break
     default:
       uni.showToast({ title: '请选择正确的签字文件类型', icon: 'error' })
       break
@@ -350,10 +376,20 @@ const checkSignStatus = (orderDetail: any) => {
       (row: any) =>
         row.businessType === targetBusinessType && row.securityCheckId === securityCheckId.value,
     )
+    isSigned.value = signFile == null || signFile.isSignature == '0' ? '0' : '1'
+  } else if (targetBusinessType == businessTypeMap.RECORD_TESTER) {
+    console.log("签名由报告数据判断")
   } else {
     signFile = signFileList.find((row: any) => row.businessType === targetBusinessType)
+    isSigned.value = signFile == null || signFile.isSignature == '0' ? '0' : '1'
+  }
+}
+const setSignStatus = (reportData: any) => {
+  if (routeType.value === 'RECORD_TESTER') {
+    if(reportData["checkName_1"] != null && reportData["checkName_1"] !== '') {
+      isSigned.value = "1"
+    }
   }
-  isSigned.value = signFile == null || signFile.isSignature == '0' ? '0' : '1'
 }
 
 const handleToSign = () => {
@@ -398,40 +434,6 @@ const base64ToFile = (base64Data: string, fileName: string = 'signature.png'): F
   }
   // #endif
 }
-
-const base64ToTempFilePath = (base64Data: string): Promise<string> => {
-  return new Promise((resolve, reject) => {
-    // #ifdef H5
-    const file = base64ToFile(base64Data)
-    if (file) {
-      resolve('') // H5 使用 File 对象
-    } else {
-      reject(new Error('转换失败'))
-    }
-    // #endif
-
-    // #ifndef H5
-    const fs = uni.getFileSystemManager()
-    const filePath = `${uni.env.USER_DATA_PATH}/signature_${Date.now()}.png`
-
-    // 移除 data:image/png;base64, 前缀
-    const base64 = base64Data.replace(/^data:image\/\w+;base64,/, '')
-
-    fs.writeFile({
-      filePath,
-      data: base64,
-      encoding: 'base64',
-      success: () => {
-        resolve(filePath)
-      },
-      fail: (err) => {
-        reject(err)
-      },
-    })
-    // #endif
-  })
-}
-
 const uploadSignature = async (base64Data: string): Promise<string> => {
   try {
     // #ifdef H5
@@ -545,15 +547,22 @@ const submitConfirm = async () => {
       // securityCheckId: ,
     }
 
+    let result: any
     if (routeType.value === 'FWD') {
       params.receiverPhone = fwdInputPhone.value
       params.orderReportId = orderReportId.value
+      result = await requestFunc(SignFuncName.SubmitSign, equipType, params)
     } else if (routeType.value === 'AQJC') {
       params.securityCheckId = securityCheckId.value
+      result = await requestFunc(SignFuncName.SubmitSign, equipType, params)
+    } else if (routeType.value === 'RECORD_TESTER') {
+      reportData.value["checkName_1"] = uploadedSignUrl.value || signImg.value
+      result = await saveDynamicTbVal({
+        params: reportData.value,
+        instId: reportDataInstId.value,
+      })
     }
 
-    const result: any = await requestFunc(SignFuncName.SubmitSign, equipType, params)
-
     if (result?.code === 0) {
       isSigned.value = '1'
       uni.showToast({