shiyanyu 3 týždňov pred
rodič
commit
6e2ef44ba1

+ 10 - 1
src/views/EntDeclaration/auditTaskManagement/components/DynamicTableDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    title="调查表填报"
+    :title="dialogTitle"
     :visible.sync="dialogVisible"
     width="90%"
     :close-on-click-modal="false"
@@ -366,6 +366,9 @@
       }
     },
     computed: {
+      dialogTitle() {
+        return String(this.requestType) === '2' ? '报送材料填报' : '调查表填报'
+      },
       // 分页后的表格数据
       paginatedTableData() {
         const start =
@@ -437,11 +440,17 @@
         const auditedUnitId = this.auditedUnitId || sd.auditedUnitId || ''
         const catalogId = this.catalogId || sd.catalogId || ''
         const taskId = this.taskId || sd.taskId || ''
+        // 本次上传对应的行ID(监审期间记录ID)
+        const periodRecordId =
+          (this.pendingUploadRow &&
+            (this.pendingUploadRow.id || this.pendingUploadRow.recordId)) ||
+          ''
         if (surveyTemplateId)
           formData.append('surveyTemplateId', surveyTemplateId)
         if (auditedUnitId) formData.append('auditedUnitId', auditedUnitId)
         if (catalogId) formData.append('catalogId', catalogId)
         if (taskId) formData.append('taskId', taskId)
+        if (periodRecordId) formData.append('periodRecordId', periodRecordId)
         formData.append('type', this.requestType)
         try {
           this.uploading = true

+ 229 - 31
src/views/EntDeclaration/auditTaskManagement/components/FixedAssetsTable.vue

@@ -114,6 +114,10 @@
             size="mini"
             :disabled="isViewMode"
             :maxlength="computeNumberMaxlength(column)"
+            :class="getCellErrorClass(scope.row, column.prop)"
+            :title="getCellError(scope.row, column.prop)"
+            :data-rowid="scope.row.rowid || scope.row.id"
+            :data-prop="column.prop"
             @input="sanitizeNumberInput(scope.row, column)"
             @keypress.native="
               onNumberKeypressNumeric($event, scope.row, column)
@@ -133,6 +137,11 @@
             :placeholder="column.placeholder || '请输入' + column.label"
             size="mini"
             :disabled="isViewMode"
+            :maxlength="column.maxlength"
+            :class="getCellErrorClass(scope.row, column.prop)"
+            :title="getCellError(scope.row, column.prop)"
+            :data-rowid="scope.row.rowid || scope.row.id"
+            :data-prop="column.prop"
             @blur="handleCellBlur(scope.row, column.prop)"
           />
         </template>
@@ -264,6 +273,8 @@
         fixedAssetsData: [],
         // 验证错误
         validationErrors: [],
+        // 字段级错误映射:key = rowid::prop -> message
+        fieldErrors: {},
         // 记录被删除的行rowid/id,用于保存时过滤
         deletedRowids: [],
         // 扁平化的表格数据(响应式)
@@ -473,6 +484,18 @@
               if (f.format) col.format = f.format
               if (f.valueFormat) col.valueFormat = f.valueFormat
             }
+            // 纯数字格式表示最大长度,且仅对非日期/数字类字段生效
+            const fmtStr = (meta.format && String(meta.format).trim()) || ''
+            if (
+              /^\d+$/.test(fmtStr) &&
+              !['date', 'datetime', 'year', 'number'].includes(col.type)
+            ) {
+              col.maxlength = Number(fmtStr)
+            }
+            // 数字字段:若 format 为纯数字,按“总数字位数上限”处理
+            if (/^\d+$/.test(fmtStr) && ['number'].includes(col.type)) {
+              col.maxDigits = Number(fmtStr)
+            }
             // 元数据上的长度规则(兼容多种命名)
             const rawType = String(
               meta.type || meta.fieldType || ''
@@ -621,6 +644,7 @@
         // 限制整数/小数位
         const intLimit = Number(column.totalLength)
         const decLimit = Number(column.decimalLength)
+        const maxDigits = Number(column.maxDigits)
         const sign = v.startsWith('-') || v.startsWith('+') ? v[0] : ''
         const unsigned = sign ? v.slice(1) : v
         const [iRaw, dRaw = ''] = unsigned.split('.')
@@ -632,6 +656,21 @@
         if (!isNaN(decLimit) && decLimit >= 0 && d.length > decLimit) {
           d = d.slice(0, decLimit)
         }
+        // 若定义了总数字位数上限(来自 format 纯数字),优先再截断到总位数
+        if (!isNaN(maxDigits) && maxDigits > 0) {
+          let total = i.length + d.length
+          if (total > maxDigits) {
+            // 先截断小数部分,再截断整数部分
+            let allow = maxDigits
+            if (i.length > allow) {
+              i = i.slice(0, allow)
+              d = ''
+            } else {
+              allow -= i.length
+              d = d.slice(0, allow)
+            }
+          }
+        }
         if (!isNaN(decLimit) && decLimit === 0) {
           // 整数:不保留小数点
           v = sign + i
@@ -1458,6 +1497,74 @@
             Message.warning(`${this.getFieldLabel(field)}必须是正整数`)
           }
         }
+        // 失焦进行字段级校验以清除或设置错误
+        this.validateField(row, field)
+      },
+      // 获取单元格错误class
+      getCellErrorClass(row, prop) {
+        const key = `${row.rowid || row.id}::${prop}`
+        return this.fieldErrors && this.fieldErrors[key] ? 'input-error' : ''
+      },
+      // 获取单元格错误消息(可用于title/提示)
+      getCellError(row, prop) {
+        const key = `${row.rowid || row.id}::${prop}`
+        return (this.fieldErrors && this.fieldErrors[key]) || ''
+      },
+      // 单字段校验:基于 columnsMeta 的 isRequired 与 format(数字=最大长度)
+      validateField(row, prop) {
+        if (!row || !prop) return
+        const col = (this.dynamicColumns || []).find(
+          (c) => c && c.prop === prop
+        )
+        const metas = Array.isArray(this.columnsMeta) ? this.columnsMeta : []
+        const byLabel = new Map()
+        const byId = new Map()
+        metas.forEach((m) => {
+          if (!m) return
+          if (m.label) byLabel.set(String(m.label), m)
+          if (m.fieldId) byId.set(String(m.fieldId), m)
+        })
+        const meta =
+          (col && col.label && byLabel.get(String(col.label))) ||
+          (col && col.fieldId && byId.get(String(col.fieldId))) ||
+          (prop && byLabel.get(String(prop))) ||
+          null
+        const value = row[prop]
+        const isEmpty = (v) => v === undefined || v === null || v === ''
+        const fmt = meta && meta.format ? String(meta.format).trim() : ''
+        const toBool = (v) => {
+          if (v === true) return true
+          if (typeof v === 'number') return v === 1
+          if (typeof v === 'string') {
+            const s = v.trim().toLowerCase()
+            return s === 'true' || s === '1'
+          }
+          return false
+        }
+        const requiredFlag = toBool(meta && (meta.required || meta.isRequired))
+        const key = `${row.rowid || row.id}::${prop}`
+        // 清理
+        if (this.fieldErrors && this.fieldErrors[key])
+          this.$delete(this.fieldErrors, key)
+        if (requiredFlag && isEmpty(value)) {
+          this.$set(
+            this.fieldErrors,
+            key,
+            `${col && col.label ? col.label : prop}不能为空`
+          )
+          return
+        }
+        if (!isEmpty(value) && /^\d+$/.test(fmt)) {
+          const expect = Number(fmt)
+          const actual = String(value).length
+          if (actual > expect) {
+            this.$set(
+              this.fieldErrors,
+              key,
+              `${col && col.label ? col.label : prop}长度不能超过${expect}位`
+            )
+          }
+        }
       },
       // 获取字段标签
       getFieldLabel(field) {
@@ -1637,14 +1744,58 @@
 
         return syncDataToNested(this.fixedAssetsData)
       },
+      // 聚焦第一个错误单元格
+      focusFirstError() {
+        try {
+          // 优先使用 fieldErrors 顺序中的第一个
+          const keys = Object.keys(this.fieldErrors || {})
+          if (!keys.length) return
+          const [first] = keys
+          const [rowKey, prop] = first.split('::')
+          if (!rowKey || !prop) return
+          this.$nextTick(() => {
+            // 查找带有 data-rowid 和 data-prop 的输入
+            const cssEscape = (s) => {
+              try {
+                return window.CSS && CSS.escape
+                  ? CSS.escape(s)
+                  : String(s).replace(/"/g, '\\"')
+              } catch (e) {
+                return String(s)
+              }
+            }
+            const selector = `[data-rowid="${cssEscape(
+              rowKey
+            )}"][data-prop="${cssEscape(prop)}"] input`
+            let el = this.$el && this.$el.querySelector(selector)
+            if (!el) {
+              // 兜底:第一个标红输入
+              el =
+                this.$el &&
+                this.$el.querySelector(
+                  '.input-error input, .input-error .el-input__inner'
+                )
+            }
+            if (el) {
+              el.scrollIntoView({ behavior: 'smooth', block: 'center' })
+              el.focus()
+            }
+          })
+        } catch (e) {
+          // ignore
+        }
+      },
       // 保存数据(调用 saveSingleRecordSurvey 接口)
       async saveData() {
         // 保存前校验
         const validationErrors = this.validateBeforeSave()
-        // if (validationErrors && validationErrors.length > 0) {
-        //   Message.error('表单验证失败,请检查后再保存')
-        //   return false
-        // }
+        if (validationErrors && validationErrors.length > 0) {
+          // 聚焦并提示第一个错误
+          this.focusFirstError()
+          const firstMsg = validationErrors[0]
+          Message.error(firstMsg || '表单验证失败,请检查标红字段后再保存')
+          return false
+        }
         try {
           // 格式化保存数据为接口需要的格式
           const saveData = []
@@ -1754,27 +1905,29 @@
 
         const metaByLabel = new Map()
         const metaByFieldId = new Map()
+        const metaByFieldName = new Map()
+        const metaByName = new Map()
         metaArr.forEach((m) => {
           if (!m) return
-          if (m.label) metaByLabel.set(String(m.label), m)
-          if (m.fieldId) metaByFieldId.set(String(m.fieldId), m)
+          if (m.label) metaByLabel.set(String(m.label).trim(), m)
+          if (m.fieldId) metaByFieldId.set(String(m.fieldId).trim(), m)
+          if (m.fieldName) metaByFieldName.set(String(m.fieldName).trim(), m)
+          if (m.name) metaByName.set(String(m.name).trim(), m)
         })
 
         const getMetaForProp = (prop, column) => {
-          // column.label 优先,其次 fieldId
-          if (column && column.label && metaByLabel.has(String(column.label))) {
-            return metaByLabel.get(String(column.label))
+          const tryKeys = []
+          if (column) {
+            if (column.label) tryKeys.push(String(column.label).trim())
+            if (column.fieldId) tryKeys.push(String(column.fieldId).trim())
           }
-          if (
-            column &&
-            column.fieldId &&
-            metaByFieldId.has(String(column.fieldId))
-          ) {
-            return metaByFieldId.get(String(column.fieldId))
+          if (prop) tryKeys.push(String(prop).trim())
+          for (const k of tryKeys) {
+            if (metaByLabel.has(k)) return metaByLabel.get(k)
+            if (metaByFieldId.has(k)) return metaByFieldId.get(k)
+            if (metaByFieldName.has(k)) return metaByFieldName.get(k)
+            if (metaByName.has(k)) return metaByName.get(k)
           }
-          // 最后尝试用 prop 当作 label 匹配
-          if (prop && metaByLabel.has(String(prop)))
-            return metaByLabel.get(String(prop))
           return null
         }
 
@@ -1800,26 +1953,33 @@
         }
 
         const errors = []
+        const fieldErrMap = {}
         const flat = Array.isArray(this.flattenedData) ? this.flattenedData : []
         flat.forEach((row, rowIndex) => {
-          // 跳过分类行
-          if (
-            row.parentid === '-1' ||
-            row.parentid === -1 ||
-            row.parentId === '-1' ||
-            row.parentId === -1 ||
-            row.isCategory
-          ) {
-            return
-          }
           this.dynamicColumns.forEach((col) => {
             const prop = col.prop
             const value = row[prop]
             const meta = getMetaForProp(prop, col)
             if (!meta) return
 
-            // 必填
-            if (meta.required && isEmpty(value)) {
+            // 必填:兼容 meta.required 以及 meta.isRequired 为 'true'/'1'
+            const requiredFlag = (() => {
+              const r = meta.required
+              const ir = meta.isRequired
+              const toBool = (v) => {
+                if (v === true) return true
+                if (typeof v === 'number') return v === 1
+                if (typeof v === 'string') {
+                  const s = v.trim().toLowerCase()
+                  return s === 'true' || s === '1'
+                }
+                return false
+              }
+              return toBool(r) || toBool(ir)
+            })()
+            if (requiredFlag && isEmpty(value)) {
+              const key = `${row.rowid || row.id}::${prop}`
+              fieldErrMap[key] = `第${rowIndex + 1}行【${col.label}】不能为空`
               errors.push(`第${rowIndex + 1}行【${col.label}】不能为空`)
               return
             }
@@ -1891,6 +2051,22 @@
                   )
                 }
               }
+              // 数字字段:若 format 为纯数字,校验“总数字位数”不超过上限
+              if (/^\d+$/.test(fmt)) {
+                const maxDigits = Number(fmt)
+                const digits = String(value).replace(/[^0-9]/g, '')
+                if (digits.length > maxDigits) {
+                  const key = `${row.rowid || row.id}::${prop}`
+                  fieldErrMap[key] = `第${rowIndex + 1}行【${
+                    col.label
+                  }】长度不能超过${maxDigits}位`
+                  errors.push(
+                    `第${rowIndex + 1}行【${
+                      col.label
+                    }】长度不能超过${maxDigits}位`
+                  )
+                }
+              }
             } else if (t === 'date' || t === 'datetime' || t === 'year') {
               if (t === 'year') {
                 if (!isValidYear(value)) {
@@ -1907,6 +2083,23 @@
                   )
                 }
               }
+            } else {
+              // 非日期/数字:当 meta.format 为纯数字(如 '5')时,按“最大长度”校验
+              if (/^\d+$/.test(fmt)) {
+                const expectLen = Number(fmt)
+                const actualLen = String(value).length
+                if (actualLen > expectLen) {
+                  const key = `${row.rowid || row.id}::${prop}`
+                  fieldErrMap[key] = `第${rowIndex + 1}行【${
+                    col.label
+                  }】长度不能超过${expectLen}位`
+                  errors.push(
+                    `第${rowIndex + 1}行【${
+                      col.label
+                    }】长度不能超过${expectLen}位`
+                  )
+                }
+              }
             }
 
             // 字典/枚举:优先使用 dictCode 的选项校验;否则退回 meta.options
@@ -1952,8 +2145,8 @@
             }
           })
         })
-
         this.validationErrors = errors
+        this.fieldErrors = fieldErrMap
         return errors
       },
       isCategoryItem(item) {
@@ -2277,5 +2470,10 @@
     ::v-deep .el-date-editor {
       width: 100%;
     }
+
+    // 错误高亮
+    ::v-deep .input-error .el-input__inner {
+      border-color: #f56c6c !important;
+    }
   }
 </style>

+ 6 - 1
src/views/EntDeclaration/auditTaskManagement/components/FixedTableDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    title="调查表填报"
+    :title="dialogTitle"
     :visible.sync="dialogVisible"
     width="90%"
     :close-on-click-modal="false"
@@ -326,6 +326,11 @@
         dictData: {},
       }
     },
+    computed: {
+      dialogTitle() {
+        return String(this.requestType) === '2' ? '报送材料填报' : '调查表填报'
+      },
+    },
     watch: {
       visible: {
         async handler(newVal) {

+ 15 - 11
src/views/EntDeclaration/auditTaskManagement/components/SurveyFormDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <el-dialog
-    title="调查表填报"
+    :title="dialogTitle"
     :visible.sync="dialogVisible"
     width="800px"
     :close-on-click-modal="false"
@@ -271,6 +271,9 @@
       }
     },
     computed: {
+      dialogTitle() {
+        return String(this.requestType) === '2' ? '报送材料填报' : '调查表填报'
+      },
       // 计算需要获取的字典类型
       dictTypes() {
         const types = new Set()
@@ -432,12 +435,10 @@
             : '')
         this.$set(this.form, field.prop, (isNeg ? '-' : '') + clipped)
       },
-      // 用于控制红色必填星标:对必填或包含数值规则的字段加红色标记
+      // 用于控制红色必填星标:真正必填字段加红色标记
       isFieldMarkedRequired(field) {
         if (!field) return false
-        if (field.required) return true
-        const kind = this._detectNumericKind(field)
-        return !!kind
+        return !!field.required
       },
       _detectNumericKind(field) {
         const ft = String(
@@ -851,14 +852,16 @@
                   }
                 }
               } else if (numericKind === 'decimal') {
-                // 数字,且小数位不得超过 fieldTypenointlen
-                const num = Number(str)
-                if (Number.isNaN(num)) {
+                // 小数:校验小数位不超过 fieldTypenointlen/decimalLength
+                if (Number.isNaN(Number(str))) {
                   callback(new Error(`${label}必须为数字`))
                   return
                 }
                 if (decimalLength !== undefined && decimalLength !== null) {
-                  const decimals = str.split('.')[1] || ''
+                  const decimals = (str.split('.')[1] || '').replace(
+                    /[^0-9]/g,
+                    ''
+                  )
                   if (decimals.length > decimalLength) {
                     callback(
                       new Error(`${label}小数位不能超过${decimalLength}位`)
@@ -889,9 +892,10 @@
                 callback(new Error(`${label}总位数不能超过${numberTotal}`))
                 return
               }
+              // 小数位校验:保留位数不超过 fieldTypenointlen/decimalLength
               if (decimalLength !== undefined && decimalLength !== null) {
-                const decimals = pure.split('.')[1] || ''
-                if (decimals.length > decimalLength) {
+                const dec = (pure.split('.')[1] || '').replace(/[^0-9]/g, '')
+                if (dec.length > decimalLength) {
                   callback(
                     new Error(`${label}小数位不能超过${decimalLength}位`)
                   )

+ 1 - 3
src/views/EntDeclaration/auditTaskProcessing/index.vue

@@ -181,9 +181,7 @@
             v-if="
               (scope.row.currentNode === 'clcs' ||
                 scope.row.currentNode === 'tjcl') &&
-              (scope.row.status === '100' ||
-                scope.row.status === '500' ||
-                scope.row.status === '600')
+              (scope.row.status === '100' || scope.row.status === '500')
             "
             size="mini"
             type="text"

+ 8 - 0
src/views/costAudit/baseInfo/catalogManage/index.vue

@@ -1065,6 +1065,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         selectPolicyFilesData: [],
         selectedDocFiles: [],
@@ -1072,6 +1073,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         selectPolicySearchForm: {
           title: '',
@@ -1095,6 +1097,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         selectLegalBasis: [],
         legalDialogVisible: false,
@@ -1115,6 +1118,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         templateColumns: [
           {
@@ -1193,6 +1197,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         costSurveyColumns: [
           {
@@ -1236,6 +1241,7 @@
           currentPage: 1,
           pageSize: 50,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         },
         // 内容编辑弹窗相关
         contentEditDialogVisible: false,
@@ -1948,6 +1954,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         }),
           this.getSelectPolicyFilesData()
       },
@@ -1961,6 +1968,7 @@
           currentPage: 1,
           pageSize: 10,
           total: 0,
+          pageSizes: [10, 20, 50, 100],
         }
         this.getSelectPolicyFilesData()
       },