MEMORY.md 6.7 KB

项目长期记忆(kyj-yanglao-web-new)

项目身份

湖北省养老机构系统前端(yudao 框架:Vue3 + TS + Element Plus)。重点模块:九防看板、合同(湖北2024版)、入住登记、长期/临时医嘱单、护士执行医嘱。

⚠️ 登录页真实组件是 src/views/Login/Login.vue

  • components/LoginForm.vue(yudao 原版)在 Login.vue 里被注释掉,改它不生效。真实页面用原生 <input> + .inputM/.inputS 自定义样式,特征文案「账号登录/使用谷歌浏览器」
  • 2026-09-23 已在 Login.vue 实现「记住密码」:企业名/账号下拉(Teleport 到 body + fixed 定位 + 点击外部关闭,不依赖 blur)、密码框下「记住密码」复选框;账号列表存 src/utils/auth.ts 的 SavedAccounts(localStorage,RSA 加密,365 天,上限 20 条),两处登录组件共用

⚠️ 两个踩过的坑(改登录/记住密码相关代码前必看)

  1. utils/auth.ts 的 setLoginForm() 曾原地改写传入对象(password = encrypt(...))→ 后续再拿同一表单去存账号会二次加密导致密码永久丢失。 已改成 { ...loginForm, password: encrypt(...) }。教训:写枚举/工具函数不要就地修改入参
  2. 自绘下拉面板不要用 absolute 放在本项目 overflow:auto 容器里(会撑出滚动条触发重排→失焦→面板关闭); 用 Teleport to="body" + fixed + getBoundingClientRect() 定位,关闭逻辑用 document.mousedown 判定而不是 blur

医嘱模块(2026-08 搭建)

  • 长期医嘱 long-doctor-order/:长者 + 多条 entries 子表,条目含 status 启用/停止
  • 临时医嘱 short-doctor-order/:一次性,无启停,含 executeTime
  • 护士执行 execute-doctor-order/:前端按频次展开待执行行 + 执行/跳过弹窗 + 打印 Dialog(v-print 走可见 Dialog,禁止离屏)
  • API 集中在 src/api/elderly/elder/bed-change/index.ts
  • 时间字段请求契约(双向):orderTime/stopTime/executeTime/planTime/execTime 双向统一用毫秒时间戳(13位)。
    • 请求方向:API 层 serializeLongOrder/serializeShortOrder/serializeExecution 把字符串转毫秒(dayjs().valueOf())
    • 响应方向:API 层 fromTs helper 把毫秒(兼容 10 位秒)转回 "YYYY-MM-DD HH:mm:ss" 字符串,挂载在 5 个 GET 接口的 .then(getLongDoctorOrderPage / getLongDoctorOrder / getShortDoctorOrderPage / getShortDoctorOrder / getDoctorOrderExecutionPage),列表与详情/打印/表单都按字符串使用
    • planDate 查询参数保持 YYYY-MM-DD 字符串(按日期匹配,不是具体时刻)
  • 给药三要素(frequency/medTime/route)存缩写,前端 MED_*_OPTIONS 翻译;状态枚举 LONG_ORDER_STATUS / EXEC_STATUS
  • 后端规格文档(不在项目内):用户侧 doctor-order-backend-spec.md,已交付后端

用户偏好 / 约定

  • ⚠️ 保持项目目录整洁:非源码文件(.md 规格、交付物等)不要 Write 到项目根;交付文档走用户指定位置或先问
  • 打印页(v-print)打印区必须在可见 <Dialog> 内,禁止 position:absolute; left:-9999px 离屏(会空白)
  • 列表/接口未通时填充 mock 数据便于看效果
  • 多项目并行,不愿为单项目切 Node 版本(本地 Node v22 vs Vue2 项目要求 ≤20 冲突已知)

⚠️ axios 拦截器兼容性陷阱(已修复)

  • 项目拦截器 src/config/axios/service.ts:277 原只认 code === 200(result_code=200),code !== 200 走 Promise.reject('error') 分支
  • 本系统后端用 code:0 表示成功(医嘱/执行接口),所以调用方 await 后其实是被 reject 的,但 Network 看似"成功"
  • 排查信号:看到「Network 200 + 后端响应 code:0 + UI 完全不刷新 + 没抛错」,立刻打开 service.ts 看拦截器
  • 预防范式:写接口调用方 try/catch 都按成功处理(catch 里 swallow,因为后端实际已写入),然后无条件 emit/继续 UI 流程,关键 UI 刷新不能依赖 await 不抛错
  • ✅ 已全量实施:service.ts:277 改为 code !== 200 && code !== 0,兼容 yudao 标准成功码 code:0(不影响原 code:200 接口)。所有返回 code:0 的接口(含 doctorOrderExecution/page 读接口)现在 await 正常返回 data
  • 执行业务状态真相源:后端 doctorOrderExecution/page 接口返回的 execution 记录(含 orderType/orderEntryId/planTime/status)。buildRows 合并时 execMap(后端)优先;前端不持久化业务状态到 localStorage(换浏览器/清缓存/多端会不一致——用户 2026-08-26 明确否决该方案),仅在执行瞬间用纯内存 Map 防 UI 闪回。page 接口缺关键字段时会 console.warn('[执行医嘱] execution 记录缺少关键字段')——若刷新后状态回退为「待执行」,说明该接口未返回正确 execution 记录,需后端修接口,前端无法凭空持久
  • ⚠️ 后端 planTime 入库 BUG(待修):elderly/doctorOrderExecution/execute 入库时 planTime 未按请求体毫秒戳精确写入,page 返回的 planTime 总是同一个固定值/当前时间,与前端展开值对不上。前端在 execute-doctor-order/index.vue buildRows 做了精确优先 + (entry,索引) 兜底两层匹配:
    1. 精确 key = ${orderType}-${orderEntryId}-${planTime}
    2. 兜底:execution 按 createTime 升序、row 按 planTime 升序,用各自桶内索引对齐
    3. 兜底命中会 console.warn('[执行医嘱] 检测到 N 条 execution.planTime 与前端展开值不一致...') 输出 {rowPlanTime, execPlanTime, key}
    4. 总命中统计:console.info('[执行医嘱] execution 合并: 精确 X / 兜底 Y / 总行 N')
  • 兜底适用大多数真实场景(护士按时间顺序逐项执行),但用户倒序执行(如先点 bid 第二行)会错配。根除需后端按请求体 planTime 精确入库

⚠️ 表单 reset 失效陷阱(2026-08-27 已修)

  • 症状:编辑→关闭→新增 → Dialog 标题误判「编辑」、选择器锁死、提交参数带旧 id
  • 禁用模式:Object.assign(form, JSON.parse(JSON.stringify(defaultForm)))
    • 原因:JSON.stringify({id:undefined, ...}) 会丢弃 undefined 字段(不写入 JSON 字符串)
    • 反序列化后 source 对象没有 id/elderId key
    • Object.assign 只复制 source 自身可枚举属性 → target 上的 form.id 残留不变
  • 范式:表单 reset 必须逐字段显式重置(form.id = undefined; form.entries = [emptyEntry()]; ...),不依赖 JSON 拷贝。如果 defaultForm 不含 undefined 字段才能用 JSON 拷贝路径
  • 详见两个 Form.vue 文件头注释