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

fix: 成本调查表单记录格式校验完善

shiyanyu 1 месяц назад
Родитель
Сommit
e9af902b47
1 измененных файлов с 406 добавлено и 18 удалено
  1. 406 18
      src/views/EntDeclaration/auditTaskManagement/components/SurveyFormDialog.vue

+ 406 - 18
src/views/EntDeclaration/auditTaskManagement/components/SurveyFormDialog.vue

@@ -13,7 +13,7 @@
       <el-row :gutter="20">
       <el-row :gutter="20">
         <!-- 动态生成表单字段 -->
         <!-- 动态生成表单字段 -->
         <el-col
         <el-col
-          v-for="(field, index) in formFields"
+          v-for="(field, index) in effectiveFormFields"
           :key="field.prop || field.id || `field-${index}`"
           :key="field.prop || field.id || `field-${index}`"
           :span="field.colSpan || 12"
           :span="field.colSpan || 12"
         >
         >
@@ -44,7 +44,9 @@
 
 
             <!-- 下拉选择框(字典类型) -->
             <!-- 下拉选择框(字典类型) -->
             <el-select
             <el-select
-              v-else-if="field.type === 'select' && field.dictType"
+              v-else-if="
+                field.type === 'select' && (field.dictCode || field.dictType)
+              "
               v-model="form[field.prop]"
               v-model="form[field.prop]"
               :placeholder="field.placeholder || `请选择${field.label}`"
               :placeholder="field.placeholder || `请选择${field.label}`"
               :disabled="field.disabled || isViewMode"
               :disabled="field.disabled || isViewMode"
@@ -53,7 +55,7 @@
               :multiple="field.multiple"
               :multiple="field.multiple"
             >
             >
               <el-option
               <el-option
-                v-for="item in getDictOptions(field.dictType)"
+                v-for="item in getDictOptions(field.dictCode || field.dictType)"
                 :key="item.key || item.value"
                 :key="item.key || item.value"
                 :label="item.name || item.label"
                 :label="item.name || item.label"
                 :value="item.key || item.value"
                 :value="item.key || item.value"
@@ -139,6 +141,7 @@
   import { Message } from 'element-ui'
   import { Message } from 'element-ui'
   import { dictMixin } from '@/mixins/useDict'
   import { dictMixin } from '@/mixins/useDict'
   import { saveSingleRecordSurvey } from '@/api/audit/survey'
   import { saveSingleRecordSurvey } from '@/api/audit/survey'
+  import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
 
 
   export default {
   export default {
     name: 'SurveyFormDialog',
     name: 'SurveyFormDialog',
@@ -205,25 +208,37 @@
         form: {},
         form: {},
         rules: {},
         rules: {},
         dictData: {}, // 初始化字典数据对象
         dictData: {}, // 初始化字典数据对象
+        internalFormFields: [],
       }
       }
     },
     },
     computed: {
     computed: {
       // 计算需要获取的字典类型
       // 计算需要获取的字典类型
       dictTypes() {
       dictTypes() {
         const types = new Set()
         const types = new Set()
-        this.formFields.forEach((field) => {
-          if (field.type === 'select' && field.dictType) {
-            types.add(field.dictType)
+        this.effectiveFormFields.forEach((field) => {
+          const dictKey = field.dictCode || field.dictType
+          if (field.type === 'select' && dictKey) {
+            types.add(dictKey)
           }
           }
         })
         })
         return Array.from(types)
         return Array.from(types)
       },
       },
+      effectiveFormFields() {
+        if (this.internalFormFields && this.internalFormFields.length > 0) {
+          return this.internalFormFields
+        }
+        if (Array.isArray(this.formFields) && this.formFields.length > 0) {
+          return this.formFields
+        }
+        return this.getDefaultFormFields()
+      },
     },
     },
     watch: {
     watch: {
       visible: {
       visible: {
-        handler(newVal) {
+        async handler(newVal) {
           this.dialogVisible = newVal
           this.dialogVisible = newVal
           if (newVal) {
           if (newVal) {
+            await this.ensureTemplateFields()
             // 弹窗打开时初始化表单
             // 弹窗打开时初始化表单
             this.initForm()
             this.initForm()
           }
           }
@@ -236,7 +251,10 @@
         }
         }
       },
       },
       formFields: {
       formFields: {
-        handler() {
+        handler(newVal) {
+          if (Array.isArray(newVal) && newVal.length > 0) {
+            this.internalFormFields = []
+          }
           // 字段配置变化时重新初始化
           // 字段配置变化时重新初始化
           if (this.dialogVisible) {
           if (this.dialogVisible) {
             this.initForm()
             this.initForm()
@@ -245,7 +263,8 @@
         deep: true,
         deep: true,
       },
       },
       surveyData: {
       surveyData: {
-        handler() {
+        async handler() {
+          await this.ensureTemplateFields()
           // 详情数据变化时重新初始化表单(用于回显数据)
           // 详情数据变化时重新初始化表单(用于回显数据)
           if (this.dialogVisible) {
           if (this.dialogVisible) {
             this.initForm()
             this.initForm()
@@ -253,12 +272,381 @@
         },
         },
         deep: true,
         deep: true,
       },
       },
+      surveyTemplateId: {
+        async handler() {
+          await this.ensureTemplateFields()
+          if (this.dialogVisible) {
+            this.initForm()
+          }
+        },
+      },
     },
     },
-    created() {
+    async created() {
+      await this.ensureTemplateFields()
       // 初始化字典数据
       // 初始化字典数据
       this.initDictData()
       this.initDictData()
     },
     },
     methods: {
     methods: {
+      async ensureTemplateFields() {
+        const hasExternalFields =
+          Array.isArray(this.formFields) && this.formFields.length > 0
+        const templateId =
+          this.surveyTemplateId ||
+          this.surveyData.surveyTemplateId ||
+          this.surveyData.surveyId ||
+          ''
+        if (!templateId) {
+          return
+        }
+        this.internalFormFields = []
+        try {
+          const params = {
+            surveyTemplateId: templateId,
+          }
+          const res = await getListBySurveyTemplateIdAndVersion(params)
+          if (res && res.code === 200) {
+            let mapped = []
+            if (Array.isArray(res.value)) {
+              mapped = res.value
+                .map((item, index) => this.mapApiFieldToFormField(item, index))
+                .filter(Boolean)
+            } else if (res.value && typeof res.value === 'object') {
+              const { fixedFields, fixedFieldids } = res.value
+              if (fixedFields && fixedFieldids) {
+                const labels = String(fixedFields)
+                  .split(',')
+                  .map((item) => item.trim())
+                  .filter(Boolean)
+                const ids = String(fixedFieldids)
+                  .split(',')
+                  .map((item) => item.trim())
+                  .filter(Boolean)
+                mapped = labels.map((label, index) => ({
+                  prop: ids[index] || `field_${index}`,
+                  label,
+                  type: 'input',
+                  colSpan: 12,
+                  placeholder: `请输入${label}`,
+                  required: false,
+                }))
+              }
+            }
+            if (!hasExternalFields && mapped.length > 0) {
+              this.internalFormFields = mapped
+            }
+          }
+        } catch (error) {
+          console.error('获取调查表字段失败:', error)
+        }
+      },
+      mapApiFieldToFormField(item, index = 0) {
+        if (!item) return null
+        const getVal = (keys, fallback) => {
+          for (const key of keys) {
+            if (
+              key &&
+              item[key] !== undefined &&
+              item[key] !== null &&
+              item[key] !== ''
+            ) {
+              return item[key]
+            }
+          }
+          return fallback
+        }
+        const toBool = (value) => {
+          if (value === undefined || value === null) return false
+          if (typeof value === 'boolean') return value
+          if (typeof value === 'number') return value === 1
+          const str = String(value).trim().toLowerCase()
+          return ['1', 'true', 'y', 'yes', '是'].includes(str)
+        }
+        const toNumber = (value) => {
+          if (value === undefined || value === null || value === '')
+            return undefined
+          const num = Number(value)
+          return Number.isNaN(num) ? undefined : num
+        }
+        const prop =
+          getVal(
+            [
+              'fieldName',
+              'field_name',
+              'columnName',
+              'column_name',
+              'fieldCode',
+            ],
+            undefined
+          ) || `field_${index}`
+        const label =
+          getVal(
+            [
+              'columnComment',
+              'column_comment',
+              'fieldCname',
+              'field_cname',
+              'fieldLabel',
+              'field_label',
+            ],
+            prop
+          ) || prop
+        const columnType =
+          (getVal(
+            ['columnType', 'column_type', 'fieldType', 'field_type'],
+            ''
+          ) || '') + ''
+        const columnTypeLower = columnType.toLowerCase()
+        const totalLength = toNumber(
+          getVal(
+            ['fieldTypeLen', 'field_typelen', 'length', 'fieldLength'],
+            undefined
+          )
+        )
+        const decimalLength = toNumber(
+          getVal(
+            ['fieldTypeNointLen', 'field_typenointlen', 'scale'],
+            undefined
+          )
+        )
+        const isAuditPeriod = toBool(
+          getVal(['isAuditPeriod', 'is_audit_period'], false)
+        )
+        const dictCode =
+          getVal(
+            [
+              'dictCode',
+              'dict_code',
+              'dictId',
+              'dictid',
+              'dictType',
+              'dict_type',
+            ],
+            ''
+          ) || ''
+        const optionsRaw = getVal(['options'], [])
+        let options = []
+        if (Array.isArray(optionsRaw)) {
+          options = optionsRaw
+        } else if (typeof optionsRaw === 'string' && optionsRaw.trim() !== '') {
+          options = optionsRaw.split(',').map((value) => ({
+            label: value.trim(),
+            value: value.trim(),
+          }))
+        }
+        let type = getVal(['componentType', 'type'], '')
+        if (!type) {
+          if (dictCode || options.length > 0) {
+            type = 'select'
+          } else if (
+            columnTypeLower.includes('datetime') ||
+            columnTypeLower.includes('timestamp') ||
+            columnTypeLower.includes('date time')
+          ) {
+            type = 'datetime'
+          } else if (columnTypeLower.includes('date')) {
+            type = 'date'
+          } else if (columnTypeLower.includes('year')) {
+            type = 'year'
+          } else if (
+            columnTypeLower.includes('int') ||
+            columnTypeLower.includes('number') ||
+            columnTypeLower.includes('decimal') ||
+            columnTypeLower.includes('float') ||
+            columnTypeLower.includes('double')
+          ) {
+            type = 'number'
+          } else {
+            type = 'input'
+          }
+        }
+        const required = toBool(
+          getVal(['isRequired', 'is_required', 'required'], false)
+        )
+        const multiple = toBool(
+          getVal(['isMultiple', 'is_multiple', 'multiple'], false)
+        )
+        const colSpan =
+          toNumber(
+            getVal(['colSpan', 'colspan', 'columnSpan', 'column_span'], 12)
+          ) || 12
+        const placeholder =
+          getVal(
+            ['placeholder', 'columnComment', 'column_comment'],
+            undefined
+          ) || (type === 'select' ? `请选择${label}` : `请输入${label}`)
+        const defaultValue = getVal(
+          ['defaultValue', 'default_value', 'defaultVal', 'default_val'],
+          undefined
+        )
+        const precision = toNumber(
+          getVal(
+            ['fieldTypeNointLen', 'field_typenointlen', 'precision'],
+            undefined
+          )
+        )
+        const min = toNumber(getVal(['min'], undefined))
+        const max = toNumber(getVal(['max'], undefined))
+        const format = getVal(['format'], undefined)
+        const valueFormat =
+          getVal(['valueFormat', 'value_format'], undefined) ||
+          (type === 'datetime'
+            ? 'yyyy-MM-dd HH:mm:ss'
+            : type === 'date'
+            ? 'yyyy-MM-dd'
+            : type === 'year'
+            ? 'yyyy'
+            : undefined)
+
+        const formatLength = this.extractLengthFromFormat(format)
+        const rules = this.buildFieldRules({
+          type,
+          label,
+          required,
+          totalLength,
+          decimalLength,
+          formatLength,
+          format,
+          isAuditPeriod,
+        })
+
+        return {
+          prop,
+          label,
+          type,
+          colSpan,
+          placeholder,
+          dictCode,
+          dictType: dictCode,
+          options,
+          required,
+          defaultValue,
+          multiple,
+          precision,
+          min,
+          max,
+          format,
+          valueFormat,
+          totalLength,
+          decimalLength,
+          formatLength,
+          rules,
+        }
+      },
+      extractLengthFromFormat(format) {
+        if (!format) return undefined
+        const str = String(format).trim()
+        if (!str) return undefined
+        const match = str.match(/\d+/)
+        if (match && match[0]) {
+          const len = Number(match[0])
+          return Number.isNaN(len) ? undefined : len
+        }
+        return undefined
+      },
+      buildFieldRules(meta) {
+        const {
+          type,
+          label,
+          required,
+          totalLength,
+          decimalLength,
+          formatLength,
+          format,
+          isAuditPeriod,
+        } = meta || {}
+        const rules = []
+        const trigger = type === 'select' ? 'change' : 'blur'
+        if (required) {
+          rules.push({
+            required: true,
+            message: `${type === 'select' ? '请选择' : '请输入'}${label}`,
+            trigger,
+          })
+        }
+        const inputMaxLength = formatLength || totalLength
+        if (type === 'input' && inputMaxLength) {
+          rules.push({
+            validator: (_, value, callback) => {
+              if (value === undefined || value === null || value === '') {
+                callback()
+                return
+              }
+              const str = String(value)
+              if (str.length > inputMaxLength) {
+                callback(
+                  new Error(`${label}长度不能超过${inputMaxLength}个字符`)
+                )
+              } else {
+                callback()
+              }
+            },
+            trigger: 'blur',
+          })
+        }
+        const numberTotal = totalLength || formatLength
+        if (type === 'number') {
+          rules.push({
+            validator: (_, value, callback) => {
+              if (value === undefined || value === null || value === '') {
+                callback()
+                return
+              }
+              if (Number.isNaN(Number(value))) {
+                callback(new Error(`${label}必须为数字`))
+                return
+              }
+              const pure = String(value).replace('-', '')
+              if (numberTotal && pure.replace('.', '').length > numberTotal) {
+                callback(new Error(`${label}总位数不能超过${numberTotal}`))
+                return
+              }
+              if (decimalLength !== undefined && decimalLength !== null) {
+                const decimals = pure.split('.')[1] || ''
+                if (decimals.length > decimalLength) {
+                  callback(
+                    new Error(`${label}小数位不能超过${decimalLength}位`)
+                  )
+                  return
+                }
+              }
+              callback()
+            },
+            trigger: 'blur',
+          })
+        }
+        if (type === 'datetime' || type === 'date') {
+          if (format) {
+            rules.push({
+              validator: (_, value, callback) => {
+                if (value === undefined || value === null || value === '') {
+                  callback()
+                  return
+                }
+                callback()
+              },
+              trigger: 'change',
+            })
+          }
+        }
+        if (type === 'year' || isAuditPeriod) {
+          rules.push({
+            validator: (_, value, callback) => {
+              if (value === undefined || value === null || value === '') {
+                callback()
+                return
+              }
+              const pattern = /^\d{4}$/
+              if (!pattern.test(String(value))) {
+                callback(new Error(`${label}必须是四位年份`))
+              } else {
+                callback()
+              }
+            },
+            trigger: 'change',
+          })
+        }
+        return rules
+      },
       // 初始化字典数据
       // 初始化字典数据
       initDictData() {
       initDictData() {
         if (this.dictTypes.length > 0) {
         if (this.dictTypes.length > 0) {
@@ -276,7 +664,7 @@
       },
       },
       // 获取字典选项
       // 获取字典选项
       getDictOptions(dictType) {
       getDictOptions(dictType) {
-        if (!this.dictData || !this.dictData[dictType]) {
+        if (!dictType || !this.dictData || !this.dictData[dictType]) {
           return []
           return []
         }
         }
         return this.dictData[dictType] || []
         return this.dictData[dictType] || []
@@ -286,11 +674,7 @@
         const form = {}
         const form = {}
         const rules = {}
         const rules = {}
 
 
-        // 如果没有传入字段配置,使用默认配置
-        const fields =
-          this.formFields && this.formFields.length > 0
-            ? this.formFields
-            : this.getDefaultFormFields()
+        const fields = this.effectiveFormFields
 
 
         fields.forEach((field) => {
         fields.forEach((field) => {
           // 初始化表单值
           // 初始化表单值
@@ -312,7 +696,11 @@
           }
           }
 
 
           // 初始化验证规则
           // 初始化验证规则
-          if (field.rules && Array.isArray(field.rules)) {
+          if (
+            field.rules &&
+            Array.isArray(field.rules) &&
+            field.rules.length > 0
+          ) {
             rules[field.prop] = field.rules
             rules[field.prop] = field.rules
           } else if (field.required) {
           } else if (field.required) {
             // 如果字段标记为必填,自动添加必填验证
             // 如果字段标记为必填,自动添加必填验证
@@ -438,7 +826,7 @@
               )
               )
 
 
               // 将表单数据转换为接口需要的格式
               // 将表单数据转换为接口需要的格式
-              const saveData = this.formFields.map((field) => {
+              const saveData = this.effectiveFormFields.map((field) => {
                 const dataItem = {
                 const dataItem = {
                   auditedUnitId:
                   auditedUnitId:
                     this.auditedUnitId || this.surveyData.auditedUnitId || '',
                     this.auditedUnitId || this.surveyData.auditedUnitId || '',