|
|
<template> <div v-loading="loading" class="da3"> <!-- <div class="toolbar"> <button @click="fillDemo">填入示例数据</button> <button @click="clearAll">清空</button> <button @click="exportJSON">导出当前填报 JSON</button> <button @click="exportExcel">导出 Excel</button> <span class="hint">数据驱动渲染 · 来自 template_item 等表</span> </div> -->
<table v-if="data" class="form"> <!-- 附件2 + 题名 --> <tr><td class="attach" colspan="9">{{ data.template.attach }}</td></tr> <tr><td class="title" colspan="9">{{ data.template.title }}</td></tr>
<!-- 元信息(表号/制定机关/批准机关) --> <tr v-for="(m, i) in metaTop" :key="'mt' + i" class="meta-row"> <td colspan="9" class="meta"> <div class="meta-flex"> <!-- <i class="colon">:</i> --> <span class="meta-label">{{ m.label }}</span> <i class="colon">:</i> <span class="meta-value">{{ m.value }}</span> </div> </td> </tr>
<!-- 单位信息卡 01-07(左列 01-05 + 右列 06-07 + 批准文号/有效期) --> <tr v-for="(u, i) in leftUnits" :key="'lu' + i" :class="['unit-row', { 'unit-row-bordered': i >= 2 }]"> <td class="ucode">{{ u.code }}</td> <td class="ulabel" colspan="4">{{ u.label }}:<span v-if="isView" class="plain">{{ form['u_' + u.code] }}</span><input v-else v-model="form['u_' + u.code]" type="text"></td>
<template v-if="i === 0"> <td class="meta" colspan="4"> <div class="meta-flex"> <span class="meta-label">批准文号</span> <i class="colon">:</i> <span class="meta-value">{{ data.template.approve_doc_no }}</span> </div> </td></template> <template v-else-if="i === 1"> <td class="meta" colspan="4"> <div class="meta-flex"> <span class="meta-label">有效期至</span> <i class="colon">:</i> <span class="meta-value" style="text-align: right !important; text-align-last: right !important;">{{ data.template.valid_to }}</span> </div> </td> </template> <template v-if="i === 2 && rightUnits[0]"> <td class="ucode">{{ rightUnits[0].code }}</td> <td class="ulabel" colspan="3">{{ rightUnits[0].label }}:<span v-if="isView" class="plain">{{ form['u_' + rightUnits[0].code] }}</span><input v-else v-model="form['u_' + rightUnits[0].code]" type="text"></td> </template> <template v-else-if="i === 3 && rightUnits[1]"> <td class="ucode ucell" rowspan="2">{{ rightUnits[1].code }}</td> <td class="ulabel ucell" colspan="3" rowspan="2">{{ rightUnits[1].label }}:<span v-if="isView" class="plain">{{ form['u_' + rightUnits[1].code] }}</span><input v-else v-model="form['u_' + rightUnits[1].code]" type="text"></td> </template> <template v-else-if="i === 4" /> <template v-else> <td colspan="4" /> </template> </tr>
<!-- 列头 --> <tr> <th class="colname" colspan="5">指标名称</th> <th class="colname">计量单位</th> <th class="colname">代码</th> <th class="colname">数量({{ data.template.cur_year }}年)</th> <th class="colname">上年度数据({{ data.template.prev_year }}年)</th> </tr> <tr> <th class="colsub" colspan="5">甲</th> <th class="colsub">乙</th> <th class="colsub">丙</th> <th class="colsub">1</th> <th class="colsub" /> </tr>
<!-- 主体 --> <template v-for="(it, idx) in data.body"> <!-- 章节 / 分组 --> <tr v-if="it.type === 'section' || it.type === 'group'" :key="'s' + idx" :class="it.type + ' body-row'"> <td class="nm" colspan="5" :style="{ paddingLeft: (it.level * 1.6) + 'em' }">{{ it.name }}</td> <td class="dash">{{ it.unit || '—' }}</td> <td class="dash">—</td> <td class="dash">—</td> <td class="dash" /> </tr> <!-- 指标 / 续行 --> <tr v-if="it.type !== 'section' && it.type !== 'group'" :key="'i' + idx" class="body-row"> <td class="nm" colspan="5" :style="{ paddingLeft: (it.level * 1.6) + 'em' }">{{ it.name }}</td> <td class="unit">{{ it.unit }}</td> <td class="code">{{ it.code }}</td> <td class="data"> <span v-if="isView" class="plain">{{ form[it.code + '_c'] }}</span> <select v-else-if="it.input === 'yesno'" v-model="form[it.code + '_c']"> <option value="" /><option value="是">是</option><option value="否">否</option> </select> <input v-else-if="it.input === 'text'" v-model="form[it.code + '_c']" type="text"> <input v-else v-model="form[it.code + '_c']" type="text" inputmode="decimal"> </td> <td class="data"> <span v-if="isView" class="plain">{{ form[it.code + '_p'] }}</span> <select v-else-if="it.input === 'yesno'" v-model="form[it.code + '_p']"> <option value="" /><option value="是">是</option><option value="否">否</option> </select> <input v-else-if="it.input === 'text'" v-model="form[it.code + '_p']" type="text"> <input v-else v-model="form[it.code + '_p']" type="text" inputmode="decimal"> </td> </tr> </template>
<!-- 页脚签章 --> <tr class="footer-row"> <!-- 单位负责人 col1-4 --> <td colspan="4" class="footer-cell"> <span class="footer-label">单位负责人:</span> <span v-if="isView" class="plain">{{ footerForm.unitHead }}</span> <input v-else v-model="footerForm.unitHead" type="text" class="footer-input"> </td> <!-- 统计负责人 col5 --> <td class="footer-cell"> <span class="footer-label">统计负责人:</span> <span v-if="isView" class="plain">{{ footerForm.statsHead }}</span> <input v-else v-model="footerForm.statsHead" type="text" class="footer-input"> </td> <!-- 填表人 col6 --> <td class="footer-cell"> <span class="footer-label">填表人:</span> <span v-if="isView" class="plain">{{ footerForm.filler }}</span> <input v-else v-model="footerForm.filler" type="text" class="footer-input"> </td> <!-- 报出时间 col7-9 --> <td colspan="3" class="footer-cell"> <span class="footer-label">报出时间:</span> <span v-if="isView" class="plain">{{ footerForm.submitTime }}</span> <input v-else v-model="footerForm.submitTime" type="text" class="footer-input footer-input-wide" placeholder="20 年 月 日"> </td> </tr> </table>
<!-- 编辑模式:底部固定保存栏 --> <!-- 查看模式工具栏 --> <div v-if="isView" class="save-bar"> <el-button @click="exportExcel">导出Excel</el-button> <el-button @click="handleCancel">关闭</el-button> </div> <!-- 编辑模式工具栏 --> <div v-if="!isView" class="save-bar"> <el-button class="saveetch-btn" type="primary" :loading="refetchLoading" @click="handleRefetch">重新获取统计数据</el-button> <el-button @click="exportExcel">导出Excel</el-button> <el-button @click="handleCancel">取消</el-button> <el-button class="save-btn" type="primary" :loading="saving" @click="handleSave">保存</el-button> </div> </div></template>
<script>import { FetchInitStatisticsTemplate, FetchRecalculateArchivesDoReport, FetchEditArchivesReportDetails } from '@/api/statistics/statistics'import ExcelJS from 'exceljs'import { saveAs } from 'file-saver'import { mapGetters } from 'vuex'
export default { name: 'Da3Report', data() { return { form: {}, // 接口返回的报表模型
remoteModel: null, // 接口返回的原始数据(保存时按同结构回传)
rawReport: null, loading: false, saving: false, refetchLoading: false, // 页脚签章:单位负责人 / 统计负责人 / 填表人 / 报出时间
footerForm: { unitHead: '', statsHead: '', filler: '', submitTime: '' } } }, computed: { ...mapGetters(['user']), data() { return this.remoteModel }, // 查看模式(不可输入)还是编辑模式(可输入),默认查看
isView() { return this.$route.query.mode !== 'edit' }, leftUnits() { if (!this.data) return [] return this.data.units.filter(u => u.card === 1) // 01-05
}, rightUnits() { if (!this.data) return [] return this.data.units.filter(u => u.card === 2) // 06-07
}, metaTop() { if (!this.data) return [] return [ { label: '表号', value: this.data.template.form_no }, { label: '制定机关', value: this.data.template.issuing_org }, { label: '批准机关', value: this.data.template.approving_org } // { label: '批准文号', value: this.data.template.approve_doc_no },
// { label: '有效期至', value: this.data.template.valid_to }
] } }, watch: { data: { handler(d) { if (!d) return this.initForm(d) // 接口模型自带填报值时回显
if (d.formData) this.fillFormData(d.formData) // 回显页脚签章
if (d.footerForm) { Object.keys(this.footerForm).forEach(k => { this.footerForm[k] = d.footerForm[k] != null ? d.footerForm[k] : '' }) } }, immediate: true } }, created() { this.fetchReport() }, beforeDestroy() { sessionStorage.removeItem('da3_fondsName') }, methods: { fetchReport() { const { reportingYear, unitId, templateId } = this.$route.query
this.loading = true FetchInitStatisticsTemplate({ reportingYear, unitId, templateId: Number(templateId) || 1 }).then(res => { console.log('[Da3Report] FetchInitStatisticsTemplate res:', res) if (res) { // 留存接口原始数据,保存时按同结构回传
this.rawReport = res // 将接口结构(顶层表头字段 + rows 树形嵌套)归一化为组件模型 { template/units/body/footer/formData }
const model = this.normalizeModel(res) if (model) { // remoteModel 变化会触发 watch(data):initForm 重建表单后自动用 formData 回填
this.remoteModel = model } } }).catch(() => { // 接口异常时兜底展示 mock 数据
// this.fillDemo()
}).finally(() => { this.loading = false this.refetchLoading = false }) }, // 将接口返回结构归一化为组件内置模型结构:{ template, units, body, footer, formData }
normalizeModel(res) { if (!res || !Array.isArray(res.rows)) return null
// 1. rows 是 children 树形嵌套,先递归拍平,再按 displayOrder 升序还原真实展示顺序
const flat = [] const walk = list => { list.forEach(r => { flat.push(r) if (r.children && r.children.length) walk(r.children) }) } walk(res.rows) flat.sort((a, b) => (a.displayOrder || 0) - (b.displayOrder || 0))
// 2. 01-07 单位信息填报值(顶层字段优先,row.valCurText 兜底)
const unitValues = { '01': res.unitCreditCode, '02': res.unitName, '03': res.unitCategoryCode, '04': res.postalCode, '05': res.unitAddress, '06': res.telephone, '07': res.orgSituation }
const units = [] const body = [] const formData = { units: {}, data: {}} const leftCards = ['01', '02', '03', '04', '05'] // 填报值:优先按 inputType 取对应字段,没有则取另一个字段,都没有则为空
const pickVal = (r, which) => { const t = r.inputType if (which === 'cur') { // 本年:code/number 优先 valCurNum,text 优先 valCurText
if (t === 'code' || t === 'number') { if (r.valCurNum != null) return r.valCurNum if (r.valCurText != null) return r.valCurText } else { if (r.valCurText != null) return r.valCurText if (r.valCurNum != null) return r.valCurNum } } else { // 上年:code/number 优先 valPrevNum,text 优先 valPrevText
if (t === 'code' || t === 'number') { if (r.valPrevNum != null) return r.valPrevNum if (r.valPrevText != null) return r.valPrevText } else { if (r.valPrevText != null) return r.valPrevText if (r.valPrevNum != null) return r.valPrevNum } } return '' }
flat.forEach(r => { // 章节(section)或顶级叶子指标(如 82/95/96/97/109)需把中文序号拼到名称前:一、人员情况 / 五、检索工具
const needPrefix = (r.itemType === 'section' || (r.isLeaf && r.level === 0)) && r.sectionNo && r.sectionNo !== '表头' const fullName = needPrefix ? r.sectionNo + '、' + r.name : r.name
// 3. 表头单位信息 01-07
if (r.itemType === 'unit') { units.push({ code: r.code, label: r.name, card: leftCards.indexOf(r.code) !== -1 ? 1 : 2, kind: r.inputType === 'code' ? 'code' : 'text' }) const curVal = pickVal(r, 'cur') formData.units[r.code] = unitValues[r.code] != null ? unitValues[r.code] : (curVal != null && curVal !== '' ? curVal : (r.valCurText || r.valCurNum || '')) return }
// 4. 主体行:section / group / indicator / continuation
let type if (r.itemType === 'section') { type = 'section' } else if (r.isGroup) { type = 'group' } else if (r.name === '(续行)' || r.name === '') { type = 'continuation' } else { type = 'indicator' }
const item = { type, code: r.code || '', name: type === 'continuation' ? '' : fullName, unit: (type === 'section' || type === 'group') ? (r.unit || '—') : (r.unit || (r.inputType === 'yesno' ? '—' : '')), level: r.level }
if (type === 'indicator' || type === 'continuation') { item.input = r.inputType === 'yesno' ? 'yesno' : (r.inputType || 'number') const cur = pickVal(r, 'cur') const prev = pickVal(r, 'prev') formData.data[r.code] = { cur, prev } } body.push(item) })
// 5. 模板表头元信息
const template = { attach: '附件2', title: res.formTitle, form_no: res.formNo, issuing_org: res.issuingOrg, approving_org: res.approvingOrg, approve_doc_no: res.approveDocNo, valid_to: res.validTo || '', cur_year: res.reportingYear, prev_year: res.prevYear }
// 6. 页脚签章(值取自顶层字段,同时存入 formData 用于回填)
const footerFormData = { unitHead: res.unitHead || '', statsHead: res.statsHead || '', filler: res.filler || '', submitTime: res.submitTime || '' } const footer = [ { col: 1, label: '单位负责人:' + footerFormData.unitHead }, { col: 5, label: '统计负责人:' + footerFormData.statsHead }, { col: 6, label: '填表人:' + footerFormData.filler }, { col: 8, label: '报出时间:' + footerFormData.submitTime } ]
return { template, units, body, footer, formData, footerForm: footerFormData } }, // 回填填报值:{ u_01: 'xx', '08_c': '12' } 或 { units: {01:'xx'}, data: {08:{cur:'12'}} }
fillFormData(formData) { console.log('fillFormData', formData) if (!formData) return if (formData.units) { Object.keys(formData.units).forEach(code => { this.$set(this.form, 'u_' + code, formData.units[code]) }) } if (formData.data) { Object.keys(formData.data).forEach(code => { const item = formData.data[code] || {} this.$set(this.form, code + '_c', item.cur) this.$set(this.form, code + '_p', item.prev) }) } // 回填页脚签章(formData.footer 或 d.footerForm 都会走到这里)
if (formData.footer) { Object.keys(this.footerForm).forEach(k => { if (formData.footer[k] != null) this.footerForm[k] = formData.footer[k] }) } Object.keys(formData).forEach(k => { if (Object.prototype.hasOwnProperty.call(this.form, k)) { this.$set(this.form, k, formData[k]) } }) }, initForm(d) { if (!d || !Array.isArray(d.units) || !Array.isArray(d.body)) { this.form = {} return } const form = {} d.units.forEach(u => { form['u_' + u.code] = '' }) d.body.forEach(it => { if (it.type === 'indicator' || it.type === 'continuation') { form[it.code + '_c'] = '' // 本年
form[it.code + '_p'] = '' // 上年
} }) this.form = form }, fillDemo() { const DEMO = { u_01: '91420100MA4KXXXXXX', u_02: '阅行客(武汉)科技有限公司', u_03: '130', u_04: '430000', u_05: '武汉市xx区xx路1号', u_06: '027-88888888', u_07: '独立档案室', '08_c': '12', '08_p': '10', '09_c': '8', '09_p': '7', '10_c': '2', '11_c': '5', '12_c': '5', '34_c': '1200', '35_c': '35000', '36_c': '42.5', '37_c': '800', '38_c': '600', '39_c': '120', '82_c': '5', '95_c': '是', '96_c': '否', '97_c': '否', '109_c': '1' } Object.assign(this.form, DEMO) }, clearAll() { Object.keys(this.form).forEach(k => { this.form[k] = '' }) }, buildPayload() { const units = {}; const reportData = {} this.data.units.forEach(u => { units[u.code] = this.form['u_' + u.code] || '' }) this.data.body.forEach(it => { if (it.type === 'indicator' || it.type === 'continuation') { reportData[it.code] = { cur: this.form[it.code + '_c'] || '', prev: this.form[it.code + '_p'] || '' } } }) return { units, data: reportData, footer: { ...this.footerForm }} }, exportJSON() { const payload = this.buildPayload() console.log(JSON.stringify(payload, null, 2)) this.$emit('export', payload) // alert('已导出到控制台(F12),并通过 emit("export") 回传父组件')
}, // 导出 Excel
async exportExcel() { if (!this.data) { this.$message.warning('数据未加载完成,请稍后') return }
const wb = new ExcelJS.Workbook() const ws = wb.addWorksheet('档案室基本情况表')
// 9 列布局:A-E 指标名称(合并) F计量单位 G代码 H本年 I上年
ws.columns = [ { width: 4 }, // A
{ width: 4 }, // B
{ width: 4 }, // C
{ width: 4 }, // D
{ width: 4 }, // E
{ width: 10 }, // F - 计量单位
{ width: 8 }, // G - 代码
{ width: 14 }, // H - 本年
{ width: 14 } // I - 上年
]
// ===== 第1行:附件2 =====
ws.getCell('A1').value = '附件2' ws.getCell('A1').font = { size: 12 } ws.getCell('A1').alignment = { horizontal: 'left', vertical: 'middle' } ws.getRow(1).height = 18
// ===== 第2行:标题 =====
ws.mergeCells('A2:I2') const titleCell = ws.getCell('A2') titleCell.value = this.data.template.title titleCell.font = { size: 22, bold: true, name: '黑体' } titleCell.alignment = { horizontal: 'center', vertical: 'middle' } ws.getRow(2).height = 35
// ===== 第3行:表号(右对齐) =====
ws.getCell('I3').value = `表号:${this.data.template.form_no}` ws.getCell('I3').font = { size: 10 } ws.getCell('I3').alignment = { horizontal: 'right', vertical: 'middle' } ws.getRow(3).height = 16
// ===== 第4行:制定机关(右对齐) =====
ws.getCell('I4').value = `制定机关:${this.data.template.issuing_org}` ws.getCell('I4').font = { size: 10 } ws.getCell('I4').alignment = { horizontal: 'right', vertical: 'middle' } ws.getRow(4).height = 16
// ===== 第5行:批准机关(右对齐) =====
ws.getCell('I5').value = `批准机关:${this.data.template.approving_org}` ws.getCell('I5').font = { size: 10 } ws.getCell('I5').alignment = { horizontal: 'right', vertical: 'middle' } ws.getRow(5).height = 16
// ===== 第6-10行:单位信息(左右两栏) =====
const units = this.data.units || [] // const leftUnits = units.filter(u => u.card === 1) // 01-05
// const rightUnits = units.filter(u => u.card === 2) // 06-07
const unitValueMap = {} units.forEach(u => { unitValueMap[u.code] = this.form['u_' + u.code] || '' })
// 左侧 01-05(A-E列),右侧 06-07(F-I列)
const leftRows = [ { code: '01', label: '统一社会信用代码:' }, { code: '02', label: '单位名称:' }, { code: '03', label: '单位类别代码:' }, { code: '04', label: '邮政编码:' }, { code: '05', label: '单位地址:' } ] const rightRows = [ { code: '06', label: '电话:' }, { code: '07', label: '机构情况:' } ]
leftRows.forEach((item, idx) => { const r = 6 + idx // 左侧代码 A列
ws.getCell(`A${r}`).value = item.code ws.getCell(`A${r}`).font = { size: 10, bold: true } ws.getCell(`A${r}`).alignment = { horizontal: 'center', vertical: 'middle' } // 左侧标签 B-E列
ws.mergeCells(`B${r}:E${r}`) ws.getCell(`B${r}`).value = item.label + unitValueMap[item.code] ws.getCell(`B${r}`).font = { size: 10 } ws.getCell(`B${r}`).alignment = { horizontal: 'left', vertical: 'middle' } ws.getRow(r).height = 16 })
// 右侧 06-07 从第8行开始
rightRows.forEach((item, idx) => { const r = 8 + idx ws.getCell(`F${r}`).value = item.code ws.getCell(`F${r}`).font = { size: 10, bold: true } ws.getCell(`F${r}`).alignment = { horizontal: 'center', vertical: 'middle' } ws.mergeCells(`G${r}:I${r}`) ws.getCell(`G${r}`).value = item.label + unitValueMap[item.code] ws.getCell(`G${r}`).font = { size: 10 } ws.getCell(`G${r}`).alignment = { horizontal: 'left', vertical: 'middle' } })
// 批准文号、有效期至(右侧第6、7行)
ws.getCell('I6').value = `批准文号:${this.data.template.approve_doc_no}` ws.getCell('I6').font = { size: 10 } ws.getCell('I6').alignment = { horizontal: 'right', vertical: 'middle' } ws.getCell('I7').value = `有效期至:${this.data.template.valid_to}` ws.getCell('I7').font = { size: 10 } // 第7行 06 上方加当前年报年份
ws.getCell('G7').value = `${this.data.template.cur_year}年` ws.getCell('G7').font = { size: 10 } ws.getCell('G7').alignment = { horizontal: 'center', vertical: 'middle' } ws.getCell('I7').alignment = { horizontal: 'right', vertical: 'middle' }
// ===== 第11行:表头(指标名称合并A-E) =====
ws.mergeCells('A11:E11') ws.getCell('A11').value = '指标名称' ws.getCell('F11').value = '计量单位' ws.getCell('G11').value = '代码' ws.getCell('H11').value = `数量(${this.data.template.cur_year}年)` ws.getCell('I11').value = `上年度数据(${this.data.template.prev_year}年)` const headerRow = ws.getRow(11) for (let c = 1; c <= 9; c++) { const cell = headerRow.getCell(c) cell.font = { size: 10, bold: true } cell.alignment = { horizontal: 'center', vertical: 'middle' } cell.border = { top: { style: 'thin' }, bottom: { style: 'thin' }, left: { style: 'thin' }, right: { style: 'thin' } } } ws.getRow(11).height = 20
// ===== 第12行:甲乙丙1 =====
ws.mergeCells('A12:E12') ws.getCell('A12').value = '甲' ws.getCell('F12').value = '乙' ws.getCell('G12').value = '丙' ws.getCell('H12').value = '1' ws.getCell('I12').value = '' const subHeaderRow = ws.getRow(12) for (let c = 1; c <= 9; c++) { const cell = subHeaderRow.getCell(c) cell.font = { size: 10 } cell.alignment = { horizontal: 'center', vertical: 'middle' } cell.border = { top: { style: 'thin' }, bottom: { style: 'thin' }, left: { style: 'thin' }, right: { style: 'thin' } } } ws.getRow(12).height = 18
// ===== 数据行:从第13行开始 =====
const body = this.data.body || [] let rowIdx = 13
body.forEach(it => { const row = ws.getRow(rowIdx) const indent = ' '.repeat(it.level || 0) const name = it.type === 'section' || it.type === 'group' ? it.name : indent + it.name
// A-E 合并为指标名称
ws.mergeCells(`A${rowIdx}:E${rowIdx}`) row.getCell(1).value = name row.getCell(6).value = it.unit || '—' // F - 计量单位
row.getCell(7).value = it.code || '' // G - 代码
row.getCell(8).value = this.form[it.code + '_c'] || '' // H - 本年
row.getCell(9).value = this.form[it.code + '_p'] || '' // I - 上年
// 样式
row.font = { size: 10 } row.getCell(1).alignment = { horizontal: 'left', vertical: 'middle' } row.getCell(6).alignment = { horizontal: 'center', vertical: 'middle' } row.getCell(7).alignment = { horizontal: 'center', vertical: 'middle' } row.getCell(8).alignment = { horizontal: 'center', vertical: 'middle' } row.getCell(9).alignment = { horizontal: 'center', vertical: 'middle' }
// section/group 加粗
if (it.type === 'section' || it.type === 'group') { row.font = { size: 10, bold: true } }
// 边框(A-I 共9列)
for (let c = 1; c <= 9; c++) { row.getCell(c).border = { top: { style: 'thin' }, bottom: { style: 'thin' }, left: { style: 'thin' }, right: { style: 'thin' } } }
row.height = 16 rowIdx++ })
// ===== 页脚:单位负责人 | 统计负责人 | 填表人 | 报出时间 =====
rowIdx++ // 单位负责人(A-D 合并)
ws.mergeCells(`A${rowIdx}:D${rowIdx}`) const unitHeadCell = ws.getCell(`A${rowIdx}`) unitHeadCell.value = '单位负责人:' + (this.footerForm.unitHead || '') unitHeadCell.font = { size: 10 } unitHeadCell.alignment = { horizontal: 'left', vertical: 'middle' }
// 统计负责人(E 列)
const statsHeadCell = ws.getCell(`E${rowIdx}`) statsHeadCell.value = '统计负责人:' + (this.footerForm.statsHead || '') statsHeadCell.font = { size: 10 } statsHeadCell.alignment = { horizontal: 'left', vertical: 'middle' }
// 填表人(F-G 合并)
ws.mergeCells(`F${rowIdx}:G${rowIdx}`) const fillerCell = ws.getCell(`F${rowIdx}`) fillerCell.value = '填表人:' + (this.footerForm.filler || '') fillerCell.font = { size: 10 } fillerCell.alignment = { horizontal: 'left', vertical: 'middle' }
// 报出时间(H-I 合并)
ws.mergeCells(`H${rowIdx}:I${rowIdx}`) const submitTimeCell = ws.getCell(`H${rowIdx}`) submitTimeCell.value = '报出时间:' + (this.footerForm.submitTime || '') submitTimeCell.font = { size: 10 } submitTimeCell.alignment = { horizontal: 'left', vertical: 'middle' }
const footerRow = ws.getRow(rowIdx) footerRow.height = 20
// 生成并下载
const buffer = await wb.xlsx.writeBuffer() const fondsName = sessionStorage.getItem('da3_fondsName') || (this.rawReport && this.rawReport.fondsName) || this.form.u_02 || '' const fileName = `${this.data.template.title}_${this.data.template.cur_year}年_${fondsName}.xlsx` saveAs(new Blob([buffer]), fileName) }, // 保存:基于接口原始结构回填用户输入(数值也写入 text 字段,与回显口径一致)
handleSave() { if (!this.rawReport) { this.$message.warning('报表数据加载中,请稍后再试') return } const payload = JSON.parse(JSON.stringify(this.rawReport))
// 01-07 单位信息回填到顶层字段
const unitFieldMap = { '01': 'unitCreditCode', '02': 'unitName', '03': 'unitCategoryCode', '04': 'postalCode', '05': 'unitAddress', '06': 'telephone', '07': 'orgSituation' } Object.keys(unitFieldMap).forEach(code => { const v = this.form['u_' + code] payload[unitFieldMap[code]] = v === '' || v == null ? null : v })
// 指标行回填:inputType 为 'code'/'number' 写 valCurNum/valPrevNum,'text' 写 valCurText/valPrevText
const walk = list => { (list || []).forEach(r => { // 单位信息行(01-07)回填到 rows 对应节点
if (r.itemType === 'unit' && r.code) { const v = this.form['u_' + r.code] const val = v === '' || v == null ? null : v if (r.inputType === 'code' || r.inputType === 'number') { const num = Number(val) if (val != null && !isNaN(num)) { r.valCurNum = num } else { r.valCurText = val != null ? String(val) : null } } else { r.valCurText = val != null ? String(val) : null } } if (r.itemType === 'indicator' && r.isLeaf && r.code) { const cur = this.form[r.code + '_c'] const prev = this.form[r.code + '_p'] const curVal = cur === '' || cur == null ? null : cur const prevVal = prev === '' || prev == null ? null : prev if (r.inputType === 'code' || r.inputType === 'number') { r.valCurNum = curVal != null ? Number(curVal) : null r.valPrevNum = prevVal != null ? Number(prevVal) : null } else { r.valCurText = curVal != null ? String(curVal) : null r.valPrevText = prevVal != null ? String(prevVal) : null } } if (r.children && r.children.length) walk(r.children) }) } walk(payload.rows)
// 拍平 rows,只保留接口需要的字段
const flatList = [] const flatten = list => { (list || []).forEach(r => { if (r.code) { flatList.push({ indicatorCode: r.code, reportId: payload.reportId != null ? payload.reportId : null, valCurNum: r.valCurNum != null ? r.valCurNum : null, valCurText: r.valCurText != null ? r.valCurText : null, valPrevNum: r.valPrevNum != null ? r.valPrevNum : null, valPrevText: r.valPrevText != null ? r.valPrevText : null }) } if (r.children && r.children.length) flatten(r.children) }) } flatten(payload.rows)
// 组装最终传参
const savePayload = { reportId: payload.reportId != null ? payload.reportId : null, unitCreditCode: this.form.u_01 || null, unitName: this.form.u_02 || null, unitCategoryCode: this.form.u_03 || null, postalCode: this.form.u_04 || null, unitAddress: this.form.u_05 || null, telephone: this.form.u_06 || null, orgSituation: this.form.u_07 || null, unitHead: this.footerForm.unitHead || null, statsHead: this.footerForm.statsHead || null, filler: this.footerForm.filler || null, submitTime: this.footerForm.submitTime || null, reportDataList: flatList }
this.saving = true console.log('payload', savePayload) FetchEditArchivesReportDetails(savePayload).then((res) => { console.log('保存成功', res) if (res.code !== 500) { this.$message({ message: res, type: 'success', offset: 80 }) this.fetchReport() } else { this.$message({ message: res.message || '保存失败', type: 'error', offset: 80 }) } }).catch(() => { this.$message({ message: '保存失败', type: 'error', offset: 80 }) }).finally(() => { this.saving = false }) }, // 重新获取统计数据
handleRefetch() { this.refetchLoading = true FetchRecalculateArchivesDoReport(this.rawReport.reportId).then((res) => { console.log('重新计算成功', res) if (res.code !== 500) { // 根据 value{code}_{1|2} 回填表单:1=本年,2=上年
if (res && typeof res === 'object') { Object.keys(res).forEach(key => { const m = key.match(/^value(\d+)_(1|2)$/) if (m) { const code = m[1] const type = m[2] // 1=本年 2=上年
const val = res[key] if (type === '1') { this.$set(this.form, code + '_c', val) } else { this.$set(this.form, code + '_p', val) } } }) } this.$message({ message: '重新计算成功', type: 'success', offset: 80 }) } else { this.$message({ message: res.message || '重新计算失败', type: 'error', offset: 80 }) } }).finally(() => { this.refetchLoading = false }) }, // 取消:点击后关闭当前路由
handleCancel() { sessionStorage.removeItem('da3_fondsName') window.close() } }}</script>
<style scoped>* { box-sizing: border-box; }.da3 { font-family: "SimSun", "宋体", serif; color: #000; background: #fff; padding: 20px 20px 76px; }/* 编辑模式底部固定保存栏 */.save-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 2000; display: flex; justify-content: flex-end; align-items: center; padding: 10px 28px; background: #fff; border-top: 1px solid #e4e7ed; box-shadow: 0 -2px 8px rgba(0, 0, 0, .06);}.save-bar .el-button.save-btn,.save-bar .el-button.saveetch-btn { min-width: 88px; color: #fff; background-color: #0348f3; border-color: #0348f3; }.toolbar { max-width: 1040px; margin: 0 auto 12px; font-size: 13px; color: #444; }.toolbar button { font-family: inherit; padding: 4px 10px; margin-right: 8px; cursor: pointer; }.toolbar .hint { color: #888; }table.form { width: 1000px; margin: 0 auto; border-collapse: collapse; background: #fff; border: hidden;}/* 标题、附件、元信息区无边框 */table.form .attach, table.form .title,table.form .meta-row td { border: none;}/* 01-02 无边框 */table.form .unit-row td { border: none;}/* 03-07 有边框 */table.form .unit-row-bordered td { border: 1px solid #000;}/* 列头 + 数据区有边框 */table.form .colname, table.form .colsub,table.form .body-row td { border-right: 1px solid #000;}table.form .colname:last-child,table.form .colsub:last-child,table.form .body-row td:last-child { border-right: none;}table.form td, table.form th { padding: 2px 6px; vertical-align: middle; font-size: 13px; line-height: 1.6; }.attach { font-size: 12px; text-align: left; }table.form td.title { font-size: 30px !important; font-weight: bold; text-align: center; letter-spacing: 6px; padding: 12px 0; font-family: "SimHei", "黑体", sans-serif; }.meta { font-size: 12px; white-space: nowrap; }.meta-flex { display: flex; justify-content: flex-end; align-items: baseline; width: 210px; margin-left: auto; }.meta-label { display: block; width: 60px; text-align: justify; text-align-last: justify; }.meta-flex .colon { font-style: normal; }.meta-value { display: block; flex: 1; margin-left: 5px; text-align: justify; text-align-last: justify; word-break: break-all; }.ucode { text-align: center; width: 30px; font-weight: bold; white-space: nowrap; }.ulabel { font-size: 13px; white-space: nowrap; border-right: none !important; }.ulabel input { width: auto; min-width: 200px; border: none; border: 1px solid #ccc ; font-family: inherit; font-size: 13px; padding: 2px; outline: none; }.check-line { text-align: left; font-size: 13px; line-height: 1.8; }.check-cell { white-space: nowrap; border-right: none !important; }.colname { font-weight: bold; text-align: center; background: #fff; border-top: 1px solid #000; border-bottom: 1px solid #000; white-space: nowrap; }.colsub { text-align: center; font-size: 12px; color: #000; font-weight: normal; border-bottom: 1px solid #000; white-space: nowrap; }.section td { font-weight: bold; }.nm { white-space: pre; }.unit { text-align: center; width: 60px; white-space: nowrap; }.code { text-align: center; width: 42px; white-space: nowrap; }.data { width: 160px; text-align: center; white-space: nowrap; }.data input, .data select { width: 96%; border: none; border: 1px solid #ccc; padding: 4px 0; font-family: inherit; font-size: 13px; text-align: center; outline: none; }/* 查看模式纯文本展示 */.plain { display: inline-block; min-width: 60px; min-height: 1em; }.dash { text-align: center; }.footer-row td { border: none; border-top: 1px solid #000; padding: 8px 6px; }.footer-cell { white-space: nowrap; }.footer-label { white-space: nowrap; }.footer-input { border: 1px solid #ccc; font-family: inherit; font-size: 13px; padding: 2px; outline: none; width: 70px; display: inline-block; vertical-align: middle; }.footer-input-wide { width: 120px; }.footer-note td { border: none; padding: 8px 6px; font-size: 12px; }</style>
|