| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084 |
- <template>
- <el-dialog
- title="调查表填报"
- :visible.sync="dialogVisible"
- width="90%"
- :close-on-click-modal="false"
- :show-close="true"
- append-to-body
- :modal="false"
- @close="handleClose"
- >
- <el-table
- :data="tableData"
- border
- style="width: 100%"
- :row-class-name="getRowClassName"
- >
- <!-- 序号列 -->
- <el-table-column prop="seq" label="序号" width="80" align="center">
- <template slot-scope="scope">
- <span>{{ scope.row.seq }}</span>
- </template>
- </el-table-column>
- <!-- 项目列(只读) -->
- <el-table-column
- prop="itemName"
- label="项目"
- min-width="200"
- align="left"
- >
- <template slot-scope="scope">
- <span v-if="scope.row.isCategory" class="category-name">
- {{ scope.row.itemName }}
- </span>
- <span v-else>{{ scope.row.itemName }}</span>
- </template>
- </el-table-column>
- <!-- 单位列(只读) -->
- <el-table-column prop="unit" label="单位" width="100" align="center">
- <template slot-scope="scope">
- <span v-if="!scope.row.isCategory">{{ scope.row.unit }}</span>
- </template>
- </el-table-column>
- <!-- 指标编号(cellCode,只读) -->
- <el-table-column
- prop="cellCode"
- label="指标编号"
- width="120"
- align="center"
- >
- <template slot-scope="scope">
- <span v-if="!scope.row.isCategory">{{ scope.row.cellCode }}</span>
- </template>
- </el-table-column>
- <!-- 计算方式(calculationFormula,只读) -->
- <el-table-column
- prop="calculationFormula"
- label="计算方式"
- min-width="180"
- align="left"
- >
- <template slot-scope="scope">
- <span v-if="!scope.row.isCategory">
- {{ scope.row.calculationFormula }}
- </span>
- </template>
- </el-table-column>
- <!-- 动态年份列 -->
- <el-table-column
- v-for="year in yearColumns"
- :key="year"
- :label="`${year}年`"
- :prop="`year_${year}`"
- width="150"
- align="center"
- >
- <template slot-scope="scope">
- <el-input
- v-if="!scope.row.isCategory"
- v-model="scope.row[`year_${year}`]"
- :placeholder="`请输入${year}年数据`"
- :disabled="isViewMode"
- @blur="handleCellBlur(scope.row, year)"
- @input="handleCellInput(scope.row, year)"
- />
- </template>
- </el-table-column>
- <!-- 备注列 -->
- <el-table-column prop="remark" label="备注" min-width="150" align="left">
- <template slot-scope="scope">
- <el-input
- v-if="!scope.row.isCategory"
- v-model="scope.row.remark"
- placeholder="请输入备注"
- :disabled="isViewMode"
- />
- </template>
- </el-table-column>
- </el-table>
- <div slot="footer" class="dialog-footer">
- <el-button type="primary" @click="handleSave">保存</el-button>
- <el-button @click="handleCancel">取消</el-button>
- </div>
- </el-dialog>
- </template>
- <script>
- import { Message } from 'element-ui'
- import { saveSingleRecordSurvey, getSurveyDetail } from '@/api/audit/survey'
- export default {
- name: 'FixedTableDialog',
- props: {
- visible: {
- type: Boolean,
- default: false,
- },
- surveyData: {
- type: Object,
- default: () => ({}),
- },
- // 表格数据配置
- // 格式: [
- // {
- // id: '1',
- // itemName: '班级数',
- // unit: '个',
- // isCategory: false,
- // parentId: null,
- // categoryId: 'III',
- // seq: 1,
- // validateRules: {
- // required: true,
- // type: 'number',
- // min: 0,
- // },
- // linkageRules: {
- // parent: 'III',
- // relation: 'sum',
- // },
- // },
- // {
- // id: 'III',
- // itemName: '在取做保职工总人数',
- // unit: '人',
- // isCategory: true,
- // categorySeq: 'III',
- // children: [...],
- // }
- // ]
- tableItems: {
- type: Array,
- default: () => [],
- },
- // 监审期间(年份数组)
- // 格式: ['2022', '2023', '2024']
- auditPeriods: {
- type: Array,
- default: () => [],
- },
- // 立项信息中的监审期间(优先使用)- 数组
- projectAuditPeriods: {
- type: Array,
- default: () => [],
- },
- // 立项信息中的监审期间(字符串,如 '2022,2023,2024' 或 '2022-2024')
- projectAuditPeriod: {
- type: String,
- default: '',
- },
- // 是否查看模式
- isViewMode: {
- type: Boolean,
- default: false,
- },
- // 被监审单位ID
- auditedUnitId: {
- type: String,
- default: '',
- },
- // 上传记录ID
- uploadId: {
- type: String,
- default: '',
- },
- // 成本调查表模板ID
- surveyTemplateId: {
- type: String,
- default: '',
- },
- // 目录ID
- catalogId: {
- type: String,
- default: '',
- },
- // 统一控制接口 type(1=成本调查表,2=报送资料)
- requestType: {
- type: [String, Number],
- default: 1,
- },
- // 任务ID
- taskId: {
- type: [String, Number],
- default: '',
- },
- },
- data() {
- return {
- dialogVisible: false,
- tableData: [],
- yearColumns: [],
- validationErrors: [],
- cellCodeIndex: {}, // cellCode -> row
- dependentsMap: {}, // cellCode -> Set(rows depending on it)
- }
- },
- watch: {
- visible: {
- async handler(newVal) {
- this.dialogVisible = newVal
- if (newVal) {
- // 先初始化表格数据
- this.initTableData()
- // 等待 DOM 更新后,如果有 uploadId,调用接口获取详情数据并回显
- await this.$nextTick()
- this.loadDetailData()
- }
- },
- immediate: true,
- },
- dialogVisible(newVal) {
- if (!newVal) {
- this.$emit('update:visible', false)
- }
- },
- tableItems: {
- handler() {
- if (this.dialogVisible) {
- this.initTableData()
- }
- },
- deep: true,
- },
- auditPeriods: {
- handler() {
- if (this.dialogVisible) {
- this.initYearColumns()
- this.initTableData()
- }
- },
- deep: true,
- },
- projectAuditPeriods: {
- handler() {
- if (this.dialogVisible) {
- this.initYearColumns()
- this.initTableData()
- }
- },
- deep: true,
- },
- projectAuditPeriod(newVal) {
- if (this.dialogVisible) {
- this.initYearColumns()
- this.initTableData()
- }
- },
- },
- mounted() {
- this.initYearColumns()
- },
- methods: {
- // 初始化年份列
- initYearColumns() {
- // 1) 优先使用立项信息中的监审期间(数组)
- if (this.projectAuditPeriods && this.projectAuditPeriods.length > 0) {
- this.yearColumns = this.projectAuditPeriods.map((period) => {
- if (typeof period === 'string' && period.includes('-')) {
- return period.split('-')[0]
- }
- return String(period)
- })
- } else if (this.projectAuditPeriod) {
- // 2) 立项信息中的监审期间(字符串)
- const periods = this.parseAuditPeriod(this.projectAuditPeriod)
- this.yearColumns = periods
- } else if (this.auditPeriods && this.auditPeriods.length > 0) {
- // 3) 组件入参的监审期间(数组)
- // 如果传入了监审期间,使用监审期间
- this.yearColumns = this.auditPeriods.map((period) => {
- // 如果是日期格式,提取年份
- if (typeof period === 'string' && period.includes('-')) {
- return period.split('-')[0]
- }
- return String(period)
- })
- } else if (this.surveyData && this.surveyData.auditPeriod) {
- // 如果从 surveyData 中获取监审期间
- const periods = this.parseAuditPeriod(this.surveyData.auditPeriod)
- this.yearColumns = periods
- } else {
- // 默认使用最近3年
- const currentYear = new Date().getFullYear()
- this.yearColumns = [
- String(currentYear - 2),
- String(currentYear - 1),
- String(currentYear),
- ]
- }
- },
- // 解析监审期间字符串(如 "2022,2023,2024" 或 "2022-2024")
- parseAuditPeriod(periodStr) {
- if (!periodStr) return []
- if (periodStr.includes(',')) {
- return periodStr.split(',').map((p) => p.trim())
- }
- if (periodStr.includes('-')) {
- const parts = periodStr.split('-')
- if (parts.length === 2) {
- const start = parseInt(parts[0].trim())
- const end = parseInt(parts[1].trim())
- const years = []
- for (let year = start; year <= end; year++) {
- years.push(String(year))
- }
- return years
- }
- }
- return [String(periodStr)]
- },
- // 初始化表格数据
- initTableData() {
- console.log(this.isViewMode, '只读')
- if (!this.tableItems || this.tableItems.length === 0) {
- // 如果没有传入数据,使用假数据
- this.tableData = this.getMockTableData()
- return
- }
- // 根据 parentid 关系排列:parentid 为 '-1' 的是父项,子项的 parentid 等于父项的 rowid/id
- const flatData = []
- const processedIds = new Set()
- // 先处理所有父项(parentid 为 '-1')
- this.tableItems.forEach((item) => {
- const parentid = item.parentid
- const rowid = item.rowid || item.id
- if (parentid === '-1' || parentid === -1) {
- const rowData = this.createRowData(item, false, rowid)
- flatData.push(rowData)
- processedIds.add(rowid)
- }
- })
- // 再处理所有子项,紧跟在父项后面,按正序排列
- // 先收集所有子项,按序号或顺序排序
- const childItems = this.tableItems.filter((item) => {
- const rowid = item.rowid || item.id
- const parentid = item.parentid
- return (
- !processedIds.has(rowid) && parentid !== '-1' && parentid !== -1
- )
- })
- // 对子项按序号正序排序
- childItems.sort((a, b) => {
- const seqA =
- a.seq !== undefined
- ? typeof a.seq === 'number'
- ? a.seq
- : parseInt(a.seq) || 0
- : 0
- const seqB =
- b.seq !== undefined
- ? typeof b.seq === 'number'
- ? b.seq
- : parseInt(b.seq) || 0
- : 0
- return seqA - seqB
- })
- // 需要多次遍历,确保所有子项都能找到父项
- let hasUnprocessed = true
- while (hasUnprocessed && childItems.length > 0) {
- hasUnprocessed = false
- const remainingItems = []
- childItems.forEach((item) => {
- const rowid = item.rowid || item.id
- const parentid = item.parentid
- // 跳过已处理的项
- if (processedIds.has(rowid)) {
- return
- }
- // 查找父项在 flatData 中的位置
- const parentIndex = flatData.findIndex((row) => {
- const parentRowid = row.rowid || row.id
- return parentRowid === parentid
- })
- if (parentIndex >= 0) {
- // 找到父项,插入到父项后面
- // 找到父项后面最后一个子项的位置
- let insertIndex = parentIndex + 1
- while (
- insertIndex < flatData.length &&
- (flatData[insertIndex].rowid || flatData[insertIndex].id) !==
- parentid &&
- flatData[insertIndex].parentid === parentid
- ) {
- insertIndex++
- }
- const rowData = this.createRowData(item, true, rowid)
- flatData.splice(insertIndex, 0, rowData)
- processedIds.add(rowid)
- } else {
- // 如果找不到父项,标记为未处理,等待下一轮
- remainingItems.push(item)
- hasUnprocessed = true
- }
- })
- // 更新待处理的子项列表
- childItems.length = 0
- childItems.push(...remainingItems)
- }
- // 处理剩余未找到父项的项(可能是数据异常)
- this.tableItems.forEach((item) => {
- const rowid = item.rowid || item.id
- if (!processedIds.has(rowid)) {
- const rowData = this.createRowData(item, false, rowid)
- flatData.push(rowData)
- processedIds.add(rowid)
- }
- })
- this.tableData = flatData
- // 构建公式索引并计算一次
- this.buildFormulaEngine()
- this.recomputeAllFormulas()
- },
- // 创建行数据
- createRowData(item, isChild, rowid) {
- const rowData = {
- ...item,
- rowid: rowid || item.rowid || item.id,
- seq:
- item.seq !== undefined
- ? item.seq
- : isChild
- ? ''
- : item.categorySeq || item.id || '',
- isCategory: item.isCategory || false,
- }
- // 初始化年份数据
- this.yearColumns.forEach((year) => {
- rowData[`year_${year}`] = item[`year_${year}`] || ''
- })
- // 初始化备注
- rowData.remark = item.remark || ''
- // 如果有传入的数据,填充值
- if (this.surveyData && this.surveyData[rowid]) {
- const savedData = this.surveyData[rowid]
- this.yearColumns.forEach((year) => {
- if (savedData[year] !== undefined) {
- rowData[`year_${year}`] = savedData[year]
- }
- })
- if (savedData.remark !== undefined) {
- rowData.remark = savedData.remark
- }
- }
- return rowData
- },
- // 加载详情数据并回显
- async loadDetailData() {
- // 如果有 uploadId 和 auditedUnitId,调用接口获取详情数据
- const uploadId =
- this.uploadId || this.surveyData.uploadId || this.surveyData.id
- const auditedUnitId =
- this.auditedUnitId || this.surveyData.auditedUnitId
- // 只要有 uploadId 就尝试获取回显数据
- if (uploadId) {
- try {
- const params = {
- uploadId: uploadId,
- auditedUnitId: auditedUnitId,
- type: this.requestType,
- }
- const res = await getSurveyDetail(params)
- console.log('固定表详情数据', res)
- if (res && res.code === 200 && res.value) {
- // 将接口返回的数据转换为表格数据格式
- const detailData = Array.isArray(res.value)
- ? res.value
- : res.value.items || res.value.records || []
- // 按 rowid 分组数据
- const dataByRowid = {}
- detailData.forEach((item) => {
- if (item.rowid) {
- if (!dataByRowid[item.rowid]) {
- dataByRowid[item.rowid] = {}
- }
- dataByRowid[item.rowid][item.rkey] = item.rvalue
- }
- })
- // 回显数据到表格
- this.tableData.forEach((row) => {
- const rowid = row.rowid || row.id
- const rowData = dataByRowid[rowid]
- if (rowData) {
- // 回显基本信息
- if (rowData['序号'] !== undefined) {
- row.seq = rowData['序号']
- }
- if (rowData['项目'] !== undefined) {
- row.itemName = rowData['项目']
- }
- if (rowData['单位'] !== undefined) {
- row.unit = rowData['单位']
- }
- if (rowData['备注'] !== undefined) {
- row.remark = rowData['备注']
- }
- // 回显年份数据
- this.yearColumns.forEach((year) => {
- if (rowData[year] !== undefined) {
- row[`year_${year}`] = rowData[year]
- }
- })
- }
- })
- // 强制更新视图
- this.$forceUpdate()
- // 回显后根据公式重算
- this.recomputeAllFormulas()
- }
- } catch (err) {
- console.error('获取固定表详情失败', err)
- }
- }
- },
- // 获取假数据(用于测试)
- getMockTableData() {
- return [
- {
- id: '1',
- itemName: '班级数',
- unit: '个',
- isCategory: false,
- seq: 1,
- year_2022: '',
- year_2023: '',
- year_2024: '',
- remark: '',
- },
- {
- id: '2',
- itemName: '幼儿学生人数',
- unit: '人',
- isCategory: false,
- seq: 2,
- year_2022: '',
- year_2023: '',
- year_2024: '',
- remark: '',
- },
- {
- id: 'III',
- itemName: '在取做保职工总人数',
- unit: '人',
- isCategory: true,
- categorySeq: 'III',
- seq: 'III',
- },
- {
- id: '3-1',
- itemName: '行政管理人员数',
- unit: '人',
- isCategory: false,
- categoryId: 'III',
- categorySeq: 'III',
- seq: 3,
- year_2022: '',
- year_2023: '',
- year_2024: '',
- remark: '',
- },
- {
- id: '3-2',
- itemName: '教师人数',
- unit: '人',
- isCategory: false,
- categoryId: 'III',
- categorySeq: 'III',
- seq: 4,
- year_2022: '',
- year_2023: '',
- year_2024: '',
- remark: '',
- },
- {
- id: '3-3',
- itemName: '保育员人数',
- unit: '人',
- isCategory: false,
- categoryId: 'III',
- categorySeq: 'III',
- seq: 5,
- year_2022: '',
- year_2023: '',
- year_2024: '',
- remark: '',
- },
- ]
- },
- // 获取行样式类名
- getRowClassName({ row }) {
- if (row.isCategory) {
- return 'category-row'
- }
- return ''
- },
- // 单元格输入事件
- handleCellInput(row, year) {
- // 实时验证勾稽关系
- this.validateLinkage(row, year)
- // 实时联动计算
- this.recomputeDependentsForRow(row, year)
- },
- // 单元格失焦事件
- handleCellBlur(row, year) {
- // 验证格式和非空
- this.validateCell(row, year)
- // 验证勾稽关系
- this.validateLinkage(row, year)
- // 失焦后再次联动计算,确保取整/格式等影响后结果一致
- this.recomputeDependentsForRow(row, year)
- },
- // 验证单元格(非空和格式验证)
- validateCell(row, year) {
- const fieldName = `year_${year}`
- const value = row[fieldName]
- // 非空验证
- if (row.validateRules && row.validateRules.required && !value) {
- this.showFieldError(
- row,
- year,
- `${row.itemName}的${year}年数据不能为空`
- )
- return false
- }
- // 格式验证
- if (value && row.validateRules) {
- if (row.validateRules.type === 'number') {
- const numValue = Number(value)
- if (isNaN(numValue)) {
- this.showFieldError(
- row,
- year,
- `${row.itemName}的${year}年数据必须是数字`
- )
- return false
- }
- if (
- row.validateRules.min !== undefined &&
- numValue < row.validateRules.min
- ) {
- this.showFieldError(
- row,
- year,
- `${row.itemName}的${year}年数据不能小于${row.validateRules.min}`
- )
- return false
- }
- if (
- row.validateRules.max !== undefined &&
- numValue > row.validateRules.max
- ) {
- this.showFieldError(
- row,
- year,
- `${row.itemName}的${year}年数据不能大于${row.validateRules.max}`
- )
- return false
- }
- }
- }
- return true
- },
- // 验证勾稽关系
- validateLinkage(row, year) {
- if (!row.linkageRules) return true
- const { parent, relation } = row.linkageRules
- if (relation === 'sum') {
- // 验证子项之和等于父项
- const parentRow = this.tableData.find((r) => r.id === parent)
- if (!parentRow || parentRow.isCategory) return true
- const children = this.tableData.filter(
- (r) => r.categoryId === parent && !r.isCategory
- )
- const parentValue = Number(parentRow[`year_${year}`]) || 0
- const sumValue = children.reduce((sum, child) => {
- return sum + (Number(child[`year_${year}`]) || 0)
- }, 0)
- if (parentValue !== sumValue) {
- // 可以选择自动修正或提示错误
- // 这里仅提示,不自动修正
- console.warn(
- `${parentRow.itemName}的${year}年数据(${parentValue})与子项之和(${sumValue})不相等`
- )
- }
- }
- return true
- },
- // 显示字段错误
- showFieldError(row, year, message) {
- // 这里可以添加更详细的错误提示
- console.error(message)
- },
- // 保存前验证
- validateBeforeSave() {
- this.validationErrors = []
- let isValid = true
- // 验证所有单元格
- this.tableData.forEach((row) => {
- if (row.isCategory) return
- this.yearColumns.forEach((year) => {
- const cellValid = this.validateCell(row, year)
- if (!cellValid) {
- isValid = false
- this.validationErrors.push({
- row: row.itemName,
- year,
- message: `${row.itemName}的${year}年数据验证失败`,
- })
- }
- })
- // 验证勾稽关系
- this.yearColumns.forEach((year) => {
- const linkageValid = this.validateLinkage(row, year)
- if (!linkageValid) {
- isValid = false
- }
- })
- })
- // 验证所有勾稽关系
- this.validateAllLinkages()
- return isValid
- },
- // 验证所有勾稽关系
- validateAllLinkages() {
- const categories = this.tableData.filter((r) => r.isCategory)
- categories.forEach((category) => {
- const children = this.tableData.filter(
- (r) => r.categoryId === category.id && !r.isCategory
- )
- this.yearColumns.forEach((year) => {
- const categoryValue = Number(category[`year_${year}`]) || 0
- const sumValue = children.reduce((sum, child) => {
- return sum + (Number(child[`year_${year}`]) || 0)
- }, 0)
- if (categoryValue !== 0 && categoryValue !== sumValue) {
- this.validationErrors.push({
- row: category.itemName,
- year,
- message: `${category.itemName}的${year}年数据(${categoryValue})与子项之和(${sumValue})不相等`,
- })
- }
- })
- })
- },
- // 关闭弹窗
- handleClose() {
- this.dialogVisible = false
- this.$emit('update:visible', false)
- },
- // 取消
- handleCancel() {
- this.handleClose()
- },
- // 保存
- async handleSave() {
- // 验证数据
- if (!this.validateBeforeSave()) {
- const errorMessages = this.validationErrors
- .map((err) => `${err.row}的${err.year}年:${err.message}`)
- .join('\n')
- Message.error('数据验证失败:\n' + errorMessages)
- return
- }
- try {
- // 判断是否有数据(编辑模式):如果有 uploadId,说明是编辑已有数据
- const hasData = !!(
- this.uploadId ||
- this.surveyData.uploadId ||
- this.surveyData.id
- )
- // 格式化保存数据为接口需要的格式
- const saveData = []
- // 遍历所有行数据
- this.tableData.forEach((row) => {
- const rowid = row.rowid || row.id
- // 跳过分类行(如果分类行不需要保存基本信息)
- if (!row.isCategory) {
- // 保存基本信息:序号、项目、单位
- if (row.seq !== undefined && row.seq !== null && row.seq !== '') {
- saveData.push({
- rowid: rowid,
- rkey: '序号',
- rvalue: String(row.seq),
- })
- }
- if (row.itemName) {
- saveData.push({
- rowid: rowid,
- rkey: '项目',
- rvalue: String(row.itemName),
- })
- }
- if (row.unit) {
- saveData.push({
- rowid: rowid,
- rkey: '单位',
- rvalue: String(row.unit),
- })
- }
- }
- // 保存年份数据(所有行都可以有年份数据)
- 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),
- })
- }
- })
- // 保存备注(所有行都可以有备注)
- if (
- row.remark !== undefined &&
- row.remark !== null &&
- row.remark !== ''
- ) {
- saveData.push({
- rowid: rowid,
- rkey: '备注',
- rvalue: String(row.remark),
- })
- }
- })
- // 为每条数据添加公共字段
- const finalSaveData = saveData.map((item) => {
- const dataItem = {
- ...item,
- auditedUnitId:
- this.auditedUnitId || this.surveyData.auditedUnitId || '',
- surveyTemplateId:
- this.surveyTemplateId || this.surveyData.surveyTemplateId || '',
- catalogId: this.catalogId || this.surveyData.catalogId || '',
- taskId: this.taskId || this.surveyData.taskId || '',
- type: this.requestType,
- }
- // 如果有数据(编辑模式),添加 uploadId 字段
- if (hasData) {
- dataItem.uploadId =
- this.uploadId ||
- this.surveyData.uploadId ||
- this.surveyData.id ||
- ''
- }
- return dataItem
- })
- console.log('保存数据:', finalSaveData)
- // 调用保存接口
- const res = await saveSingleRecordSurvey(finalSaveData)
- if (res && res.code === 200) {
- Message.success('保存成功')
- // 触发保存事件
- this.$emit('save', finalSaveData)
- // 触发刷新事件
- this.$emit('refresh')
- this.handleClose()
- } else {
- Message.error(res.message || '保存失败')
- }
- } catch (err) {
- console.error('保存失败', err)
- // 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>
- <style scoped lang="scss">
- .dialog-footer {
- text-align: center;
- margin-top: 20px;
- .el-button {
- margin: 0 10px;
- }
- }
- ::v-deep .el-dialog__header {
- padding: 20px 20px 10px;
- .el-dialog__title {
- font-size: 18px;
- font-weight: 600;
- color: #303133;
- }
- }
- // 分类行样式
- ::v-deep .category-row {
- background-color: #f5f7fa !important;
- td {
- background-color: #f5f7fa !important;
- font-weight: bold;
- }
- .category-name {
- color: #409eff;
- font-weight: bold;
- }
- .category-seq {
- color: #409eff;
- font-weight: bold;
- }
- }
- // 表格输入框样式
- ::v-deep .el-table {
- .el-input {
- .el-input__inner {
- border: none;
- padding: 0 5px;
- text-align: center;
- &:focus {
- border: 1px solid #409eff;
- }
- }
- }
- }
- </style>
|