This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
# 医院救援 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>`(自己关弹窗)。