10 KiB
10 KiB
医院救援 APP(hospital-rescue-app-v2)· 项目长期约定
技术栈
- uni-app x(uvue + uts),不是 uni-app(vue)。页面后缀
.uvue,脚本<script lang="uts" setup>。 - UI 组件库 Tmui4(uni_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 px(uni-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 6px(25%),3/5 Tab 与两位数都成立;别改回 right 定位或库默认半出血(会盖住 icon 42%~100%)。 x-tabbar传:offset="[0,5]",Tab 项高度写死 60px(内容约 53px,上下余量仅 ~3.5px)。
uvue 硬约束(写样式前必读)
- 不支持
::before/::after→ 装饰一律用实体元素 - 不支持 CSS 变量
var()→ 颜色写实 - 不使用
display:grid、gap→ flex + margin - 只使用 class 选择器(不写后代选择器);复合选择器
.a.b也不要用 → 选中态改用 JS 计算 style 字符串内联下发 - Vue API 必须显式 import:
import { ref, computed } from 'vue' computed有返回类型时必须写computed(() : T => {}),不能写computed() : T => {}- 模板里调用的函数必须在 script 中定义,否则运行期崩
_ctx.xxx is not a function(编译期不一定报错) - 页面生命周期是 uvue 全局函数,不要从
@dcloudio/uni-appimport; 显示/隐藏要用onPageShow/onPageHide(不是 onShow/onHide) onLoad取参用下标opt['id'];res.content是string|null|undefined要先判空uni.showToast的icon是字面量联合类型('none'|'success'|'error'|'loading'…),封装时形参别写string- 不用
aspect-ratio;尺寸用 windowWidth 计算或用固定值 - 不用内联
<svg>;图形用 view/border-radius/x-circle-progress 等替代 - 不支持
@keyframes/animation(App 端编译期直接 ERROR,之前记的「可用」是错的); 也不支持page/body元素选择器(页面底色由.hr-page或页面根节点承担) → 动效一律改setInterval+ref驱动内联样式(参见 event/index 红点、warehouse/scan 与 hr-scan-cam 扫描线、study/center 与 wrong-book 手风琴);扫描步长取 1/帧数 才能精确命中端点 - 模板里不能直接调用 import 进来的函数:编译成
unref(fn)(x),App 端 Kotlin 报找不到名称"invoke"/Function invocation 'xxx(...)' expected(#error18) → 在页面 script 里包一层本文件定义的 wrapper 再给模板用 - UTS 不提升函数声明:被调用的函数必须写在调用点之前(computed 回调里最容易踩)
→ 扫描脚本
check-uts-order.js 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- 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 |
分段切换(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.1px(89%)安全;改文案先跑
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里登记角色归属
页面骨架用法
<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>(自己关弹窗)。