合同组件(广州版 ContractBody.vue + 湖北版 ContractBodyHubei.vue)的月度费用列表,增加「手动添加费用行」能力:点击添加一行,格式与现有行一致,数据随合同保存、重新打开能解析回显。
contractForm.monthlyExpenses push 空行,标记 isManual: true,字段 { id: 'manual-'+Date.now(), isManual: true, itemName:'', itemCategoryName:'', actualAmount:'' }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",打印模式隐藏)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 标记保留,回显正确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:
- <selectElder> 加 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',模板加 <CheckOutForm ref="checkOutRef" @success="getList" />
- 操作栏 #next 增加 <el-button link type="warning" @click="openCheckOut(scope)">退住申请</el-button>(无 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 一致预期零新增错误