# 2026-09-03 工作日志 ## 合同费用列表(monthlyExpenses)手动添加功能 ### 需求 合同组件(广州版 ContractBody.vue + 湖北版 ContractBodyHubei.vue)的月度费用列表,增加「手动添加费用行」能力:点击添加一行,格式与现有行一致,数据随合同保存、重新打开能解析回显。 ### 实现 - **addMonthlyExpense()**:向 `contractForm.monthlyExpenses` push 空行,标记 `isManual: true`,字段 `{ id: 'manual-'+Date.now(), isManual: true, itemName:'', itemCategoryName:'', actualAmount:'' }` - **removeMonthlyExpense(index)**:splice 删除(仅手动行提供删除入口) - **渲染区分**: - 手动行(`isManual`)名称用 `FieldItem v-model="item.itemName"` 可编辑,后缀 `¥:` - 系统行名称保持纯文本 `{{ item.itemCategoryName+'¥' || item.itemName+'¥' }}` - 金额 `FieldItem v-model="item.actualAmount"` 不变,后缀 `元/月;` - 删除按钮 `el-button link type=danger size=small`(`v-if="item.isManual && !isTextMode"`) - 添加按钮 `el-button link type=primary`「+ 添加费用」(`v-if="!isTextMode"`,打印模式隐藏) - **v-for key** 改为 `item.id ?? 'manual-'+index`(手动行 id 为字符串 `manual-xxx`,避免与系统行冲突/空 key 重复) - 新增 `.expense-del` scoped 样式(margin-left:6px 对齐) ### 保存/解析链路(无需额外改动) - 保存:`saveContract` → `JSON.stringify({...contractForm})`,手动行作为 monthlyExpenses 数组元素完整落库 - 解析:`fetchContractData` → `JSON.parse(contractInfo)` → `Object.assign(contractForm, parsedData)` 整体替换 monthlyExpenses,`isManual` 标记保留,回显正确 - totalFee 自动汇总 watch 遍历 `monthlyExpensesList`,手动行 actualAmount 一并计入总额 ### 涉及文件 - `src/views/elderly/contracts/components/ContractBody.vue`(script 745-760 + template 1411-1449 + style) - `src/views/elderly/contracts/components/ContractBodyHubei.vue`(script 809-824 + template 1466-1504 + style) ### 备注 - 系统行名称表达式 `item.itemCategoryName+'¥' || item.itemName+'¥'` 存在既有逻辑瑕疵(空 category 时 `''+'¥'='¥'` 为 truthy,|| 永远短路返回前者),本次未改动、仅保留原行为 ## 费用名称显示严谨化(后续修复) ### 背景 上一轮遗留的 `displayExpenseName` 函数内 `return category + '¥' || name + '¥' || ''` 仍有短路 bug:`category` 为空时 `''+'¥'='¥'` 为真值,永远返回 `'¥'` 不回退到 `name`;且 category/name 都为空时返回孤零零 `¥`。 ### 修复 - **两个文件 `displayExpenseName`** 统一改为: ```js const label = category || name return label ? `${label}¥` : '' ``` 严谨优先级:类别+项目都存在且不同 → `类别(项目)¥`;否则取非空者;都空返回 `''`。 - **广州版模板** 系统行名称旧表达式 `{{ item.itemCategoryName + '¥' || item.itemName + '¥' }}` → `{{ displayExpenseName(item) }}`,与湖北版及打印渲染处(oneTime/stage 费用)保持一致。 - 已在函数上补注释说明禁用 `category + '¥' || name + '¥'` 的原因,防止后人重蹈。 ## 月度费用金额预览/打印模式去下划线 ### 需求 合同月度费用列表(monthlyExpenses)的金额,在预览/打印(isTextMode)下 FieldItem 的 `.fill-text` 有 `border-bottom: 1px solid #333` 下划线,与上方费用名称(纯文本)不一致,希望去掉下划线。**仅限月度费用这一块,其他费用块(一次性/阶段性)金额横线保留。** ### 实现 - `FieldItem.vue` 新增 `noUnderline?: boolean` prop;isTextMode 下 `.fill-text`/`.fill-blank` 加 `:class="{ 'no-underline': noUnderline }"`;样式新增 `.no-underline { border-bottom: none; }`(放在 `.is-text` 内,仅 isTextMode 生效,编辑模式 input 无影响)。 - 两个合同组件的月度费用金额 FieldItem(`width="120px"`)加 `no-underline`,一次性费用金额(`width="100px"`)不改。 ### 涉及文件 - `src/views/elderly/contracts/components/FieldItem.vue` - `src/views/elderly/contracts/components/ContractBody.vue`(月度费用金额) - `src/views/elderly/contracts/components/ContractBodyHubei.vue`(月度费用金额) ### 补充:手动行「费用名称」也去下划线 用户纠正「金额和名称都要改」。手动添加行(isManual)的费用名称 FieldItem(`width="140px"` `placeholder="请输入费用名称"`)在预览/打印模式下同样带下划线,已给两个合同组件的名称 FieldItem 补 `no-underline`。系统行名称本就是纯文本 `displayExpenseName(item)`,无下划线,无需改。 ## 出院小结模块(全新,apply/discharge-summary) ### 需求 在 `views/elderly/apply/discharge-summary/` 实现「出院小结」功能列表,参照桌面模板 `1.8-出院小结(第一版).doc`(由医生发起:先选长者 → 按模板逐段填写 → 保存)。 ### 模板结构提取技巧 `.doc` 是 OLE2 复合文档(文件头 D0CF11E0),无 LibreOffice 环境下用 **Python 按 UTF-16LE 解码全文件 + 正则提取连续中文片段**即可拿到完整文本(前 ~60 段有效,后面是乱码噪声)。 模板五段结构: 1. 基本信息:长者姓名/性别/年龄/居住区域(楼 区 房 床)/入住日期/出院日期 2. 一、入住基本健康评估状况:既往病史、近期健康问题、身体活动能力(完全自理/部分自理/完全依赖)、认知与精神状态(正常/轻/中/重度) 3. 二、在院期间发生疾病和治疗情况:疾病治疗与控制、近期就医情况 4. 三、出院长者状况:出院原因(居家照护/医院就诊/死亡退住院内/死亡退住院外/其他)、生命体征(P 次/分、R 次/分、HP / mmhg)、身体功能 5. 四、用药情况(死亡出院不需填):药名/剂量/用法/频次 动态表格 6. 五、出院指导(死亡出院不需填):重点关注问题/疾病变化关注/安全防护/饮食与就医 7. 签名:医生签名 + 签名日期 ### 实现(4 个新文件 + 1 个 API 文件) - `src/api/elderly/elder/discharge-summary/index.ts`:枚举(DISCHARGE_REASON/PHYSICAL_ABILITY/COGNITIVE_STATUS)+ `isDeathDischarge()` + 文本映射函数 + 5 个接口(page/get/create/update/delete),url 前缀 `elderly/dischargeSummary/*`(与医嘱 `elderly/longDoctorOrder/*` 同风格) - `columns.ts`:9 列。**出院原因/年龄是模块自定义枚举(非系统字典)**,Table2 的 `dictArr` 只支持系统字典,故在 index.vue 的 getList 里预格式化 `dischargeReasonText`/`elderAgeText`,columns 用普通 field 渲染 - `Form.vue`:Dialog 70%,el-divider 分段。selectElder 选长者 → @elder 回填性别字典/年龄/居住区域(buildName-floorName-roomName-bedName 拼)/入住日期;**不绑 v-model:bed-name**(区域需拼四段,避免与 @elder 覆盖顺序冲突)。出院原因=死亡(3/4)时 v-if 隐藏用药情况+出院指导,提交时清空这些字段。resetForm 逐字段显式重置(禁用 JSON 拷贝) - `Print.vue`:A4 portrait + #555 细线 + 弹填 dialog。主表 6 列(标签 12%/值 21.33%),用药情况用**嵌套子表**(4 列 30/20/25/25),最少补 4 空行 - `index.vue`:搜索(机构+姓名+出院日期范围)+ Table2 + 4 个操作(编辑/详情/删除/打印)+ mock 兜底 ### 路由 路由是**后端菜单动态生成**(医嘱模块也没在前端 router 里注册),前端只需保证组件文件存在。菜单配置:组件路径 `elderly/apply/discharge-summary/index`。操作按钮**暂未加 v-hasPermi**(新模块权限标识待后端定义,加了会导致按钮不显示) ### 环境备注 - element-plus 2.9.1:`el-radio` 用 `value`(2.6+ 推荐),项目老代码用 `label`(兼容别名,会 deprecation 告警)。新代码统一用 value + 插槽文本 ### 用药子表字段名:usageMethod(不是 usage) **`USAGE` 是 MySQL 保留字**(`GRANT USAGE`),直接做列名必须反引号包裹,易踩坑。三个文件统一用 `usageMethod`: - `api/.../discharge-summary/index.ts` 的 `DischargeMedication.usageMethod` - `Form.vue` 的 `emptyMedication()` + 表格 `row.usageMethod` - `Print.vue` 的 `paddedMedications` padding + `m.usageMethod` (这类"字段名 = SQL 保留字"的坑,做子表字段设计时要先排查) ### 后端接口规格文档(已生成) 路径:`C:\Users\chenjun\Desktop\出院小结-后端接口规格.md`(桌面,非项目根,方便直接交付后端) 内容:功能概述 / 主表+子表 DDL / 5 个接口(入参出参 JSON 示例)/ 27 个字段对照表 / 3 组枚举 / 权限标识建议 / 菜单配置 / 注意事项 / 联调检查清单 ### 待后端确认 - 接口路径与字段命名是否与后端一致(当前按项目命名惯例自拟 `elderly/dischargeSummary/*`) - 权限标识(`discharge-summary:create/update/delete/print`)待定义后再补 `v-hasPermi` - ⚠️ 日期字段必须返回 `YYYY-MM-DD` 字符串,不能是毫秒时间戳(前端 el-date-picker value-format 固定,返时间戳会不回显——项目已踩过此坑) ## 出院小结调整:删除「疾病变化关注」+ 入住日期自动回填只读 ### 需求 1. 出院指导删掉「疾病变化关注」,按文档模板只保留 3 项(重点关注问题 / 安全防护 / 饮食与就医),相关字段与打印同步删除 2. 入住日期改成选择长者后自动回填(**取长者档案的 `checkInTime` 字段**),且不可编辑 ### 实现 - **删除 diseaseChanges 全链路**(4 处):`api/.../discharge-summary/index.ts` 的 `DischargeSummaryVO`、`Form.vue`(模板表单项 / defaultForm / resetForm / submitForm 死亡清空的 payload)、`Print.vue`(打印行 / defaultForm)。源码 grep `diseaseChanges|疾病变化关注` 已零残留 - **入住日期**:`el-date-picker` → `el-input :model-value="form.checkInDate" disabled placeholder="选择长者后自动填充"`,与性别/年龄/居住区域三个自动回填字段视觉一致 - **handleSelectElder** 改为无条件覆盖:`form.checkInDate = formatDate(elder.checkInTime ?? elder.checkInDate ?? elder.inDate ?? '')` - **新增 formatDate helper**(dayjs):兼容毫秒时间戳(13位)/秒级时间戳(10位,×1000)/Date/字符串,非法值返回 `''`。避免长者档案 `checkInTime` 返回时间戳时输入框显示一串数字 - open() 编辑回显时同步格式化 3 个日期字段(checkInDate/dischargeDate/signDate),防后端返时间戳导致 date-picker 不回显 - **桌面后端规格文档同步**:DDL 删 `disease_changes` 列、两处 JSON 示例删字段、死亡兜底清理说明删该字段、字段对照表 27→26 条并重排序号 ### 复用要点 - 长者档案「入住日期」字段名是 **`checkInTime`**(不是 checkInDate/inDate)——合同模块 `AddForm.vue` 的 `elderUp(e)` 里就是 `dataForm.checkInTime = e.checkInTime` + `dayjs().format('YYYY-MM-DD')` 显示,可作为范式参考 ## 出院小结入住日期自动回填修正(本轮回填实际不生效,已修) ### 根因 上一轮在 `Form.vue handleSelectElder` 写 `form.checkInDate = formatDate(elder.checkInTime ?? ...)`,但 selectElder 在**下拉选择**走 `handleSelect` 分支,emit 的 `elder` 是【分页列表项】(`elderly-Info` 的 `listType`,字段仅 id/elderName/elderSex/buildName/floorName/roomName/bedName/birthday/elderAge/papersType/idCard/socialSecurityCardNumber),**不含 checkInTime**。只有 `upData`(watch 初次回填)路径 emit 的才是 `getElderInfoById` 完整档案、才带 checkInTime。故正常"选长者"后 入住日期 永远回填为空——上一轮"@elder 能直接取到 checkInTime"的复用要点对下拉路径不成立。 ### 修复 `handleSelectElder` 改为 async:拿到 `elder.id` 后 `getElderInfoById(id)`(`elderlyInfo/findDetailById`)拉完整档案取 `checkInTime`,失败回退列表项。居住区域四段同样详情优先、列表项兜底。入住日期 `el-input disabled` 只读不变。导入 `getElderInfoById` from `@/api/elderly/elder/elderly-Info`。 ## 出院小结操作栏新增「退住申请」按钮(带入长者,默认选中) ### 需求 出院小结列表操作栏增加一颗**无需权限**的「退住申请」按钮,功能等同退住申请页面(`elderly/bak/check-out`)的新增:打开弹窗后,选择长者组件**默认选中当前行长者**(带入 `elderId`)。 ### 实现(最小侵入,复用退住申请页面组件) - **`bak/check-out/ProcessForm.vue`**: - `` 加 `ref="selectElderRef"`(原组件已 `defineExpose({ upData })`,按 id 拉档案并 `emit('elder')` 回填 证件号/入住日期/床位号) - 新增 `selectElderRef` + `setElderById(id)`:`selectElderRef.value?.upData('', id)`(沿用全项目 ~60 处 `upData(res.elderName, res.elderId)` 范式,TS 类型一致) - `defineExpose` 增加 `setElderById` - **`bak/check-out/Form.vue`**:`open(tId, id, detail, status, elderId?)` 增加第 5 参。新增且带 elderId 时 `await nextTick()` 后先 `setTenantId(tId)` 再 `setElderById(elderId)`(详情/重新发起路径不变;老调用方只传 4 参,第 5 参 undefined,行为不变) - **`apply/discharge-summary/index.vue`**: - `import CheckOutForm from '@/views/elderly/bak/check-out/Form.vue'`,模板加 `` - 操作栏 `#next` 增加 `退住申请`(**无 v-hasPermi**) - `openCheckOut(row)`:解析租户(优先 `row.tenantId`,否则 `queryParams.tenantIds` 单选),无租户则 `message.error('请先选择一个机构')`;调用 `checkOutRef.value.open(tId, undefined, false, undefined, row.elderId)` - 注意 `queryParams.tenantIds` 可能是数组,需 `Array.isArray` 处理 ### 验证 - `selectElder` `upData` 已 emit `'elder'` → ProcessForm `handleSelectElder` 回填 idCard/checkInTime/bedName,长者姓名下拉框也因 `dataForm.elderlyId` 被赋值而默认选中 - 提交走退住申请真实接口 `elderlyBakAdd`,与退住申请页面新增完全一致 ### 实时问题:点击「退住申请」长者未默认选中(9-03 下午,已解决) - **用户截图**:退住申请弹窗打开后「长者姓名」下拉是空的;顶部有「服务器错误,请联系管理员!」 - **根因(已确认)**: 1. 出院小结 list 接口未通 → 列表走 mock 兜底,mock 数据 `elderId` 是 801/802/803 这种假值 2. `selectElder.upData('', id)` 内部调真实接口 `getElderInfoById(id)`(line 106),mock 假 ID 在真实后端不存在 → 返回失败 → upData 内部 `try/catch` 静默吞掉 → `inputValue` 未赋值 + `emit('elder')` 不触发 → 下拉框空 - **链路本身是对的**:upData → getElderInfoById → inputValue.value=res.id → emit('elder', res) → ProcessForm.handleSelectElder 回填 idCard/checkInTime/bedName - **验证方法**:临时把 openCheckOut 中 `row.elderId` 硬编码成真实 ID `17824`;用户硬刷后点击退住申请 → 下拉自动选中真实长者 + 其余字段回填 ✅(证明 selectElderRef + setElderById 新链路 OK) - **已回滚**:验证完把 `17824` 改回 `row.elderId`,并删除 debug console.log,代码恢复正常 - **接口接通后注意**:columns.ts 没列 `elderId` 字段,列表行对象的 `elderId` 字段名待后端确认(项目惯例用 `elderId` 或 `id`,万一返 `elderlyId` 也常见)——若真实接口接通后预选失效,优先核对 list 返回的字段名是否是 `elderId` - 类型检查(vue-tsc 仅筛 discharge-summary/check-out)待跑,范式与既有 AddForm 一致预期零新增错误