2026-08-06.md 18 KB

2026-08-06

elderly/apply/nine-precautions/risk-disclosure-statement/Form.vue — secondElderSign 字符串/数组互转

用户要求:用 TableUploadsecondElderSign 字段,提交时转成字符串、回显时转成数组(参照 check-in ProcessForm.vue 的做法)。

背景:TableUploadsrc/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()submitDatasecondElderSign: toFileString(dataForm.secondElderSign)(提交转字符串)。
  • reactivesecondElderSign: [] 被推成 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。

根因/决策:原导出用 exportWithExpandedObjectArrayssrc/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,对每行 secondElderSignparseFileList 解析出 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 — 对接真实看板接口(前两个对象)

用户要求:后台接口 nineRiskBoardGetsrc/api/data-board/nine-safety.ts 第一个接口)已写好,先对接返回的 overviewitemRiskDistributions 两个对象,其余字段后续再对接。

改动:

  • loadAllData
    • 在参数里补 tenantIds(从 userStore.orgTenantId 取,数组化后传入)。
    • getNineSafetyOverviewgetNineRiskDistribution 两个 mock 调用合并成一次真实请求 echartsApi.nineRiskBoardGet(params)
    • 其余 11 个 mock 接口继续保留(月度趋势、热力图、散点、箱线图、签署状态、红名单、评估人工作量、护理级别、相关性、雷达、3D 柱状图)。
  • 字段映射:
    • overview
    • disclosureTotaltotalElders(知情书总数)
    • highRiskTimeshighRiskCount(高风险总人次)
    • highRiskTimesRatiohighRiskRatio(高风险人次占比 %)
    • disclosureUnsignedCountpendingSignatures(待签署知情书)
    • 100 - disclosureUnsignedRatiosignedRate(签署率 %)
    • assessCompleteRateevalCompletionRate(评估完成率 %)
    • assessorCountassessorCount(评估人员数)
    • itemRiskDistributions
    • itemNamename
    • highCounthigh
    • middleCountmedium
    • lowCountlow
    • unknownCountnone
    • totalCounttotal

类型检查: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 / buildIditems / 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 一次返回的数据里映射。
  • 字段映射:
    • riskLevelRatiosrenderOverallPieriskLevelName/riskLevelnamecountvalue,按 riskLevel 映射颜色。
    • monthlyTrendsriskTrendmonth/highCount/middleCount/lowCount/unknownCountmonth/high/medium/low/none
    • highRiskHeatMapfloorHeatmap / bar3D:items → risks,builds → floors,cells → [buildIndex, itemIndex, value]
    • highRiskEldersageScatterelderName/elderAge/elderSex/highRiskCount/nurseLevelNamename/age/sex/highCount/nurseLevel
    • itemScoreDistributionsscoreDist:真实返回 buckets,按 count 累计计算五数概括(min/q1/median/q3/max)供箱线图使用。
    • disclosureSignRatesignaturesignedCount/unsignedCount/total/signRatesigned/unsigned/total/signedRatefirstSignedOnly 给 0。
    • nurseLevelHighRiskElderCountsnurseLevelRisknurseLevelName/countname/high
    • assessorStatsassessorWorkloadassessor/assessedElderCount/highRiskElderCountname/count/highCount
    • multiHighRiskEldersredListelderId/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 / buildIditems / 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 调用 getRiskRadarnineRiskBoardGet 成为唯一数据源。
  • 护理级别数据映射:从 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 / buildIditems / builds 里查找索引。
  • heatCells 过滤掉找不到对应索引的异常单元格。
  • 顺带把 maxValue 存到 floorHeatmap / bar3D 对象里,供后续色阶归一化使用(当前渲染仍用自身计算的最大值,兼容安全)。

类型检查:NO_NINE_SAFETY_ERRORS。

data-board/nine-safety-measures/index.vue — 评估得分分布箱线图数据映射修正

用户反馈:「评估得分分布(箱线图)」渲染异常,大部分项目被拉成 0~100 的细长线条,只有防跌倒、防噎食等少数项目能看出箱子。

根因:后台 itemScoreDistributions 返回了真实的 minScore / maxScore,也返回了 buckets(分桶统计)。原映射用 buckets[0].minbuckets[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 数据结构已变化,需要按新结构重新渲染「各护理级别 · 九防高危占比雷达」。

新结构:

[
  {
    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-selectclearable 可能让空值判断不够健壮。
  • 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。