|
|
@ -1,20 +1,23 @@ |
|
|
<script> |
|
|
<script> |
|
|
import embeddedModel from './da3-model.js' |
|
|
|
|
|
|
|
|
// import embeddedModel from './da3-model.js' |
|
|
|
|
|
import { FetchInitStatisticsTemplate, SaveStatisticsTemplate } from '@/api/statistics/statistics' |
|
|
|
|
|
|
|
|
export default { |
|
|
export default { |
|
|
name: 'Da3Report', |
|
|
name: 'Da3Report', |
|
|
props: { |
|
|
|
|
|
// 后台聚合返回的数据模型(template/units/body/footer);不传则用内置 demo 数据 |
|
|
|
|
|
model: { type: Object, default: null } |
|
|
|
|
|
}, |
|
|
|
|
|
data() { |
|
|
data() { |
|
|
return { |
|
|
return { |
|
|
form: {} |
|
|
|
|
|
|
|
|
form: {}, |
|
|
|
|
|
// 接口返回的报表模型 |
|
|
|
|
|
remoteModel: null, |
|
|
|
|
|
// 接口返回的原始数据(保存时按同结构回传) |
|
|
|
|
|
rawReport: null, |
|
|
|
|
|
loading: false, |
|
|
|
|
|
saving: false |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
computed: { |
|
|
computed: { |
|
|
data() { |
|
|
data() { |
|
|
return this.model || embeddedModel |
|
|
|
|
|
|
|
|
return this.remoteModel |
|
|
}, |
|
|
}, |
|
|
// 查看模式(不可输入)还是编辑模式(可输入),默认查看 |
|
|
// 查看模式(不可输入)还是编辑模式(可输入),默认查看 |
|
|
isView() { |
|
|
isView() { |
|
|
@ -40,13 +43,172 @@ export default { |
|
|
data: { |
|
|
data: { |
|
|
handler(d) { |
|
|
handler(d) { |
|
|
this.initForm(d) |
|
|
this.initForm(d) |
|
|
// TODO: 接口就绪后,改为按路由参数(id)请求真实填报数据后回显 |
|
|
|
|
|
this.fillDemo() |
|
|
|
|
|
|
|
|
// 接口模型自带填报值时回显 |
|
|
|
|
|
if (d.formData) this.fillFormData(d.formData) |
|
|
}, |
|
|
}, |
|
|
immediate: true |
|
|
immediate: true |
|
|
} |
|
|
} |
|
|
}, |
|
|
}, |
|
|
|
|
|
created() { |
|
|
|
|
|
this.fetchReport() |
|
|
|
|
|
}, |
|
|
methods: { |
|
|
methods: { |
|
|
|
|
|
// 按路由参数拉取年报详情;无参数(独立预览)时使用 mock 数据 |
|
|
|
|
|
fetchReport() { |
|
|
|
|
|
const { reportingYear, unitId, templateId } = this.$route.query |
|
|
|
|
|
// if (!reportingYear || !unitId) { |
|
|
|
|
|
// this.fillDemo() |
|
|
|
|
|
// return |
|
|
|
|
|
// } |
|
|
|
|
|
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 |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
|
|
|
// 将接口返回结构归一化为组件内置模型结构:{ 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'] |
|
|
|
|
|
// 填报值统一以文本字段(valCurText/valPrevText)为准,text 为空时再回退取数值字段 |
|
|
|
|
|
const pickVal = (text, num) => (text != null && text !== '' ? text : (num != null ? num : '')) |
|
|
|
|
|
|
|
|
|
|
|
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' |
|
|
|
|
|
}) |
|
|
|
|
|
formData.units[r.code] = unitValues[r.code] != null ? unitValues[r.code] : (r.valCurText || '') |
|
|
|
|
|
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' : 'number' |
|
|
|
|
|
const cur = pickVal(r.valCurText, r.valCurNum) |
|
|
|
|
|
const prev = pickVal(r.valPrevText, r.valPrevNum) |
|
|
|
|
|
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. 页脚签章(值取自顶层字段) |
|
|
|
|
|
const footer = [ |
|
|
|
|
|
{ col: 1, label: '单位负责人:' + (res.unitHead || '') }, |
|
|
|
|
|
{ col: 5, label: '统计负责人:' + (res.statsHead || '') }, |
|
|
|
|
|
{ col: 6, label: '填表人:' + (res.filler || '') }, |
|
|
|
|
|
{ col: 8, label: '报出时间:' + (res.submitTime || '') } |
|
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
|
|
return { template, units, body, footer, formData } |
|
|
|
|
|
}, |
|
|
|
|
|
// 回填填报值:{ u_01: 'xx', '08_c': '12' } 或 { units: {01:'xx'}, data: {08:{cur:'12'}} } |
|
|
|
|
|
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) |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
Object.keys(formData).forEach(k => { |
|
|
|
|
|
if (Object.prototype.hasOwnProperty.call(this.form, k)) { |
|
|
|
|
|
this.$set(this.form, k, formData[k]) |
|
|
|
|
|
} |
|
|
|
|
|
}) |
|
|
|
|
|
}, |
|
|
initForm(d) { |
|
|
initForm(d) { |
|
|
const form = {} |
|
|
const form = {} |
|
|
d.units.forEach(u => { form['u_' + u.code] = '' }) |
|
|
d.units.forEach(u => { form['u_' + u.code] = '' }) |
|
|
@ -87,13 +249,57 @@ export default { |
|
|
console.log(JSON.stringify(payload, null, 2)) |
|
|
console.log(JSON.stringify(payload, null, 2)) |
|
|
this.$emit('export', payload) |
|
|
this.$emit('export', payload) |
|
|
alert('已导出到控制台(F12),并通过 emit("export") 回传父组件') |
|
|
alert('已导出到控制台(F12),并通过 emit("export") 回传父组件') |
|
|
|
|
|
}, |
|
|
|
|
|
// 保存:基于接口原始结构回填用户输入(数值也写入 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 |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// 指标行回填 valCurText / valPrevText(递归 children 树形结构) |
|
|
|
|
|
const walk = list => { |
|
|
|
|
|
(list || []).forEach(r => { |
|
|
|
|
|
if (r.itemType === 'indicator' && r.isLeaf && r.code) { |
|
|
|
|
|
const cur = this.form[r.code + '_c'] |
|
|
|
|
|
const prev = this.form[r.code + '_p'] |
|
|
|
|
|
r.valCurText = cur === '' || cur == null ? null : String(cur) |
|
|
|
|
|
r.valPrevText = prev === '' || prev == null ? null : String(prev) |
|
|
|
|
|
} |
|
|
|
|
|
if (r.children && r.children.length) walk(r.children) |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
walk(payload.rows) |
|
|
|
|
|
|
|
|
|
|
|
this.saving = true |
|
|
|
|
|
SaveStatisticsTemplate(payload).then(() => { |
|
|
|
|
|
this.$message({ message: '保存成功', type: 'success', offset: 80 }) |
|
|
|
|
|
}).finally(() => { |
|
|
|
|
|
this.saving = false |
|
|
|
|
|
}) |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
</script> |
|
|
</script> |
|
|
|
|
|
|
|
|
<template> |
|
|
<template> |
|
|
<div class="da3"> |
|
|
|
|
|
|
|
|
<div v-loading="loading" class="da3"> |
|
|
<!-- <div class="toolbar"> |
|
|
<!-- <div class="toolbar"> |
|
|
<button @click="fillDemo">填入示例数据</button> |
|
|
<button @click="fillDemo">填入示例数据</button> |
|
|
<button @click="clearAll">清空</button> |
|
|
<button @click="clearAll">清空</button> |
|
|
@ -207,12 +413,26 @@ export default { |
|
|
<td v-for="(f, fi) in data.footer" :key="'f' + fi" :colspan="fi < data.footer.length - 1 ? data.footer[fi + 1].col - f.col : (10 - f.col)">{{ f.label }}</td> |
|
|
<td v-for="(f, fi) in data.footer" :key="'f' + fi" :colspan="fi < data.footer.length - 1 ? data.footer[fi + 1].col - f.col : (10 - f.col)">{{ f.label }}</td> |
|
|
</tr> |
|
|
</tr> |
|
|
</table> |
|
|
</table> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 编辑模式:底部固定保存栏 --> |
|
|
|
|
|
<div v-if="!isView" class="save-bar"> |
|
|
|
|
|
<el-button type="primary" :loading="saving" @click="handleSave">保存</el-button> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
|
|
|
|
|
|
<style scoped> |
|
|
<style scoped> |
|
|
* { box-sizing: border-box; } |
|
|
* { box-sizing: border-box; } |
|
|
.da3 { font-family: "SimSun", "宋体", serif; color: #000; background: #fff; padding: 20px; } |
|
|
|
|
|
|
|
|
.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 { 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 { 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 button { font-family: inherit; padding: 4px 10px; margin-right: 8px; cursor: pointer; } |
|
|
.toolbar .hint { color: #888; } |
|
|
.toolbar .hint { color: #888; } |
|
|
|