|
|
@@ -171,6 +171,8 @@
|
|
|
tableData: [],
|
|
|
yearColumns: [],
|
|
|
validationErrors: [],
|
|
|
+ cellCodeIndex: {}, // cellCode -> row
|
|
|
+ dependentsMap: {}, // cellCode -> Set(rows depending on it)
|
|
|
}
|
|
|
},
|
|
|
watch: {
|
|
|
@@ -369,6 +371,10 @@
|
|
|
})
|
|
|
|
|
|
this.tableData = flatData
|
|
|
+
|
|
|
+ // 构建公式索引并计算一次
|
|
|
+ this.buildFormulaEngine()
|
|
|
+ this.recomputeAllFormulas()
|
|
|
},
|
|
|
// 创建行数据
|
|
|
createRowData(item, isChild, rowid) {
|
|
|
@@ -471,6 +477,9 @@
|
|
|
|
|
|
// 强制更新视图
|
|
|
this.$forceUpdate()
|
|
|
+
|
|
|
+ // 回显后根据公式重算
|
|
|
+ this.recomputeAllFormulas()
|
|
|
}
|
|
|
} catch (err) {
|
|
|
console.error('获取固定表详情失败', err)
|
|
|
@@ -562,6 +571,8 @@
|
|
|
handleCellInput(row, year) {
|
|
|
// 实时验证勾稽关系
|
|
|
this.validateLinkage(row, year)
|
|
|
+ // 实时联动计算
|
|
|
+ this.recomputeDependentsForRow(row, year)
|
|
|
},
|
|
|
// 单元格失焦事件
|
|
|
handleCellBlur(row, year) {
|
|
|
@@ -569,6 +580,8 @@
|
|
|
this.validateCell(row, year)
|
|
|
// 验证勾稽关系
|
|
|
this.validateLinkage(row, year)
|
|
|
+ // 失焦后再次联动计算,确保取整/格式等影响后结果一致
|
|
|
+ this.recomputeDependentsForRow(row, year)
|
|
|
},
|
|
|
// 验证单元格(非空和格式验证)
|
|
|
validateCell(row, year) {
|
|
|
@@ -855,6 +868,84 @@
|
|
|
Message.error(err.message || '保存失败')
|
|
|
}
|
|
|
},
|
|
|
+ // 构建 cellCode 索引与依赖图
|
|
|
+ buildFormulaEngine() {
|
|
|
+ const index = {}
|
|
|
+ const deps = {}
|
|
|
+ this.tableData.forEach((row) => {
|
|
|
+ if (row && row.cellCode) {
|
|
|
+ index[row.cellCode] = row
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 构建依赖映射:A1 -> [依赖 A1 的行...]
|
|
|
+ this.tableData.forEach((row) => {
|
|
|
+ if (!row || !row.calculationFormula) return
|
|
|
+ const codes =
|
|
|
+ row.calculationFormula.toString().match(/[A-Z]+\d+/g) || []
|
|
|
+ codes.forEach((code) => {
|
|
|
+ if (!deps[code]) deps[code] = new Set()
|
|
|
+ deps[code].add(row)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ this.cellCodeIndex = index
|
|
|
+ this.dependentsMap = deps
|
|
|
+ },
|
|
|
+ // 重新计算一个公式行在某年的值
|
|
|
+ recomputeRowForYear(row, year) {
|
|
|
+ if (!row || !row.calculationFormula) return
|
|
|
+ const expr = row.calculationFormula.toString()
|
|
|
+ // 替换变量为对应年的数值
|
|
|
+ const replaced = expr.replace(/[A-Z]+\d+/g, (code) => {
|
|
|
+ const refRow = this.cellCodeIndex[code]
|
|
|
+ const v = refRow ? Number(refRow[`year_${year}`]) : 0
|
|
|
+ return isNaN(v) ? '0' : String(v)
|
|
|
+ })
|
|
|
+ // 仅允许数字与运算符
|
|
|
+ const safe = this.safeEvalExpression(replaced)
|
|
|
+ if (safe !== null && !isNaN(safe)) {
|
|
|
+ row[`year_${year}`] = String(safe)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 在某年针对一个输入行,联动所有依赖它的行
|
|
|
+ recomputeDependentsForRow(row, year) {
|
|
|
+ if (!row) return
|
|
|
+ const code = row.cellCode
|
|
|
+ if (!code) return
|
|
|
+ const dependents = this.dependentsMap[code]
|
|
|
+ if (!dependents || dependents.size === 0) return
|
|
|
+ dependents.forEach((depRow) => this.recomputeRowForYear(depRow, year))
|
|
|
+ // 可能存在链式依赖,递归触发
|
|
|
+ dependents.forEach((depRow) =>
|
|
|
+ this.recomputeDependentsForRow(depRow, year)
|
|
|
+ )
|
|
|
+ },
|
|
|
+ // 对所有含公式的行、所有年重算
|
|
|
+ recomputeAllFormulas() {
|
|
|
+ if (!this.yearColumns || this.yearColumns.length === 0) return
|
|
|
+ const formulaRows = this.tableData.filter(
|
|
|
+ (r) => r && r.calculationFormula
|
|
|
+ )
|
|
|
+ this.yearColumns.forEach((year) => {
|
|
|
+ formulaRows.forEach((r) => this.recomputeRowForYear(r, year))
|
|
|
+ })
|
|
|
+ this.$forceUpdate()
|
|
|
+ },
|
|
|
+ // 简单安全表达式求值:仅支持数字、+ - * / 和括号
|
|
|
+ safeEvalExpression(expr) {
|
|
|
+ const cleaned = expr.replace(/\s+/g, '')
|
|
|
+ if (!/^[-+*/().\d]+$/.test(cleaned)) {
|
|
|
+ // 存在不允许的字符,放弃计算
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ // eslint-disable-next-line no-new-func
|
|
|
+ const fn = new Function(`return (${cleaned})`)
|
|
|
+ const res = fn()
|
|
|
+ return typeof res === 'number' && isFinite(res) ? res : null
|
|
|
+ } catch (e) {
|
|
|
+ return null
|
|
|
+ }
|
|
|
+ },
|
|
|
},
|
|
|
}
|
|
|
</script>
|