|
|
@@ -232,6 +232,7 @@
|
|
|
getSurveyDetail,
|
|
|
getDynamicTableData,
|
|
|
} from '@/api/audit/survey'
|
|
|
+ import { getListBySurveyTemplateIdAndVersion } from '@/api/costSurveyTemplateHeaders'
|
|
|
|
|
|
export default {
|
|
|
name: 'CostSurveyTab',
|
|
|
@@ -458,7 +459,7 @@
|
|
|
},
|
|
|
// 初始化表单字段配置
|
|
|
async initFormFields() {
|
|
|
- // 如果是单记录类型,调用接口获取表单字段配置
|
|
|
+ // 如果是单记录类型,调用接口获取表单字段配置(包含校验规则)
|
|
|
if (
|
|
|
this.currentSurveyRow &&
|
|
|
this.currentSurveyRow.tableType === '单记录' &&
|
|
|
@@ -468,35 +469,53 @@
|
|
|
const params = {
|
|
|
surveyTemplateId: this.currentSurveyRow.surveyTemplateId,
|
|
|
}
|
|
|
- const res = await getSingleRecordSurveyList(params)
|
|
|
- console.log('单记录表单字段配置', res)
|
|
|
- if (res && res.code === 200 && res.value) {
|
|
|
- // 将接口返回的数据转换为表单字段配置格式
|
|
|
- const { fixedFields, fixedFieldids } = res.value
|
|
|
-
|
|
|
- if (fixedFields && fixedFieldids) {
|
|
|
- // 将 fixedFields 和 fixedFieldids 按逗号分割
|
|
|
- const labels = fixedFields.split(',').map((item) => item.trim())
|
|
|
- const ids = fixedFieldids.split(',').map((item) => item.trim())
|
|
|
- console.log('labels', labels)
|
|
|
- console.log('ids', ids)
|
|
|
+ // 调用 getListBySurveyTemplateIdAndVersion 获取完整的字段配置(包含校验规则)
|
|
|
+ const res = await getListBySurveyTemplateIdAndVersion(params)
|
|
|
+ console.log('单记录表单字段配置(含校验规则)', res)
|
|
|
+ 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') {
|
|
|
+ // 对象格式:从 fixedFields 和 fixedFieldids 解析(兼容旧格式)
|
|
|
+ const { fixedFields, fixedFieldids } = res.value
|
|
|
+ if (fixedFields && fixedFieldids) {
|
|
|
+ const labels = fixedFields
|
|
|
+ .split(',')
|
|
|
+ .map((item) => item.trim())
|
|
|
+ const ids = fixedFieldids
|
|
|
+ .split(',')
|
|
|
+ .map((item) => item.trim())
|
|
|
+ mapped = labels.map((label, index) => ({
|
|
|
+ prop: ids[index] || `field_${index}`,
|
|
|
+ label: label,
|
|
|
+ type: 'input',
|
|
|
+ colSpan: 12,
|
|
|
+ placeholder: `请输入${label}`,
|
|
|
+ rules: [],
|
|
|
+ defaultValue: '',
|
|
|
+ disabled: false,
|
|
|
+ clearable: true,
|
|
|
+ multiple: false,
|
|
|
+ required: false,
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 组合成表单字段配置数组
|
|
|
- this.formFields = labels.map((label, index) => ({
|
|
|
- prop: ids[index] || `field_${index}`, // 使用 fixedFieldids 作为 prop
|
|
|
- label: label, // 使用 fixedFields 作为 label
|
|
|
- type: 'input', // 默认类型为 input
|
|
|
- colSpan: 12, // 默认占一半宽度
|
|
|
- placeholder: `请输入${label}`,
|
|
|
- rules: [],
|
|
|
- defaultValue: '',
|
|
|
- disabled: false,
|
|
|
- clearable: true,
|
|
|
- multiple: false,
|
|
|
- required: false,
|
|
|
- }))
|
|
|
+ if (mapped.length > 0) {
|
|
|
+ // 使用包含完整校验规则的字段配置
|
|
|
+ this.formFields = mapped
|
|
|
+ console.log(
|
|
|
+ '转换后的表单字段配置(含校验规则)',
|
|
|
+ this.formFields
|
|
|
+ )
|
|
|
} else {
|
|
|
- // 如果没有 fixedFields 和 fixedFieldids,使用默认配置
|
|
|
+ // 如果没有数据,使用默认配置
|
|
|
this.formFields = this.getMockFormFields()
|
|
|
}
|
|
|
} else {
|
|
|
@@ -521,6 +540,317 @@
|
|
|
this.surveyFormDialogVisible = true
|
|
|
}
|
|
|
},
|
|
|
+ // 将 API 返回的字段数据映射为表单字段配置(包含校验规则)
|
|
|
+ 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, // 包含完整的校验规则
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 从 format 中提取长度
|
|
|
+ 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
|
|
|
+ },
|
|
|
// 获取假数据表单字段配置(用于测试)
|
|
|
getMockFormFields() {
|
|
|
return [
|