Преглед изворни кода

Merge branch 'master' of http://1.71.9.215:3000/feiyi/cbjsxt-front-master

suhp пре 1 месец
родитељ
комит
9517db2c2b

+ 16 - 4
src/views/EntDeclaration/auditTaskManagement/components/DynamicTableDialog.vue

@@ -200,6 +200,8 @@
         </div> -->
         <!-- 固定资产表格 -->
         <fixed-assets-table
+          v-if="detailDialogVisible"
+          :key="tableInstanceKey"
           ref="fixedAssetsTable"
           :table-items="localTableItems"
           :saved-data="resolvedFixedAssetsSavedData"
@@ -358,6 +360,8 @@
         fixedFields: '',
         fixedFieldids: '',
         columnsMeta: [],
+        // 用于强制重建子组件实例,避免未保存数据残留
+        tableInstanceKey: 0,
       }
     },
     computed: {
@@ -399,6 +403,14 @@
           this.$emit('update:visible', false)
         }
       },
+      // 详情弹窗开关:关闭时重置子组件相关数据,避免缓存
+      detailDialogVisible(newVal) {
+        if (newVal) return
+        // 关闭时清空传给子组件的数据,避免二次打开沿用
+        this.columnsMeta = []
+        this.detailSavedData = null
+        this.localTableItems = []
+      },
       tableData: {
         handler(newVal) {
           this.loadDynamicTableData()
@@ -901,6 +913,7 @@
         this.isEditMode = false
         this.detailDialogTitle = '详情'
         this.detailDialogVisible = true
+        this.tableInstanceKey += 1
         // 清空后由接口回显,否则使用行内 data 兜底
         this.detailSavedData = null
         this.loadSingleRecordSurveyList()
@@ -911,6 +924,7 @@
         this.isEditMode = true
         this.detailDialogTitle = '编辑'
         this.detailDialogVisible = true
+        this.tableInstanceKey += 1
         this.detailSavedData = null
         this.loadSingleRecordSurveyList()
       },
@@ -1122,10 +1136,8 @@
                   : headerRes.value && Array.isArray(headerRes.value.items)
                   ? headerRes.value.items
                   : []
-                const meta = fieldsArr
-                  .map((it) => this.mapHeaderFieldToColumnMeta(it))
-                  .filter(Boolean)
-                this.columnsMeta = meta
+                // 直接把接口返回的字段元数据完整传给子组件
+                this.columnsMeta = fieldsArr
               } else {
                 this.columnsMeta = []
               }

+ 294 - 45
src/views/EntDeclaration/auditTaskManagement/components/FixedAssetsTable.vue

@@ -33,8 +33,8 @@
             type="date"
             placeholder="选择日期"
             size="mini"
-            format="yyyy-MM-dd"
-            value-format="yyyy-MM-dd"
+            :format="column.format || 'yyyy-MM-dd'"
+            :value-format="column.valueFormat || 'yyyy-MM-dd'"
             style="width: 100%"
             :disabled="isViewMode"
           />
@@ -44,8 +44,8 @@
             type="datetime"
             placeholder="选择日期时间"
             size="mini"
-            format="yyyy-MM-dd HH:mm:ss"
-            value-format="yyyy-MM-dd HH:mm:ss"
+            :format="column.format || 'yyyy-MM-dd HH:mm:ss'"
+            :value-format="column.valueFormat || 'yyyy-MM-dd HH:mm:ss'"
             style="width: 100%"
             :disabled="isViewMode"
           />
@@ -55,11 +55,31 @@
             type="year"
             placeholder="选择年份"
             size="mini"
-            format="yyyy"
-            value-format="yyyy"
+            :format="column.format || 'yyyy'"
+            :value-format="column.valueFormat || 'yyyy'"
             style="width: 100%"
             :disabled="isViewMode"
           />
+          <!-- 字典下拉,与 SurveyFormDialog 一致写法 -->
+          <el-select
+            v-else-if="
+              column.type === 'select' && (column.dictCode || column.dictType)
+            "
+            v-model="scope.row[column.prop]"
+            :placeholder="column.placeholder || '请选择' + column.label"
+            size="mini"
+            style="width: 100%"
+            :disabled="isViewMode"
+            :clearable="true"
+          >
+            <el-option
+              v-for="item in getDictOptions(column.dictCode || column.dictType)"
+              :key="item.key || item.value"
+              :label="item.name || item.label"
+              :value="item.key || item.value"
+            />
+          </el-select>
+          <!-- 自定义选项下拉 -->
           <el-select
             v-else-if="
               column.type === 'select' && Array.isArray(column.options)
@@ -69,19 +89,13 @@
             size="mini"
             style="width: 100%"
             :disabled="isViewMode"
-            clearable
+            :clearable="true"
           >
             <el-option
               v-for="opt in column.options"
-              :key="
-                opt &&
-                (opt.value != null ? String(opt.value) : String(opt.label))
-              "
-              :label="opt && (opt.label != null ? opt.label : opt.value)"
-              :value="
-                opt &&
-                (opt.value != null ? String(opt.value) : String(opt.label))
-              "
+              :key="opt.value || opt.key"
+              :label="opt.label || opt.name"
+              :value="opt.value || opt.key"
             />
           </el-select>
           <el-switch
@@ -175,9 +189,11 @@
 <script>
   import { Message } from 'element-ui'
   import { saveSingleRecordSurvey } from '@/api/audit/survey'
+  import { dictMixin } from '@/mixins/useDict'
 
   export default {
     name: 'FixedAssetsTable',
+    mixins: [dictMixin],
     props: {
       // 表格数据配置(嵌套结构)
       tableItems: {
@@ -250,6 +266,8 @@
         autoIdSeed: 1,
         // 原始保存的数据(用于回显)
         rawSavedData: null,
+        // 字典数据容器(用于 dictMixin 拉取字典)
+        dictData: {},
       }
     },
     computed: {
@@ -298,11 +316,37 @@
         const metas = Array.isArray(this.columnsMeta) ? this.columnsMeta : []
         const byLabel = new Map()
         const byId = new Map()
+        const byFieldName = new Map()
+        const byName = new Map()
         metas.forEach((m) => {
-          if (m && m.label) byLabel.set(String(m.label), m)
-          if (m && m.fieldId) byId.set(String(m.fieldId), m)
+          if (!m) return
+          if (m.label) byLabel.set(String(m.label).trim(), m)
+          if (m.fieldId) byId.set(String(m.fieldId).trim(), m)
+          if (m.fieldName) byFieldName.set(String(m.fieldName).trim(), m)
+          if (m.name) byName.set(String(m.name).trim(), m)
         })
         const normalizeType = (meta, fallback) => {
+          // 优先根据 format 判定日期类型
+          const formatStr =
+            meta && meta.format ? String(meta.format).trim() : ''
+          if (formatStr) {
+            const f = formatStr.toLowerCase()
+            if (/(hh|hh:mm|hh:mm:ss)/i.test(formatStr) || f.includes('hh')) {
+              return 'datetime'
+            }
+            if (/^y{4}$/i.test(formatStr)) {
+              return 'year'
+            }
+            if (
+              (/y{4}/i.test(formatStr) && /m{2}/i.test(formatStr)) ||
+              f.includes('yyyy-mm') ||
+              f.includes('yyyy-mm-dd')
+            ) {
+              return 'date'
+            }
+          }
+
+          // 其次根据后端类型字段判定
           const t = (
             meta && (meta.type || meta.fieldType)
               ? String(meta.type || meta.fieldType)
@@ -325,34 +369,159 @@
           if (t.includes('select')) return 'select'
           return fallback
         }
+        const deriveDateFormats = (meta, type) => {
+          const res = { format: undefined, valueFormat: undefined }
+          const fmt = meta && meta.format ? String(meta.format).trim() : ''
+          if (type === 'year') {
+            res.format = fmt && /^y{4}$/i.test(fmt) ? fmt : 'yyyy'
+            res.valueFormat = res.format
+          } else if (type === 'datetime') {
+            const def = 'yyyy-MM-dd HH:mm:ss'
+            res.format = fmt && /(H|h)/.test(fmt) ? fmt : def
+            res.valueFormat = res.format
+          } else if (type === 'date') {
+            const def = 'yyyy-MM-dd'
+            res.format = fmt && /y{2,}[-/ ]?m{2}/i.test(fmt) ? fmt : def
+            res.valueFormat = res.format
+          }
+          return res
+        }
+        const pickNumberLengths = (meta, tLower) => {
+          // 明确优先级:整数位用 fieldTypelen, 小数位用 fieldTypenointlen
+          const totalLen =
+            meta.fieldTypelen ??
+            meta.fieldTypeLen ??
+            meta.field_typelen ??
+            meta.totalLength ??
+            meta.length ??
+            meta.fieldLength
+          const decimalLen =
+            meta.fieldTypenointlen ??
+            meta.fieldTypeNointLen ??
+            meta.field_typenointlen ??
+            meta.decimalLength ??
+            meta.scale
+          // 若后端标明 integer 或整型,强制小数位为0
+          const t = String(tLower || '').toLowerCase()
+          const dec = t.includes('int') || t === 'integer' ? 0 : decimalLen
+          return {
+            totalLen: totalLen !== undefined ? Number(totalLen) : undefined,
+            decimalLen: dec !== undefined ? Number(dec) : undefined,
+          }
+        }
         return cols.map((col) => {
-          const meta =
-            (col.label && byLabel.get(String(col.label))) ||
-            (col.fieldId && byId.get(String(col.fieldId))) ||
+          // 更宽松的 meta 匹配:label/fieldName/name/fieldId
+          const labelKey = col.label ? String(col.label).trim() : ''
+          const idKey = col.fieldId ? String(col.fieldId).trim() : ''
+          let meta =
+            (labelKey &&
+              (byLabel.get(labelKey) ||
+                byFieldName.get(labelKey) ||
+                byName.get(labelKey))) ||
+            (idKey && byId.get(idKey)) ||
             null
           if (meta) {
-            col.type = normalizeType(meta, col.type || 'input')
-            if (Array.isArray(meta.options) && meta.options.length) {
+            // 仅按是否存在字典编码决定是否为下拉(忽略 fieldType)
+            const codeFromMeta =
+              (meta.dictCode && String(meta.dictCode).trim()) ||
+              (meta.dictType && String(meta.dictType).trim()) ||
+              (meta.typeKey && String(meta.typeKey).trim()) ||
+              (meta.dictId && String(meta.dictId).trim()) ||
+              (meta.dictid && String(meta.dictid).trim()) ||
+              ''
+
+            const codeFromCol =
+              (col.dictCode && String(col.dictCode).trim()) ||
+              (col.dictType && String(col.dictType).trim()) ||
+              ''
+
+            const dictCode = codeFromMeta || codeFromCol
+
+            if (dictCode) {
+              // 有字典编码:强制下拉
+              col.type = 'select'
+              col.dictCode = dictCode
+              col.dictType = dictCode
+              const dictOpts = this.getDictOptions(dictCode) || []
+              col.options = (dictOpts || []).map((d) => ({
+                key: d.key != null ? d.key : d.value,
+                name: d.name != null ? d.name : d.label,
+                value: d.value,
+                label: d.label,
+              }))
+              if (!Array.isArray(col.options)) col.options = []
+            } else if (Array.isArray(meta.options) && meta.options.length) {
+              // 无字典编码但给了 options:也下拉
+              col.type = 'select'
               col.options = meta.options
+            } else {
+              // 非字典:按原规则回退(仅用于非字典字段)
+              col.type = normalizeType(meta, col.type || 'input')
+            }
+            // 若为日期类,按 meta.format 派生 format/valueFormat,供模板绑定
+            if (
+              col.type === 'date' ||
+              col.type === 'datetime' ||
+              col.type === 'year'
+            ) {
+              const f = deriveDateFormats(meta, col.type)
+              if (f.format) col.format = f.format
+              if (f.valueFormat) col.valueFormat = f.valueFormat
             }
-            col.totalLength = meta.totalLength
-            // 若为整数类型,强制小数位为0
+            // 元数据上的长度规则(兼容多种命名)
             const rawType = String(
               meta.type || meta.fieldType || ''
             ).toLowerCase()
-            if (rawType.includes('int') || rawType === 'integer') {
-              col.decimalLength = 0
-            } else {
-              col.decimalLength = meta.decimalLength
-            }
+            const lens = pickNumberLengths(meta, rawType)
+            col.totalLength = lens.totalLen
+            col.decimalLength = lens.decimalLen
+          } else if (
+            labelKey &&
+            (labelKey === '字典' || labelKey.includes('字典'))
+          ) {
+            // 兜底:标题包含“字典”的列一律渲染为下拉
+            col.type = 'select'
+            // 确保为数组,避免模板条件不触发
+            if (!Array.isArray(col.options)) col.options = []
           }
           return col
         })
       },
     },
     watch: {
+      // 当字段元数据到达/变化时,预置需要的字典键,让 dictMixin 能拉取对应的字典
+      columnsMeta: {
+        handler(newVal) {
+          console.log(newVal)
+          const metas = Array.isArray(newVal) ? newVal : []
+          const codes = new Set()
+          metas.forEach((m) => {
+            if (!m) return
+            const code =
+              (m.dictCode && String(m.dictCode).trim()) ||
+              (m.dictType && String(m.dictType).trim()) ||
+              (m.typeKey && String(m.typeKey).trim()) ||
+              (m.dictId && String(m.dictId).trim()) ||
+              (m.dictid && String(m.dictid).trim()) ||
+              ''
+            if (code) codes.add(code)
+          })
+          // 初始化 dictData 的键,供 mixin 的 getDictType 使用
+          if (!this.dictData) this.dictData = {}
+          codes.forEach((k) => {
+            if (!this.dictData[k]) this.$set(this.dictData, k, [])
+          })
+          // 触发批量获取
+          if (codes.size > 0 && typeof this.getDictType === 'function') {
+            this.getDictType()
+          }
+        },
+        immediate: true,
+        deep: true,
+      },
       tableItems: {
         handler(newVal) {
+          console.log('表头:', newVal)
           // 仅根据列定义刷新视图,不修改数据来源(数据仅来自父组件传入的 savedData)
           if (
             Array.isArray(this.fixedAssetsData) &&
@@ -419,6 +588,13 @@
       },
     },
     methods: {
+      // 获取字典选项(兼容本组件与 dictMixin)
+      getDictOptions(dictType) {
+        if (!dictType || !this.dictData) return []
+        const key = String(dictType)
+        const arr = this.dictData[key]
+        return Array.isArray(arr) ? arr : []
+      },
       sanitizeNumberInput(row, column) {
         const prop = column && column.prop
         if (!prop) return
@@ -1601,6 +1777,7 @@
         const isValidDate = (v) =>
           typeof v === 'string' &&
           /^\d{4}-\d{2}-\d{2}(?:\s+\d{2}:\d{2}(:\d{2})?)?$/.test(v)
+        const isValidYear = (v) => typeof v === 'string' && /^\d{4}$/.test(v)
         const isIntegerWithLen = (v, len) => {
           if (typeof v !== 'string') v = v == null ? '' : String(v)
           if (!/^[-+]?\d+$/.test(v)) return false
@@ -1643,9 +1820,47 @@
             }
             if (isEmpty(value)) return
 
-            const t = String(meta.type || meta.fieldType || '').toLowerCase()
-            const intLen = meta.totalLength
-            const decLen = meta.decimalLength
+            // 根据 format 优先推断类型
+            const fmt = (meta.format && String(meta.format).trim()) || ''
+            let inferred = ''
+            if (fmt) {
+              const fl = fmt.toLowerCase()
+              if (/(hh|hh:mm|hh:mm:ss)/i.test(fmt) || fl.includes('hh')) {
+                inferred = 'datetime'
+              } else if (/^y{4}$/i.test(fmt)) {
+                inferred = 'year'
+              } else if (
+                (/y{4}/i.test(fmt) && /m{2}/i.test(fmt)) ||
+                fl.includes('yyyy-mm') ||
+                fl.includes('yyyy-mm-dd')
+              ) {
+                inferred = 'date'
+              }
+            }
+            const tRaw = String(meta.type || meta.fieldType || '').toLowerCase()
+            const t = inferred || tRaw
+            const lens = ((m) => ({
+              totalLen:
+                m.totalLength ??
+                m.fieldTypeLen ??
+                m.fieldTypelen ??
+                m.field_typelen ??
+                m.length ??
+                m.fieldLength,
+              decimalLen:
+                m.decimalLength ??
+                m.fieldTypeNointLen ??
+                m.fieldTypenointlen ??
+                m.field_typenointlen ??
+                m.scale,
+            }))(meta || {})
+            const intLen =
+              lens.totalLen !== undefined ? Number(lens.totalLen) : undefined
+            const decLenRaw =
+              lens.decimalLen !== undefined
+                ? Number(lens.decimalLen)
+                : undefined
+            const decLen = t.includes('int') || t === 'integer' ? 0 : decLenRaw
             if (
               t === 'number' ||
               t === 'int' ||
@@ -1672,22 +1887,56 @@
                 }
               }
             } else if (t === 'date' || t === 'datetime' || t === 'year') {
-              if (!isValidDate(value)) {
-                errors.push(
-                  `第${rowIndex + 1}行【${
-                    col.label
-                  }】日期格式不正确,应为YYYY-MM-DD或YYYY-MM-DD HH:mm:ss`
-                )
+              if (t === 'year') {
+                if (!isValidYear(value)) {
+                  errors.push(
+                    `第${rowIndex + 1}行【${col.label}】必须是四位年份`
+                  )
+                }
+              } else {
+                if (!isValidDate(value)) {
+                  errors.push(
+                    `第${rowIndex + 1}行【${
+                      col.label
+                    }】日期格式不正确,应为YYYY-MM-DD或YYYY-MM-DD HH:mm:ss`
+                  )
+                }
               }
             }
 
-            // 字典/枚举
-            if (Array.isArray(meta.options) && meta.options.length > 0) {
+            // 字典/枚举:优先使用 dictCode 的选项校验;否则退回 meta.options
+            const dictCode =
+              (meta.dictCode && String(meta.dictCode).trim()) ||
+              (meta.dictType && String(meta.dictType).trim()) ||
+              (meta.typeKey && String(meta.typeKey).trim()) ||
+              (meta.dictId && String(meta.dictId).trim()) ||
+              (meta.dictid && String(meta.dictid).trim()) ||
+              ''
+            let optionsToCheck = []
+            if (dictCode) {
+              const dictOpts = this.getDictOptions(dictCode) || []
+              optionsToCheck = Array.isArray(dictOpts) ? dictOpts : []
+            }
+            if (
+              (!optionsToCheck || optionsToCheck.length === 0) &&
+              Array.isArray(meta.options)
+            ) {
+              optionsToCheck = meta.options
+            }
+            if (Array.isArray(optionsToCheck) && optionsToCheck.length > 0) {
               const allowed = new Set(
-                meta.options.map(
-                  (o) =>
-                    o && (o.value != null ? String(o.value) : String(o.label))
-                )
+                optionsToCheck
+                  .map((o) => {
+                    const v =
+                      o &&
+                      (o.key != null
+                        ? o.key
+                        : o.value != null
+                        ? o.value
+                        : o.label)
+                    return v != null ? String(v) : undefined
+                  })
+                  .filter(Boolean)
               )
               const valStr = String(value)
               if (!allowed.has(valStr)) {

+ 4 - 4
src/views/costAudit/baseInfo/costFormManage/infoMaintain.vue

@@ -735,7 +735,7 @@
                     </el-select>
                     <el-select
                       v-if="scope.row.isDict === 'true'"
-                      v-model="scope.row.dictid"
+                      v-model="scope.row.dictCode"
                       placeholder="请选择字典"
                       class="dict-select"
                       size="small"
@@ -746,7 +746,7 @@
                         v-for="(item, index) in dictTypeList"
                         :key="index"
                         :label="item.name"
-                        :value="String(item.id)"
+                        :value="String(item.typeKey)"
                       ></el-option>
                     </el-select>
                   </div>
@@ -1186,7 +1186,7 @@
                     </el-select>
                     <el-select
                       v-if="scope.row.isDict === 'true'"
-                      v-model="scope.row.dictid"
+                      v-model="scope.row.dictCode"
                       placeholder="请选择字典"
                       class="dict-select"
                       size="small"
@@ -1197,7 +1197,7 @@
                         v-for="(item, index) in dictTypeList"
                         :key="index"
                         :label="item.name"
-                        :value="String(item.id)"
+                        :value="String(item.typeKey)"
                       ></el-option>
                     </el-select>
                   </div>