1
This commit is contained in:
@@ -0,0 +1,695 @@
|
||||
# 2026-09-24 医院救援 APP(uniappx + Tmui4)UI 全量重构
|
||||
|
||||
## 目标
|
||||
按 `mobile-design/v2/hospital-rescue-ui-v2.html`(35 屏设计稿)在 uniappx 项目中 1:1 还原 UI,并实现全部交互,兼容 APP + H5,双角色(救援端 / 仓管端)。
|
||||
|
||||
## 已完成
|
||||
- **清空 pages/** 并重建:33 个页面(35 屏中 s07/s42 合并为事件中心同一页的执行状态 Tab)
|
||||
- **设计令牌落地**:新增 `common/styles/hr-design.css`(App.uvue 全局 @import),因 uvue 限制做了三类适配:
|
||||
1. 不支持 `::before/::after` → 全部改为实体元素(如 `.hr-sec-bar`、`.bm-*` 人体图鉴)
|
||||
2. 不支持 CSS 变量 `var()` → 颜色全部写实
|
||||
3. 不用 `grid` / `gap` → 全部 flex + margin;只使用 class 选择器(避免复合/后代选择器风险)
|
||||
- **全局配置**:main.uts 设 `designSize:375 / unit:'px' / color:'#2563EB' / dark:'light'`;manifest 改为「医院救援管理平台 v2.0.0」
|
||||
- **mock 数据层**:`mock/types.uts`(类型)+ `mock/index.uts`(数据),页面只依赖类型,后续换接口只改 mock 层
|
||||
- **角色体系**:`utils/role.uts`(hrSession / 双角色 TabBar / canAccess 权限拦截)、`utils/nav.uts`(统一跳转+权限拦截)、`utils/quiz.uts`(答题态,供答题页与答题卡共享)
|
||||
- **16 个通用组件**(components/,easycom):hr-page / hr-statusbar / hr-card / hr-sec / hr-lrow / hr-chip / hr-wb / hr-pc / hr-timeline / hr-tip / hr-empty / hr-progress / hr-wband / hr-tabbar / hr-scan-cam / hr-rec-list
|
||||
- **复杂交互**:真实相机连续扫码(x-mlkit-scannig-u,批量处置/批量转运/出入库)、P0 全屏推送 60s 倒计时接单、2D 人体图鉴伤情标记增删、多模态上传(chooseImage)、答题卡联动、离线队列模拟重传、设备 5 项自检门禁、接收医院床位满员拦截
|
||||
- **双角色 TabBar**:救援端 3 Tab(首页/事件/我的)、仓管端 5 Tab(扫码/仓库/设备/消息/我),登录页可切换角色演示
|
||||
|
||||
## 关键坑(务必记住)
|
||||
- uvue 语法:`computed() : T => {}` 是**非法**写法,必须 `computed(() : T => { ... })`——本次批量修了 17 处
|
||||
- Vue API 需要**显式** `import { ref, computed } from 'vue'`,不能依赖自动注入
|
||||
- `onLoad` 参数用下标访问:`opt['id']`(官方示例 `event["post_id"] ?? ""`),不要用 `getString`
|
||||
- 视频/取景框不能用 `aspect-ratio` → 用 `uni.getWindowInfo().windowWidth` 计算像素高度
|
||||
- 覆盖层(底部选择器/全屏推送)通过 hr-page 的 `overlay` 具名插槽渲染,且在根节点上需 `position:relative`
|
||||
- 三段式作业页(批量处置/转运)用 hr-page 的 `no-scroll`,内部自管滚动区
|
||||
- 图片/图表类设计元素(圆环→x-circle-progress、人体图→几何 view 拼装)不能照搬 SVG
|
||||
|
||||
## 静态校验方法(可复用)
|
||||
用 esbuild 的 `transformSync(code,{loader:'ts'})` 校验 `<script>` 语法 + 正则校验模板标签配平 + 比对 pages.json 路由与文件 + 校验组件标签是否存在于 components/ 或 uni_modules。
|
||||
已固化为技能 `~/.workbuddy/skills/uvue-design-port/scripts/check-uvue.js`(含「模板调用了未声明函数」检查)。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-24 第二轮:H5 运行期报错修复
|
||||
|
||||
用户跑 H5 报错,逐条定位修复:
|
||||
|
||||
### 崩溃(必须修)
|
||||
- **`_ctx.gradeStyle is not a function` @ create.uvue:51** —— 模板写了 `:style="gradeStyle(i)"`,
|
||||
但 script 里只定义了 `gradeClass(i)`,漏写 gradeStyle。
|
||||
→ 补 `gradeStyle(i)`(内联下发边框/底色),删掉 `gradeClass` 与 `.c-grade-sX.c-grade-on` 复合选择器。
|
||||
- **连带报错 `Cannot read properties of null (reading 'attachVmProps')` @ nav.uts:13**
|
||||
是上面渲染崩溃导致的 H5 导航状态损坏,**不是独立 bug**,页面修好后自动消失。
|
||||
经验:H5 下页面 render 抛错会连带污染 `uni.navigateTo`,先修渲染错误再看后续栈。
|
||||
|
||||
### 顺带修的问题
|
||||
- `gradeClass` 之外还发现 `pickClass(i)` 逻辑写死 `i==0` 恒为选中 —— 改为四项统一用 `.c-pick`
|
||||
(设计稿本就是四个浅蓝填充的已选外观),删掉 pickClass。
|
||||
- `computed` 实现有 bug:`pickValues` 初值改为按索引逐个取,避免依赖数组展开。
|
||||
|
||||
### 编译警告修复
|
||||
- `nav.uts` 的 `toast(title, icon: string)` → 形参改字面量联合类型 `HrToastIcon`,
|
||||
否则 `uni.showToast` 报 `Type 'string' is not assignable to type 'Icon | null | undefined'`。
|
||||
- 5 个文件误从 `@dcloudio/uni-app` import 页面生命周期 → 全部删除 import,
|
||||
改用 uvue 全局函数;并纠正 quiz/answer-card 的 `onShow` → **`onPageShow`**(uvue 组合式是 onPageShow/onPageHide)。
|
||||
- `res.content` 类型是 `string|null|undefined`(3 处 editable showModal)→ 先判空再 `as string`。
|
||||
- `hr-tabbar` 去掉 setup 期间写全局 `xTabbarConfig` 的调用,避免多页面互相干扰 / 响应式风暴。
|
||||
|
||||
### 保真度回补
|
||||
- 补回 CSS 动效:出入库/扫腕带的**扫描激光线**(@keyframes 上下往复)、事件中心**待接收红点呼吸**。
|
||||
|
||||
修复后 49 个 .uvue 静态校验 0 错误。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-24 第三轮:全局尺寸升级 + 全面改用 tmx-ui 组件
|
||||
|
||||
用户提出 13 项全局改造,已完成主体。
|
||||
|
||||
### ① 字号/尺寸偏小 → 全量 px 转 rpx
|
||||
- **根因**:tmx-ui 本版本的 `checkIsCssUnit()` 是**空实现**(只补单位,不做 designSize 缩放),
|
||||
所以库组件与我的样式都渲染成「设计稿原始 px」,在宽屏(H5 桌面)上就显得又小又拉伸。
|
||||
- **方案**:全量 `px → rpx`(设计稿 px × 2),375 基准下 1:1 不变,宽屏按比例放大且不溢出。
|
||||
脚本批量转换 **1922 处 / 41 个文件**(`common/styles/hr-design.css` + 所有 `<style>` 块)。
|
||||
字号额外再 ×1.12(10.5→11.8、14→15.7、21→23.5 等)。
|
||||
- 手工处理 6 处 JS 动态尺寸:视频区高 386rpx、答题卡格子 92rpx、趋势柱 176rpx、进度条 h×2 rpx、
|
||||
hr-sec 内联 margin、扫码取景框 686rpx(保持正方形)。
|
||||
- `manifest.json → web` 增加 `rpxCalcMaxDeviceWidth/rpxCalcBaseDeviceWidth = 480`,
|
||||
避免 H5 桌面端 rpx 无限放大(若该字段被忽略则回退默认行为,无副作用)。
|
||||
- **注意**:`hr-statusbar` 的高度必须保持 px(来自 `uni.getWindowInfo().statusBarHeight`)。
|
||||
|
||||
### ② 底部 tabbar「我的」凸起
|
||||
- 真因:`x-tabbar` 的 `outIndex` **默认值是 2**,救援端第 3 个 Tab(我的)正好被当成凸起项。
|
||||
→ 传 `:out-index="-1"` 关闭凸起,并把 `font-size/icon-size` 改为 rpx(26rpx / 48rpx)。
|
||||
|
||||
### ③⑤⑥⑦⑨ 用「包装组件」一处改造全站生效(关键手法)
|
||||
| 需求 | 做法 | 覆盖范围 |
|
||||
|---|---|---|
|
||||
| xCell | `hr-lrow` 内部改为 `<x-cell>`(card=false + avatar 插槽 + right 插槽) | 30+ 处列表 |
|
||||
| xAlert | `hr-tip` 内部改为 `<x-alert skin="thin">` | 15 处提示条 |
|
||||
| xSteps | `hr-timeline` 内部改为 `<x-steps vertical>` + `<x-steps-item>` | 6 处时间线 |
|
||||
| xIcon | 新增 `utils/icon.uts` 的 `toRemixIcon()` 字符→RemixIcon 映射,组件内统一解析 | 所有图标 |
|
||||
|
||||
> 好处:页面调用方式不变,30+ 调用点零改动即换成库组件;新页面可直接写 `<x-cell>` / RemixIcon 名。
|
||||
|
||||
### 新增/替换的库组件
|
||||
- **xSearch**:`triage/victims`、`course/library`、`warehouse/stock` 的搜索框
|
||||
- **xTabs**:`event/index`(3 执行状态)、`triage/detail`(4 分页)
|
||||
- **xAvatarGroup**:`event/index` 卡片头像组、`event/detail` 救援团队
|
||||
- **xIndexbar**:`message/contacts` 整页重写为索引列表
|
||||
- **xUploadFile**:`triage/create` 多模态记录附件
|
||||
- **xInput**:`login` 工号/密码
|
||||
- **xSkeleton**:新增 `components/hr-skeleton`(基于 xSkeleton),`event/index` 已接入
|
||||
|
||||
### 待办(未完成,下次继续)
|
||||
- xTabs 还剩 4 处:`study/center`、`warehouse/scan`、`warehouse/devices`、`study/exams` 的 seg
|
||||
- xInput 还剩:`triage/create` 表单字段、批量处置/转运的联系人字段
|
||||
- xSkeleton 还剩:`home`、`triage/victims`、`warehouse/stock` 等列表页加载态
|
||||
- 首页/详情页里仍有少量 `{{ glyph }}` 形式的独立字符图标(tr-quick / lc-tabs 等)待换 x-icon
|
||||
|
||||
### 复用的排查手法(已验证)
|
||||
静态校验脚本 `check-uvue.js` 已能拦住:`computed() : T =>` 写法、模板调用未声明函数、组件标签不存在、路由未注册。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-24 第四轮:8 项 UI 缺陷修复
|
||||
|
||||
| # | 问题 | 根因 / 解法 |
|
||||
|---|---|---|
|
||||
| 1 | tabbar 页面底部没预留高度 | **`x-tabbar` 的 `position` 默认是 `'fixed'`** → 脱离文档流,其内部占位高度不生效,内容被底栏遮住。改传 `position="relative"` 即可自动占位。 |
|
||||
| 2 | 首页列表行文字比图标高、字偏大 | `x-cell` 的 `.xCellWrap` **写死 `padding:12px 0`**(且不随 rpx 缩放)导致行虚高。用全局 `!important` 覆盖收 0;同时 title 31.4→28rpx、desc 25.8→24rpx(两行合计≈64rpx,与左侧图标同高);hr-lrow 的 padding 22→16rpx。 |
|
||||
| 3 | 首页工具箱图标没换 | 工具箱是独立 `{{ q.icon }}` 字符渲染,没走 `toRemixIcon`。改为 `<x-icon>` 并把 mock `toolBox` 改成 remixicon 名;`deviceList` 同步改造。 |
|
||||
| 4 | 通讯录样式异常 | x-indexbar 的行是绝对定位 + `translateY`,行高必须与 `cell-height` 严格一致。原样式带圆角+左右外边距+104rpx 高(cell 120rpx)导致错位。改为满宽白底行、高 120rpx、底部 2rpx 分隔线。 |
|
||||
| 5 | 去掉 design/tokens | 删除页面 + pages.json 条目 + 「我的」入口(顺带把帮助与反馈的字符图标换成 remixicon) |
|
||||
| 6 | 事件页头像组 & 顶部栏 | `:max-count="5"`;移除 ev-nav(标题栏) |
|
||||
| 7 | 伤员查询扫码按钮 | 移除 `vs-scan-btn`(x-search 不支持自定义右侧图标,故改成搜索占满整行) |
|
||||
| 8 | 全局 1s 请求 + 骨架屏 | 新增 `utils/request.uts`(`MOCK_REQUEST_DELAY=1000` + `mockRequest(onDone)`);**把骨架屏做进 `hr-page`**:传 `:skeleton="N"` 即在请求期间用 xSkeleton 占位,页面零改造。已接入 15 个页面;手写的 stock / devices / message-index / event-index 单独接。 |
|
||||
|
||||
### 关键经验
|
||||
- 包一层「骨架屏能力」到页面骨架组件(`hr-page`)比每页写 loading 高效得多:新增页面只要加 `:skeleton="4"`。
|
||||
- 覆盖库组件内部写死样式时,scoped 样式带 `[data-v-xxx]` 属性选择器,优先级高于普通 class → **必须用 `!important`**(本项目 hr-design.css 在 App.uvue 里后引入)。
|
||||
- **x-cell 的 padding 参数是「左右在前、上下在后」**:`fillArrayCssValue` 对 2 元素数组返回 `[ar1,ar0,ar1,ar0]`
|
||||
(按 CSS 上右下左展开)⇒ 第一个元素是左右、第二个才是上下,所以 `['24rpx','0']` = 左右 24rpx、上下 0。
|
||||
⚠️ 第六轮已纠正:此前的记录写反了。
|
||||
- 卡片内列表行间距调宽:`hr-lrow` 上下 padding 16→24rpx(相邻两行内容间隔 48rpx),行 min-height 96→112rpx。
|
||||
- **骨架屏要「整页」而非几行**:`hr-skeleton` 改为「N 张卡片 × M 行」结构(默认 5 张卡 × 3 行,≈800px+),
|
||||
卡片用白底圆角 + 边框,视觉上就是「页面本身在加载」;再加 `.hr-sk{min-height:100%}` 兜底。
|
||||
只渲染几行会在下方露出大片底色,看起来就是「显示不全」。
|
||||
- **⚠️ uvue 头号布局坑:scroll-view 是 flex 容器,直接子项默认 `flex-shrink:1`**,
|
||||
内容高于容器时会被「压缩到刚好装下」,表现就是**页面/骨架屏只显示顶部一小截、且滑不动**。
|
||||
已在 `.hr-body` 与骨架屏内部(`.hr-sk` / `.hr-sk-card` / `.hr-sk-row`)显式加
|
||||
`flex-shrink:0; flex-grow:0;` + `min-height` 兜底。**今后任何写进 scroll-view 直接子级的容器都要加。**
|
||||
|
||||
### 骨架屏最终方案:绝对定位覆盖层(此前的 flex 修复还不够)
|
||||
即使加了 `flex-shrink:0`,真机上骨架屏仍只显示顶部一小截。
|
||||
**排查方法(很有用)**:把结构与 CSS 复刻成纯 HTML,用本机 Chrome 无头截图看真实渲染 ——
|
||||
`chrome.exe --headless=new --hide-scrollbars --window-size=375,812 --screenshot=out.png file:///xxx.html`
|
||||
结果 HTML 复现**完全正常**,说明 CSS 结构没问题,问题出在 uvue 的渲染差异上。
|
||||
|
||||
**最终解法**:不把骨架屏当 scroll-view 的 flex 子项,而是**绝对定位覆盖层**:
|
||||
```
|
||||
.hr-page(relative, flex column)
|
||||
├ statusbar / x-navbar
|
||||
├ .hr-flow ← 新增,flex:1 + min-height:0 + position:relative(内容区定位基准)
|
||||
│ ├ .hr-scroll > <slot/> 真实内容
|
||||
│ └ .hr-sk-mask (absolute inset:0) 骨架屏覆盖层(不参与 flex,永不被压缩)
|
||||
│ └ .hr-scroll > .hr-body > hr-skeleton
|
||||
└ footer / tabbar slots
|
||||
```
|
||||
绝对定位元素完全脱离 flex 布局,高度必然等于内容区 → 稳定铺满整页;覆盖层自带 scroll-view,
|
||||
内容超高时可滚。手写根节点的 4 个页面(stock / devices / message-index / event-index)也已同步改成此结构。
|
||||
|
||||
### 最终定为:不用骨架屏,统一用 xLoading(用户决定)
|
||||
骨架屏在真机上始终显示不全,用户决定**去掉全部骨架屏,加载中改用组件库 `xLoading`**。
|
||||
- `hr-page` 的 prop 由 `:skeleton="N"` 改为 **`:xloading="true"`**(Boolean);15 个页面已批量替换。
|
||||
- 覆盖层容器复用已验证可行的绝对定位方案:`.hr-loading-mask`(absolute inset:0 + flex 居中)。
|
||||
- 手写根节点的 4 个页面同步改为 `<view v-if="loading" class="hr-loading-mask"><x-loading label="加载中…"/></view>`。
|
||||
- **`components/hr-skeleton` 已删除**,`hr-design.css` 里的 `.hr-sk*` 样式已移除。
|
||||
- `x-loading` 组件用法:`<x-loading label="加载中…" :icon-size="'44rpx'" :text-size="'26rpx'" :vertical="true" color="#2563EB" text-color="#6B7280" />`
|
||||
(props:color/textColor/textSize/iconSize/vertical/icon/hideText/label;默认 icon 为 `loader-line`、vertical 默认 true)
|
||||
|
||||
### 经验(写给以后的自己)
|
||||
布局问题别靠读代码猜 —— **先把结构+CSS 复刻成 HTML,用本机 Chrome 无头截图看真实渲染**,
|
||||
一眼就能判断是「CSS 结构问题」还是「uvue 渲染差异」,避免连续几轮盲改。
|
||||
|
||||
### 仍未完成
|
||||
- `study/center`、`warehouse/scan`、`study/exams` 的分段控件待换 xTabs
|
||||
- `triage/create` 表单字段待换 xInput
|
||||
- `course/library`(noScroll)等少数页面未接骨架屏
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-24 第五轮:全站角标尺寸统一
|
||||
|
||||
用户要求:事件页底部 tabbar 角标、事件列表页顶部切换栏(xTabs)角标、以及其他页面角标,
|
||||
**尺寸统一按「我的」页消息中心 item 右侧角标**(基准 = 高/圆角 36rpx、字号 22.4rpx、红底白字)。
|
||||
|
||||
### 角标的两条来源(关键认知)
|
||||
全站角标其实分两类,必须分别处理:
|
||||
|
||||
| 类型 | 出现在哪 | 怎么改 |
|
||||
|---|---|---|
|
||||
| **自写角标** | `hr-lrow` 右侧插槽(我的页消息中心、消息中心各分组) | 统一用一个全局 class `.hr-badge` / `.hr-badge-t` |
|
||||
| **组件库角标 xBadge** | `x-tabbar`(`dotLabel`)、`x-tabs`(`dotType:'label'`+`dotText`) | 只能靠全局 `!important` 覆盖 `.xBadge-countAndLabel` / `.xBadge-countAndLabelText` |
|
||||
|
||||
- 自写角标:`hr-badge`/`hr-badge-t` 落在 `hr-design.css`,`pages/mine/index.uvue`(基准)与
|
||||
`pages/message/index.uvue` 都改用它;原先各自的 `.mine-dot`/`.mine-dot-t`、`.ms-dot`/`.ms-dot-t` 已删除。
|
||||
- xBadge:`x-tabbar.uvue` 里是 `<x-badge :dot="item.dotType=='dot'" :label="item.dotLabel">`,
|
||||
`x-tabs.uvue` 里是 `<x-badge :bg-color="item.dotColor" :dot="..." :label="item.dotText">`。
|
||||
**两处都没传 `fontSize`,所以用的是组件默认 `fontSize:"9"`(9px)**,比基准的 22.4rpx 明显小 —— 这就是用户看到的不一致。
|
||||
- xBadge 的尺寸来源:盒子靠自身 `.xBadge-countAndLabel{ padding:0rpx 4px }`,字号靠**内联 style**
|
||||
(`:style='{color:_fontColor,fontSize:_fontSize}'`)。**内联样式只能用 `!important` 压**(CSS 规定
|
||||
`!important` 声明 > 内联 style)。颜色(`dotColor`)不要动,各 Tab 语义色要保留。
|
||||
|
||||
### 验证手法(这次用来「不再猜」)
|
||||
`rpx` 浏览器不认!第一次复刻 HTML 时所有 `36rpx` 都被判为非法值整条丢弃,测出来还是 9px,差点误判。
|
||||
**正确做法**:按 375 逻辑宽折算 `1rpx = 0.5px` 再写进 HTML,然后用
|
||||
|
||||
```
|
||||
chrome.exe --headless=new --virtual-time-budget=1500 --dump-dom file:///xxx.html
|
||||
```
|
||||
在页面里用 `getComputedStyle()` 把高度/圆角/内边距/字号打到 `<pre>`,直接读到硬数字。
|
||||
实测结果:xBadge 与自写 `hr-badge` 都是 **28.0 × 18.0 px**(即 56×36rpx),完全一致 —— 证明
|
||||
全局 `!important` 确实压得住库组件的内联 `font-size:9px`。
|
||||
|
||||
### 明确没动的「长得像角标但不是数字角标」的元素(避免误伤)
|
||||
- `event/index` 的 `.evt-dot`(16rpx 优先级状态圆点)、`.ev-push-badge`(P0 派单白色胶囊)
|
||||
- `home/index` 的 `.tr-bell-dot`(14rpx 红点提醒)、`hr-rec-list` 的 `.rl-c`(浅蓝「N 人」信息 chip)
|
||||
- `hr-wb` / `hr-wband` 的状态小圆点、`triage/create` 的 `.c-grade-dot`(44rpx 分级选择圈)
|
||||
- `course/media` 的 `.md-badge`(按类型着色的分类标签,不是计数角标)
|
||||
这些是「状态点 / 标签 / 胶囊」,与计数角标语义不同;若也要统一需先确认。
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-24 第六轮:5 项修复(崩溃 / 角标裁切 / 图标 / 学习中心 / 未读隐藏)
|
||||
|
||||
### ① H5 崩溃:`val.map is not a function` @ x-alert.uvue:266(★ 重要通用坑)
|
||||
**根因**:`x-alert` 的 `round` prop 类型是 **`string[]`(数组)**,而 `hr-tip` 传了 `:round="'22rpx'"`(字符串)。
|
||||
`_round` computed 里 `props.round.length == 0` 对字符串成立("22rpx".length == 5),于是走到
|
||||
`fillArrayCssValueByround(props.round)` → `val.map` → 字符串没有 `.map` → 崩。
|
||||
|
||||
**通用教训**:tmx-ui 里凡是「数组型几何 prop」——
|
||||
`round / padding / margin / border / borderColor / darkBorderColor / radius` ——
|
||||
**必须传数组**,传字符串/布尔会在 `fillArrayCssValue*` 的 `val.map` 处直接抛异常。
|
||||
两元素约定恒为 **`[左右, 上下]`**(`fillArrayCssValue` 对 2 元素返回 `[ar1, ar0, ar1, ar0]`,
|
||||
按 CSS 上右下左展开 ⇒ 第二个元素才是上下)。
|
||||
顺带修正第四轮记错的一条:x-cell / x-alert 的 2 元素都是 `[左右, 上下]`,不是「上下在前」。
|
||||
(`hr-tip` 原来的 `['20rpx','24rpx']` 因此被理解成左右 20 / 上下 24,已一并纠正为 `['24rpx','20rpx']`。)
|
||||
|
||||
**现存同类隐患**(没崩但类型不对,无视觉效果,先记着):
|
||||
`stock.uvue` / `victims.uvue` 的 `<x-search :border="true">`,而 `x-search.border` 是 `string[]`;
|
||||
内部 `props.border.length != 3 → "none"`,所以现在等于「无边框」(与库默认一致)。
|
||||
|
||||
### ② 角标尺寸统一(承接第五轮)→ tabbar 角标被裁
|
||||
把 xBadge 放大到 36rpx 后,tabbar 上角标出现「超出部分被隐藏」。
|
||||
**根因**:x-badge 默认把角标**半出血**挂在右上角(`transform: translate(50%,-50%)`),
|
||||
需要容器上方留出半个角标高度;而 **x-tabbar 的 Tab 项高度写死 `height = ref(60)`(px,且不可配)**,
|
||||
icon(30px 固定) + 文字已占满,没有出血空间 → 超出的部分被裁。
|
||||
**解法**(`hr-design.css`):
|
||||
```css
|
||||
.xBadge-countAndLabel { top: 0 !important; transform: none !important; } /* 取消出血,贴容器内右上角 */
|
||||
.xBadge, .xBadgeWrap, .xTabbarBox, .xTabbarWrap, .xTabbarItem { overflow: visible !important; }
|
||||
```
|
||||
取消出血后角标任意屏宽都完整落在 `item` 内,不再依赖上方空间。
|
||||
(用 Chrome 无头 + 1:1 复刻 x-tabbar 结构验证过:角标完整落在虚线 item 框内。)
|
||||
|
||||
### ③ 课程详情页(`pages/course/subject.uvue`)图标替换
|
||||
- 三入口 `✗ / ★ / ▸` → `<x-icon :name="toRemixIcon(e.icon)" :color="e.color">`
|
||||
- 资料行 `▶` → `<x-icon name="play-fill">`(PDF/DOC/PPT/XLS 是文件格式标签,**保留文字**,不要转图标)
|
||||
- 资料行右侧 `›` → `<x-icon name="arrow-right-s-line">`(`.doc-go` 由文字样式改为 flex 容器)
|
||||
- 删除失效样式 `.sj-entry-ic-t` / `.doc-ic-play`
|
||||
|
||||
### ④ 学习中心(`pages/study/center.uvue`)
|
||||
- 自绘分段控件 `lc-tabs` → **`<x-tabs>`**(`item-width="25%"`,写法抄 `triage/detail.uvue`);
|
||||
`cat` 由 `ref(0)` 改为由 `activeId`(字符串 id)换算的 `computed`
|
||||
- 「查看解析」`⌃ / ⌄` 字符 → `<x-icon name="arrow-up/down-s-line">`
|
||||
- 折叠框改手风琴:新增 `.qa-acc` 容器 + `@keyframes qa-acc-in`(opacity + translateY 240ms);
|
||||
`toggle()` 改为**同时只展开一项**(真正的手风琴语义,原为多项可同时展开)
|
||||
|
||||
### ⑤ 未读角标「查看后隐藏」
|
||||
新增 `utils/unread.uts`(唯一来源,`reactive`):
|
||||
```ts
|
||||
export const hrUnread = reactive({ events: 4, messages: 4, msgCenter: 4 })
|
||||
export function badgeText(n) { return n > 0 ? n.toString() : '' } // 空串 → xBadge 自动隐藏
|
||||
export function markEventsRead() { hrUnread.events = 0 }
|
||||
export function markMessagesRead() { hrUnread.messages = 0; hrUnread.msgCenter = 0 }
|
||||
```
|
||||
- `utils/role.uts`:`rescuerTabs` / `keeperTabs` 由 **const 数组改为函数**(每次重建数组,
|
||||
读 `badgeText(hrUnread.xxx)`)→ 未读数变化时 hr-tabbar 的 computed 重新求值,角标自动消失。
|
||||
- 「我的」页消息中心入口:`v-if="hrUnread.msgCenter > 0"`
|
||||
- `event/index` `onPageShow` → `markEventsRead()`;`message/index`、`message/list` `onPageShow` → `markMessagesRead()`
|
||||
- `message/index` 单条角标:新增本地 `readKeys`(key = `分组下标-条目下标`),点开单条即消失,
|
||||
「全部已读」清空全部;`onMsgClick` 增加 `gi, i` 两个参数。
|
||||
|
||||
### 校验
|
||||
48 个 .uvue 静态校验 **0 错误**;另用 esbuild 单独校验了 3 个改动的 `.uts`(check-uvue.js 不覆盖 .uts)。
|
||||
|
||||
### 仍未做(用户没提,待确认)
|
||||
- `pages/study/wrong-book.uvue` 有与学习中心**完全相同**的 `qa-fold` 字符图标 + 折叠逻辑,未同步改造
|
||||
- `lc-tabs` 在 `pages/message/index.uvue` 还剩一处(`.lc-tabs span.active` 样式块,模板可能已不用,待清理)
|
||||
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 第七轮:收尾清理 + 数组型 prop 全站审计
|
||||
|
||||
### ① 补齐第六轮遗留
|
||||
- `pages/study/wrong-book.uvue`:「查看解析」`⌃ / ⌄` → `<x-icon>`,折叠框改手风琴
|
||||
(`.qa-acc` + `@keyframes qa-acc-in`,`toggle()` 先全收起再切换),与 `study/center.uvue` 完全对齐
|
||||
- `pages/message/index.uvue`:删除孤儿样式块 `.lc-tabs span.active`(模板早已不用)
|
||||
|
||||
### ② 修复 `x-search.border` 误传(真实设计偏差)
|
||||
`x-search.border` 是 `string[]`,且内部守卫是 `if (borderstr.length != 3) return "none"` ——
|
||||
**恰好要 3 项 `[宽度, 线型, 颜色]`**,不做单位换算,原样拼进 inline style。
|
||||
- `warehouse/stock.uvue`、`triage/victims.uvue`:` :border="true"` → `:border="searchBorder"`
|
||||
(`const searchBorder : string[] = ['1px', 'solid', '#E5E7EB']`)。
|
||||
原先布尔传参**不崩但永远无边框**,而设计稿 `.field` / `.srch` 都是 `1px solid #E5E7EB` → 属静默还原偏差。
|
||||
同时删掉死属性 `border-color`(颜色是数组第 3 项,不是 prop)。
|
||||
- `course/library.uvue`:删掉无效的 `:border="false"`(默认 `[]` 即 "none",等价且更诚实)。
|
||||
- ⚠️ 别把 `round` 一起改:`x-search.round` 是 **String**(`round="24rpx"` 正确)。
|
||||
- 顺带核对:`x-search._border` 不做 `fillArrayCssValue`,所以布尔**不会**触发 `val.map` 崩溃;
|
||||
但 `x-alert.round/padding/margin` 走 `fillArrayCssValue*`,传布尔照样崩——_不同组件行为不同_。
|
||||
|
||||
### ③ 新增两个可复用校验脚本(放在隔离 node 工作区,不进仓库)
|
||||
`C:/Users/Hyj61/.workbuddy/binaries/node/workspace/`
|
||||
- `check-uvue-hr.js <项目根>`:.uvue script 语法(esbuild)+ 标签配平 + pages.json 路由 + **.uts 语法**
|
||||
- `audit-array-props.js <项目根>`:读 tmx-ui 各组件声明的 `prop: string[],`,反查 pages/components
|
||||
实际传参,命中布尔/数字/字符串即报出(本次扫出并修完 3 处)
|
||||
|
||||
⚠️ **校验脚本自身两个坑(踩过)**:
|
||||
1. `/<template>([\s\S]*?)<\/template>/` 惰性匹配会被内层 `<template v-slot:right>` 的闭合标签
|
||||
**提前截断** → 满屏「未闭合标签 hr-page, template」假报错。
|
||||
正确:**先抠掉 `<script>` / `<style>` 整块,再对剩余全文做标签配平**。
|
||||
2. 遍历必须排除 `uni_modules/`,否则 49 个业务 .uvue 被算成 194 个,还会扫到
|
||||
`x-svg-u` 等库内非常规写法 → 必然假报错。
|
||||
|
||||
### ④ 校验结果(基线更新)
|
||||
`.uvue 49 / 路由 32 / .uts 11 / 错误 0`;`audit-array-props.js` → 未发现数组型 prop 误用。
|
||||
> 上一轮记的「48 个 .uvue」是因为脚本漏扫了 `components/` 目录,**49 才是正确基线**。
|
||||
|
||||
### ⑤ 环境备注
|
||||
本会话 shell 工具的 PATH 异常,直接跑 `ls/grep/node` 会 `command not found`。
|
||||
规避:命令前加
|
||||
`export PATH="/c/Users/Hyj61/.workbuddy/binaries/PortableGit/versions/1.2.0/usr/bin:/c/Users/Hyj61/.workbuddy/binaries/PortableGit/versions/1.2.0/mingw64/bin:$PATH"`
|
||||
(或直接用 Glob / Grep / Read 专用工具)。
|
||||
另:同一文件连续两次 Edit 时,第一次的改动可能被第二次覆盖,**改完要回读确认**。
|
||||
|
||||
---
|
||||
|
||||
## 第八轮:5 项 UI 缺陷修复(XCell 单行省略 / 顶部角标 / tabbar 角标 / 通讯录 / 上传误报)
|
||||
|
||||
### ① 所有 XCell 的 title 超出一行显示省略号
|
||||
根因:库 `x-cell` 的 `.title` 写死 `lines:2`(scoped 样式,`!important` 压字号压不掉行数)。
|
||||
解法:`hr-lrow` 改用**默认插槽自渲染**标题 `<text class="hr-lrow-t">`(`lines:1` + `text-overflow:ellipsis`)。
|
||||
已核对 x-cell 模板:默认插槽的 fallback 才是库自己的 `<text class="title">`,传入插槽即整体替换,不会双渲染。
|
||||
(x-cell 上的 `:title="title"` 保留无害,只是不再被渲染。)全站 30+ 处 hr-lrow 零改动生效。
|
||||
|
||||
### ② pages/event/index 顶部切换栏的角标去掉
|
||||
根因:`tabs` computed 给 x-tabs 传了 `dotType:'label'`+`dotText`(数量)。
|
||||
解法:tabs 只留标题;顺带删除 `.ev-tab-c*`/`.ev-tab-ct*` 整组角标胶囊样式。
|
||||
|
||||
### ③ 底部 tabbar 角标遮挡 icon(★ 本轮最有价值,连错两轮才修对)
|
||||
**两个算错的前提:**
|
||||
1. **角标实际宽 28px,不是 18px** —— `min-width:36rpx` 是「内容盒」最小值,还要加左右各
|
||||
10rpx 内边距 ⇒ 18 + 10 = 28px。按 18px 推算定位必然偏左(上一轮 `right:-20rpx` 因此只压 58%)。
|
||||
2. **库默认「半出血」本身就会盖住 icon 42%** —— x-tabbar 给 x-badge 传 `:offset="[0,5]"`,
|
||||
叠加 `translate(50%,-50%)` = 「角标中心 = 容器右上角」,28px 宽的角标有 10px 压在 icon 上。
|
||||
用户最初反馈的「遮挡」正是这个;第六轮改成 `top:0;transform:none` 反而变 100% 全盖,是倒退。
|
||||
|
||||
**定稿(`hr-design.css`)=改「左锚定」,复刻设计稿 `.dot` 的 `left:calc(50% + 6px)`:**
|
||||
```css
|
||||
.xBadge-countAndLabel { top:-4rpx !important; left:44rpx !important; right:auto !important; transform:none !important; }
|
||||
.xBadge { padding:0 !important; } /* 去掉库为半出血预留的内边距 */
|
||||
.xBadge,.xBadgeWrap,.xTabbarBox,.xTabbarWrap,.xTabbarItem { overflow:visible !important; }
|
||||
```
|
||||
- `left:44rpx = 22px`:定位基准 `.xBadgeWrap` 宽 = icon 盒宽(icon 24px + 左右各 4px = 32px,半宽 16),
|
||||
其水平中心与 Tab 项中心重合(.xBadge/.xBadgeWrap/.xTabbarItem 三层都 align-items:center)⇒ 16+6=22px。
|
||||
- 左锚定**与角标自身宽度无关**(两位数/三位数只向右生长,不会回头盖 icon)。
|
||||
- `top:-4rpx`:Tab 项 60px、内容约 53px、上下余量 ~3.5px,取 2px 后顶边仍在项内 ~1.5px。
|
||||
|
||||
**Chrome 无头实测(复刻真实 DOM + .xBadge padding:0,375 逻辑宽):**
|
||||
|
||||
| 方案 | 角标压住 icon | 裁切 |
|
||||
|---|---|---|
|
||||
| 修复前 right:0 | 24.0×18.0(**100%**) | 无 |
|
||||
| 错解 right:-20rpx | 14.0×16.0(58%) | 无 |
|
||||
| **定稿 left:44rpx(3 Tab / 5 Tab / 两位数)** | **6.0×16.0(25%)** | **无** |
|
||||
| 库默认半出血 | 10.0×14.0(42%) | 无 |
|
||||
|
||||
验证页 `.workbuddy/badge-fix-preview.html` + 截图 `badge-fix-preview.png`(都未进仓库)。
|
||||
⚠️ 复刻页第一版有个 bug:`tabbar(v).replace('class="xTabbar '+cls+'"', ...)` 把方案类名拼丢了,
|
||||
A/B/C 测出来完全一样。**「三个方案结果完全一致」本身就是没区分度的信号**,先查脚本再下结论。
|
||||
另外复刻页要补 `.xTabbar{width:100%}`,否则 item 收缩成 32px,裁切检查全失真。
|
||||
|
||||
### ④ pages/message/contacts 显示异常
|
||||
根因:x-indexbar 的行是绝对定位 + `translateY`,行高必须与 `cell-height` 严格一致;
|
||||
原实现 120rpx cell + 104rpx 行 + 圆角/外边距 → 错位。
|
||||
解法:整页重写为静态分组卡片列表(指挥调度/协作医院/本队同事),弃用 x-indexbar;
|
||||
`blkText/blkStyle`(头像块主题色,内联下发避开复合选择器)、`extractPhone`(按「·」分段、
|
||||
跳过「工号」、取纯数字≥4 位为可拨号)、`onCall`。对齐设计稿 s28。
|
||||
|
||||
### ⑤ pages/triage/create 选择文件无论多大都提示「超过大小」
|
||||
根因:`:max-file-size="50"` 被 x-upload-file 当成 **50 字节**(该 prop 单位是字节,默认 30MB)。
|
||||
解法:`const maxFileSize = 50*1024*1024` + `:max-file-size="maxFileSize"`;
|
||||
`onFileChange` 只 toast,**不回写 fileList**(回写会触发库内 watch→covertList 把附件
|
||||
重置成「已上传无大小」,列表错乱)。
|
||||
|
||||
### 校验
|
||||
静态校验 49 .uvue / 32 路由 / 11 .uts / **0 错误**;hr-design.css 花括号 171/171 平衡、注释未失配。
|
||||
|
||||
### 环境(本会话续)
|
||||
- PATH 异常升级:`ls/sed/head/tail/which/grep/find` 都没有,但 **`cd/echo/pwd` 内建可用**、
|
||||
重定向 `>` 可用、Chrome/node 可用绝对路径跑。
|
||||
→ 测 DOM 用 `chrome --dump-dom > 文件`,再用 Read/Grep 读文件,**绕开 sed/grep 管道**。
|
||||
- 无头 Chrome `--screenshot` 只截 `--window-size` 给的视口,看全页要把高给够(本次 420×1780)。
|
||||
- node `-e` 调试脚本里别混用「原串下标」去切「去注释后的串」,下标会错位(本次踩过)。
|
||||
|
||||
---
|
||||
|
||||
## 第九轮:检伤建档 2D 人体图伤情标记(完整落地)
|
||||
|
||||
需求:点 2D 人体图部位 → xDrawer 弹窗(部位自动填、其余默认填充)→ 多点标记 + 走向可旋转 → 存 JSON 可回显。
|
||||
|
||||
### 三个落点
|
||||
1. **`utils/injury.uts`(数据层,新建)**:HrBodyPart / HrInjury(纯基础类型,可直接 JSON)、
|
||||
9 个正/背热区(buildParts,互不重叠竖条)、13 性质 / 4 出血 / 6 尺寸 / 4 走向 选项与默认值、
|
||||
`defaultInjuryDesc` 自动描述、`bleedingColor/bleedingBarColor`(红/橙/绿/灰四色语义)、
|
||||
`sizeBarWidth`(1cm≈9rpx,夹 44~132)、`newInjuryId/copyInjury/renumberInjuries/excludeInjury/
|
||||
findInjury/injuriesOfSide/nextMarkAnchor`(同部位环序错开 8px)、
|
||||
`injuriesToJson/injuriesFromJson`(逐字段 getString/getNumber 兜底,脏 JSON 不崩)。
|
||||
2. **`components/hr-bodymap/hr-bodymap.uvue`(展示组件,新建)**:props `side/parts/marks`,
|
||||
emit `part-tap(partId)/mark-tap(id)`;热区点亮 `.bmp-hit-on`;标记 = 76rpx 容器
|
||||
(负 margin 居中,transform 留给旋转)+ `.bmp-bar` 长轴条(长=尺寸、rotate=走向)+ `.bmp-dot` 编号点(不旋转)。
|
||||
3. **`pages/triage/create.uvue`(重写伤情标记区)**:两个 hr-bodymap(正/背)+ 概览行
|
||||
(`injurySummary` + 复制 JSON 到剪贴板)+ 标记列表(点行=编辑、✕ 走 showModal 确认删除);
|
||||
x-drawer 表单:正/背分段切换、部位/性质/出血/尺寸/走向 chips、±45° 步进、走向实时预览条、
|
||||
描述 textarea + 「按当前选项重生成」;编辑态多「删除这处标记」。
|
||||
- 表单字段**全用顶层 ref**(uvue 嵌套对象响应式不可靠)。
|
||||
- 自动描述用 `lastAutoDesc` 对比判断「用户是否手改过」:描述为空或等于上次自动值才覆盖。
|
||||
- 存草稿 = `uni.setStorageSync(HR_DRAFT_KEY, injuriesToJson(marks))`;onLoad 里 `injuriesFromJson` 回显。
|
||||
- 编辑时部位没变则沿用原落点(防止「编辑一下位置就跳走」);变了才按 nextMarkAnchor 重算。
|
||||
|
||||
### 验证(三条腿,全部通过)
|
||||
- 静态校验 `check-uvue-hr.js`:**50 .uvue / 32 路由 / 12 .uts / 0 错误**(新增 hr-bodymap + injury.uts)。
|
||||
- **数据层行为断言 `hr-injury-test.js`(新手法,很值)**:esbuild 把 .uts 转 CJS 直接在 node 里跑。
|
||||
关键是 mock `JSON.parseArray` 成「包了 getString/getNumber 的对象」(UTSJSONObject 语义:
|
||||
取不到或类型不符返回 null)——直接 JSON.parse 的普通对象没有这些方法会假崩。
|
||||
44 条断言全过:正背面命名方向、热区不重叠不越界、JSON 无损往返、缺字段/脏 JSON/越界坐标兜底、
|
||||
同部位 9 点落点互异、重排返回新数组不改原数组。脚本在 `~/.workbuddy/binaries/node/workspace/`。
|
||||
- **视觉预览 `.workbuddy/bodymap-preview.html`(+png)**:rpx 按 0.5 折算 px 复刻,
|
||||
核对热区覆盖轮廓、标记横/竖/45° 长轴居中旋转、抽屉 chips 与走向预览,截图确认无误。
|
||||
|
||||
### x-drawer 使用要点(本次确认)
|
||||
- `v-model:show` 可用(库内 x-picker 也这么用);bottom 时 `size` 就是内容高(默认 50%,本次 72%)。
|
||||
- 自定义 footer:**必须 `:show-footer="true"`** 再给 `<template v-slot:footer>`;此时确认按钮自己
|
||||
`@click` 处理并自己把 show 置 false(用内置 footer 时 confirm 事件后会自动关)。
|
||||
- H5 下 teleport 到 uni-page、APP 内联渲染,都安全;标题可用 `<template v-slot:title>` 自定义。
|
||||
- `.hr-mask`(absolute)与 drawer(fixed zIndex 1100)同放 hr-page 的 overlay 插槽互不干扰。
|
||||
- `:key="p.id + dSide"`:正/背面切换时 chips 强制重建,避免同 key 只改文本不刷新。
|
||||
|
||||
---
|
||||
|
||||
## 第十轮:App 端编译错误批量修复(CSS / UTS / 类型三类)
|
||||
|
||||
### ① App uvue CSS:`@keyframes`/`animation`/`page`/`body` 全部不可用(★ 推翻旧结论)
|
||||
构建日志:`[plugin:uni:app-uvue-css] ERROR: Selector '0%'/'75%'/'from'/'to'/'page'/'body' is not supported` +
|
||||
`WARNING: 'animation' is not a standard property`。
|
||||
**第六/七轮记的「@keyframes + animation 可用」是错的**,App 端 keyframe 选择器直接 ERROR。
|
||||
- 5 处动效全部改为 **setInterval + ref 驱动内联样式**:
|
||||
| 动效 | 文件 | 实现 |
|
||||
|---|---|---|
|
||||
| 待接收红点呼吸 | `pages/event/index.uvue` | `pulseF` 25 帧×68ms=1.7s,前 19 帧扩散(0→16rpx、0.55→0),后 6 帧静默;`dotStyle(e)` 下发 box-shadow |
|
||||
| 扫描线 ×2 | `pages/warehouse/scan.uvue`、`components/hr-scan-cam` | `laserT` 步长 **0.03125(=1/32)**,32 帧×68ms≈2.2s 三角波;`laserStyle` 下发 top(rpx 定值,不用 %) |
|
||||
| 手风琴展开 ×2 | `pages/study/center.uvue`、`pages/study/wrong-book.uvue` | `accT` 30ms/帧 0→1,`accStyle(i)` 下发 opacity+translateY,完成后返回空串 |
|
||||
- **步长必须取 1/帧数**:用 0.035 这类值永远差几 rpx 打不到端点(`hr-anim-test.js` 断言抓出来的)。
|
||||
红点扩散边界是 `i > PULSE_RAMP`(= 号要让峰值帧留在扩散段)。
|
||||
- `App.uvue` 删掉 `page, body { background-color }`(页面底色由 `.hr-page`/根节点承担);
|
||||
`#ifdef WEB` 里的 `body::-webkit-scrollbar` 保留(条件编译会剔除,不进 App 编译)。
|
||||
|
||||
### ② UTS「先使用后定义」收尾(24 处全清)
|
||||
UTS **不提升函数声明**,调用点必须在定义之后。最后两处:`warehouse/scan.uvue`(shortCode/nowTime/handleCode
|
||||
提到 onScan 前)、`utils/role.uts`(rescueHas/keeperHas 提到 canAccess 前)。扫描脚本 `check-uts-order.js` → 0。
|
||||
|
||||
### ③ 模板里直接调用 import 函数 → App 端崩(★ 新坑)
|
||||
`pages/course/subject.uvue:48` `<x-icon :name="toRemixIcon(e.icon)">`:
|
||||
编译产物是 **`unref(toRemixIcon)(e.icon)`**(编译器分不清 import 的是 ref 还是函数),
|
||||
Kotlin 报 `找不到名称"invoke"` / `Function invocation 'toRemixIcon(...)' expected`。
|
||||
**script 内定义的函数走 `_ctx.xxx` 没事;只有 import 的会这样。**
|
||||
解法:页面里包一层 `function entryIcon(g : string) : string { return toRemixIcon(g) }`。
|
||||
已在 `utils/icon.uts` 头注释、`MEMORY.md`、skill 里三处记录;扫描脚本 `check-uts-tpl-import.js` → 0。
|
||||
|
||||
### ④ x-tabs 的 change 事件类型:下发 TABS_ITEM,不是 TABS_ITEM_INFO(★ 新坑)
|
||||
`IllegalArgumentException: ...argument 1 has type TABS_ITEM_INFO, got TABS_ITEM`。
|
||||
库内部 `_list()` 把传入的 `TABS_ITEM_INFO[]` 转成 `TABS_ITEM[]` 再 `$emit('change', item, index)`。
|
||||
四个页面同错(event/index、warehouse/devices、triage/detail、study/center),
|
||||
形参改 `item : TABS_ITEM`,判空改 `item.id == ''`(字段全是非可选,别再 `!` 断言)。
|
||||
**教训**:tmx-ui 凡「XXX_ITEM_INFO 传入、XXX_ITEM 下发」的组件,回调形参要用下发的类型,先读源码确认。
|
||||
|
||||
### 新增校验脚本(`~/.workbuddy/binaries/node/workspace/`)
|
||||
- `check-uvue-css.js`:App 端 CSS 兼容性(识别 `#ifdef` 跳过非 App 分支,注释剥离要在条件编译判定**之后**)
|
||||
- `check-uts-tpl-import.js`:模板调用 import 函数
|
||||
- `hr-anim-test.js`:动效数值断言(端点命中 / 区间 / 周期 / 样式串格式),29 条全过
|
||||
- 基线:50 .uvue / 32 路由 / 12 .uts / CSS ERROR 0 / 顺序 0 / 模板 import 0
|
||||
|
||||
### 环境备注
|
||||
- Edit 偶发 `EBUSY: resource busy or locked`(同轮并行改多个文件时)→ 重试即可成功。
|
||||
- Bash 里 `ls/tail` 仍不可用,管道尾接 `tail` 会把整条命令拖成 127 → 直接跑命令不加管道。
|
||||
|
||||
---
|
||||
|
||||
## 第十一轮:`background-image` App 端兼容(用户提问 → 全站修正)
|
||||
|
||||
用户问:**「APP 中不支持 background-image,如何解决?」** 查官方文档
|
||||
`doc.dcloud.net.cn/uni-app-x/css/background-image` 后结论如下。
|
||||
|
||||
### 规则(两条完全不同的路)
|
||||
| 场景 | App 端 | 解法 |
|
||||
|---|---|---|
|
||||
| `background-image: url(...)` 照片 | **完全不支持**(原生视图没有背景图概念) | ① `<image>` 组件 `position:absolute` 铺满容器(推荐);② `onReady` 后用 `DrawableContext`(`uniElement.getDrawableContext()`)原生绘制 |
|
||||
| `linear-gradient(...)` 渐变 | **支持,但只有一种合法形态** | `linear-gradient(<关键字方向>, <色1>, <色2>)` |
|
||||
|
||||
渐变的三条硬约束:
|
||||
1. **恰好 3 个参数**(方向 + 起色 + 止色);
|
||||
2. **方向必须是关键字**:`to right/left/top/bottom` 或四向对角线 `to bottom right`…(**`135deg`/`140deg`/`160deg` 在 App 端无效**,H5 能跑、App 静默失效);
|
||||
3. **恰好 2 个颜色**,不能带 `%` 色标(`#fff 0%`/`#fff 55%` 非法)、不能 3 色。
|
||||
另:`background-image` 优先级 > `background-color`;**flatten/vapor 模式下 `background-image` 被完全禁用**(本项目未用 vapor,已 grep 确认)。
|
||||
|
||||
### 本项目实际情况:10 处「渐变」全部违规 → 已全改
|
||||
原本全是角度 + 多色 / 百分比色标(H5 好看、App 不生效),统一改成 **`to bottom right` + 首尾两色**:
|
||||
| 文件 | 原 | 现 |
|
||||
|---|---|---|
|
||||
| `pages/login/index.uvue` | `140deg, #2563EB, #1D4ED8` | `to bottom right, #2563EB, #1D4ED8` |
|
||||
| `pages/home/index.uvue` | `135deg, #2563EB 0%, #1D4ED8 55%, #1E40AF 100%` | `to bottom right, #2563EB, #1E40AF` |
|
||||
| `pages/event/index.uvue` | `160deg, #7F1D1D 0%, #B91C1C 55%, #DC2626 100%` | `to bottom, #7F1D1D, #DC2626` |
|
||||
| `pages/mine/index.uvue` | 角度+多色 | `to bottom right, #2563EB, #1E40AF` |
|
||||
| `pages/course/practice.uvue` | `135deg, #2563EB, #1D4ED8` | `to bottom right, #2563EB, #1D4ED8` |
|
||||
| `pages/course/subject.uvue` | 同上 | 同上 |
|
||||
| `pages/event/detail.uvue` | `135deg, #1E3A8A 0%, #2563EB 72%` | `to bottom right, #1E3A8A, #2563EB` |
|
||||
| `pages/triage/detail.uvue` | 角度+多色 | `to bottom right, #F59E0B, #D97706` |
|
||||
| `pages/triage/batch-treat.uvue` | 角度 | `to bottom right, #2563EB, #1D4ED8` |
|
||||
| `pages/triage/batch-transfer.uvue` | 角度 | `to bottom right, #2563EB, #1D4ED8` |
|
||||
|
||||
> 项目**没有**用照片背景,全部是渐变 → 无需 `<image>` 兜底,只需改渐变语法。
|
||||
|
||||
### 新增校验脚本
|
||||
`~/.workbuddy/binaries/node/workspace/check-uvue-bg.js <项目根>`:扫 `<style>` 与内联 `:style` 里的
|
||||
`background`/`background-image`,报四类问题(`url()` / 角度 / 非关键字方向 / 颜色数≠2 或带 `%`),跳过 `uni_modules`。
|
||||
**结果:52 文件 / ERROR 0**。
|
||||
|
||||
---
|
||||
|
||||
## 第十二轮:hr-row + hr-chip 筛选 → xRadioButton 分段切换
|
||||
|
||||
用户要求「将页面的 hr-row 换成 xRadioButton 进行切换」。全站排查后确认这种「切换」共 **5 处**,
|
||||
且是同一套写法(`<scroll-view :scroll-x>` 包 `<view class="hr-row">` + `hr-chip v-for`):
|
||||
|
||||
| 页面 | 文案 |
|
||||
|---|---|
|
||||
| `pages/study/center.uvue` | 全部 / 心肺复苏 5 / 气道管理 4 / AED 3 |
|
||||
| `pages/study/wrong-book.uvue` | 全部 12 / 心肺复苏 5 / 气道管理 4 / AED 3 |
|
||||
| `pages/message/list.uvue` | 全部 5 / 派单通知 2 / 系统消息 3 |
|
||||
| `pages/message/index.uvue` | 全部 9 / 库存预警 4 / 借用审批 3 / 系统 2 |
|
||||
| `pages/triage/treat-records.uvue` | 全部 4 / 止血 1 / 循环 1 / 分级 2 |
|
||||
|
||||
(`hr-chip` 另有 2 处是「错 N 次」红色标签,非切换,保留。)
|
||||
|
||||
### 改造要点
|
||||
1. **`RADIO_BUTTON`(interface.uts)= `{ id:string, icon?, title, disabled? }`**,change 下发 `(string, number)`。
|
||||
与 x-tabs 不同(那边是 TABS_ITEM 对象),**没有形参类型坑**;库自身 `x-color-view` 就是 `v-model` 用法,照抄安全。
|
||||
2. 列表直接写字面量:`chipTabs : RADIO_BUTTON[] = [{ id:'0', title:'全部' } as RADIO_BUTTON, ...]`;
|
||||
`chipId = ref('0')` + `v-model`。原 `chip = ref(0)` 换成
|
||||
`computed(() : number => { const i = parseInt(chipId.value); return isNaN(i) ? 0 : i })`
|
||||
(3 处筛选逻辑依赖序号;center/wrong-book 本来就没用到 `chip`,直接删掉避免 unused)。
|
||||
3. **删掉外层 `<scroll-view :scroll-x>`**:xRadioButton 是等宽 `flex:1` 分段,塞进横向滚动容器会收窄、
|
||||
滑块 `createSelectorQuery` 定位错。用 `<view style="margin-bottom:8rpx">` 包一层保间距。
|
||||
4. **字号实测必须 22rpx**:等宽 4 段时每段文本可用宽 60.75px(375 基准:343 内容宽 − 轨道 4px 内距,
|
||||
÷4,再减每段左右 12px padding)。无头 Chrome 实测 26rpx 加粗「心肺复苏 5」= 63.9px → **截断**;
|
||||
24rpx = 59.0px(97%,跨机型太险);**22rpx = 54.1px(89%)安全**。
|
||||
测量页 `node-workspace/radiobtn-fit-test.html`(canvas measureText + DOM scrollWidth 双重验证)。
|
||||
5. 圆角单位坑:滑块圆角 = `parseInt(round) - parseInt(space)` 后**按 px 下发**(外层才走 rpx),
|
||||
`round=22rpx / space=4rpx` → 内 18px、外 11px,滑块呈胶囊、轨道 22rpx 圆角,视觉成立。
|
||||
6. 样式基线(延续设计稿「选中=蓝底白字」语义):`bg-color #E5E7EB / active-color #2563EB /
|
||||
active-font-color #FFFFFF / font-color #6B7280 / text-style font-weight:bold`。
|
||||
|
||||
### 校验
|
||||
`check-uvue-hr`(50 .uvue / 32 路由 / 12 .uts)、`check-uts-order`、`check-uts-tpl-import`、
|
||||
`check-uvue-css`、`check-uvue-bg`、`audit-array-props` 全部 0 错误。
|
||||
预览 `.workbuddy/radiobutton-preview.html` + `.png`(改造前/后对比 + 5 组文案可点击滑动)。
|
||||
|
||||
---
|
||||
|
||||
## 第十三轮:事件中心只保留「进行中 / 历史」
|
||||
|
||||
用户要求 `/pages/event/index` 只保留进行中和历史 Tab,并删除「待接收」相关部分。
|
||||
用户在选项中确认:**删待接收 Tab + 全部待接收相关逻辑**。
|
||||
|
||||
### 删除清单(pages/event/index.uvue)
|
||||
| 项 | 内容 |
|
||||
|---|---|
|
||||
| Tab | 3 → 2(进行中 / 历史),`item-width 33.33% → 50%`,`stageIds ['0','1','2'] → ['0','1']` |
|
||||
| P0 全屏推送 | 整块模板(`ev-push`,60 秒倒计时 / 立即接收 / 稍后处理)+ `pushing`/`countdown`/`timer` 状态 + `stopTimer`/`onLater`/`openPush`/`onAccept` 四个函数 |
|
||||
| 卡片 pending 分支 | `onCardClick` 里的 `stage=='pending' → openPush` 判断(现在统一 `navTo('/pages/event/detail')`);`ev-foot` 的 `v-if="e.stage != 'pending'"` 守卫 |
|
||||
| 死样式 | `.ev-push*`(12 条)+ 早前遗留的 `.ev-nav*` / `.ev-tabs*` / `.ev-tab*` / `.ev-avt*`(共 30+ 条),`{}` 11/11 平衡,模板与样式 100% 对应 |
|
||||
| 文案修正 | 空态 sub 由「新的派单会通过全屏告警推送给你」→「分配给你的派单会出现在「进行中」,处置完成后归档到「历史」」 |
|
||||
|
||||
### 保留(用户未要求删)
|
||||
- **紧急红点呼吸**(`dotStyle` / `startPulse`,setInterval 25 帧):触发只看 `urgent` 字段,与 stage 无关;
|
||||
注释已从「待接收红点」改写为「紧急事件红点」,并注明触发条件(还顺手改了注释里 `pulseK`→`pulseF` 的笔误)。
|
||||
- 「进行中」下方两行灰字说明(仅显示与我相关的派单 / 时间与处理状态由后台同步)。
|
||||
- `.evt-urgent`(urgent 卡片描边)。
|
||||
|
||||
### 连带修正(跨页,仅注释/文案)
|
||||
- `pages/message/list.uvue` 文件头注释:「与事件中心的待接收流程同源」→「跳转到事件中心查看进行中的派单」。
|
||||
|
||||
### ⚠ 遗留(已告知,待用户决定)
|
||||
1. `mock/index.uts` 里 e3(城东工业园化学品泄漏)/ e4(城南小学集体腹痛)仍是 `stage:'pending'`,
|
||||
两个 Tab 都不会显示它们;
|
||||
2. `pages/message/list.uvue` 的 P0 消息 `action:'去接收 ›'` 仍 `navTo('/pages/event/index')`,
|
||||
跳过去后进行中 Tab 看不到该事件 —— **可选解法:把 e3/e4 改成 'ongoing'(消息跳转闭环)或删掉这两条 mock**。
|
||||
|
||||
### 校验
|
||||
全量静态校验 0 错误(50 .uvue / 32 路由 / 12 .uts / CSS / 顺序 / 模板 import / background / 数组 prop)。
|
||||
预览 `.workbuddy/event-index-preview.html` + `.png`(Tab 可切换,复刻 2 Tab + 卡片 + 灰字说明)。
|
||||
|
||||
---
|
||||
|
||||
## 第十四轮 · 批量处置/批量转运:点击开启相机 + 接收医院 hr-sheet
|
||||
|
||||
### 1. hr-scan-cam:相机改为「点击开启」(两页共用,仅此两个消费方)
|
||||
- `cameraOn` 默认 `true` → `false`:进页面**不再自动占用摄像头**。
|
||||
- 整块取景区 `.cm-view` 加 `@click="openCamera"`;未开启时渲染 `.cm-idle` 居中引导
|
||||
(camera-line 图标 + 「点击开启摄像头」+ 副文案),取景框/扫描线随 `v-if="cameraOn"` 隐藏。
|
||||
- 底部按钮文案「已暂停扫码」→「开启扫码」,图标 pause-line → play-circle-line(已核对 remixicon.uts 存在)。
|
||||
- **扫描线定时器随相机启停**:startLaser/stopLaser 改在 openCamera/toggleCamera 内调用,
|
||||
删除 onMounted 里的 startLaser(不再空跑);import 同步去掉 onMounted。
|
||||
- 脚本重排:激光相关函数移到 openCamera/toggleCamera 之前(UTS 不提升)。
|
||||
|
||||
### 2. batch-transfer:op-hos 内联单选 → hr-sheet 弹层
|
||||
- 底部表单的医院 v-for 单选(op-hos* 7 条样式)→ 折叠字段 `.op-pick`(医院名 + 床位 + ⌄,点击整行 openHos)。
|
||||
- hr-page overlay 插槽:`hr-mask` + `hr-sheet`(沿用 victims/create 同一套全局类;
|
||||
`.hr-sheet-mask` 虽在 hr-design.css 里但全项目没人用,实际惯例是 hr-mask)。
|
||||
- 弹层选项为两行式:医院名(os-name,选中蓝/满员灰)+ hospitalMeta(距现场 km · 约N分钟)+ 床位/满员红字 + ✓;
|
||||
mock 新导入 hospitalMeta。
|
||||
- pickHospital:满员 → toast「该医院急诊已满员,不可选择」且**不关弹层**;可用 → 选中并关弹层。
|
||||
- 删除 hosClass 与 op-hos* 死样式(中途误复制过一份 hosOptTextClass,已清理);
|
||||
batch-treat 仅改头部注释(组件级改动自动生效)。
|
||||
|
||||
### 校验 & 预览
|
||||
- 全量静态校验 0 错误(50 .uvue / 62 uts 顺序 / CSS / 模板 import / background / 数组 prop)。
|
||||
- 预览 `.workbuddy/batch-transfer-preview.html` + `.png`(无头 Chrome 880x2080 截图:
|
||||
状态A 相机未开启 + 折叠字段 / 状态B 弹层展开,均正确)。
|
||||
|
||||
---
|
||||
|
||||
## 第十五轮 · 修 x-upload-file 运行期 NPE(defaultList 缺 request)
|
||||
|
||||
### 报错
|
||||
```
|
||||
error: java.lang.NullPointerException: null cannot be cast to non-null type kotlin.Any
|
||||
at uni_modules/tmx-ui/components/x-upload-file/x-upload-file.uvue:234:5 (return { ... })
|
||||
```
|
||||
触发点:`pages/triage/create.uvue` 的 `:default-list="fileList"`(项为 `{ name, url }`,**没有 `request`**)。
|
||||
|
||||
### 根因
|
||||
- `x-file-s/utssdk/interface.uts` 里 `xFileSListType.request?: any` 是**可选字段**,
|
||||
模板从头到尾也没读过 `request`(只原样透传给宿主),所以缺它是合法的。
|
||||
- 但 `covertList()` 里写的是 `let requestdata = el.getAny("request")` →
|
||||
`request: requestdata as any`。UTS 的 `any` = Kotlin **非空** `Any`,
|
||||
`as any` 就是 `as Any`,值为 null 时直接抛。
|
||||
- 同一段里 `type`(L228-229)、`name`(L230-231)都做了 `== null` 兜底,**只有 `request` 漏了**,
|
||||
属于组件自身缺陷,不是调用方传错。
|
||||
- 佐证 `getAny` 本身不抛:`tmx-ui/config/xConfig.uts:198` 就是 `if(cfg.getAny('theme')!=null)`;
|
||||
且栈指向 `return {` 字面量那一行而非 `let x = getAny(...)` 那行 → 是字面量里的 `as` 强转炸的。
|
||||
|
||||
### 改法(就地补丁,两个条件编译分支都改)
|
||||
`uni_modules/tmx-ui/components/x-upload-file/x-upload-file.uvue` `covertList()`:
|
||||
```uts
|
||||
// 原:let requestdata = el.getAny("request") → request: requestdata as any
|
||||
let requestdata : any | null = el.getAny("request") // 类型留可空
|
||||
// 字面量里:request: requestdata, // 去掉 as any 强转
|
||||
```
|
||||
补丁处已加「⚠ 本地补丁(2026-09-24)」标记注释。`#ifdef APP || MP-WEIXIN` 与 `#ifdef WEB`
|
||||
两个 return 分支都改了(只改一个会剩下一个平台崩)。
|
||||
|
||||
### 校验
|
||||
- `{ } / ( ) / [ ]` 计数配平(137/137、221/221、37/37);
|
||||
- 全量静态校验 0 错误(check-uvue-hr / check-uts-order / check-uvue-css /
|
||||
check-uts-tpl-import / check-uvue-bg)。
|
||||
- ⚠ **本环境没有 HBuilderX,跑不起来真机**,需要用户重新编译验证。
|
||||
|
||||
### 顺带登记的隐患(未改,同类写法)
|
||||
- `x-picker-selected.uvue:250/341/346` `el.getAny(props.idKey)!` —— 项缺 idKey 时会崩(`!` 断言)。
|
||||
- `x-table.uvue` 模板里 `{{item.getAny(item2.key)}}` —— 列 key 与数据不匹配时可能崩。
|
||||
- 这两处本项目当前未触发,先记着;用到时按同一套改法处理。
|
||||
|
||||
### 沉淀
|
||||
- 技能 `uvue-design-port`:第三步新增第 18 条(`background-image` 三参两色关键字渐变,
|
||||
之前只写在脚本表里、正文漏了)+ 第 19 条(`any` 非空 / `as any` 崩 / getAny 语义 / 栈定位技巧);
|
||||
第四步新增「库组件运行期崩溃:就地打补丁的标准流程」(标记注释 + 双分支 + 补丁清单 + 只做结构自检);
|
||||
description 补上「运行期 NPE」触发词。
|
||||
- 项目 MEMORY.md:uvue 硬约束加第 19 条 + 新增「uni_modules 本地补丁清单」表。
|
||||
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
# 医院救援 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 硬约束(写样式前必读)
|
||||
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.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` 里登记角色归属
|
||||
|
||||
## 页面骨架用法
|
||||
```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>`(自己关弹窗)。
|
||||
Reference in New Issue
Block a user