# 2026-08-06 ## elderly/apply/nine-precautions/risk-disclosure-statement/Form.vue — secondElderSign 字符串/数组互转 用户要求:用 `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),为改动前既有的,与本次无关、不影响运行。 ## elderly/apply/nine-precautions/risk-disclosure-statement/index.vue — 导出 secondElderSign 为图片 用户要求:导出(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 行之前的既有问题,与本次无关。 ## data-board/nine-safety-measures/index.vue — 对接真实看板接口(前两个对象) 用户要求:后台接口 `nineRiskBoardGet`(`src/api/data-board/nine-safety.ts` 第一个接口)已写好,先对接返回的 `overview` 和 `itemRiskDistributions` 两个对象,其余字段后续再对接。 改动: - `loadAllData`: - 在参数里补 `tenantIds`(从 `userStore.orgTenantId` 取,数组化后传入)。 - 把 `getNineSafetyOverview` 和 `getNineRiskDistribution` 两个 mock 调用合并成一次真实请求 `echartsApi.nineRiskBoardGet(params)`。 - 其余 11 个 mock 接口继续保留(月度趋势、热力图、散点、箱线图、签署状态、红名单、评估人工作量、护理级别、相关性、雷达、3D 柱状图)。 - 字段映射: - `overview`: - `disclosureTotal` → `totalElders`(知情书总数) - `highRiskTimes` → `highRiskCount`(高风险总人次) - `highRiskTimesRatio` → `highRiskRatio`(高风险人次占比 %) - `disclosureUnsignedCount` → `pendingSignatures`(待签署知情书) - `100 - disclosureUnsignedRatio` → `signedRate`(签署率 %) - `assessCompleteRate` → `evalCompletionRate`(评估完成率 %) - `assessorCount` → `assessorCount`(评估人员数) - `itemRiskDistributions`: - `itemName` → `name` - `highCount` → `high` - `middleCount` → `medium` - `lowCount` → `low` - `unknownCount` → `none` - `totalCount` → `total` 类型检查:NO_NINE_SAFETY_ERRORS。 ## data-board/nine-safety-measures/index.vue — highRiskHeatMap 结构变更适配 用户反馈:`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。 ## data-board/nine-safety-measures/index.vue — 对接真实看板接口(剩余全部字段) 用户发了 12 张接口字段截图,要求把剩余字段全部对接。 改动: - 新增 `riskLevelRatios` ref,`renderOverallPie` 改为从该 ref 读取(不再从 `nineRiskDist` 聚合)。 - `loadAllData`: - 只保留 `getRiskRadar` 一个 mock 调用(真实接口未提供"护理级别 × 九防高危占比"雷达图明细)。 - 其余 10 个 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。 ## data-board/nine-safety-measures/index.vue — highRiskHeatMap 结构变更适配 用户反馈:`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。 ## data-board/nine-safety-measures/index.vue — 雷达图改用 nurseLevelHighRiskRecordCounts 用户要求:「各护理级别 · 九防高危占比雷达」使用 `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。 ## data-board/nine-safety-measures/index.vue — highRiskHeatMap 结构变更适配 用户反馈:`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。 ## data-board/nine-safety-measures/index.vue — 评估得分分布箱线图数据映射修正 用户反馈:「评估得分分布(箱线图)」渲染异常,大部分项目被拉成 0~100 的细长线条,只有防跌倒、防噎食等少数项目能看出箱子。 根因:后台 `itemScoreDistributions` 返回了真实的 `minScore / maxScore`,也返回了 `buckets`(分桶统计)。原映射用 `buckets[0].min` 和 `buckets[last].max` 作为箱线图的 min/max,导致每个项目的上下须都钉死在 0 和 100,箱子被压扁;四分位数也只用桶下边界,没有插值。 改动: - min/max 改为优先使用后台真实 `minScore / maxScore`,缺失时才回退到桶边界。 - 四分位数 `q1 / median / q3` 在桶内做线性插值,而不是直接取桶下边界。 - 空桶跳过,避免异常点干扰累计。 - 五数概括统一保留 2 位小数,避免浮点精度导致十几位小数。 类型检查:NO_NINE_SAFETY_ERRORS。 ## data-board/nine-safety-measures/index.vue — 按护理级别 · 高危项漏斗字段修正 用户反馈:「按护理级别 · 高危项漏斗」图表为空,后台真实字段是 `nurseLevelHighRiskElderCounts`。 根因:`loadAllData` 里护理级别数据仍从 `bd.nurseLevelHighRiskRecordCounts` 读取,与后台实际返回字段不一致,导致 `nurseLevelRisk` 为空,漏斗图没有数据。 改动: - `nurseLevelRisk` 改为优先读取 `bd.nurseLevelHighRiskElderCounts`,同时兼容旧名 `bd.nurseLevelHighRiskRecordCounts` 作为 fallback。 - 漏斗图和雷达图共用 `nurseLevelRisk`,结构保持 `{name, count, ratio, none, low, medium, high}` 不变。 类型检查:NO_NINE_SAFETY_ERRORS。 ## data-board/nine-safety-measures/index.vue — 雷达图适配 nurseLevelHighRiskRecordCounts 新结构 用户反馈:`nurseLevelHighRiskRecordCounts` 数据结构已变化,需要按新结构重新渲染「各护理级别 · 九防高危占比雷达」。 新结构: ```js [ { 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。 ## data-board/nine-safety-measures/index.vue — 评估人工作量统计 X 轴防拥挤 用户反馈:「评估人工作量统计」图表 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。 ## data-board/nine-safety-measures/index.vue — 修复 3D 楼栋图 tooltip 显示 undefined 问题: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。 ## data-board/nine-safety-measures/index.vue — 红名单九防筛选生效 + 评估日期格式化 问题: 1. 重点人群红名单的九防风险下拉筛选没有过滤效果。 2. 评估日期显示为数组形式 `[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。 ## data-board/nine-safety-measures/index.vue — 红名单风险筛选仍显示 0 人 问题:选择"防噎食"后,红名单显示"共 0 人",但数据中应包含该风险项的长者。 根因: - `e.highRiskItems` 可能以数组形式返回,原代码只按字符串 `.split(',')` 解析,导致数组被序列化为 `"防噎食,防压疮"` 这样的单一项。 - 即使解析正确,精确匹配 `===` 对空格、前缀不一致等边界情况不够健壮。 改动: - `redList.risks` 解析改为同时支持数组和字符串:数组逐项 `String().trim()`,字符串按逗号拆分。 - `filteredRedList` 匹配逻辑增强: - 精确相等 `s === redRiskFilter.value` - 去掉"防"前缀后相等 `s.replace('防', '') === keyword` - 互相包含匹配 `s.includes(redRiskFilter.value) || redRiskFilter.value.includes(s)` - 空字符串风险项会被过滤掉。 类型检查:NO_NINE_SAFETY_ERRORS。 ## data-board/nine-safety-measures/index.vue — 简易精神状态映射为防误食 问题:后端把「防误食」这条风险命名为「简易精神状态」,导致红名单选「防误食」过滤不出数据,且页面各图表会显示「简易精神状态」。 改动: - 新增 `normalizeRiskName` 工具函数:输入 `"简易精神状态"` 时返回 `"防误食"`,其他名称原样返回。 - 在 `loadAllData` 中所有涉及九防项目名称的地方统一应用该映射: - `nineRiskDist`(九防风险等级分布) - `heatItems`(热力图/3D 图的九防轴标签) - `scoreDist`(评估得分分布箱线图) - `nurseLevelRadarData`(各护理级别雷达 indicator) - `redList.risks`(红名单高风险项标签与下拉筛选) - 红名单下拉选项本身来自 `NINE_RISKS`,已固定为「防误食」,映射后筛选可正常命中。 类型检查:NO_NINE_SAFETY_ERRORS。