Files
hospital-rescue-app-v2/.workbuddy/memory/MEMORY.md
T
2026-09-24 16:25:22 +08:00

133 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 医院救援 APPhospital-rescue-app-v2)· 项目长期约定
## 技术栈
- **uni-app xuvue + uts**,不是 uni-appvue)。页面后缀 `.uvue`,脚本 `<script lang="uts" setup>`
- UI 组件库 **Tmui4uni_modules/tmx-ui**,全局配置在 `main.uts`designSize 375 / unit px / 主色 #2563EB / dark light)。
- easycom 自动扫描 `components/组件名/组件名.uvue`(本项目自研组件统一 `hr-` 前缀)。
## 设计规范
- 设计稿基准 **375 × 812**`mobile-design/v2/hospital-rescue-ui-v2.html` 为唯一视觉依据(35 屏)。
- 设计稿 px = uvue pxuni-app x px 为逻辑像素),不要乘 2。
- 主色 #2563EB;四色腕带分级:危重 #DC2626 / 重伤 #F59E0B / 轻伤 #10B981 / 死亡 #1F2937
-#F3F4F6、卡片 #FFF、分割线 #F3F4F6 / #E5E7EB、文本 #1F2937 / #6B7280 / #9CA3AF
## 角标(xBadge)几何 — 已定稿,别再改尺寸
- 全站角标基准(用户指定):高/圆角 36rpx、字号 22.4rpx、红底白字;`.xBadge-countAndLabel``!important` 覆盖。
- **角标实际宽 28px,不是 18px**`min-width:36rpx` 是内容盒最小值,还要加左右各 10rpx 内边距。
- 定位走 `hr-design.css` 的「左锚定」:`top:-4rpx; left:44rpx; right:auto; transform:none`(复刻设计稿
`left:calc(50%+6px)`),配 `.xBadge{padding:0!important}` + 全链 `overflow:visible!important`
实测只压 icon 6px25%),3/5 Tab 与两位数都成立;**别改回 right 定位或库默认半出血**(会盖住 icon 42%~100%)。
- `x-tabbar``:offset="[0,5]"`,Tab 项高度写死 60px(内容约 53px,上下余量仅 ~3.5px)。
## uvue 硬约束(写样式前必读)
1. **不支持** `::before` / `::after` → 装饰一律用实体元素
2. **不支持** CSS 变量 `var()` → 颜色写实
3. **不使用** `display:grid``gap` → flex + margin
4. **只使用** class 选择器(不写后代选择器);**复合选择器 `.a.b` 也不要用** → 选中态改用 JS 计算 style 字符串内联下发
5. **Vue API 必须显式 import**`import { ref, computed } from 'vue'`
6. `computed` 有返回类型时必须写 `computed(() : T => {})`,不能写 `computed() : T => {}`
7. **模板里调用的函数必须在 script 中定义**,否则运行期崩 `_ctx.xxx is not a function`(编译期不一定报错)
8. **页面生命周期是 uvue 全局函数,不要从 `@dcloudio/uni-app` import**
显示/隐藏要用 `onPageShow` / `onPageHide`(不是 onShow/onHide
9. `onLoad` 取参用下标 `opt['id']``res.content``string|null|undefined` 要先判空
10. `uni.showToast``icon` 是字面量联合类型('none'|'success'|'error'|'loading'…),封装时形参别写 `string`
11. 不用 `aspect-ratio`;尺寸用 windowWidth 计算或用固定值
12. 不用内联 `<svg>`;图形用 view/border-radius/x-circle-progress 等替代
13. **不支持** `@keyframes` / `animation`(App 端编译期直接 ERROR,之前记的「可用」是错的);
也不支持 `page` / `body` 元素选择器(页面底色由 `.hr-page` 或页面根节点承担)
→ 动效一律改 `setInterval` + `ref` 驱动内联样式(参见 event/index 红点、warehouse/scan 与
hr-scan-cam 扫描线、study/center 与 wrong-book 手风琴);扫描步长取 1/帧数 才能精确命中端点
14. **模板里不能直接调用 import 进来的函数**:编译成 `unref(fn)(x)`App 端 Kotlin 报
`找不到名称"invoke"` / `Function invocation 'xxx(...)' expected`#error18
→ 在页面 script 里包一层本文件定义的 wrapper 再给模板用
15. **UTS 不提升函数声明**:被调用的函数必须写在调用点之前(computed 回调里最容易踩)
→ 扫描脚本 `check-uts-order.js`
16. **`background-image` 在 App 端**`url(...)` 图片**完全不支持**(原生视图无背景图概念)
→ 铺底图/照片用 `<image>` 绝对定位铺满,或 `DrawableContext` 原生绘制;
`linear-gradient` **支持但只有一种合法形态**`linear-gradient(<关键字方向>, <色1>, <色2>)`
—— **必须 3 参数、方向必须是关键字(`to bottom right` 等,不是 `135deg`)、恰好 2 个颜色
(不能带 `%` 色标、不能 3 色)**flatten/vapor 模式下 `background-image` 直接失效。
→ 扫描脚本 `check-uvue-bg.js`
19. **UTS 里 `any` = Kotlin 非空 `Any`**null 强转必崩:
`java.lang.NullPointerException: null cannot be cast to non-null type kotlin.Any`
→ 想表达可空必须写 `any | null`**不要对可空值写 `as any`**(等于 `as Any`);
`UTSJSONObject.getAny(k)` 键不存在返回 null(不抛),调用方要自己兜;
结构体可选字段(`field?: any`)本就是可空,赋值时别强转。
排查技巧:栈指向哪个字面量 `return {` 那一行 → 就是字面量里某个 `as` 强转炸了。
## uni_modules 本地补丁清单(升级插件会被覆盖,务必回归)
| 文件 | 位置 | 现象 / 改法 |
|---|---|---|
| `uni_modules/tmx-ui/components/x-upload-file/x-upload-file.uvue` | `covertList()`(标记「⚠ 本地补丁(2026-09-24)」) | `defaultList` 项不带 `request``el.getAny("request") as any``null cannot be cast to non-null type kotlin.Any`;改为 `let requestdata : any | null = el.getAny("request")` + 去掉 `as any`APP/MP 与 WEB 两个 return 分支都改) |
## 分段切换(xRadioButton)— 全站 5 处已统一
- 原「`hr-row` + `hr-chip` 横向滚动」筛选全部换成 `x-radio-button`study/center、study/wrong-book、
message/list、message/index、triage/treat-records。
- `RADIO_BUTTON = { id:string, icon?, title, disabled? }`**change 下发 `(string, number)`**
与 x-tabs 不同、无类型坑。`v-model` 绑 string id,业务要序号时用
`chip = computed(() : number => { const i = parseInt(chipId.value); return isNaN(i) ? 0 : i })`
- 列表直接写字面量数组(项目惯例 `{ id:'0', title:'x' } as RADIO_BUTTON`),别再用 string[] + v-for 拼。
- **必须去掉外层 `<scroll-view :scroll-x>`**:组件是等宽 `flex:1` 分段,放进横向 scroll-view 会收窄、滑块定位错。
- 圆角坑:滑块圆角 = `parseInt(round) - parseInt(space)` 且**按 px 下发**(只有外层 round 跟 rpx),
round/space 传 rpx 时内外单位不一致 → 现配 `height=64rpx space=4rpx round=22rpx`(滑块呈胶囊,视觉 OK)。
- **字号必须 ≤22rpx**:等宽 4 段每段文本可用宽仅 60.75px,26rpx 加粗「心肺复苏 5」=63.9px 会截断
(无头 Chrome 实测),22rpx=54.1px89%)安全;改文案先跑 `node-workspace/radiobtn-fit-test.html`
- 样式基线:`bg-color #E5E7EB / active-color #2563EB / active-font-color #FFFFFF / font-color #6B7280`
## 事件中心(pages/event/index
- 用户定稿:**只保留「进行中 / 历史」两个 Tab(各占 50%)**,原「待接收」Tab 与 P0 全屏推送
(60 秒倒计时 / 立即接收 / 稍后处理 / 卡片 pending 分支)已整体移除——别按旧设计稿加回来。
- 紧急红点呼吸(dotStyle / startPulse)保留,触发只看数据的 `urgent` 字段(与 stage 无关)。
- ⚠ 遗留(已告知用户,待决定):mock 里 e3/e4 仍是 `stage:'pending'`(页面上不可见);
message/list 的「去接收 ›」仍跳事件中心,但进行中 Tab 看不到对应事件 —— 需把这两条改 'ongoing' 或删除。
## 三段式扫码作业页(批量处置 / 批量转运)
- `hr-scan-cam` **默认不启动相机**cameraOn=false):点击整块取景区 `openCamera()` 才开启,
未开启时显示居中引导「点击开启摄像头」;底部按钮文案 开启扫码 ⇄ 连续扫码中。
扫描线定时器随相机开关启停(openCamera/toggleCamera 里 startLaser/stopLaser),不要 onMounted 空跑。
- 批量转运「接收医院」是折叠字段 `.op-pick`(医院名+床位+⌄),点击整行唤起
`hr-mask + hr-sheet` 弹层(与 victims/create 同一套类);选项为「医院名 + hospitalMeta 距离」两行,
满员医院名置灰(os-name-dim)+ 床位红字,点它弹 toast 且**不关闭弹层**,选可用医院才关。
## 目录约定
```
components/ 自研通用组件(hr- 前缀,easycom
common/styles/hr-design.css 全局设计系统(App.uvue 里 @import
mock/types.uts 数据模型类型
mock/index.uts mock 数据(换接口只改这里)
utils/role.uts 会话 + 双角色 TabBar + 页面权限
utils/nav.uts 统一跳转(含权限拦截)
utils/quiz.uts 答题状态(跨页共享)
pages/ 页面(按模块分子目录:home/event/mine/course/study/triage/warehouse/message/design
```
## 双角色
- 救援人员端 rescuer:Tab 首页 / 事件 / 我的;工号以 `1` 开头
- 仓管人员端 keeper:Tab 扫码 / 仓库 / 设备 / 消息 / 我;工号以 `2` 开头
- 新增页面务必在 `utils/role.uts``canAccess` 里登记角色归属
## 页面骨架用法
```vue
<hr-page page-bg="#F3F4F6" status-bg="#FFFFFF" nav-title="标题">
<template v-slot:right>导航右侧</template>
<view class="hr-body">内容</view>
<template v-slot:footer>底部固定条</template>
<template v-slot:tabbar><hr-tabbar :active="0"></hr-tabbar></template>
<template v-slot:overlay>全屏/底部弹层</template>
</hr-page>
```
- 三段式作业页(相机+列表+表单)用 `:no-scroll="true"`
- 需要自定义头部的页面(伤员详情)直接手写根 view + hr-statusbar
## 数据来源
当前全部走 `mock/index.uts`;对接接口时替换 mock 取数实现,页面层不改。
## 伤情标记(2D 人体图鉴)
- 数据层 `utils/injury.uts`:HrInjury 纯基础类型可直接 JSON`injuriesToJson/injuriesFromJson` 存草稿与回显(接后端只换这两个)。
- 部位 id 按视野命名(armVL=图左臂)、name 按伤员左右(正面 armVL→右上肢);9 个热区互不重叠。
- 展示组件 `components/hr-bodymap`(正/背面,背面有脊柱线);业务/弹窗在 `pages/triage/create.uvue`(x-drawer 表单:部位自动填 + 其余默认填充 + 描述自动生成)。
- 标记 = 编号圆点(不旋转,色=出血情况)+ 长轴条(长=尺寸、rotate=走向);改数组必须重建再赋值。
- x-drawer 要点:`v-model:show`bottom 时 `size`=内容高;自定义 footer 需 `:show-footer="true"` + `<template v-slot:footer>`(自己关弹窗)。