From aa02cd4e589a5f924108c55b4b9d34d035f0a627 Mon Sep 17 00:00:00 2001 From: 12451 <12451@example.com> Date: Sun, 20 Sep 2026 04:31:42 +0800 Subject: [PATCH] =?UTF-8?q?fix(mp):=20=E6=8E=A5=E5=85=A5=20vue-tsc=20?= =?UTF-8?q?=E7=9C=9F=E7=B1=BB=E5=9E=8B=E6=A3=80=E6=9F=A5=E5=B9=B6=E4=BF=AE?= =?UTF-8?q?=E6=8E=89=E5=AE=83=E6=8A=93=E5=87=BA=E7=9A=84=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 小程序端此前**没有任何类型门禁**:tsc 不检查 .vue,而 9 个页面全是 .vue, npm run build:mp-weixin 只验语法不验类型。接入 vue-tsc 后立刻抓到 17 个错误, 其中两个是真 BUG: 1. me/index.vue:写 const user = authStore.user 之后又写 user.value —— Pinia setup store 对外已把 ref 解包,user.value 恒为 undefined ⇒ **头像上传成功后写回 store 的那行是空操作**(要等冷启动才显示新头像); 且直接取值是快照、丢掉响应式。改为 computed(() => authStore.user),模板无需改动。 2. api/index.ts:CompartmentDetail 漏了后端确实会返回的 temp (routes/h5.rs 的 COMPARTMENT_REALTIME_FIELDS 第 6 项,有单测守门) ⇒ compartment-detail 页读 data.temp 报 TS2339。已按后端契约补上。 其余 12 处是宿主全局 wx 未声明(@dcloudio/types 只声明跨端 uni), 在 env.d.ts 里**只声明实际用到的两个 API**(刻意不写 [k:string]:any, 否则等于把这一层检查关掉)。 顺带清理: - 删除死页面 pages/devices/index.vue(不在 pages.json、无任何跳转引用,grep 确认); - 删除仓控板「在线/离线」徽标:cabin_boards.status **全后端没有任何写入方** (生产库实测取值只有 0),该徽标恒定显示"离线",属对用户的谎报; 协议层面也没有"仓控板在线"这个概念(TCP 长连接的是柜控板,按 IMEI 标识)。 按「柜的就显示到柜子」+「不显示推不出来的状态」去掉,并在类型上加禁止说明。 已按仓库铁律自证门禁能失败:植入 export const probe: number = 'x' → exit 2;撤销 → exit 0。 --- software/h5-uniapp/package.json | 6 ++- software/h5-uniapp/src/api/index.ts | 45 +++++++++++++++++++ software/h5-uniapp/src/env.d.ts | 28 ++++++++++++ .../src/pages/device-detail/index.vue | 27 ++++++++--- software/h5-uniapp/src/pages/home/index.vue | 15 ++++++- software/h5-uniapp/src/pages/me/index.vue | 9 +++- software/web/src/pages/devices/cabinet.tsx | 30 +++++++++++-- 7 files changed, 145 insertions(+), 15 deletions(-) diff --git a/software/h5-uniapp/package.json b/software/h5-uniapp/package.json index b128ecf..c279bff 100644 --- a/software/h5-uniapp/package.json +++ b/software/h5-uniapp/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "private": true, "scripts": { - "build:mp-weixin": "uni build --platform mp-weixin" + "build:mp-weixin": "uni build --platform mp-weixin", + "typecheck": "vue-tsc --noEmit -p tsconfig.json" }, "dependencies": { "@dcloudio/uni-app": "3.0.0-4060620250520001", @@ -21,6 +22,7 @@ "@dcloudio/uni-stacktracey": "3.0.0-4060620250520001", "@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001", "typescript": "^5.4.5", - "vite": "^5.2.8" + "vite": "^5.2.8", + "vue-tsc": "^2.2.12" } } diff --git a/software/h5-uniapp/src/api/index.ts b/software/h5-uniapp/src/api/index.ts index 16bdf33..e95145e 100644 --- a/software/h5-uniapp/src/api/index.ts +++ b/software/h5-uniapp/src/api/index.ts @@ -116,7 +116,21 @@ export interface CabinetItem { id: number abstract_id: string name?: string + /** + * **配置态**:`1` = 启用(默认)/ `2` = 禁用(后端据此拦截开合闸、开门、充电)。 + * + * ⛔ **不表示在线** —— 全仓没有任何写入方会把它写成 `0`(离线)。 + * 展示"在线/离线"一律用 [`CabinetItem.is_online`]。 + */ status: number + /** + * **实时在线**(`GET /api/h5/cabinets` 返回,值来自 Redis `device:{imei}.online`) + * + * `true` 在线 / `false` 明确离线 / `null`、缺失 = 无数据(键或字段不存在)。 + * 口径与 PC 设备列表 `is_online` **同源**(后端同一纯函数)。 + * ⚠️ 与 `status`(启用/禁用配置态)严格分离,不得互相推断。 + */ + is_online?: boolean | null project_id?: number rssi?: number imei?: string @@ -164,6 +178,16 @@ export interface CompartmentInfo { export interface CabinBoard { id: number board_id: number + /** + * ⛔ **不要用它推断"仓控板在线"** —— `cabin_boards.status` 在**整个后端没有任何写入方** + * (全仓只有建表默认值 0,`INSERT INTO cabin_boards (cabinet_id, board_id)` 没写这一列; + * 生产库实测取值分布只有 `0`)。协议里也**没有"仓控板在线"的概念**: + * 与平台保持 TCP 长连接的是**柜控板**(按 IMEI 标识),仓控板挂在柜内 485 总线上, + * 没有独立上行通路 ⇒ 这个状态**推不出来**。 + * 该列保留仅为兼容后端响应形状;**界面不得据此显示"在线/离线"** + * (此前小程序展示的板级徽标恒定显示"离线",属谎报,已删除)。 + * 柜级在线状态请看 `CabinetDetail.is_online`(Redis 实时值)。 + */ status: number compartments: CompartmentInfo[] } @@ -174,7 +198,19 @@ export interface CabinetDetail { name?: string imei?: string iccid?: string + /** + * **配置态**:`1` = 启用 / `2` = 禁用(后端据此拦截指令链路)。 + * + * ⛔ **不表示在线**;展示"在线/离线"请用 [`CabinetDetail.is_online`]。 + */ status: number + /** + * **实时在线**(`GET /api/cabinets/{id}` 返回,值来自 Redis `device:{imei}.online`) + * + * `true` 在线 / `false` 明确离线 / `null`、缺失 = 无数据。 + * 判据与设备列表 `is_online` **同源**(后端同一纯函数)。 + */ + is_online?: boolean | null rssi?: number /** 直流停电(协议 §5.4);true=停电 / false=正常 / null=无数据 */ pow_fail_dc?: boolean | null @@ -214,6 +250,15 @@ export interface CompartmentDetail { batt_status?: string | null /** 电源状态(协议 §3.8,2 位 hex);与电池状态相互独立,null/缺失 = 展示 "-" */ pow_status?: string | null + /** + * **仓体温度**(协议附 A,K×100) + * + * 后端 `GET /api/h5/compartments/{id}` 会返回该字段 + * (`routes/h5.rs` 的 `COMPARTMENT_REALTIME_FIELDS` 第 6 项 `"temp"`,有单测守门), + * 展示走 `utils/deviceStatus.ts::formatCelsius`。 + * 此前类型里漏了它 ⇒ 页面 `data.temp` 报 TS2339(类型契约与后端不一致,已补)。 + */ + temp?: number | null } export interface ChargeRecord { diff --git a/software/h5-uniapp/src/env.d.ts b/software/h5-uniapp/src/env.d.ts index f1f8dc3..ee75c71 100644 --- a/software/h5-uniapp/src/env.d.ts +++ b/software/h5-uniapp/src/env.d.ts @@ -5,3 +5,31 @@ declare module '*.vue' { const component: DefineComponent<{}, {}, any> export default component } + +/** + * 微信小程序宿主全局 `wx`。 + * + * **为什么需要手工声明**:`@dcloudio/types` 只声明跨端的 `uni`,**不声明宿主特有的 `wx`**; + * 而自定义导航栏的安全区计算必须用宿主 API(胶囊按钮位置没有等价的跨端取法), + * 所以 6 个页面里直接用了 `wx.*`。不声明时 `vue-tsc` 会报 12 处 + * `TS2304: Cannot find name 'wx'` —— 噪声会把**真正的类型错误淹掉** + * (与「打印 ❌ 却 exit 0」是同一类问题:判据不再指向真实缺陷)。 + * + * ⚠️ **刻意只声明实际用到的两个 API**,不写 `[k: string]: any`: + * 后者会让 `wx.anything()` 全部通过,等于把这一层检查关掉。需要新宿主 API 时, + * 按同样方式在下面补一条(签名照官方文档抄)。 + * + * 运行期说明:`wx` 只存在于 `mp-weixin` 产物中,而本工程只构建该目标(`build:mp-weixin`)。 + */ +declare const wx: { + /** 取宿主系统信息(本工程只消费 `statusBarHeight`) */ + getSystemInfoSync(): { + /** 状态栏高度(px) */ + statusBarHeight?: number + } + /** 取右上角胶囊按钮布局,用于按"胶囊高度 + 上下间距"反推自定义导航栏高度 */ + getMenuButtonBoundingClientRect(): { + top: number + height: number + } +} diff --git a/software/h5-uniapp/src/pages/device-detail/index.vue b/software/h5-uniapp/src/pages/device-detail/index.vue index 689ca0b..9ef64aa 100644 --- a/software/h5-uniapp/src/pages/device-detail/index.vue +++ b/software/h5-uniapp/src/pages/device-detail/index.vue @@ -15,7 +15,13 @@ {{ data.abstract_id }} - {{ data.status === 1 ? '在线' : '离线' }} + + {{ data.status === 2 ? '禁用' : (data.is_online === true ? '在线' : '离线') }} {{ data.rssi != null ? data.rssi : '—' }} @@ -71,7 +77,16 @@ {{ data.imei }}-{{ board.board_id }} - {{ board.status === 1 ? '在线' : '离线' }} + @@ -247,7 +262,9 @@ function openLocation() { .status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: 11px; font-weight: 500; border-radius: 999px; } .status-badge.online { background: #f0fdf4; color: #22c55e; } .status-badge.offline { background: #f1f5f9; color: #94a3b8; } -.status-badge.online::before, .status-badge.offline::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; } +/* 配置态「禁用」(`cabinets.status === 2`),与"离线"区分 */ +.status-badge.disabled { background: #fff7ed; color: #f97316; } +.status-badge.online::before, .status-badge.offline::before, .status-badge.disabled::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; } .signal { display: flex; align-items: flex-end; gap: 1.5px; height: 14px; } .signal-bar { width: 2.5px; border-radius: 1px; } .signal-bar.l1 { height: 4px; } .signal-bar.l2 { height: 7px; } .signal-bar.l3 { height: 10px; } .signal-bar.l4 { height: 13px; } .signal-bar.l5 { height: 14px; } @@ -287,8 +304,8 @@ function openLocation() { .board-section { margin: 12px 16px; background: #fff; border-radius: 12px; padding: 14px; box-shadow: 0 1px 2px rgba(0,0,0,0.04); } .board-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; } .board-title { font-size: 14px; font-weight: 600; color: #111827; font-variant-numeric: tabular-nums; } -.board-status { font-size: 11px; color: #94a3b8; } -.board-status.online { color: #22c55e; } +/* `.board-status` 样式已随"板级在线徽标"一并删除:该状态无数据来源(见模板注释), + 留着只会诱使后来者再加回那个恒定显示"离线"的徽标。 */ .comp-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; } .comp { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 8px; border: 1.5px solid transparent; } .comp:active { transform: scale(0.97); } diff --git a/software/h5-uniapp/src/pages/home/index.vue b/software/h5-uniapp/src/pages/home/index.vue index d6c737f..ed7b1cb 100644 --- a/software/h5-uniapp/src/pages/home/index.vue +++ b/software/h5-uniapp/src/pages/home/index.vue @@ -40,7 +40,16 @@ {{ cab.abstract_id }} - {{ cab.status === 1 ? '在线' : '离线' }} + + {{ cab.status === 2 ? '禁用' : (cab.is_online === true ? '在线' : '离线') }} {{ formatCelsius(cab.temp) }} @@ -267,7 +276,9 @@ function showScanInput() { .status-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; font-size: 11px; font-weight: 500; border-radius: 999px; } .status-badge.online { background: #f0fdf4; color: #22c55e; } .status-badge.offline { background: #f1f5f9; color: #94a3b8; } -.status-badge.online::before, .status-badge.offline::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; } +/* 配置态「禁用」(`cabinets.status === 2`):与"离线"区分开的橙色徽标 */ +.status-badge.disabled { background: #fff7ed; color: #f97316; } +.status-badge.online::before, .status-badge.offline::before, .status-badge.disabled::before { content: ''; width: 5px; height: 5px; border-radius: 50%; background: currentColor; } .temp-badge { font-size: 12px; color: #475569; font-variant-numeric: tabular-nums; flex-shrink: 0; } .header-right { display: flex; align-items: center; gap: 8px; } .signal-inline { display: flex; align-items: center; gap: 3px; } diff --git a/software/h5-uniapp/src/pages/me/index.vue b/software/h5-uniapp/src/pages/me/index.vue index 912a040..fe65152 100644 --- a/software/h5-uniapp/src/pages/me/index.vue +++ b/software/h5-uniapp/src/pages/me/index.vue @@ -27,7 +27,7 @@