用户要求:用 TableUpload 的 secondElderSign 字段,提交时转成字符串、回显时转成数组(参照 check-in ProcessForm.vue 的做法)。
背景:TableUpload(src/components/UploadFile/src/TableUpload.vue)的 modelValue 必须是数组 FileItem[](内部 fileList = props.modelValue || [],并 push/splice),而后端以 JSON 字符串存储该字段。原 open() 用 Object.assign(dataForm, res) 直接把后端字符串塞进 secondElderSign,编辑/查看时组件会拿到字符串而崩。
改动:
toFileArray(val)(字符串→数组,带 Array.isArray/JSON.parse 兜底)与 toFileString(val)(空数组→'',否则 JSON.stringify)。open():Object.assign 后加 dataForm.secondElderSign = toFileArray(res.secondElderSign)(回显转数组)。submitForm():submitData 加 secondElderSign: toFileString(dataForm.secondElderSign)(提交转字符串)。reactive 里 secondElderSign: [] 被推成 never[],在声明处和 resetForm 处改为 [] as any[],消除 TS2322。类型检查:引入的 TS2322 已消除。Form.vue 仍剩 441/819 两处 TS6133(未使用变量 updateRiskCheckboxes / nextAssessDate),为改动前既有的,与本次无关、不影响运行。
用户要求:导出(handleImportCard / ButtonImport)时把 secondElderSign(后端存 JSON 字符串,形如 [{fileUrl, fileName, fileSize, createTime}],存的是 OSS 图片 URL)导出成真实图片嵌入 Excel。
根因/决策:原导出用 exportWithExpandedObjectArrays(src/utils/excel-export.ts,底层 SheetJS/xlsx 社区版)无法往 Excel 嵌图片,且原代码压根没导出该字段。改用项目已有的 exceljs(^4.4.0,已在 node_modules;social-worker/floor-activity-record/photo/index.vue 已有可复用的「ExcelJS + addImage + fetch 二进制」范式)。
改动:
exportWithExpandedObjectArrays 导入,新增 import ExcelJS from 'exceljs'、import { getAccessToken } from '@/utils/auth'、import { saveAs } from 'file-saver'。handleImportCard:拉取列表后交给新函数 exportRiskDisclosureToExcel(exportList)。exportRiskDisclosureToExcel:建 workbook/worksheet,18 个文本列 + 动态「二次告知签名」图片列(列数=所有行签名图最大数量,无图则不生成图片列);逐行 addRow,对每行 secondElderSign 用 parseFileList 解析出 fileUrl 数组,fetch 成 ArrayBuffer 后 workbook.addImage({buffer, extension}) + worksheet.addImage(imageId, {tl, ext:{width:90,height:90}}) 嵌入;失败则单元格写「图片加载失败」。parseFileList(JSON 字符串/数组→{fileUrl,fileName}[],带兜底)、fetchImageAsBuffer(复用 photo 页写法,fetch 带 Authorization: Bearer token)、getImageExtension(png→png,其余→jpeg;注意 ExcelJS 不支持 'jpg' 字面量,必须映射到 'jpeg')。类型检查:引入的 getImageExtension 返回类型含 'jpg' 导致 TS2322,已改为返回 'png' | 'jpeg' 修正。index.vue 其余报错(模板 el-tag :type 返回 string、handleEdit/handleView 多传参、若干 TS6133 未用变量)均为 385 行之前的既有问题,与本次无关。
用户要求:后台接口 nineRiskBoardGet(src/api/data-board/nine-safety.ts 第一个接口)已写好,先对接返回的 overview 和 itemRiskDistributions 两个对象,其余字段后续再对接。
改动:
loadAllData:
tenantIds(从 userStore.orgTenantId 取,数组化后传入)。getNineSafetyOverview 和 getNineRiskDistribution 两个 mock 调用合并成一次真实请求 echartsApi.nineRiskBoardGet(params)。overview:disclosureTotal → totalElders(知情书总数)highRiskTimes → highRiskCount(高风险总人次)highRiskTimesRatio → highRiskRatio(高风险人次占比 %)disclosureUnsignedCount → pendingSignatures(待签署知情书)100 - disclosureUnsignedRatio → signedRate(签署率 %)assessCompleteRate → evalCompletionRate(评估完成率 %)assessorCount → assessorCount(评估人员数)itemRiskDistributions:itemName → namehighCount → highmiddleCount → mediumlowCount → lowunknownCount → nonetotalCount → total类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:highRiskHeatMap 字段结构有变化(顶部加了楼栋/楼层筛选后),cells 改为对象数组。
新结构:
items:{code: itemCode, name: 九防名}builds:{code: buildId, name: 楼栋名}cells:{buildId, buildName, itemCode, itemName, highRiskElderCount, maxValue}改动:
x/y 下标,改为通过 itemCode / buildId 在 items / builds 里查找索引。heatCells 过滤掉找不到对应索引的异常单元格。maxValue 存到 floorHeatmap / bar3D 对象里,供后续色阶归一化使用(当前渲染仍用自身计算的最大值,兼容安全)。类型检查:NO_NINE_SAFETY_ERRORS。
用户发了 12 张接口字段截图,要求把剩余字段全部对接。
改动:
riskLevelRatios ref,renderOverallPie 改为从该 ref 读取(不再从 nineRiskDist 聚合)。loadAllData:
getRiskRadar 一个 mock 调用(真实接口未提供"护理级别 × 九防高危占比"雷达图明细)。nineRiskBoardGet 一次返回的数据里映射。riskLevelRatios → renderOverallPie:riskLevelName/riskLevel → name,count → value,按 riskLevel 映射颜色。monthlyTrends → riskTrend:month/highCount/middleCount/lowCount/unknownCount → month/high/medium/low/none。highRiskHeatMap → floorHeatmap / bar3D:items → risks,builds → floors,cells → [buildIndex, itemIndex, value]。highRiskElders → ageScatter:elderName/elderAge/elderSex/highRiskCount/nurseLevelName → name/age/sex/highCount/nurseLevel。itemScoreDistributions → scoreDist:真实返回 buckets,按 count 累计计算五数概括(min/q1/median/q3/max)供箱线图使用。disclosureSignRate → signature:signedCount/unsignedCount/total/signRate → signed/unsigned/total/signedRate,firstSignedOnly 给 0。nurseLevelHighRiskElderCounts → nurseLevelRisk:nurseLevelName/count → name/high。assessorStats → assessorWorkload:assessor/assessedElderCount/highRiskElderCount → name/count/highCount。multiHighRiskElders → redList:elderId/elderName/elderSex/elderAge/bedName/buildName/nurseLevelName/assessor/highRiskItems/highRiskCount/assessDate → 页面字段;highRiskItems 按逗号拆分并去掉"防"字。2022-12-12 改为 r.assessDate。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:highRiskHeatMap 字段结构有变化(顶部加了楼栋/楼层筛选后),cells 改为对象数组。
新结构:
items:{code: itemCode, name: 九防名}builds:{code: buildId, name: 楼栋名}cells:{buildId, buildName, itemCode, itemName, highRiskElderCount, maxValue}改动:
x/y 下标,改为通过 itemCode / buildId 在 items / builds 里查找索引。heatCells 过滤掉找不到对应索引的异常单元格。maxValue 存到 floorHeatmap / bar3D 对象里,供后续色阶归一化使用(当前渲染仍用自身计算的最大值,兼容安全)。类型检查:NO_NINE_SAFETY_ERRORS。
用户要求:「各护理级别 · 九防高危占比雷达」使用 nurseLevelHighRiskRecordCounts 数组。
背景:该数组只有 {nurseLevelId, nurseLevelName, count, ratio},没有按九防拆分。原 mock 雷达图是「护理级别 × 九防」矩阵,真实数据无法直接还原该形态。
改动:
loadAllData:移除最后一个 mock 调用 getRiskRadar,nineRiskBoardGet 成为唯一数据源。bd.nurseLevelHighRiskRecordCounts 读取,给 nurseLevelRisk 增加 count/ratio,保留 high(=count)供漏斗图继续使用。renderRiskRadar:改为读 nurseLevelRisk;indicator = 各护理级别名称;series 两个:「高危占比(%)」用真实 ratio,「记录数(归一化)」用 count/maxCount*100 避免数量级失真;tooltip 显示真实记录数和占比。riskRadar ref。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:highRiskHeatMap 字段结构有变化(顶部加了楼栋/楼层筛选后),cells 改为对象数组。
新结构:
items:{code: itemCode, name: 九防名}builds:{code: buildId, name: 楼栋名}cells:{buildId, buildName, itemCode, itemName, highRiskElderCount, maxValue}改动:
x/y 下标,改为通过 itemCode / buildId 在 items / builds 里查找索引。heatCells 过滤掉找不到对应索引的异常单元格。maxValue 存到 floorHeatmap / bar3D 对象里,供后续色阶归一化使用(当前渲染仍用自身计算的最大值,兼容安全)。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:「评估得分分布(箱线图)」渲染异常,大部分项目被拉成 0~100 的细长线条,只有防跌倒、防噎食等少数项目能看出箱子。
根因:后台 itemScoreDistributions 返回了真实的 minScore / maxScore,也返回了 buckets(分桶统计)。原映射用 buckets[0].min 和 buckets[last].max 作为箱线图的 min/max,导致每个项目的上下须都钉死在 0 和 100,箱子被压扁;四分位数也只用桶下边界,没有插值。
改动:
minScore / maxScore,缺失时才回退到桶边界。q1 / median / q3 在桶内做线性插值,而不是直接取桶下边界。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:「按护理级别 · 高危项漏斗」图表为空,后台真实字段是 nurseLevelHighRiskElderCounts。
根因:loadAllData 里护理级别数据仍从 bd.nurseLevelHighRiskRecordCounts 读取,与后台实际返回字段不一致,导致 nurseLevelRisk 为空,漏斗图没有数据。
改动:
nurseLevelRisk 改为优先读取 bd.nurseLevelHighRiskElderCounts,同时兼容旧名 bd.nurseLevelHighRiskRecordCounts 作为 fallback。nurseLevelRisk,结构保持 {name, count, ratio, none, low, medium, high} 不变。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:nurseLevelHighRiskRecordCounts 数据结构已变化,需要按新结构重新渲染「各护理级别 · 九防高危占比雷达」。
新结构:
[
{
nurseLevelId,
nurseLevelName,
ratio,
totalCount,
items: [
{ itemCode, itemName, count, ratio }
]
}
]
改动:
nurseLevelRadarData ref,结构为 { indicators: [{name, max}], series: [{name, value, count, lineStyle, itemStyle, areaStyle}] }。loadAllData:从 bd.nurseLevelHighRiskRecordCounts 解析雷达数据;indicator 取第一条护理级别的 items.itemName;每个护理级别作为一条 radar series,value 为各九防项的 ratio(保留 2 位小数),count 为原始人数供 tooltip 使用。renderRiskRadar:改为读取 nurseLevelRadarData;indicator = 九防项目名称;legend 显示各护理级别名称;tooltip 展示该护理级别下每个九防项的占比和人数。nurseLevelRisk(来自 nurseLevelHighRiskElderCounts),与雷达图数据源解耦。类型检查:NO_NINE_SAFETY_ERRORS。
用户反馈:「评估人工作量统计」图表 X 轴标签太多、互相重叠。
改动:
renderAssessorWorkload:
grid.bottom 从 30 加大到 90,给旋转标签留出垂直空间。xAxis.axisLabel.rotate 从 40 度加大到 60 度,fontSize 从 11 降到 10,margin 从 8 加到 12。dataZoom slider,默认展示前 12 条(或 40%,取较小者),底部出现横向滚动条,可拖动查看其余评估人。类型检查:NO_NINE_SAFETY_ERRORS。
问题:3D 楼栋×九防高危人数图表 hover 时 tooltip 显示 undefined - undefined,高危人数:undefined 人。
根因:ECharts bar3D 的 tooltip formatter 参数里,数据坐标/值位于 params.value,而 params.data 是 { value: [...] } 对象。原代码用 p.data[0] / p.data[2] 访问,拿到的是 undefined。
改动:render3DBar 的 tooltip formatter 改为从 p.value 读取 [楼栋索引, 九防索引, 高危人数],并用 floors / risks 数组反查名称;同时加 ?? '-' 兜底,避免极端情况再次显示 undefined。
类型检查:NO_NINE_SAFETY_ERRORS。
问题:
[2026,7,28],需要格式化为正常日期。根因:
nineRiskOptions 的 value 已去掉"防"前缀,但 redList.risks 存储格式与下拉 value 的对应关系存在歧义,导致筛选不直观;同时 el-select 的 clearable 可能让空值判断不够健壮。assessDate 后台直接返回了 [年,月,日] 数组,原样渲染到表格。改动:
redList.risks 保留原始值(不再 .replace('防', '')),nineRiskOptions 的 value 也使用 r.name。riskTagColor 的 key:riskTagColor(rk.replace('防', ''))。formatAssessDate 工具函数,支持数组、Date、字符串,统一输出 yyyy-MM-dd。redList 映射时 assessDate 使用 formatAssessDate(e.assessDate)。r.assessDate(函数已返回 '-' 兜底)。类型检查:NO_NINE_SAFETY_ERRORS。
问题:选择"防噎食"后,红名单显示"共 0 人",但数据中应包含该风险项的长者。
根因:
e.highRiskItems 可能以数组形式返回,原代码只按字符串 .split(',') 解析,导致数组被序列化为 "防噎食,防压疮" 这样的单一项。=== 对空格、前缀不一致等边界情况不够健壮。改动:
redList.risks 解析改为同时支持数组和字符串:数组逐项 String().trim(),字符串按逗号拆分。filteredRedList 匹配逻辑增强:
s === redRiskFilter.values.replace('防', '') === keywords.includes(redRiskFilter.value) || redRiskFilter.value.includes(s)类型检查:NO_NINE_SAFETY_ERRORS。
问题:后端把「防误食」这条风险命名为「简易精神状态」,导致红名单选「防误食」过滤不出数据,且页面各图表会显示「简易精神状态」。
改动:
normalizeRiskName 工具函数:输入 "简易精神状态" 时返回 "防误食",其他名称原样返回。loadAllData 中所有涉及九防项目名称的地方统一应用该映射:
nineRiskDist(九防风险等级分布)heatItems(热力图/3D 图的九防轴标签)scoreDist(评估得分分布箱线图)nurseLevelRadarData(各护理级别雷达 indicator)redList.risks(红名单高风险项标签与下拉筛选)NINE_RISKS,已固定为「防误食」,映射后筛选可正常命中。类型检查:NO_NINE_SAFETY_ERRORS。
用户要求:以桌面上的湖北 2024 标准合同 docx 为准,改造 src/views/elderly/contracts/components/ContractBodyHubei.vue,保留原组件设计模式(a4-page / section-title / content-text / FieldItem / SignatureItem)与功能,仅替换正文内容。
关键点(用户特别强调):
新增字段(contractForm):partyAEmail、partyAContactPhone、elderCensusAddr、elderPhone、elderEmail、roomFloor、bedNo、trialDays(默认7)、trialStart/End、renewDays、emergencyContact*、memberFeeAmt/Cn/RefundMode、penaltyPercent、disputeMode/Court/Arbitration、copyCount。