|
@@ -20,7 +20,7 @@
|
|
|
<template slot-scope="scope">
|
|
<template slot-scope="scope">
|
|
|
<span>{{ scope.row.seq }}</span>
|
|
<span>{{ scope.row.seq }}</span>
|
|
|
</template>
|
|
</template>
|
|
|
- </el-table-column> -->
|
|
|
|
|
|
|
+</el-table-column> -->
|
|
|
|
|
|
|
|
<!-- 项目列(只读) -->
|
|
<!-- 项目列(只读) -->
|
|
|
<!-- <el-table-column
|
|
<!-- <el-table-column
|
|
@@ -126,25 +126,32 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
|
|
|
|
|
|
- <!-- 动态年份列 -->
|
|
|
|
|
|
|
+ <!-- 监审期间动态列:surveyData.fixedHeaders 中 isAuditPeriod=true 的字段,按 年份 + fieldName 展开(单层表头:${year}${fieldName}) -->
|
|
|
<el-table-column
|
|
<el-table-column
|
|
|
- v-for="year in yearColumns"
|
|
|
|
|
- :key="year"
|
|
|
|
|
- :label="`${year}年`"
|
|
|
|
|
- :prop="`year_${year}`"
|
|
|
|
|
|
|
+ v-for="col in auditPeriodColumns()"
|
|
|
|
|
+ :key="col.key"
|
|
|
|
|
+ :label="col.label"
|
|
|
|
|
+ :prop="col.prop"
|
|
|
width="150"
|
|
width="150"
|
|
|
align="center"
|
|
align="center"
|
|
|
>
|
|
>
|
|
|
<template slot-scope="scope">
|
|
<template slot-scope="scope">
|
|
|
<el-input
|
|
<el-input
|
|
|
v-if="!scope.row.isCategory"
|
|
v-if="!scope.row.isCategory"
|
|
|
- v-model="scope.row[`year_${year}`]"
|
|
|
|
|
|
|
+ v-model="scope.row[col.prop]"
|
|
|
inputmode="decimal"
|
|
inputmode="decimal"
|
|
|
- :placeholder="`请输入${year}年数据`"
|
|
|
|
|
|
|
+ :placeholder="`请输入${col.label}`"
|
|
|
:disabled="isViewMode"
|
|
:disabled="isViewMode"
|
|
|
style="width: 100%"
|
|
style="width: 100%"
|
|
|
- @input="handleNumericInput(scope.row, year, $event)"
|
|
|
|
|
- @blur="handleCellBlur(scope.row, year)"
|
|
|
|
|
|
|
+ @input="
|
|
|
|
|
+ handleAuditPeriodNumericInput(
|
|
|
|
|
+ scope.row,
|
|
|
|
|
+ col.year,
|
|
|
|
|
+ col.meta,
|
|
|
|
|
+ $event
|
|
|
|
|
+ )
|
|
|
|
|
+ "
|
|
|
|
|
+ @blur="handleAuditPeriodCellBlur(scope.row, col.year, col.meta)"
|
|
|
/>
|
|
/>
|
|
|
</template>
|
|
</template>
|
|
|
</el-table-column>
|
|
</el-table-column>
|
|
@@ -512,17 +519,28 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- // 由父组件传入的 fixedFields 生成表头标签数组
|
|
|
|
|
|
|
+ // 由父组件传入的 fixedFields 生成表头标签数组(仅“非监审期间字段”)
|
|
|
dynamicColumns() {
|
|
dynamicColumns() {
|
|
|
- if (!this.fixedFields) return []
|
|
|
|
|
|
|
+ if (!this.fixedFields) {
|
|
|
|
|
+ // 即使 fixedFields 为空,也允许从元数据中补充 showVisible=1 的非期间字段
|
|
|
|
|
+ const metas = this.metaList()
|
|
|
|
|
+ return metas
|
|
|
|
|
+ .filter((m) => !this.isAuditPeriodMeta(m))
|
|
|
|
|
+ .filter((m) => m && String(m.showVisible || '').trim() === '1')
|
|
|
|
|
+ .map((m) => String(m.label || m.fieldName || m.name || '').trim())
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const labels = String(this.fixedFields)
|
|
const labels = String(this.fixedFields)
|
|
|
.split(',')
|
|
.split(',')
|
|
|
.map((s) => s.trim())
|
|
.map((s) => s.trim())
|
|
|
.filter(Boolean)
|
|
.filter(Boolean)
|
|
|
- // union: 追加任何 meta 中 (showVisible==='1' 或 有 dictCode) 的列
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // union: 追加任何 meta 中 (showVisible==='1' 或 有 dictCode) 的“非期间字段”
|
|
|
const metas = this.metaList()
|
|
const metas = this.metaList()
|
|
|
const extras = []
|
|
const extras = []
|
|
|
metas.forEach((m) => {
|
|
metas.forEach((m) => {
|
|
|
|
|
+ if (!m || this.isAuditPeriodMeta(m)) return
|
|
|
const label = String(m?.label || m?.fieldName || m?.name || '').trim()
|
|
const label = String(m?.label || m?.fieldName || m?.name || '').trim()
|
|
|
if (!label) return
|
|
if (!label) return
|
|
|
const hasDict = !!(
|
|
const hasDict = !!(
|
|
@@ -542,26 +560,86 @@
|
|
|
extras.push(label)
|
|
extras.push(label)
|
|
|
}
|
|
}
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
let base = labels.concat(extras)
|
|
let base = labels.concat(extras)
|
|
|
- // 基于元数据控制显示:showVisible==='1' 强制显示;否则当 isAuditPeriod==='false' 隐藏
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 基于元数据控制显示:固定表头只保留“非期间字段”(isAuditPeriod=true 的字段一律不显示,即使 showVisible=1 也不例外)
|
|
|
let filtered = base.filter((label) => {
|
|
let filtered = base.filter((label) => {
|
|
|
const m = this.getFieldMeta(label)
|
|
const m = this.getFieldMeta(label)
|
|
|
if (!m) return true
|
|
if (!m) return true
|
|
|
- // 强制显示优先
|
|
|
|
|
|
|
+ // 期间字段一律从固定表头剔除,避免与“${year}${fieldName}”动态列重复
|
|
|
|
|
+ if (this.isAuditPeriodMeta(m)) return false
|
|
|
|
|
+
|
|
|
|
|
+ // 非期间字段:showVisible=1 强制显示
|
|
|
const sv = m.showVisible
|
|
const sv = m.showVisible
|
|
|
if (sv != null && String(sv).trim() === '1') return true
|
|
if (sv != null && String(sv).trim() === '1') return true
|
|
|
- const v = m.isAuditPeriod
|
|
|
|
|
- if (v === undefined || v === null || v === '') return true
|
|
|
|
|
- const str = String(v).trim().toLowerCase()
|
|
|
|
|
- return !(str === 'false' || str === '0' || str === 'no')
|
|
|
|
|
|
|
+ return true
|
|
|
})
|
|
})
|
|
|
- // 移除纯年份表头(如“2023”或“2023年”),年份列统一由 yearColumns 渲染
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 移除纯年份表头(如“2023”或“2023年”),期间列统一由 yearColumns 渲染
|
|
|
filtered = filtered.filter((label) => !/^\d{4}(年)?$/.test(label))
|
|
filtered = filtered.filter((label) => !/^\d{4}(年)?$/.test(label))
|
|
|
- // 若全部被过滤导致空表头,则回退为全部显示,避免页面空白
|
|
|
|
|
|
|
+
|
|
|
return filtered.length > 0
|
|
return filtered.length > 0
|
|
|
? filtered
|
|
? filtered
|
|
|
: base.filter((label) => !/^\d{4}(年)?$/.test(label))
|
|
: base.filter((label) => !/^\d{4}(年)?$/.test(label))
|
|
|
},
|
|
},
|
|
|
|
|
+
|
|
|
|
|
+ // 期间字段:来自 fixedHeaders 中 isAuditPeriod=true
|
|
|
|
|
+ auditPeriodFields() {
|
|
|
|
|
+ const metas = this.metaList()
|
|
|
|
|
+ const list = (Array.isArray(metas) ? metas : []).filter((m) =>
|
|
|
|
|
+ this.isAuditPeriodMeta(m)
|
|
|
|
|
+ )
|
|
|
|
|
+ return list.map((m, idx) => ({
|
|
|
|
|
+ ...m,
|
|
|
|
|
+ __key: String(m.fieldName || m.label || m.name || idx),
|
|
|
|
|
+ }))
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ isAuditPeriodMeta(meta) {
|
|
|
|
|
+ if (!meta) return false
|
|
|
|
|
+ const v = meta.isAuditPeriod
|
|
|
|
|
+ if (v === true) return true
|
|
|
|
|
+ if (v === false) return false
|
|
|
|
|
+ const s = String(v == null ? '' : v)
|
|
|
|
|
+ .trim()
|
|
|
|
|
+ .toLowerCase()
|
|
|
|
|
+ return ['true', '1', 'y', 'yes', '是'].includes(s)
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 动态期间字段在行对象上的 prop:ap_${year}__${fieldName}
|
|
|
|
|
+ getAuditPeriodProp(year, meta) {
|
|
|
|
|
+ const y = String(year)
|
|
|
|
|
+ const f = String(
|
|
|
|
|
+ meta?.fieldName || meta?.label || meta?.name || ''
|
|
|
|
|
+ ).trim()
|
|
|
|
|
+ return `ap_${y}__${f}`
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 单层表头列:[{ key, label, prop, year, meta }]
|
|
|
|
|
+ auditPeriodColumns() {
|
|
|
|
|
+ const years = Array.isArray(this.yearColumns) ? this.yearColumns : []
|
|
|
|
|
+ const fields = this.auditPeriodFields()
|
|
|
|
|
+ const cols = []
|
|
|
|
|
+ years.forEach((year) => {
|
|
|
|
|
+ fields.forEach((m) => {
|
|
|
|
|
+ const fieldName = String(
|
|
|
|
|
+ m.fieldName || m.label || m.name || ''
|
|
|
|
|
+ ).trim()
|
|
|
|
|
+ if (!fieldName) return
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, m)
|
|
|
|
|
+ cols.push({
|
|
|
|
|
+ key: `${year}__${m.__key}`,
|
|
|
|
|
+ label: `${year}${fieldName}`,
|
|
|
|
|
+ prop,
|
|
|
|
|
+ year,
|
|
|
|
|
+ meta: m,
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ return cols
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
// ====== fixedFields 渲染辅助(基于 columnsMeta)======
|
|
// ====== fixedFields 渲染辅助(基于 columnsMeta)======
|
|
|
getFieldMeta(label) {
|
|
getFieldMeta(label) {
|
|
|
const metas = this.metaList()
|
|
const metas = this.metaList()
|
|
@@ -987,8 +1065,8 @@
|
|
|
// 构建公式索引并计算一次
|
|
// 构建公式索引并计算一次
|
|
|
this.buildFormulaEngine()
|
|
this.buildFormulaEngine()
|
|
|
this.recomputeAllFormulas()
|
|
this.recomputeAllFormulas()
|
|
|
- // 初始化后按年份对父项进行汇总
|
|
|
|
|
- this.recomputeAllParentYears()
|
|
|
|
|
|
|
+ // 初始化后按监审期间字段对父项进行汇总
|
|
|
|
|
+ this.recomputeAllParentAuditFields()
|
|
|
},
|
|
},
|
|
|
// 创建行数据
|
|
// 创建行数据
|
|
|
createRowData(item, isChild, rowid) {
|
|
createRowData(item, isChild, rowid) {
|
|
@@ -1004,10 +1082,18 @@
|
|
|
isCategory: item.isCategory || false,
|
|
isCategory: item.isCategory || false,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 初始化年份数据
|
|
|
|
|
- this.yearColumns.forEach((year) => {
|
|
|
|
|
- rowData[`year_${year}`] = item[`year_${year}`] || ''
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // 初始化监审期间动态字段(isAuditPeriod=true):year_${year}__${fieldName}
|
|
|
|
|
+ const auditFields = this.auditPeriodFields()
|
|
|
|
|
+ if (Array.isArray(auditFields) && auditFields.length > 0) {
|
|
|
|
|
+ this.yearColumns.forEach((year) => {
|
|
|
|
|
+ auditFields.forEach((m) => {
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, m)
|
|
|
|
|
+ if (rowData[prop] === undefined) {
|
|
|
|
|
+ this.$set(rowData, prop, item[prop] || '')
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 初始化固定表头(fixedFields)对应的列,确保 v-model 有响应式属性
|
|
// 初始化固定表头(fixedFields)对应的列,确保 v-model 有响应式属性
|
|
|
const labels = this.dynamicColumns()
|
|
const labels = this.dynamicColumns()
|
|
@@ -1062,14 +1148,48 @@
|
|
|
: res.value.items || res.value.records || []
|
|
: res.value.items || res.value.records || []
|
|
|
|
|
|
|
|
// 按 rowid 分组数据
|
|
// 按 rowid 分组数据
|
|
|
|
|
+ // 兼容:监审期间动态列的行标识为 "${rowid}_${year}",其 rkey 可能为:
|
|
|
|
|
+ // - 旧:fieldName
|
|
|
|
|
+ // - 新:fieldName_year(如 A1_2025)
|
|
|
|
|
+ // 页面侧统一转换成:dataByRowid[baseRowid]["${year}${fieldName}"]
|
|
|
const dataByRowid = {}
|
|
const dataByRowid = {}
|
|
|
detailData.forEach((item) => {
|
|
detailData.forEach((item) => {
|
|
|
- if (item.rowid) {
|
|
|
|
|
- if (!dataByRowid[item.rowid]) {
|
|
|
|
|
- dataByRowid[item.rowid] = {}
|
|
|
|
|
|
|
+ if (!item.rowid) return
|
|
|
|
|
+
|
|
|
|
|
+ const ridRaw = String(item.rowid)
|
|
|
|
|
+ let baseRowid = ridRaw
|
|
|
|
|
+ let year = ''
|
|
|
|
|
+
|
|
|
|
|
+ // 如果 rowid 形如 "xxx_2025",拆出 year
|
|
|
|
|
+ ;(Array.isArray(this.yearColumns) ? this.yearColumns : []).some(
|
|
|
|
|
+ (y) => {
|
|
|
|
|
+ const ys = String(y)
|
|
|
|
|
+ const suffix = `_${ys}`
|
|
|
|
|
+ if (ridRaw.endsWith(suffix)) {
|
|
|
|
|
+ baseRowid = ridRaw.slice(0, -suffix.length)
|
|
|
|
|
+ year = ys
|
|
|
|
|
+ return true
|
|
|
|
|
+ }
|
|
|
|
|
+ return false
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (!dataByRowid[baseRowid]) dataByRowid[baseRowid] = {}
|
|
|
|
|
+
|
|
|
|
|
+ const rkeyRaw = item.rkey
|
|
|
|
|
+ // 监审期间动态列:后端返回 rowid_年份
|
|
|
|
|
+ // - 旧:rkey=fieldName
|
|
|
|
|
+ // - 新:rkey=fieldName_year
|
|
|
|
|
+ // 页面侧用 "${year}${fieldName}" 作为回显键
|
|
|
|
|
+ let fieldName = rkeyRaw
|
|
|
|
|
+ if (year) {
|
|
|
|
|
+ const suffix = `_${year}`
|
|
|
|
|
+ if (typeof rkeyRaw === 'string' && rkeyRaw.endsWith(suffix)) {
|
|
|
|
|
+ fieldName = rkeyRaw.slice(0, -suffix.length)
|
|
|
}
|
|
}
|
|
|
- dataByRowid[item.rowid][item.rkey] = item.rvalue
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ const storeKey = year ? `${year}${fieldName}` : rkeyRaw
|
|
|
|
|
+ dataByRowid[baseRowid][storeKey] = item.rvalue
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
// 回显数据到表格
|
|
// 回显数据到表格
|
|
@@ -1092,12 +1212,23 @@
|
|
|
row.remark = rowData['备注']
|
|
row.remark = rowData['备注']
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 回显年份数据
|
|
|
|
|
- this.yearColumns.forEach((year) => {
|
|
|
|
|
- if (rowData[year] !== undefined) {
|
|
|
|
|
- row[`year_${year}`] = rowData[year]
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ // 回显监审期间动态字段(rkey 使用“${year}${fieldName}”)
|
|
|
|
|
+ const auditFields = this.auditPeriodFields()
|
|
|
|
|
+ if (Array.isArray(auditFields) && auditFields.length > 0) {
|
|
|
|
|
+ this.yearColumns.forEach((year) => {
|
|
|
|
|
+ auditFields.forEach((m) => {
|
|
|
|
|
+ const fieldName = String(
|
|
|
|
|
+ m.fieldName || m.label || m.name || ''
|
|
|
|
|
+ ).trim()
|
|
|
|
|
+ if (!fieldName) return
|
|
|
|
|
+ const rkey = `${year}${fieldName}`
|
|
|
|
|
+ if (rowData[rkey] !== undefined) {
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, m)
|
|
|
|
|
+ this.$set(row, prop, rowData[rkey])
|
|
|
|
|
+ }
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 回显固定表头字段(与保存时一致,rkey 使用中文表头 label)
|
|
// 回显固定表头字段(与保存时一致,rkey 使用中文表头 label)
|
|
|
const labels = this.dynamicColumns()
|
|
const labels = this.dynamicColumns()
|
|
@@ -1116,8 +1247,8 @@
|
|
|
|
|
|
|
|
// 回显后根据公式重算
|
|
// 回显后根据公式重算
|
|
|
this.recomputeAllFormulas()
|
|
this.recomputeAllFormulas()
|
|
|
- // 回显后根据子项汇总父项年份
|
|
|
|
|
- this.recomputeAllParentYears()
|
|
|
|
|
|
|
+ // 回显后根据子项汇总父项(按监审期间字段汇总)
|
|
|
|
|
+ this.recomputeAllParentAuditFields()
|
|
|
// 回显后再做一次字典值规范化,确保显示选中项
|
|
// 回显后再做一次字典值规范化,确保显示选中项
|
|
|
this.normalizeAllDictValues()
|
|
this.normalizeAllDictValues()
|
|
|
}
|
|
}
|
|
@@ -1126,12 +1257,14 @@
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
- // =========== 年份父项汇总 ==========
|
|
|
|
|
- recomputeAllParentYears() {
|
|
|
|
|
|
|
+ // =========== 父项汇总:按监审期间动态字段汇总 ==========
|
|
|
|
|
+ recomputeAllParentAuditFields() {
|
|
|
if (!Array.isArray(this.tableData) || this.tableData.length === 0)
|
|
if (!Array.isArray(this.tableData) || this.tableData.length === 0)
|
|
|
return
|
|
return
|
|
|
const parents = this.tableData.filter((r) => r && r.isCategory)
|
|
const parents = this.tableData.filter((r) => r && r.isCategory)
|
|
|
const years = Array.isArray(this.yearColumns) ? this.yearColumns : []
|
|
const years = Array.isArray(this.yearColumns) ? this.yearColumns : []
|
|
|
|
|
+ const auditFields = this.auditPeriodFields()
|
|
|
|
|
+
|
|
|
parents.forEach((parent) => {
|
|
parents.forEach((parent) => {
|
|
|
const pid = parent.rowid || parent.id
|
|
const pid = parent.rowid || parent.id
|
|
|
const children = this.tableData.filter(
|
|
const children = this.tableData.filter(
|
|
@@ -1140,16 +1273,20 @@
|
|
|
!r.isCategory &&
|
|
!r.isCategory &&
|
|
|
(r.parentid === pid || String(r.parentid) === String(pid))
|
|
(r.parentid === pid || String(r.parentid) === String(pid))
|
|
|
)
|
|
)
|
|
|
|
|
+
|
|
|
years.forEach((y) => {
|
|
years.forEach((y) => {
|
|
|
- const field = `year_${y}`
|
|
|
|
|
- const sum = children.reduce(
|
|
|
|
|
- (acc, c) => acc + (Number(c[field]) || 0),
|
|
|
|
|
- 0
|
|
|
|
|
- )
|
|
|
|
|
- this.$set(parent, field, sum === 0 ? '' : String(sum))
|
|
|
|
|
|
|
+ auditFields.forEach((m) => {
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(y, m)
|
|
|
|
|
+ const sum = children.reduce(
|
|
|
|
|
+ (acc, c) => acc + (Number(c[prop]) || 0),
|
|
|
|
|
+ 0
|
|
|
|
|
+ )
|
|
|
|
|
+ this.$set(parent, prop, sum === 0 ? '' : String(sum))
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
})
|
|
})
|
|
|
},
|
|
},
|
|
|
|
|
+
|
|
|
// 获取假数据(用于测试)
|
|
// 获取假数据(用于测试)
|
|
|
getMockTableData() {
|
|
getMockTableData() {
|
|
|
// return [
|
|
// return [
|
|
@@ -1231,66 +1368,59 @@
|
|
|
}
|
|
}
|
|
|
return ''
|
|
return ''
|
|
|
},
|
|
},
|
|
|
- // 单元格输入事件
|
|
|
|
|
- handleCellInput(row, year) {
|
|
|
|
|
- // 实时验证勾稽关系
|
|
|
|
|
- this.validateLinkage(row, year)
|
|
|
|
|
- // 实时联动计算
|
|
|
|
|
- this.recomputeDependentsForRow(row, year)
|
|
|
|
|
- // 失焦也触发父项汇总
|
|
|
|
|
- this.recomputeAllParentYears()
|
|
|
|
|
- },
|
|
|
|
|
- // 仅数字输入:按行规则限制小数位;整数不允许小数
|
|
|
|
|
- handleNumericInput(row, year, val) {
|
|
|
|
|
- if (!row) return
|
|
|
|
|
- const rules = (row && row.validateRules) || {}
|
|
|
|
|
- const field = `year_${year}`
|
|
|
|
|
|
|
+ // 监审期间单元格输入事件(用于公式联动/父项汇总)
|
|
|
|
|
+ handleAuditPeriodCellInput(row, year, meta) {
|
|
|
|
|
+ this.recomputeDependentsForAuditField(row, year, meta)
|
|
|
|
|
+ this.recomputeAllParentAuditFields()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 监审期间数字输入:参考原动态年份列,但根据 fixedHeaders 字段类型控制小数位
|
|
|
|
|
+ handleAuditPeriodNumericInput(row, year, meta, val) {
|
|
|
|
|
+ if (!row || !meta) return
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, meta)
|
|
|
|
|
+
|
|
|
|
|
+ const t = String(meta.fieldType || meta.type || '').toLowerCase()
|
|
|
|
|
+ const isInteger = t.includes('int')
|
|
|
|
|
+ const dlenRaw =
|
|
|
|
|
+ meta.fieldTypenointlen ||
|
|
|
|
|
+ meta.fieldTypeNointLen ||
|
|
|
|
|
+ meta.decimalLength ||
|
|
|
|
|
+ meta.scale
|
|
|
|
|
+ const dlen = isInteger
|
|
|
|
|
+ ? 0
|
|
|
|
|
+ : typeof dlenRaw === 'number'
|
|
|
|
|
+ ? dlenRaw
|
|
|
|
|
+ : parseInt(dlenRaw)
|
|
|
|
|
+
|
|
|
let s = String(val == null ? '' : val)
|
|
let s = String(val == null ? '' : val)
|
|
|
- // 允许负号和小数点,其余去除
|
|
|
|
|
s = s.replace(/[^0-9+\-\.]/g, '')
|
|
s = s.replace(/[^0-9+\-\.]/g, '')
|
|
|
- // 只保留第一个负号在最前
|
|
|
|
|
s = s.replace(/(?!^)-/g, '')
|
|
s = s.replace(/(?!^)-/g, '')
|
|
|
if (s.startsWith('+')) s = s.slice(1)
|
|
if (s.startsWith('+')) s = s.slice(1)
|
|
|
- // 多个点只保留第一个
|
|
|
|
|
|
|
+
|
|
|
const firstDot = s.indexOf('.')
|
|
const firstDot = s.indexOf('.')
|
|
|
if (firstDot >= 0) {
|
|
if (firstDot >= 0) {
|
|
|
s =
|
|
s =
|
|
|
s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
|
|
s.slice(0, firstDot + 1) + s.slice(firstDot + 1).replace(/\./g, '')
|
|
|
}
|
|
}
|
|
|
- const t = String(rules.type || '').toLowerCase()
|
|
|
|
|
- const isInteger =
|
|
|
|
|
- t === 'integer' ||
|
|
|
|
|
- (t === 'number' &&
|
|
|
|
|
- (rules.fieldTypenointlen === 0 || rules.decimalLength === 0))
|
|
|
|
|
- if (isInteger && s.includes('.')) {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ if (dlen === 0 && s.includes('.')) {
|
|
|
s = s.split('.')[0]
|
|
s = s.split('.')[0]
|
|
|
|
|
+ } else if (!isNaN(dlen) && dlen >= 0 && firstDot >= 0) {
|
|
|
|
|
+ const parts = s.split('.')
|
|
|
|
|
+ parts[1] = (parts[1] || '').slice(0, dlen)
|
|
|
|
|
+ s = parts.join('.')
|
|
|
}
|
|
}
|
|
|
- if (!isInteger) {
|
|
|
|
|
- const dlen =
|
|
|
|
|
- rules.fieldTypenointlen ||
|
|
|
|
|
- rules.fieldTypeNointLen ||
|
|
|
|
|
- rules.decimalLength
|
|
|
|
|
- if (typeof dlen === 'number' && dlen >= 0 && firstDot >= 0) {
|
|
|
|
|
- const parts = s.split('.')
|
|
|
|
|
- parts[1] = (parts[1] || '').slice(0, dlen)
|
|
|
|
|
- s = parts.join('.')
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // 写回且触发联动
|
|
|
|
|
- this.$set(row, field, s)
|
|
|
|
|
- this.handleCellInput(row, year)
|
|
|
|
|
- // 年份联动:子项变更后,汇总到父项
|
|
|
|
|
- this.recomputeAllParentYears()
|
|
|
|
|
- },
|
|
|
|
|
- // 单元格失焦事件
|
|
|
|
|
- handleCellBlur(row, year) {
|
|
|
|
|
- // 验证格式和非空
|
|
|
|
|
- this.validateCell(row, year)
|
|
|
|
|
- // 验证勾稽关系
|
|
|
|
|
- this.validateLinkage(row, year)
|
|
|
|
|
- // 失焦后再次联动计算,确保取整/格式等影响后结果一致
|
|
|
|
|
- this.recomputeDependentsForRow(row, year)
|
|
|
|
|
|
|
+
|
|
|
|
|
+ this.$set(row, prop, s)
|
|
|
|
|
+ this.handleAuditPeriodCellInput(row, year, meta)
|
|
|
},
|
|
},
|
|
|
|
|
+
|
|
|
|
|
+ handleAuditPeriodCellBlur(row, year, meta) {
|
|
|
|
|
+ // 失焦时也触发一次联动计算,保持一致
|
|
|
|
|
+ this.recomputeDependentsForAuditField(row, year, meta)
|
|
|
|
|
+ this.recomputeAllParentAuditFields()
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
// 验证单元格(非空和格式验证)
|
|
// 验证单元格(非空和格式验证)
|
|
|
validateCell(row, year) {
|
|
validateCell(row, year) {
|
|
|
const fieldName = `year_${year}`
|
|
const fieldName = `year_${year}`
|
|
@@ -1878,21 +2008,30 @@
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 保存年份数据(所有行都可以有年份数据)
|
|
|
|
|
- this.yearColumns.forEach((year) => {
|
|
|
|
|
- const yearValue = row[`year_${year}`]
|
|
|
|
|
- if (
|
|
|
|
|
- yearValue !== undefined &&
|
|
|
|
|
- yearValue !== null &&
|
|
|
|
|
- yearValue !== ''
|
|
|
|
|
- ) {
|
|
|
|
|
- saveData.push({
|
|
|
|
|
- rowid: rowid,
|
|
|
|
|
- rkey: String(year),
|
|
|
|
|
- rvalue: String(yearValue),
|
|
|
|
|
|
|
+ // 保存监审期间动态字段的值:rowid 不带年份,rkey 使用 "${fieldName}_${year}"(注意:auditPeriodFields 有多个字段时也不会冲突)
|
|
|
|
|
+ const auditFields = this.auditPeriodFields()
|
|
|
|
|
+ if (Array.isArray(auditFields) && auditFields.length > 0) {
|
|
|
|
|
+ this.yearColumns.forEach((year) => {
|
|
|
|
|
+ const yearStr = String(year)
|
|
|
|
|
+ auditFields.forEach((m) => {
|
|
|
|
|
+ const fieldName = String(
|
|
|
|
|
+ m.fieldName || m.label || m.name || ''
|
|
|
|
|
+ ).trim()
|
|
|
|
|
+ if (!fieldName) return
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(yearStr, m)
|
|
|
|
|
+ const v = row[prop]
|
|
|
|
|
+ if (v !== undefined && v !== null && v !== '') {
|
|
|
|
|
+ saveData.push({
|
|
|
|
|
+ rowid: String(rowid),
|
|
|
|
|
+ rkey: `${fieldName}_${yearStr}`,
|
|
|
|
|
+ rvalue: String(v),
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
})
|
|
})
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // (旧)year_YYYY 年份列不再保存:已由“监审期间+fieldName”替代
|
|
|
|
|
|
|
|
// 保存备注(所有行都可以有备注)
|
|
// 保存备注(所有行都可以有备注)
|
|
|
if (
|
|
if (
|
|
@@ -2032,17 +2171,85 @@
|
|
|
this.recomputeDependentsForRow(depRow, year)
|
|
this.recomputeDependentsForRow(depRow, year)
|
|
|
)
|
|
)
|
|
|
},
|
|
},
|
|
|
- // 对所有含公式的行、所有年重算
|
|
|
|
|
|
|
+ // 对所有含公式的行、所有年、所有期间字段重算
|
|
|
recomputeAllFormulas() {
|
|
recomputeAllFormulas() {
|
|
|
- if (!this.yearColumns || this.yearColumns.length === 0) return
|
|
|
|
|
|
|
+ const years = Array.isArray(this.yearColumns) ? this.yearColumns : []
|
|
|
|
|
+ const auditFields = this.auditPeriodFields()
|
|
|
|
|
+ if (years.length === 0 || auditFields.length === 0) return
|
|
|
|
|
+
|
|
|
const formulaRows = this.tableData.filter(
|
|
const formulaRows = this.tableData.filter(
|
|
|
(r) => r && r.calculationFormula
|
|
(r) => r && r.calculationFormula
|
|
|
)
|
|
)
|
|
|
- this.yearColumns.forEach((year) => {
|
|
|
|
|
- formulaRows.forEach((r) => this.recomputeRowForYear(r, year))
|
|
|
|
|
|
|
+ years.forEach((year) => {
|
|
|
|
|
+ auditFields.forEach((m) => {
|
|
|
|
|
+ formulaRows.forEach((r) =>
|
|
|
|
|
+ this.recomputeRowForAuditField(r, year, m)
|
|
|
|
|
+ )
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
this.$forceUpdate()
|
|
this.$forceUpdate()
|
|
|
},
|
|
},
|
|
|
|
|
+
|
|
|
|
|
+ // 重新计算一个公式行在某年、某个期间字段下的值
|
|
|
|
|
+ recomputeRowForAuditField(row, year, meta) {
|
|
|
|
|
+ if (!row || !row.calculationFormula || !meta) return
|
|
|
|
|
+ const expr = row.calculationFormula.toString()
|
|
|
|
|
+
|
|
|
|
|
+ // 1) 先替换跨表引用:形如 “表名.A1”(token 中包含一个点)
|
|
|
|
|
+ const replacedCross = expr.replace(
|
|
|
|
|
+ /([^+\-*/()\s]+\.[A-Za-z0-9_]+)/g,
|
|
|
|
|
+ (token) => {
|
|
|
|
|
+ if (
|
|
|
|
|
+ this.crossTableInfo &&
|
|
|
|
|
+ typeof this.crossTableInfo.getValue === 'function'
|
|
|
|
|
+ ) {
|
|
|
|
|
+ const raw = this.crossTableInfo.getValue(year, token)
|
|
|
|
|
+ const num = Number(raw)
|
|
|
|
|
+ return !isNaN(num) ? String(num) : '0'
|
|
|
|
|
+ }
|
|
|
|
|
+ const dataByYear = this.crossTableInfo && this.crossTableInfo.data
|
|
|
|
|
+ const y = year === undefined || year === null ? '' : String(year)
|
|
|
|
|
+ const raw =
|
|
|
|
|
+ dataByYear && y && dataByYear[y]
|
|
|
|
|
+ ? dataByYear[y][token]
|
|
|
|
|
+ : undefined
|
|
|
|
|
+ const num = Number(raw)
|
|
|
|
|
+ return !isNaN(num) ? String(num) : '0'
|
|
|
|
|
+ }
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 2) 再替换本表引用:A1/B2... -> 取“同年同字段”的值
|
|
|
|
|
+ const replaced = replacedCross.replace(/[A-Z]+\d+/g, (code) => {
|
|
|
|
|
+ const refRow = this.cellCodeIndex[code]
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, meta)
|
|
|
|
|
+ const v = refRow ? Number(refRow[prop]) : 0
|
|
|
|
|
+ return isNaN(v) ? '0' : String(v)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ const safe = this.safeEvalExpression(replaced)
|
|
|
|
|
+ if (safe !== null && !isNaN(safe)) {
|
|
|
|
|
+ const prop = this.getAuditPeriodProp(year, meta)
|
|
|
|
|
+ row[prop] = String(safe)
|
|
|
|
|
+ }
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
|
|
+ // 在某年、某字段针对一个输入行,联动所有依赖它的行
|
|
|
|
|
+ recomputeDependentsForAuditField(row, year, meta) {
|
|
|
|
|
+ if (!row || !meta) return
|
|
|
|
|
+ const code = row.cellCode
|
|
|
|
|
+ if (!code) return
|
|
|
|
|
+ const dependents = this.dependentsMap[code]
|
|
|
|
|
+ if (!dependents || dependents.size === 0) return
|
|
|
|
|
+
|
|
|
|
|
+ dependents.forEach((depRow) =>
|
|
|
|
|
+ this.recomputeRowForAuditField(depRow, year, meta)
|
|
|
|
|
+ )
|
|
|
|
|
+ // 可能存在链式依赖,递归触发
|
|
|
|
|
+ dependents.forEach((depRow) =>
|
|
|
|
|
+ this.recomputeDependentsForAuditField(depRow, year, meta)
|
|
|
|
|
+ )
|
|
|
|
|
+ },
|
|
|
|
|
+
|
|
|
// 简单安全表达式求值:仅支持数字、+ - * / 和括号
|
|
// 简单安全表达式求值:仅支持数字、+ - * / 和括号
|
|
|
safeEvalExpression(expr) {
|
|
safeEvalExpression(expr) {
|
|
|
const cleaned = expr.replace(/\s+/g, '')
|
|
const cleaned = expr.replace(/\s+/g, '')
|
|
@@ -2067,6 +2274,7 @@
|
|
|
.dialog-footer {
|
|
.dialog-footer {
|
|
|
text-align: right;
|
|
text-align: right;
|
|
|
margin-top: 20px;
|
|
margin-top: 20px;
|
|
|
|
|
+
|
|
|
.el-button {
|
|
.el-button {
|
|
|
margin: 0 10px;
|
|
margin: 0 10px;
|
|
|
}
|
|
}
|
|
@@ -2074,6 +2282,7 @@
|
|
|
|
|
|
|
|
::v-deep .el-dialog__header {
|
|
::v-deep .el-dialog__header {
|
|
|
padding: 20px 20px 10px;
|
|
padding: 20px 20px 10px;
|
|
|
|
|
+
|
|
|
.el-dialog__title {
|
|
.el-dialog__title {
|
|
|
font-size: 18px;
|
|
font-size: 18px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|