From 92ffd8cecbaea40294588af202c3ec144cc13b23 Mon Sep 17 00:00:00 2001 From: iven Date: Sun, 31 May 2026 21:48:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(mp):=20Veepoo=20M2=20BLE=20=E7=AE=A1?= =?UTF-8?q?=E7=BA=BF=E6=89=A9=E5=B1=95=20=E2=80=94=20=E7=B2=BE=E5=87=86?= =?UTF-8?q?=E7=9D=A1=E7=9C=A0=E6=95=B0=E6=8D=AE=20+=20=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=B5=8B=E9=87=8F=20+=20UI=20=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 VeepooBridge API:精准睡眠读取(readPreciseSleepData)、B3自动测量配置 (readAutoTestConfig/setAutoTestConfig)、开关设置(setAutoHeartRate/BP/Temp)、 体温自动数据读取(readAutoTemperatureData),共 10 个新 API - 新增 SDK 事件类型:SDK_EVENT_SLEEP(4)、SDK_EVENT_AUTO_TEST(54) - VeepooPipeline 新增:readSleepData/readAllSleepData(enableAutoMeasurement 睡眠数据 Promise 化读取 + 自动测量一键开启 - VeepooHistoryReader 新增:uploadSleepReadings 睡眠数据上传 - stores/veepoo.ts 实装:注册 onSleepData 回调、syncHistory 实际读取+上传、 readSleepData 状态管理、enableAutoMeasurement、连接后自动触发三件事 - 原生页面(native/pkg-veepoo):_onReady 后自动读取 3 天睡眠 + 开启自动测量, 新增 _readSleepData/_handleSleepEvent/_enableAutoMeasurement - UI 重构:测量页药丸式选择器+SVG 圆环仪表盘+健康评估标签 - 数据上传页:2 列结果卡片网格+彩色条标识+睡眠数据卡片(★评分+总时长) - 修复上传按钮无响应 bug:patientId 增加 URL fallback + 错误提示不再静默 - 设计原型:docs/design/veepoo-measure-prototype.html(4 状态预览) --- apps/miniprogram/native/pkg-veepoo/index.js | 118 +++ apps/miniprogram/native/pkg-veepoo/index.wxml | 113 ++- apps/miniprogram/native/pkg-veepoo/index.wxss | 541 ++++++----- .../pkg-health/veepoo-measure/index.scss | 444 +++------ .../pages/pkg-health/veepoo-measure/index.tsx | 276 +++++- .../src/services/ble/VeepooBridge.ts | 305 ++++++ .../ble/veepoo/VeepooHistoryReader.ts | 245 +++++ .../src/services/ble/veepoo/VeepooPipeline.ts | 588 ++++++++++++ .../src/services/ble/veepoo/index.ts | 21 + .../src/services/ble/veepoo/types.ts | 152 +++ apps/miniprogram/src/stores/veepoo.ts | 335 +++++++ docs/design/veepoo-measure-prototype.html | 879 ++++++++++++++++++ docs/design/veepoo-prototype-preview.png | Bin 0 -> 122330 bytes wiki/index.md | 5 +- 14 files changed, 3419 insertions(+), 603 deletions(-) create mode 100644 apps/miniprogram/src/services/ble/VeepooBridge.ts create mode 100644 apps/miniprogram/src/services/ble/veepoo/VeepooHistoryReader.ts create mode 100644 apps/miniprogram/src/services/ble/veepoo/VeepooPipeline.ts create mode 100644 apps/miniprogram/src/services/ble/veepoo/index.ts create mode 100644 apps/miniprogram/src/services/ble/veepoo/types.ts create mode 100644 apps/miniprogram/src/stores/veepoo.ts create mode 100644 docs/design/veepoo-measure-prototype.html create mode 100644 docs/design/veepoo-prototype-preview.png diff --git a/apps/miniprogram/native/pkg-veepoo/index.js b/apps/miniprogram/native/pkg-veepoo/index.js index a66516e..141c97e 100644 --- a/apps/miniprogram/native/pkg-veepoo/index.js +++ b/apps/miniprogram/native/pkg-veepoo/index.js @@ -15,11 +15,14 @@ const { veepooBle, veepooFeature, veepooLogger } = require('./libs/veepoo-sdk'); var SDK_EVENT_AUTH = 1; var SDK_EVENT_BATTERY = 2; +var SDK_EVENT_SLEEP = 4; +var SDK_EVENT_DAILY = 5; var SDK_EVENT_TEMPERATURE = 6; var SDK_EVENT_BLOOD_PRESSURE = 18; var SDK_EVENT_BLOOD_OXYGEN = 31; var SDK_EVENT_HEART_RATE = 51; var SDK_EVENT_PRESSURE = 58; +var SDK_EVENT_AUTO_TEST = 54; var MEASURE_TYPES = [ { type: 'heart_rate', label: '心率', unit: 'bpm', icon: '♥', color: '#EF4444', sdkType: SDK_EVENT_HEART_RATE }, @@ -271,6 +274,10 @@ Page({ console.log('[veepoo-native] 认证成功,设备就绪'); this.setData({ phase: 'ready' }); veepooFeature.veepooReadElectricQuantityManager(); + + // 认证成功后自动读取 3 天睡眠数据 + 开启自动测量 + this._readSleepData(); + this._enableAutoMeasurement(); }, // ── SDK 事件路由 ── @@ -301,6 +308,25 @@ Page({ return; } + // 睡眠数据回调(type=4) + if (type === SDK_EVENT_SLEEP) { + this._handleSleepEvent(data); + return; + } + + // 日常数据回调(type=5) + if (type === SDK_EVENT_DAILY) { + // 日常数据用于历史同步,原生页面暂不处理 + return; + } + + // 自动测量配置回调(type=54) + if (type === SDK_EVENT_AUTO_TEST) { + // eslint-disable-next-line no-undef + console.log('[veepoo-native] 自动测量配置回调'); + return; + } + for (var i = 0; i < MEASURE_TYPES.length; i++) { if (MEASURE_TYPES[i].sdkType === type) { this._handleMeasureEvent(MEASURE_TYPES[i].type, data); @@ -516,4 +542,96 @@ Page({ break; } }, + + // ── 睡眠数据读取 ── + + _sleepResults: null, + _sleepDay: 0, + + _readSleepData: function () { + this._sleepResults = []; + this._sleepDay = 0; + // eslint-disable-next-line no-undef + console.log('[veepoo-native] 开始读取睡眠数据(3天)'); + + // 依次读取 3 天睡眠 + var self = this; + veepooFeature.veepooSendReadPreciseSleepManager({ day: 0 }); + + // 延迟读取后续天(避免并发冲突) + // eslint-disable-next-line no-undef + setTimeout(function () { veepooFeature.veepooSendReadPreciseSleepManager({ day: 1 }); }, 3000); + // eslint-disable-next-line no-undef + setTimeout(function () { veepooFeature.veepooSendReadPreciseSleepManager({ day: 2 }); }, 6000); + }, + + _handleSleepEvent: function (data) { + var progress = data.Progress || 0; + if (progress < 100) return; + + var content = data.content || {}; + var readDay = data.readDay || 0; + var totalTime = Number(content.sleepTotalTime || 0); + + if (totalTime <= 0) return; + + var sleepResult = { + day: readDay, + deepSleepMinutes: Number(content.deepSleepTime || 0), + lightSleepMinutes: Number(content.lightSleepTime || 0), + totalSleepMinutes: totalTime, + qualityScore: Number(content.sleepQuality || 0), + fallAsleepTime: String(content.fallAsleepTime || ''), + exitSleepTime: String(content.exitSleepTime || ''), + }; + + if (!this._sleepResults) this._sleepResults = []; + this._sleepResults.push(sleepResult); + + // eslint-disable-next-line no-undef + console.log('[veepoo-native] 睡眠数据 day=' + readDay + ' 总时长=' + totalTime + '分钟 质量=' + sleepResult.qualityScore + '星'); + + // 保存到 Storage 供 Taro 页面读取 + try { + // eslint-disable-next-line no-undef + wx.setStorageSync('hms:veepoo_sleep_results', JSON.stringify(this._sleepResults)); + } catch (_) { /* ignore */ } + }, + + // ── 自动测量 ── + + _enableAutoMeasurement: function () { + // eslint-disable-next-line no-undef + console.log('[veepoo-native] 开启自动测量功能'); + + // 开启心率自动监测 + try { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticHRTest: 'open', + }); + } catch (e) { + // eslint-disable-next-line no-undef + console.warn('[veepoo-native] 开启心率自动监测失败:', e); + } + + // 开启血压自动监测 + try { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticBPTest: 'open', + }); + } catch (e) { + // eslint-disable-next-line no-undef + console.warn('[veepoo-native] 开启血压自动监测失败:', e); + } + + // 开启体温自动监测 + try { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticTemperatureTest: 'open', + }); + } catch (e) { + // eslint-disable-next-line no-undef + console.warn('[veepoo-native] 开启体温自动监测失败:', e); + } + }, }); diff --git a/apps/miniprogram/native/pkg-veepoo/index.wxml b/apps/miniprogram/native/pkg-veepoo/index.wxml index 4568ac8..73f78b3 100644 --- a/apps/miniprogram/native/pkg-veepoo/index.wxml +++ b/apps/miniprogram/native/pkg-veepoo/index.wxml @@ -1,5 +1,6 @@ @@ -26,7 +27,7 @@ - 查看测量结果并返回 + 查看测量结果并返回 @@ -51,82 +52,92 @@ - - - - {{deviceName}} - {{batteryLevel}}% + + + + {{deviceName}} + {{batteryLevel}}% - 断开 + 断开 - - + + - {{item.icon}} - {{item.label}} - + + {{item.icon}} + + {{item.label}} - + - - - - - - {{selectedIcon}} - 点击下方按钮开始测量{{selectedLabel}} - - - - {{measureDisplayValue}} - 测量中... - {{selectedUnit}} - - - - {{measureDisplayValue}} - {{selectedUnit}} - - - - ! - {{measureError}} - + + + + + + + + + + + {{selectedIcon}} + 点击下方按钮开始 + + + + {{measureDisplayValue}} + 测量中... + {{selectedUnit}} + + + + {{measureDisplayValue}} + {{selectedUnit}} + + + + ! + {{measureError}} + + + + - - + + - 本数据由智能手环采集,仅供健康趋势参考,不作为医疗诊断依据 + 测量数据仅供参考,不作为医学诊断依据。如有不适请及时就医。 - 开始测量{{selectedLabel}} - - - 停止测量 - - - - 重新测量 - 完成并返回 + + 开始测量{{selectedLabel}} + + 停止测量 + 完成并查看结果 + + + 重新测量 + 完成并查看结果 + - 重新测量 + 重新测量 diff --git a/apps/miniprogram/native/pkg-veepoo/index.wxss b/apps/miniprogram/native/pkg-veepoo/index.wxss index 817f4ae..0ae178b 100644 --- a/apps/miniprogram/native/pkg-veepoo/index.wxss +++ b/apps/miniprogram/native/pkg-veepoo/index.wxss @@ -1,355 +1,462 @@ -/* Veepoo M2 原生页面样式 */ +/** + * Veepoo M2 原生页面样式 + * 设计原型: docs/design/veepoo-measure-prototype.html + * 复刻小程序 design token + */ page { - background: #F5F5F4; + --pri: #C4623A; + --pri-l: #F0DDD4; + --bg: #F5F0EB; + --card: #FFFFFF; + --tx: #2D2A26; + --tx2: #5A554F; + --tx3: #78716C; + --bd: #E8E2DC; + --acc: #5B7A5E; + --acc-l: #E8F0E8; + --dan: #B54A4A; + --dan-l: #FDEAEA; + background: var(--bg); min-height: 100vh; } -/* ═══ 连接屏幕 ═══ */ - +/* ═══════════════════════════════════════ + 连接页面(未连接/连接中/错误) + ═══════════════════════════════════════ */ .connect-screen { display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; - padding: 40rpx; + padding: 0 40px; } .connect-anim { position: relative; - width: 200rpx; - height: 200rpx; - margin-bottom: 48rpx; + width: 120px; + height: 120px; + margin-bottom: 28px; } .connect-ring { position: absolute; - top: 0; left: 0; - width: 100%; height: 100%; + inset: 0; border-radius: 50%; - border: 4rpx solid #D6D3D1; + border: 3px solid var(--pri); + animation: pulse-ring 2s ease-out infinite; } .connect-ring--active { - border-color: #C4623A; - animation: pulse 1.5s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { transform: scale(1); opacity: 0.6; } - 50% { transform: scale(1.1); opacity: 1; } + border-color: var(--pri); + animation: pulse-ring 1.5s ease-out infinite; } .connect-center { position: absolute; - top: 50%; left: 50%; - transform: translate(-50%, -50%); - width: 80rpx; height: 80rpx; + inset: 20px; border-radius: 50%; - background: #292524; + background: var(--pri); display: flex; align-items: center; justify-content: center; } .connect-bt { - color: #FAFAF9; - font-size: 28rpx; - font-weight: 600; + color: #fff; + font-size: 20px; + font-weight: 700; } .connect-title { - font-size: 36rpx; - font-weight: 600; - color: #1C1917; - margin-bottom: 16rpx; + font-family: Georgia, 'Times New Roman', serif; + font-size: 22px; + font-weight: 700; + color: var(--tx); + margin-bottom: 8px; + line-height: 1.3; } .connect-hint { - font-size: 26rpx; - color: #78716C; - margin-bottom: 48rpx; + font-size: 14px; + color: var(--tx3); + margin-bottom: 32px; + text-align: center; } .connect-error { - background: #FEF2F2; - border-radius: 16rpx; - padding: 24rpx 32rpx; - margin-bottom: 32rpx; - width: 100%; - max-width: 600rpx; + margin-bottom: 16px; + text-align: center; } .connect-error-text { - font-size: 26rpx; - color: #DC2626; + font-size: 14px; + color: var(--dan); } .connect-btn-wrap { - width: 100%; - max-width: 600rpx; + width: 200px; } .connect-back { - width: 100%; - max-width: 600rpx; - margin-top: 24rpx; + margin-top: 16px; } -/* ═══ 按钮 ═══ */ - -.btn-primary { - background: #C4623A; - color: #FFFFFF; - font-size: 30rpx; - font-weight: 600; - text-align: center; - padding: 24rpx 0; - border-radius: 16rpx; +@keyframes pulse-ring { + 0% { transform: scale(1); opacity: 1; } + 100% { transform: scale(1.4); opacity: 0; } } -.btn-secondary { - background: #FFFFFF; - color: #44403C; - font-size: 30rpx; - font-weight: 500; - text-align: center; - padding: 24rpx 0; - border-radius: 16rpx; - border: 2rpx solid #D6D3D1; -} - -.btn-large { - width: 100%; - max-width: 600rpx; -} - -.actions-row { - display: flex; - gap: 24rpx; - width: 100%; - max-width: 600rpx; -} - -.actions-row .btn-secondary, -.actions-row .btn-primary { - flex: 1; -} - -/* ═══ 测量页面 ═══ */ - +/* ═══════════════════════════════════════ + 测量页面(就绪态) + ═══════════════════════════════════════ */ .measure-page { - padding: 24rpx; - display: flex; - flex-direction: column; min-height: 100vh; + padding-bottom: 40px; } -/* ── Header ── */ - -.header { +/* ── 设备状态栏 ── */ +.device-bar { display: flex; + align-items: center; justify-content: space-between; - align-items: center; - padding: 16rpx 24rpx; - background: #FFFFFF; - border-radius: 16rpx; - margin-bottom: 24rpx; + padding: 10px 20px; + background: var(--card); + border-bottom: 1px solid var(--bd); } -.header-device { +.device-bar__left { display: flex; align-items: center; - gap: 12rpx; + gap: 8px; } -.header-dot { - width: 16rpx; height: 16rpx; +.device-bar__dot { + width: 8px; + height: 8px; border-radius: 50%; - background: #A8A29E; + background: var(--acc); } -.header-dot--on { - background: #22C55E; -} - -.header-name { - font-size: 28rpx; +.device-bar__name { + font-size: 16px; font-weight: 600; - color: #1C1917; + color: var(--tx); } -.header-battery { - font-size: 24rpx; - color: #78716C; +.device-bar__battery { + font-size: 13px; + color: var(--tx3); + margin-left: 4px; } -.header-disconnect { - font-size: 26rpx; - color: #C4623A; +.device-bar__disconnect { + font-size: 13px; + color: var(--tx3); + padding: 6px 12px; + background: transparent; + border: 1px solid var(--bd); + border-radius: 999px; } -/* ── Selector ── */ - +/* ── 指标选择器(药丸式) ── */ .selector { display: flex; - justify-content: space-around; - padding: 24rpx 0; - background: #FFFFFF; - border-radius: 16rpx; - margin-bottom: 24rpx; + white-space: nowrap; + padding: 16px 20px; + gap: 8px; } -.selector-item { +.selector__pill { + flex-shrink: 0; + display: inline-flex; + flex-direction: column; + align-items: center; + gap: 4px; + padding: 10px 14px; + border-radius: 16px; + position: relative; + min-width: 64px; + transition: all 200ms ease; +} + +.selector__pill--active { + background: var(--card); + box-shadow: 0 2px 12px rgba(45,42,38,0.10); +} + +.selector__pill--done::after { + content: '✓'; + position: absolute; + top: 4px; + right: 6px; + font-size: 10px; + color: #fff; + background: var(--acc); + width: 16px; + height: 16px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} + +.selector__icon-wrap { + width: 36px; + height: 36px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + transition: transform 200ms ease; +} + +.selector__pill--active .selector__icon-wrap { + transform: scale(1.15); +} + +.selector__icon { + font-size: 18px; + color: #fff; +} + +.selector__label { + font-size: 13px; + color: var(--tx3); +} + +.selector__pill--active .selector__label { + color: var(--tx); + font-weight: 600; +} + +/* ── 仪表盘 ── */ +.gauge-section { display: flex; flex-direction: column; align-items: center; - gap: 8rpx; - padding: 12rpx 16rpx; - border-radius: 12rpx; + padding: 16px 0 24px; +} + +.gauge { position: relative; } -.selector-item--active { - background: #FFF7ED; +.gauge--measuring { + animation: gauge-breathe 2s ease-in-out infinite; } -.selector-item--done::after { - content: ''; +@keyframes gauge-breathe { + 0%, 100% { transform: scale(1); } + 50% { transform: scale(1.03); } } -.selector-icon { - font-size: 40rpx; +.gauge__ring-wrap { + position: relative; + width: 220px; + height: 220px; } -.selector-label { - font-size: 22rpx; - color: #57534E; -} - -.selector-check { +.gauge__ring-bg { position: absolute; - top: 4rpx; right: 4rpx; - width: 28rpx; height: 28rpx; + inset: 0; border-radius: 50%; - color: #FFFFFF; - font-size: 18rpx; - display: flex; - align-items: center; - justify-content: center; + border: 10px solid var(--bd); + box-sizing: border-box; } -/* ── Gauge ── */ +.gauge__ring-progress { + position: absolute; + inset: 0; + border-radius: 50%; +} -.gauge { - flex: 1; +.gauge__center { + position: absolute; + inset: 20px; display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 48rpx 0; } -.gauge-circle { - width: 400rpx; - height: 400rpx; - border-radius: 50%; - background: #FFFFFF; - border: 8rpx solid #E7E5E4; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-bottom: 24rpx; +.gauge__icon-lg { + font-size: 40px; + margin-bottom: 8px; } -.gauge-icon { - font-size: 64rpx; - margin-bottom: 16rpx; +.gauge__hint { + font-size: 13px; + color: var(--tx3); + text-align: center; } -.gauge-hint { - font-size: 26rpx; - color: #78716C; -} - -.gauge-value { - font-size: 80rpx; +.gauge__value { + font-family: Georgia, 'Times New Roman', serif; + font-size: 52px; font-weight: 700; - line-height: 1.1; + line-height: 1; } -.gauge-loading { - font-size: 30rpx; - color: #78716C; +.gauge__unit { + font-size: 14px; + color: var(--tx3); + margin-top: 4px; } -.gauge-err { - font-size: 72rpx; +.gauge__loading { + font-size: 16px; + color: var(--tx2); +} + +.gauge__err { + font-size: 36px; + color: var(--dan); font-weight: 700; - color: #DC2626; - margin-bottom: 8rpx; } -.gauge-err-text { - font-size: 26rpx; - color: #DC2626; +.gauge__err-text { + font-size: 13px; + color: var(--tx2); + text-align: center; } -.gauge-progress-bar { - width: 500rpx; - height: 8rpx; - background: #E7E5E4; - border-radius: 4rpx; +/* ── 进度条 ── */ +.progress-bar { + width: 240px; + height: 4px; + background: var(--bd); + border-radius: 2px; + margin-top: 16px; overflow: hidden; } -.gauge-progress-fill { +.progress-bar__fill { height: 100%; - background: #C4623A; - border-radius: 4rpx; - transition: width 0.3s ease; + border-radius: 2px; + transition: width 0.3s ease-out; } -/* ── Assessment ── */ - -.assessment { - text-align: center; - padding: 16rpx; -} - -.assessment-text { - font-size: 26rpx; - color: #16A34A; -} - -/* ── Disclaimer ── */ - +/* ── 免责声明 ── */ .disclaimer { text-align: center; - padding: 16rpx 32rpx; + padding: 0 20px; + margin-bottom: 16px; } -.disclaimer-text { - font-size: 22rpx; - color: #A8A29E; +.disclaimer__text { + font-size: 11px; + color: var(--tx3); + line-height: 1.5; } -/* ── Actions ── */ - +/* ── 操作按钮 ── */ .actions { - padding: 24rpx 0; + padding: 0 20px; + display: flex; + flex-direction: column; + gap: 12px; } -/* ── Measure Error ── */ - -.measure-error { +.btn { + height: 52px; + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 600; text-align: center; - padding: 16rpx; + transition: opacity 150ms; } -.measure-error-text { - font-size: 26rpx; - color: #DC2626; +.btn:active { + opacity: 0.85; } + +.btn--primary { + background: var(--pri); + color: #fff; + box-shadow: 0 4px 16px rgba(196,98,58,0.3); +} + +.btn--secondary { + background: var(--card); + color: var(--tx); + border: 1px solid var(--bd); +} + +.btn--text { + background: transparent; + color: var(--tx3); + height: 44px; + font-size: 14px; +} + +/* ═══ 旧版兼容样式 ═══ */ +.btn-primary { + background: var(--pri); + color: #fff; + height: 52px; + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 600; + box-shadow: 0 4px 16px rgba(196,98,58,0.3); +} +.btn-primary:active { opacity: 0.85; } + +.btn-secondary { + background: var(--card); + color: var(--tx); + height: 52px; + border-radius: 16px; + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + font-weight: 600; + border: 1px solid var(--bd); +} +.btn-secondary:active { opacity: 0.85; } + +.btn-text { + background: transparent; + color: var(--tx3); + height: 44px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; +} + +.btn-large { margin: 0; } + +/* 旧版 header/selector/gauge 兼容 */ +.header { display: none; } +.header-device { display: none; } +.header-dot { display: none; } +.header-name { display: none; } +.header-battery { display: none; } +.header-disconnect { display: none; } +.selector-item { display: none; } +.selector-icon { display: none; } +.selector-label { display: none; } +.selector-check { display: none; } +.gauge-circle { display: none; } +.gauge-icon { display: none; } +.gauge-hint { display: none; } +.gauge-value { display: none; } +.gauge-loading { display: none; } +.gauge-err { display: none; } +.gauge-err-text { display: none; } +.gauge-progress-bar { display: none; } +.gauge-progress-fill { display: none; } +.assessment { display: none; } +.assessment-text { display: none; } +.disclaimer-text { display: none; } +.measure-error { display: none; } +.measure-error-text { display: none; } diff --git a/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.scss b/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.scss index ce116d2..fbd7fd8 100644 --- a/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.scss +++ b/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.scss @@ -1,10 +1,13 @@ -// Veepoo 实时测量页样式 +// Veepoo 测量结果 + 上传页样式 +// 设计原型: docs/design/veepoo-measure-prototype.html +@import '../../../styles/variables.scss'; + .vm-page { min-height: 100vh; - background: var(--tk-bg-primary); + background: var(--tk-bg-primary, $bg); } -// ── 连接中 ── +// ── 连接中(等待跳转态) ── .vm-connect { display: flex; flex-direction: column; @@ -24,7 +27,7 @@ position: absolute; inset: 0; border-radius: 50%; - border: 3px solid var(--tk-brand, #3B82F6); + border: 3px solid $pri; animation: vm-pulse-ring 2s ease-out infinite; } @@ -32,7 +35,7 @@ position: absolute; inset: 20px; border-radius: 50%; - background: var(--tk-brand, #3B82F6); + background: $pri; display: flex; align-items: center; justify-content: center; @@ -45,33 +48,16 @@ } &__title { - font-size: 18px; - font-weight: 600; - color: var(--tk-text-primary); + font-family: Georgia, 'Times New Roman', serif; + font-size: var(--tk-font-h2, 22px); + font-weight: 700; + color: $tx; margin-bottom: 8px; } &__hint { - font-size: 14px; - color: var(--tk-text-tertiary); - margin-bottom: 24px; - } - - &__error { - margin-top: 16px; - text-align: center; - } - - &__error-text { - font-size: 14px; - color: var(--tk-color-danger, #EF4444); - display: block; - margin-bottom: 16px; - } - - &__error-btn { - width: 200px; - margin: 0 auto; + font-size: var(--tk-font-body-sm, 14px); + color: $tx3; } } @@ -80,319 +66,191 @@ 100% { transform: scale(1.4); opacity: 0; } } -// ── 就绪/测量中 ── -.vm-body { - padding: 16px; -} +// ── 上传页面 ── +.vm-upload { + min-height: 100vh; + padding-bottom: 40px; -// ── 设备状态栏 ── -.vm-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - background: var(--tk-bg-secondary); - border-radius: 12px; - margin-bottom: 16px; - - &__device { - display: flex; - align-items: center; - gap: 8px; + &__header { + padding: var(--tk-gap-lg, 24px) var(--tk-page-padding, 20px) var(--tk-gap-md, 16px); } - &__dot { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--tk-color-success, #10B981); - - &--on { background: var(--tk-color-success, #10B981); } + &__title { + display: block; + font-family: Georgia, 'Times New Roman', serif; + font-size: var(--tk-font-h2, 22px); + font-weight: 700; + color: $tx; + line-height: 1.3; } - &__name { - font-size: 14px; - font-weight: 500; - color: var(--tk-text-primary); - } - - &__battery { - font-size: 12px; - color: var(--tk-text-tertiary); - } - - &__disconnect { - font-size: 13px; - color: var(--tk-text-tertiary); - padding: 4px 8px; + &__subtitle { + display: block; + font-size: var(--tk-font-body-sm, 14px); + color: $tx3; + margin-top: 4px; } } -// ── 指标选择器 ── -.vm-selector { +// ── 结果卡片网格 ── +.vm-results-grid { + padding: 0 var(--tk-page-padding, 20px); display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 8px; - margin-bottom: 24px; + grid-template-columns: repeat(2, 1fr); + gap: var(--tk-gap-sm, 12px); +} - &__item { - display: flex; - flex-direction: column; - align-items: center; - padding: 12px 4px; - border-radius: 12px; - border: 2px solid transparent; - background: var(--tk-bg-secondary); - position: relative; - transition: all 0.2s; +.vm-result-card { + background: $card; + border-radius: var(--tk-card-radius, 16px); + padding: var(--tk-gap-md, 16px); + box-shadow: $shadow-sm; + position: relative; + overflow: hidden; - &--active { - border-color: var(--tk-brand, #3B82F6); - background: var(--tk-bg-tertiary); - } - - &--measuring { - opacity: 0.7; - } - - &--done { - border-color: var(--tk-color-success, #10B981); - } + &--full { + grid-column: 1 / -1; } - &__icon { - font-size: 22px; - margin-bottom: 4px; + &--empty { + opacity: 0.5; + } + + &__badge { + position: absolute; + top: 0; + left: 0; + width: 4px; + height: 100%; + border-radius: 0 4px 4px 0; } &__label { - font-size: 12px; - color: var(--tk-text-secondary); + display: block; + font-size: var(--tk-font-cap, 13px); + color: $tx2; + margin-bottom: 8px; + padding-left: 8px; } - &__check { - position: absolute; - top: 4px; - right: 4px; - width: 16px; - height: 16px; - border-radius: 50%; - color: #fff; - font-size: 10px; + &__row { display: flex; - align-items: center; - justify-content: center; - } -} - -// ── 仪表盘 ── -.vm-gauge { - display: flex; - justify-content: center; - padding: 16px 0; - - &__ring { - position: relative; - width: 220px; - height: 220px; - - &--measuring { - animation: vm-gauge-breathe 2s ease-in-out infinite; - } - } - - &__svg { - width: 100%; - height: 100%; - } - - &__progress { - transition: stroke-dasharray 0.3s ease-out; - } - - &__center { - position: absolute; - inset: 20px; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - } - - &__idle { - display: flex; - flex-direction: column; - align-items: center; - gap: 8px; - } - - &__icon { - font-size: 40px; - } - - &__hint { - font-size: 13px; - color: var(--tk-text-tertiary); - } - - &__measuring, &__success { - display: flex; - flex-direction: column; - align-items: center; + align-items: baseline; + gap: 4px; + padding-left: 8px; } &__value { - font-size: 48px; + font-family: Georgia, 'Times New Roman', serif; + font-size: var(--tk-font-num-lg, 34px); font-weight: 700; + color: $tx; line-height: 1; } &__unit { - font-size: 14px; - color: var(--tk-text-secondary); - margin-top: 4px; + font-size: var(--tk-font-cap, 13px); + color: $tx3; } - &__loading { - font-size: 16px; - color: var(--tk-text-secondary); - } - - &__error { - display: flex; - flex-direction: column; + &__tag { + display: inline-flex; align-items: center; - gap: 8px; - } - - &__err-icon { - font-size: 36px; - color: var(--tk-color-danger, #EF4444); - font-weight: 700; - } - - &__err-text { - font-size: 13px; - color: var(--tk-text-secondary); - text-align: center; - } -} - -@keyframes vm-gauge-breathe { - 0%, 100% { transform: scale(1); } - 50% { transform: scale(1.03); } -} - -// ── 健康评估 ── -.vm-assessment { - text-align: center; - padding: 12px; - border-radius: 8px; - margin: 0 16px 16px; - - &--normal { - background: #ECFDF5; - color: #059669; - } - - &--warning { - background: #FFFBEB; - color: #D97706; - } - - &--danger { - background: #FEF2F2; - color: #DC2626; - } - - &__text { - font-size: 14px; + gap: 3px; + margin-top: 8px; + margin-left: 8px; + padding: 2px 8px; + border-radius: 999px; + font-size: var(--tk-font-micro, 11px); font-weight: 500; + + &--normal { + background: $acc-l; + color: $acc; + } + + &--warning { + background: $wrn-l; + color: $wrn; + } + + &--danger { + background: $dan-l; + color: $dan; + } + } + + &__placeholder { + padding-left: 8px; + font-size: var(--tk-font-body-sm, 14px); + color: $tx3; + } + + &--sleep { + padding-bottom: 12px; } } -// ── 免责声明 ── -.vm-disclaimer { - text-align: center; - padding: 8px 16px; - margin-bottom: 16px; +// ── 睡眠数据行 ── +.vm-sleep-row { + display: flex; + align-items: center; + gap: 12px; + padding: 6px 8px; + margin-left: 8px; - &__text { - font-size: 11px; - color: var(--tk-text-quaternary); - line-height: 1.5; + &__day { + font-size: var(--tk-font-body-sm, 14px); + color: $tx2; + min-width: 40px; + } + + &__time { + font-family: Georgia, 'Times New Roman', serif; + font-size: var(--tk-font-body, 16px); + font-weight: 600; + color: $tx; + } + + &__quality { + font-size: 12px; + color: $wrn; + margin-left: auto; } } -// ── 操作按钮 ── -.vm-actions { - padding: 0 16px; +// ── 底部上传播区 ── +.vm-upload-footer { + padding: var(--tk-gap-lg, 24px) var(--tk-page-padding, 20px) var(--tk-gap-xl, 32px); - &__row { - display: flex; - gap: 12px; + &__hint { + display: block; + font-size: var(--tk-font-cap, 13px); + color: $tx3; + text-align: center; + margin-bottom: var(--tk-gap-sm, 12px); } -} -// ── 测量错误 ── -.vm-measure-error { - text-align: center; - padding: 8px 16px; - margin-top: 12px; + &__btn { + width: 100%; + } - &__text { - font-size: 13px; - color: var(--tk-color-danger, #EF4444); + &__time { + display: block; + font-size: var(--tk-font-micro, 11px); + color: $tx3; + text-align: center; + margin-top: var(--tk-gap-sm, 12px); } } // ── 长者模式 ── .elder-mode { - .vm-selector { - grid-template-columns: repeat(3, 1fr); - gap: 12px; + .vm-results-grid { + grid-template-columns: 1fr; } - .vm-selector__item { - padding: 16px 8px; - } - - .vm-selector__icon { - font-size: 28px; - } - - .vm-selector__label { - font-size: 16px; - } - - .vm-gauge { - &__ring { - width: 260px; - height: 260px; - } - - &__value { - font-size: 64px; - } - - &__unit { - font-size: 18px; - } - - &__hint { - font-size: 16px; - } - } - - .vm-header__name { - font-size: 16px; - } - - .vm-disclaimer__text { - font-size: 14px; - } - - .vm-assessment__text { - font-size: 16px; + .vm-result-card__value { + font-size: var(--tk-font-num-lg, 40px); } } diff --git a/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.tsx b/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.tsx index 30458d7..7858a12 100644 --- a/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.tsx +++ b/apps/miniprogram/src/pages/pkg-health/veepoo-measure/index.tsx @@ -1,6 +1,6 @@ import React, { useRef } from 'react'; import { View, Text } from '@tarojs/components'; -import Taro, { useDidShow } from '@tarojs/taro'; +import Taro, { useDidShow, useRouter } from '@tarojs/taro'; import { useAuthStore } from '@/stores/auth'; import { uploadReadings } from '@/services/device-sync'; import type { NormalizedReading } from '@/services/ble/types'; @@ -16,17 +16,89 @@ interface NativeMeasureResult { measuredAt: number; } +/** 原生页面返回的睡眠数据格式 */ +interface NativeSleepResult { + day: number; + deepSleepMinutes: number; + lightSleepMinutes: number; + totalSleepMinutes: number; + qualityScore: number; + fallAsleepTime: string; + exitSleepTime: string; +} + +/** 指标配置 */ +const METRIC_CONFIG = [ + { type: 'heart_rate', label: '心率', unit: 'bpm', color: '#EF4444', icon: '♥' }, + { type: 'blood_oxygen', label: '血氧', unit: '%', color: '#3B82F6', icon: 'O₂' }, + { type: 'blood_pressure', label: '血压', unit: 'mmHg', color: '#8B5CF6', icon: '↕' }, + { type: 'temperature', label: '体温', unit: '°C', color: '#F59E0B', icon: 'T' }, + { type: 'pressure', label: '压力', unit: '', color: '#6366F1', icon: '~' }, +] as const; + +/** 健康评估 */ +function assessHealth(type: string, values: Record): { level: 'normal' | 'warning' | 'danger'; text: string } { + switch (type) { + case 'heart_rate': { + const v = values.heart_rate ?? 0; + if (v >= 60 && v <= 100) return { level: 'normal', text: '心率正常' }; + if (v < 50 || v > 120) return { level: 'danger', text: '心率异常' }; + return { level: 'warning', text: '心率偏离正常范围' }; + } + case 'blood_oxygen': { + const v = values.blood_oxygen ?? 0; + if (v >= 95) return { level: 'normal', text: '血氧正常' }; + if (v >= 90) return { level: 'warning', text: '血氧偏低' }; + return { level: 'danger', text: '血氧过低' }; + } + case 'blood_pressure': { + const sys = values.systolic ?? 0; + const dia = values.diastolic ?? 0; + if (sys >= 90 && sys <= 140 && dia >= 60 && dia <= 90) return { level: 'normal', text: '血压正常' }; + if (sys > 160 || dia > 100) return { level: 'danger', text: '血压过高' }; + return { level: 'warning', text: '血压偏高' }; + } + case 'temperature': { + const v = values.temperature ?? 0; + if (v >= 36.0 && v <= 37.3) return { level: 'normal', text: '体温正常' }; + if (v > 38.0) return { level: 'danger', text: '发热' }; + return { level: 'warning', text: '体温偏离正常' }; + } + case 'pressure': { + const v = values.pressure ?? 0; + if (v >= 1 && v <= 40) return { level: 'normal', text: '压力正常' }; + if (v > 60) return { level: 'danger', text: '压力过高' }; + return { level: 'warning', text: '压力偏高' }; + } + default: + return { level: 'normal', text: '' }; + } +} + +/** 格式化显示值 */ +function formatValue(type: string, values: Record): string { + if (type === 'blood_pressure') { + return `${values.systolic ?? '--'}/${values.diastolic ?? '--'}`; + } + const v = Object.values(values)[0]; + return v !== undefined ? String(v) : '--'; +} + export default function VeepooMeasure() { const modeClass = useElderClass(); + const router = useRouter(); const patient = useAuthStore((s) => s.currentPatient); const navigatedRef = useRef(false); const [results, setResults] = React.useState>({}); - const [uploadStatus, setUploadStatus] = React.useState(''); + const [sleepData, setSleepData] = React.useState([]); + const [uploadStatus, setUploadStatus] = React.useState<'idle' | 'uploading' | 'success' | 'error'>('idle'); + + // 从 URL 或 store 获取 patientId + const patientId = patient?.id || router.params.patientId || ''; // C3 修复:用 ref 防重入,避免 React Strict Mode 双触发 if (!navigatedRef.current) { navigatedRef.current = true; - const patientId = patient?.id || ''; // 延迟到下一个微任务,确保页面渲染完成后再跳转 setTimeout(() => { Taro.navigateTo({ @@ -43,7 +115,7 @@ export default function VeepooMeasure() { }, 50); } - // 页面恢复时读取原生页面返回的测量结果 + // 页面恢复时读取原生页面返回的测量结果 + 睡眠数据 useDidShow(() => { try { const raw = Taro.getStorageSync('hms:veepoo_measure_results'); @@ -53,58 +125,182 @@ export default function VeepooMeasure() { Taro.removeStorageSync('hms:veepoo_measure_results'); } } catch { /* ignore */ } + + try { + const rawSleep = Taro.getStorageSync('hms:veepoo_sleep_results'); + if (rawSleep) { + const parsedSleep = JSON.parse(rawSleep) as NativeSleepResult[]; + if (parsedSleep.length > 0) { + setSleepData(parsedSleep); + } + Taro.removeStorageSync('hms:veepoo_sleep_results'); + } + } catch { /* ignore */ } }); const handleUpload = async () => { - if (!patient) return; + // 修复:添加明确的错误提示,不再静默退出 + if (!patientId) { + console.warn('[veepoo-measure] 上传失败:未获取到患者 ID'); + Taro.showToast({ title: '请先绑定患者档案', icon: 'none' }); + return; + } const allResults = Object.values(results); - if (allResults.length === 0) return; + const hasMeasureData = allResults.length > 0; + const hasSleep = sleepData.length > 0; - setUploadStatus('上传中...'); + if (!hasMeasureData && !hasSleep) { + console.warn('[veepoo-measure] 上传失败:无数据'); + Taro.showToast({ title: '暂无测量数据', icon: 'none' }); + return; + } + + setUploadStatus('uploading'); try { - // C2 修复:使用 uploadReadings,类型与 NormalizedReading 对齐 - const readings: NormalizedReading[] = allResults.map((r) => ({ - device_type: r.type as NormalizedReading['device_type'], - values: r.values, - measured_at: new Date(r.measuredAt).toISOString(), - })); - await uploadReadings(patient.id, 'veepoo_m2', 'Veepoo M2', readings); - setUploadStatus('上传成功'); + const allReadings: NormalizedReading[] = []; + + // 测量结果 + if (hasMeasureData) { + console.log('[veepoo-measure] 上传测量数据', allResults.length, '项'); + allReadings.push(...allResults.map((r) => ({ + device_type: r.type as NormalizedReading['device_type'], + values: r.values, + measured_at: new Date(r.measuredAt).toISOString(), + }))); + } + + // 睡眠数据 + if (hasSleep) { + const now = new Date(); + console.log('[veepoo-measure] 上传睡眠数据', sleepData.length, '天'); + allReadings.push(...sleepData.map((s) => { + const baseDate = new Date(now.getTime() - s.day * 86400000); + return { + device_type: 'sleep' as const, + values: { + deep_sleep_minutes: s.deepSleepMinutes, + light_sleep_minutes: s.lightSleepMinutes, + total_sleep_minutes: s.totalSleepMinutes, + quality_score: s.qualityScore, + }, + measured_at: baseDate.toISOString(), + }; + })); + } + + await uploadReadings(patientId, 'veepoo_m2', 'Veepoo M2', allReadings); + setUploadStatus('success'); Taro.showToast({ title: '数据已上传', icon: 'success' }); - } catch { - setUploadStatus('上传失败'); - Taro.showToast({ title: '上传失败', icon: 'none' }); + } catch (err) { + console.error('[veepoo-measure] 上传失败:', err); + setUploadStatus('error'); + Taro.showToast({ title: '上传失败,请重试', icon: 'none' }); } }; const hasResults = Object.keys(results).length > 0; + const measuredCount = Object.keys(results).length; + const measuredAt = hasResults + ? new Date(Object.values(results)[0].measuredAt).toLocaleString('zh-CN', { month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }) + : ''; return ( - - - - BT - - M2 手环健康测量 - - {hasResults ? ( - - {Object.entries(results).map(([type, r]) => ( - - {type} - {JSON.stringify(r.values)} - - ))} - {uploadStatus} - - 上传测量数据 - + {hasResults ? ( + + {/* 页面标题 */} + + 测量结果 + Veepoo M2 · 刚刚完成测量 - ) : ( + + {/* 结果卡片网格 */} + + {METRIC_CONFIG.map((metric) => { + const result = results[metric.type]; + if (result) { + const assessment = assessHealth(metric.type, result.values); + return ( + + + {metric.label} + + {formatValue(metric.type, result.values)} + {metric.unit} + + + ● {assessment.text} + + + ); + } + // 未测量占位 + return ( + + + {metric.label} + 未测量 + + ); + })} + + {/* 睡眠数据卡片 */} + {sleepData.length > 0 && ( + + + 睡眠数据({sleepData.length} 天) + {sleepData.map((sleep, idx) => { + const hours = Math.floor(sleep.totalSleepMinutes / 60); + const mins = sleep.totalSleepMinutes % 60; + const dayLabel = sleep.day === 0 ? '昨晚' : sleep.day === 1 ? '前晚' : '大前晚'; + return ( + + {dayLabel} + {hours}h{mins > 0 ? ` ${mins}min` : ''} + + {'★'.repeat(Math.min(sleep.qualityScore, 5))}{'☆'.repeat(Math.max(5 - sleep.qualityScore, 0))} + + + ); + })} + + ● 自动同步 + + + )} + + + {/* 底部上传播区 */} + + 测量数据将上传至您的健康档案 + + + {uploadStatus === 'uploading' + ? '上传中...' + : uploadStatus === 'success' + ? '✓ 已上传' + : `上传数据(${measuredCount} 项测量${sleepData.length > 0 ? ' + ' + sleepData.length + ' 天睡眠' : ''})`} + + + {measuredAt && 测量时间:{measuredAt}} + + + ) : ( + + + + BT + + M2 手环健康测量 即将跳转到设备测量页面... - )} - + + )} ); } diff --git a/apps/miniprogram/src/services/ble/VeepooBridge.ts b/apps/miniprogram/src/services/ble/VeepooBridge.ts new file mode 100644 index 0000000..72d02a0 --- /dev/null +++ b/apps/miniprogram/src/services/ble/VeepooBridge.ts @@ -0,0 +1,305 @@ +/** + * Veepoo SDK 桥接模块 + * + * 调用顺序(基于 SDK Demo 验证): + * 1. startScan() — 初始化蓝牙 + 扫描 + * 2. stopScan() — 找到设备后停止扫描 + * 3. connectDevice(deviceObj) — 传入完整设备对象(非 deviceId 字符串) + * 4. registerDataListener() — 连接成功后注册数据监听 + * 5. authenticate() — 延迟 500ms 后调用秘钥认证 + * 6. 认证结果通过数据监听回调 type=1 返回 + */ + +// @ts-ignore — SDK 类型声明为 any +import { veepooBle, veepooFeature, veepooLogger } from './veepoo-sdk'; + +// ── SDK 事件类型常量 ── + +/** 秘钥认证结果 */ +export const SDK_EVENT_AUTH = 1; +/** 日常数据 */ +export const SDK_EVENT_DAILY = 5; +/** 体温检测 */ +export const SDK_EVENT_TEMPERATURE = 6; +/** 血压 */ +export const SDK_EVENT_BLOOD_PRESSURE = 18; +/** 血氧手动测量 */ +export const SDK_EVENT_BLOOD_OXYGEN = 31; +/** 心率测量 */ +export const SDK_EVENT_HEART_RATE = 51; +/** 压力测量 */ +export const SDK_EVENT_PRESSURE = 58; + +/** 设备正忙状态枚举(SDK state 字段) */ +export const DEVICE_STATE = { + IDLE: 0, + MEASURING_BP: 1, + MEASURING_HR: 2, + AUTO_TEST: 3, + MEASURING_SPO2: 4, + MEASURING_FATIGUE: 5, + NOT_WORN: 6, + CHARGING: 7, + LOW_BATTERY: 8, + BUSY: 9, +} as const; + +/** 连接回调中 connection 字段为 true 表示连接成功 */ +export interface VeepooConnectionResult { + connection?: boolean; + errno?: number; + errCode?: number; + errMsg?: string; +} + +/** SDK 事件回调数据(统一格式) */ +export interface SdkEventData { + name: string; + type: number; + content: Record; + Progress?: number; + state?: number; + control?: number; + ack?: number; +} + +// ── 蓝牙模块 ── + +/** 初始化蓝牙 + 开始扫描 */ +export function startScan(onDeviceFound: (device: unknown) => void): void { + veepooBle.veepooWeiXinSDKStartScanDeviceAndReceiveScanningDevice( + (res: unknown) => { + const device = Array.isArray(res) ? res[0] : res; + if (device) onDeviceFound(device); + }, + ); +} + +/** 停止扫描 */ +export function stopScan(): Promise { + return new Promise((resolve) => { + veepooBle.veepooWeiXinSDKStopSearchBleManager(() => resolve()); + }); +} + +/** 连接设备 — 传入完整设备对象 */ +export function connectDevice(device: unknown): Promise { + return new Promise((resolve) => { + veepooBle.veepooWeiXinSDKBleConnectionServicesCharacteristicsNotifyManager( + device, + (res: VeepooConnectionResult) => resolve(res), + ); + }); +} + +/** 注册数据监听(必须在连接成功后调用) */ +export function registerDataListener(callback: (data: SdkEventData) => void): void { + veepooBle.veepooWeiXinSDKNotifyMonitorValueChange(callback); +} + +/** 监听蓝牙连接状态变化 */ +export function registerConnectionListener(callback: (res: { deviceId: string; connected: boolean }) => void): void { + veepooBle.veepooWeiXinSDKBLEConnectionStateChangeManager(callback); +} + +/** 断开连接 */ +export function disconnect(): Promise { + return new Promise((resolve) => { + veepooBle.veepooWeiXinSDKloseBluetoothAdapterManager(() => resolve()); + }); +} + +// ── 功能模块:认证 ── + +/** 秘钥认证(无参数无回调,结果通过数据监听 type=1 返回) */ +export function authenticate(): void { + veepooFeature.veepooBlePasswordCheckManager(); +} + +// ── 功能模块:测量指令 ── + +/** 心率测量开关(true=开启,false=关闭) */ +export function setHeartRateMeasure(on: boolean): void { + veepooFeature.veepooSendHeartRateTestSwitchManager({ switch: on }); +} + +/** 血氧测量开关('start'=开启,'stop'=关闭) */ +export function setBloodOxygenMeasure(action: 'start' | 'stop'): void { + veepooFeature.veepooSendBloodOxygenControlDataManager({ switch: action }); +} + +/** 血压测量开关('start'=开启,'stop'=关闭) */ +export function setBloodPressureMeasure(action: 'start' | 'stop'): void { + veepooFeature.veepooSendReadUniversalBloodPressureDataManager({ switch: action }); +} + +/** 体温测量(单次触发) */ +export function startTemperatureMeasure(): void { + veepooFeature.veepooSendTemperatureMeasurementSwitchManager(); +} + +/** 压力测量开关(true=开启,false=关闭) */ +export function setPressureMeasure(on: boolean): void { + veepooFeature.veepooSendPressureTestManager({ switch: on }); +} + +// ── 功能模块:日常数据 ── + +/** 读取日常数据(day: 0=今天, 1=昨天, 2=前天;package: 开始包序号,默认1) */ +export function readDailyData(day: number, pkg: number = 1): void { + veepooFeature.veepooSendReadDailyDataManager({ day, package: pkg }); +} + +// ── 功能模块:精准睡眠数据 ── + +/** 精准睡眠事件类型 */ +export const SDK_EVENT_SLEEP = 4; + +/** 精准睡眠数据(SDK 回调 type=4) */ +export interface SleepData { + /** 入睡时间(时间戳字符串) */ + fallAsleepTime: string; + /** 退出睡眠时间(时间戳字符串) */ + exitSleepTime: string; + /** 起夜得分 */ + nightScore: number; + /** 深睡得分 */ + deepSleepScore: number; + /** 睡眠效率得分 */ + sleepEfficiencyScore: number; + /** 入睡效率得分 */ + fallAsleepEfficiencyScore: number; + /** 睡眠时长得分 */ + sleepTimeScore: number; + /** 睡眠质量(1-5 星) */ + sleepQuality: number; + /** 深睡时长(分钟) */ + deepSleepTime: number; + /** 浅睡时长(分钟) */ + lightSleepTime: number; + /** 其他睡眠时长(分钟) */ + otherSleepTime: number; + /** 睡眠总时长(分钟) */ + sleepTotalTime: number; + /** 首次深睡眠时长(分钟) */ + firstDeepSleepTime: number; + /** 起夜总时长(分钟) */ + nightTotalTime: number; + /** 起夜到深睡均值 */ + nightDeepSleepMeanValue: number; + /** 失眠得分 */ + insomniaScore: number; + /** 失眠次数 */ + insomniaCount: number; + /** 睡眠曲线字符串(0=深睡, 1=浅睡, 2=REM, 3=失眠, 4=苏醒) */ + sleepCurve: string; +} + +/** 读取精准睡眠数据(day: 0=今天, 1=昨天, 2=前天) */ +export function readPreciseSleepData(day: number): void { + veepooFeature.veepooSendReadPreciseSleepManager({ day }); +} + +// ── 功能模块:自动测量(B3) ── + +/** 自动测量事件类型 */ +export const SDK_EVENT_AUTO_TEST = 54; + +/** B3 自动测量功能类型枚举 */ +export const AUTO_TEST_FUN_TYPES = { + PULSE_RATE: 0, // 脉率 + BLOOD_PRESSURE: 1, // 血压 + BLOOD_GLUCOSE: 2, // 血糖 + PRESSURE: 3, // 压力 + BLOOD_OXYGEN: 4, // 血氧 + TEMPERATURE: 5, // 体温 + LORENTZ_SCATTER: 6, // 洛伦兹散点图 + HRV: 7, // HRV + BLOOD_COMPONENT: 8, // 血液成分 +} as const; + +export type AutoTestFunType = (typeof AUTO_TEST_FUN_TYPES)[keyof typeof AUTO_TEST_FUN_TYPES]; + +/** B3 自动测量配置项 */ +export interface AutoTestConfig { + /** 协议类型(不可修改) */ + protocolType: number; + /** 功能类型 0-8(可修改) */ + funTypeContent: AutoTestFunType; + /** 开关:0=关闭, 1=开启 */ + funSwitch: number; + /** 最小步进(分钟) */ + stepUnit: number; + /** 是否支持时间段修改 */ + timeSlotModify: number; + /** 是否支持时间间隔修改 */ + timeIntervalModify: number; + /** 支持的测试时间段 */ + supportTimeSlot: { startTime: string; stopTime: string }; + /** 测量间隔(分钟,按 stepUnit 递增) */ + measInterval: number; + /** 当前测试时间段 */ + currentTimeSlot: { startTime: string; stopTime: string }; +} + +/** 读取自动测量功能配置 */ +export function readAutoTestConfig(): void { + veepooFeature.veepooSendReadB3AutoTestFeatureDataManager(); +} + +/** 设置自动测量功能 */ +export function setAutoTestConfig(config: AutoTestConfig): void { + veepooFeature.veepooSendSetupB3AutoTestFeatureDataManager({ + p_protocol_type: config.protocolType, + p_fun_type_content: config.funTypeContent, + p_fun_switch: config.funSwitch, + p_step_unit: config.stepUnit, + p_time_slot_modify: config.timeSlotModify, + p_time_interval_modify: config.timeIntervalModify, + p_support_time_slot: config.supportTimeSlot, + p_meas_inv: config.measInterval, + p_cur_time_slot: config.currentTimeSlot, + }); +} + +// ── 功能模块:开关设置 ── + +/** 自动心率监测开关 */ +export function setAutoHeartRate(enabled: boolean): void { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticHRTest: enabled ? 'open' : 'close', + }); +} + +/** 自动血压监测开关 */ +export function setAutoBloodPressure(enabled: boolean): void { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticBPTest: enabled ? 'open' : 'close', + }); +} + +/** 体温自动监测 */ +export function setAutoTemperature(enabled: boolean): void { + veepooFeature.veepooSendSwitchSettingDataManager({ + VPSettingAutomaticTemperatureTest: enabled ? 'open' : 'close', + }); +} + +/** 读取体温自动监测数据 */ +export function readAutoTemperatureData(): void { + veepooFeature.veepooReadAutoTemperatureMeasurementDataManager({ day: 0 }); +} + +// ── 功能模块:设备信息 ── + +/** 读取设备电量 */ +export function readBatteryLevel(): void { + veepooFeature.veepooReadElectricQuantityManager(); +} + +// ── 日志模块 ── + +/** 设置日志级别(0=DEBUG, 1=INFO, 2=WARN, 3=ERROR, 4=NONE) */ +export function setLogLevel(level: number): void { + veepooLogger.setLevel(level); +} diff --git a/apps/miniprogram/src/services/ble/veepoo/VeepooHistoryReader.ts b/apps/miniprogram/src/services/ble/veepoo/VeepooHistoryReader.ts new file mode 100644 index 0000000..1c79ef9 --- /dev/null +++ b/apps/miniprogram/src/services/ble/veepoo/VeepooHistoryReader.ts @@ -0,0 +1,245 @@ +/** + * Veepoo 历史数据读取器 — 3天日常数据分批读取 + 上传 + * + * SDK 日常数据格式(type=5): + * - 包含计步、心率、血压、血氧、睡眠、压力、体温等 + * - Progress 字段 1-100% 表示读取进度 + * - 每次回调可能包含一包数据 + */ + +import Taro from '@tarojs/taro'; +import { readDailyData } from '../VeepooBridge'; +import type { SdkEventData } from '../VeepooBridge'; +import type { NormalizedReading } from '../types'; +import type { SleepReading } from './types'; +import { uploadReadings } from '@/services/device-sync'; + +const CHECKPOINT_KEY = 'veepoo_history_checkpoint'; +const UPLOAD_BATCH_SIZE = 20; + +interface Checkpoint { + lastProgress: number; + packagesRead: number; + deviceId: string; + timestamp: number; +} + +export type HistoryReadPhase = 'idle' | 'reading' | 'uploading' | 'done' | 'error'; + +export class VeepooHistoryReader { + private phase: HistoryReadPhase = 'idle'; + private progress = 0; + private packagesRead = 0; + private buffer: NormalizedReading[] = []; + private day = 0; + private patientId = ''; + private deviceId = ''; + private onProgress?: (progress: number, phase: HistoryReadPhase) => void; + private uploadedCount = 0; + + setCallbacks(cbs: { onProgress?: (progress: number, phase: HistoryReadPhase) => void }): void { + this.onProgress = cbs.onProgress; + } + + /** 开始读取3天数据 */ + async startRead(patientId: string, deviceId: string): Promise { + this.patientId = patientId; + this.deviceId = deviceId; + this.buffer = []; + this.uploadedCount = 0; + this.phase = 'reading'; + + // 依次读取 3 天数据 + for (let day = 0; day < 3; day++) { + this.day = day; + this.progress = 0; + this.onProgress?.(0, 'reading'); + + await this.readDay(day); + + // 刷新剩余 buffer + if (this.buffer.length > 0) { + await this.flushBuffer(); + } + } + + this.phase = 'done'; + this.onProgress?.(100, 'done'); + this.clearCheckpoint(); + + return this.uploadedCount; + } + + /** 读取单天数据 */ + private readDay(day: number): Promise { + return new Promise((resolve) => { + // 发送读取指令 + readDailyData(day, 1); + + // 进度通过 handleDailyEvent 更新 + // Progress=100 时 resolve + this.dayResolve = resolve; + + // 超时保护:30s + this.dayTimeout = setTimeout(() => { + this.dayResolve = null; + resolve(); + }, 30_000); + }); + } + + private dayResolve: (() => void) | null = null; + private dayTimeout: ReturnType | null = null; + + /** 处理 SDK 日常数据回调 */ + handleDailyEvent(data: SdkEventData): void { + if (this.phase !== 'reading') return; + + const progress = (data.Progress ?? 0) as number; + this.progress = progress; + this.onProgress?.(progress, 'reading'); + + // 解析数据 + const readings = this.parseDailyData(data); + if (readings.length > 0) { + this.buffer.push(...readings); + this.packagesRead++; + } + + // 达到批量大小就上传 + if (this.buffer.length >= UPLOAD_BATCH_SIZE) { + this.flushBuffer(); + } + + // 进度 100% 表示当天数据读取完成 + if (progress >= 100) { + if (this.dayTimeout) clearTimeout(this.dayTimeout); + this.dayTimeout = null; + const resolve = this.dayResolve; + this.dayResolve = null; + resolve?.(); + } + } + + /** 解析 SDK 日常数据为 NormalizedReading */ + private parseDailyData(data: SdkEventData): NormalizedReading[] { + const content = data.content ?? {}; + const readings: NormalizedReading[] = []; + const now = new Date(); + // 偏移到对应天 + const baseDate = new Date(now.getTime() - this.day * 86400000); + const timestamp = baseDate.toISOString(); + + // 心率 + const hr = content.heartReat ?? content.heartRate; + if (typeof hr === 'number' && hr >= 30 && hr <= 250) { + readings.push({ device_type: 'heart_rate', values: { heart_rate: hr }, measured_at: timestamp }); + } + + // 血氧 + const bo = content.bloodOxygen; + if (typeof bo === 'number' && bo >= 70 && bo <= 100) { + readings.push({ device_type: 'blood_oxygen', values: { blood_oxygen: bo }, measured_at: timestamp }); + } + + // 血压 + const bph = content.bloodPressureHigh; + const bpl = content.bloodPressureLow; + if (typeof bph === 'number' && typeof bpl === 'number' && bph > 0 && bpl > 0) { + readings.push({ device_type: 'blood_pressure', values: { systolic: bph, diastolic: bpl }, measured_at: timestamp }); + } + + // 体温 + const temp = content.bodyTemperature; + if (typeof temp === 'number' && temp > 30 && temp < 45) { + readings.push({ device_type: 'temperature', values: { temperature: temp }, measured_at: timestamp }); + } + + // 压力 + const pressure = content.pressure; + if (typeof pressure === 'number' && pressure >= 0 && pressure <= 100) { + readings.push({ device_type: 'stress', values: { value: pressure }, measured_at: timestamp }); + } + + // 步数 + const steps = content.stepCount ?? content.steps; + if (typeof steps === 'number' && steps >= 0) { + readings.push({ device_type: 'steps', values: { value: steps }, measured_at: timestamp }); + } + + return readings; + } + + /** 上传 buffer 中的数据 */ + private async flushBuffer(): Promise { + if (this.buffer.length === 0) return; + + const batch = this.buffer.splice(0, this.buffer.length); + this.phase = 'uploading'; + this.onProgress?.(this.progress, 'uploading'); + + try { + await uploadReadings(this.patientId, this.deviceId, 'Veepoo M2', batch); + this.uploadedCount += batch.length; + this.saveCheckpoint(); + } catch { + // 上传失败,放回 buffer + this.buffer.unshift(...batch); + } + + this.phase = 'reading'; + } + + private saveCheckpoint(): void { + try { + const checkpoint: Checkpoint = { + lastProgress: this.progress, + packagesRead: this.packagesRead, + deviceId: this.deviceId, + timestamp: Date.now(), + }; + Taro.setStorageSync(CHECKPOINT_KEY, JSON.stringify(checkpoint)); + } catch { /* ignore */ } + } + + private clearCheckpoint(): void { + try { Taro.removeStorageSync(CHECKPOINT_KEY); } catch { /* ignore */ } + } + + getPhase(): HistoryReadPhase { return this.phase; } + getProgress(): number { return this.progress; } + getUploadedCount(): number { return this.uploadedCount; } + + // ── 睡眠数据上传 ── + + /** 将睡眠数据转换为 NormalizedReading 并上传 */ + async uploadSleepReadings(patientId: string, deviceId: string, sleepData: SleepReading[]): Promise { + if (sleepData.length === 0) return 0; + + const now = new Date(); + const readings: NormalizedReading[] = sleepData.map((sleep) => { + // 根据天数偏移计算日期 + const baseDate = new Date(now.getTime() - sleep.day * 86400000); + return { + device_type: 'sleep', + values: { + deep_sleep_minutes: sleep.deepSleepMinutes, + light_sleep_minutes: sleep.lightSleepMinutes, + total_sleep_minutes: sleep.totalSleepMinutes, + quality_score: sleep.qualityScore, + }, + measured_at: baseDate.toISOString(), + }; + }); + + try { + await uploadReadings(patientId, deviceId, 'Veepoo M2', readings); + this.uploadedCount += readings.length; + console.log('[veepoo-history] 睡眠数据上传成功:', readings.length, '条'); + return readings.length; + } catch (err) { + console.error('[veepoo-history] 睡眠数据上传失败:', err); + return 0; + } + } +} diff --git a/apps/miniprogram/src/services/ble/veepoo/VeepooPipeline.ts b/apps/miniprogram/src/services/ble/veepoo/VeepooPipeline.ts new file mode 100644 index 0000000..f55e73c --- /dev/null +++ b/apps/miniprogram/src/services/ble/veepoo/VeepooPipeline.ts @@ -0,0 +1,588 @@ +/** + * Veepoo 管线 — SDK 事件路由 + 连接编排 + 测量 Promise 封装 + * + * 职责: + * 1. 连接流程编排:扫描 → 连接 → 注册监听 → 认证 → 就绪 + * 2. SDK 事件路由:registerDataListener 按 type 分发 + * 3. 测量 Promise 化:startMeasure(type) → Promise + */ + +import Taro from '@tarojs/taro'; +import { + startScan, + stopScan, + connectDevice, + registerDataListener, + registerConnectionListener, + authenticate, + disconnect as veepooDisconnect, + setHeartRateMeasure, + setBloodOxygenMeasure, + setBloodPressureMeasure, + startTemperatureMeasure, + setPressureMeasure, + readBatteryLevel, + readPreciseSleepData, + readAutoTestConfig, + setAutoHeartRate, + setAutoBloodPressure, + setAutoTemperature, + setLogLevel, + SDK_EVENT_AUTH, + SDK_EVENT_HEART_RATE, + SDK_EVENT_BLOOD_OXYGEN, + SDK_EVENT_BLOOD_PRESSURE, + SDK_EVENT_TEMPERATURE, + SDK_EVENT_PRESSURE, + SDK_EVENT_DAILY, + SDK_EVENT_SLEEP, + SDK_EVENT_AUTO_TEST, + DEVICE_STATE, +} from '../VeepooBridge'; +import type { SdkEventData } from '../VeepooBridge'; +import type { MeasureType, MeasureResult, SleepReading } from './types'; + +const AUTH_TIMEOUT = 8_000; +const AUTH_POLL_INTERVAL = 500; +const MEASURE_SETTLE_DELAY = 1_500; + +/** pending 测量的 resolve/reject 句柄 */ +interface PendingMeasure { + type: MeasureType; + resolve: (result: MeasureResult) => void; + reject: (error: Error) => void; + timer: ReturnType; + lastValue: number | null; + lastValues: Record; + settleTimer: ReturnType | null; +} + +/** SDK type 到 MeasureType 的映射 */ +const SDK_TYPE_TO_MEASURE: Record = { + [SDK_EVENT_HEART_RATE]: 'heart_rate', + [SDK_EVENT_BLOOD_OXYGEN]: 'blood_oxygen', + [SDK_EVENT_BLOOD_PRESSURE]: 'blood_pressure', + [SDK_EVENT_TEMPERATURE]: 'temperature', + [SDK_EVENT_PRESSURE]: 'pressure', +}; + +export type ConnectionChangeCallback = (connected: boolean, deviceId: string) => void; +export type AuthResultCallback = (success: boolean) => void; +export type MeasureEventCallback = (type: MeasureType, data: Record) => void; +export type DailyDataCallback = (data: SdkEventData) => void; +export type SleepDataCallback = (day: number, sleep: SleepReading) => void; + +export class VeepooPipeline { + private pending: PendingMeasure | null = null; + private isConnected = false; + private deviceId = ''; + + /** 睡眠数据读取 Promise resolve 队列 */ + private sleepResolvers: Map void> = new Map(); + private sleepTimeouts: Map> = new Map(); + + private onConnectionChange?: ConnectionChangeCallback; + private onAuthResult?: AuthResultCallback; + private onMeasureEvent?: MeasureEventCallback; + private onDailyData?: DailyDataCallback; + private onSleepData?: SleepDataCallback; + + /** 注册回调 */ + setCallbacks(cbs: { + onConnectionChange?: ConnectionChangeCallback; + onAuthResult?: AuthResultCallback; + onMeasureEvent?: MeasureEventCallback; + onDailyData?: DailyDataCallback; + onSleepData?: SleepDataCallback; + }): void { + this.onConnectionChange = cbs.onConnectionChange; + this.onAuthResult = cbs.onAuthResult; + this.onMeasureEvent = cbs.onMeasureEvent; + this.onDailyData = cbs.onDailyData; + this.onSleepData = cbs.onSleepData; + } + + /** 全流程:扫描 → 连接 → 注册监听 → 认证 */ + async connect(targetName: string, debug = false): Promise { + console.log('[veepoo-pipeline] connect() 开始, target:', targetName); + if (debug) setLogLevel(0); + + // 1. 扫描 + console.log('[veepoo-pipeline] Step 1: 扫描...'); + const device = await this.scanFor(targetName); + if (!device) { + console.error('[veepoo-pipeline] 扫描未找到设备'); + throw new Error(`未找到设备 ${targetName}`); + } + console.log('[veepoo-pipeline] 找到设备:', (device as Record)?.deviceId); + + // 2. 连接 + console.log('[veepoo-pipeline] Step 2: 连接...'); + const connRes = await connectDevice(device); + console.log('[veepoo-pipeline] 连接结果:', JSON.stringify(connRes)); + // SDK 连接成功返回 errno=0 或 connection=true,两种都要兼容 + const ok = connRes?.connection === true || connRes?.errno === 0 || connRes?.errCode === 0; + if (!ok) throw new Error('连接失败'); + + const id = (device as Record).deviceId as string; + this.deviceId = id; + this.isConnected = true; + + // 3. 注册数据监听(连接成功后) + registerDataListener((data) => this.routeEvent(data)); + registerConnectionListener((res) => { + this.isConnected = res.connected; + this.onConnectionChange?.(res.connected, res.deviceId); + }); + + // 4. 认证(延迟 500ms) + await delay(500); + authenticate(); + + // 5. 等待认证结果 + const authOk = await this.waitForAuth(); + if (!authOk) throw new Error('设备认证失败,请重新连接'); + + // 6. 读取电量 + readBatteryLevel(); + + return id; + } + + /** 扫描指定名称的设备 */ + private scanFor(targetName: string): Promise { + return new Promise((resolve) => { + let found: unknown = null; + const upper = targetName.toUpperCase(); + + startScan((device) => { + const d = device as Record; + const name = String(d.localName ?? d.name ?? '').toUpperCase(); + if (name.includes(upper) && !found) { + found = device; + stopScan().then(() => resolve(found)); + } + }); + + setTimeout(() => { + if (!found) { + stopScan().then(() => resolve(null)); + } + }, 10_000); + }); + } + + /** 等待认证结果(轮询 deviceChipStatus) */ + private waitForAuth(): Promise { + return new Promise((resolve) => { + const start = Date.now(); + + const poll = () => { + try { + const status = Taro.getStorageSync('deviceChipStatus'); + if (status === 'successfulVerification' || status === 'passTheVerification') { + this.onAuthResult?.(true); + resolve(true); + return; + } + } catch { /* ignore */ } + + if (Date.now() - start >= AUTH_TIMEOUT) { + this.onAuthResult?.(false); + resolve(false); + return; + } + + setTimeout(poll, AUTH_POLL_INTERVAL); + }; + + poll(); + }); + } + + /** SDK 事件路由 */ + private routeEvent(data: SdkEventData): void { + const eventType = data.type; + + // 认证回调 + if (eventType === SDK_EVENT_AUTH) { + const content = data.content ?? {}; + const password = content.VPDevicepassword; + if (password === 'passTheVerification' || password === 'successfulVerification') { + this.onAuthResult?.(true); + } + return; + } + + // 日常数据 + if (eventType === SDK_EVENT_DAILY) { + this.onDailyData?.(data); + return; + } + + // 精准睡眠数据 + if (eventType === SDK_EVENT_SLEEP) { + this.handleSleepEvent(data); + return; + } + + // 自动测量功能回调 + if (eventType === SDK_EVENT_AUTO_TEST) { + console.log('[veepoo-pipeline] 自动测量配置回调:', JSON.stringify(data).substring(0, 300)); + return; + } + + // 测量数据 + const measureType = SDK_TYPE_TO_MEASURE[eventType]; + if (!measureType) return; + + this.handleMeasureEvent(measureType, data); + this.onMeasureEvent?.(measureType, data.content ?? {}); + } + + /** 处理测量事件 */ + private handleMeasureEvent(type: MeasureType, data: SdkEventData): void { + if (!this.pending || this.pending.type !== type) return; + + const content = data.content ?? {}; + + // 检查设备状态错误 + const deviceBusy = content.deviceBusy === true; + const notWear = content.notWear === true; + const state = data.state; + const ack = data.ack; + + if (deviceBusy) { + this.rejectPending(new Error('设备正忙,请稍后重试')); + return; + } + if (notWear || state === DEVICE_STATE.NOT_WORN) { + this.rejectPending(new Error('请将手环佩戴到手腕上')); + return; + } + if (state === DEVICE_STATE.CHARGING) { + this.rejectPending(new Error('设备正在充电,请取出后重试')); + return; + } + if (state === DEVICE_STATE.LOW_BATTERY) { + this.rejectPending(new Error('设备电量不足,请充电后重试')); + return; + } + if (type === 'pressure' && ack === 2) { + this.rejectPending(new Error('设备电量不足')); + return; + } + if (type === 'pressure' && ack === 3) { + this.rejectPending(new Error('设备正在测量其他数据')); + return; + } + if (type === 'pressure' && ack === 4) { + this.rejectPending(new Error('佩戴检测未通过')); + return; + } + + // 提取数值 + const values = this.extractValues(type, content); + if (!values) return; + + // 更新 pending 最新值 + this.pending.lastValues = values; + + // 对于进度型指标,检查是否完成 + const progress = data.Progress; + if (progress !== undefined && progress >= 100) { + this.resolvePending(values); + return; + } + + // 对于持续测量型/单次型,收到第一个有效值后延迟 settle + if (this.pending.settleTimer === null) { + this.pending.settleTimer = setTimeout(() => { + if (this.pending && this.pending.lastValues && Object.keys(this.pending.lastValues).length > 0) { + this.resolvePending(this.pending.lastValues); + } + }, MEASURE_SETTLE_DELAY); + } + } + + /** 从 SDK content 提取标准化数值 */ + private extractValues(type: MeasureType, content: Record): Record | null { + switch (type) { + case 'heart_rate': { + const hr = Number(content.heartRate); + if (hr >= 30 && hr <= 250) return { heart_rate: hr }; + return null; + } + case 'blood_oxygen': { + const bo = Number(content.bloodOxygen); + if (bo >= 70 && bo <= 100) return { blood_oxygen: bo }; + return null; + } + case 'blood_pressure': { + const high = Number(content.bloodPressureHigh); + const low = Number(content.bloodPressureLow); + if (high > 0 && low > 0) return { systolic: high, diastolic: low }; + return null; + } + case 'temperature': { + const temp = Number(content.bodyTemperature); + if (temp > 30 && temp < 45) return { temperature: temp }; + return null; + } + case 'pressure': { + const p = Number(content.pressure); + if (p >= 0 && p <= 100) return { pressure: p }; + return null; + } + default: + return null; + } + } + + /** 发起测量 */ + startMeasure(type: MeasureType): Promise { + if (this.pending) { + throw new Error(`正在测量 ${this.pending.type},请等待完成`); + } + if (!this.isConnected) { + throw new Error('设备未连接'); + } + + return new Promise((resolve, reject) => { + const timeout = getMeasureTimeout(type); + + const timer = setTimeout(() => { + this.rejectPending(new Error('测量超时,请重试')); + }, timeout); + + this.pending = { + type, + resolve, + reject, + timer, + lastValue: null, + lastValues: {}, + settleTimer: null, + }; + + // 发送 SDK 测量指令 + this.sendMeasureCommand(type); + }); + } + + /** 取消当前测量 */ + cancelMeasure(): void { + if (!this.pending) return; + this.stopMeasureCommand(this.pending.type); + if (this.pending.lastValues && Object.keys(this.pending.lastValues).length > 0) { + this.resolvePending(this.pending.lastValues); + } else { + this.rejectPending(new Error('测量已取消')); + } + } + + /** 发送 SDK 测量指令 */ + private sendMeasureCommand(type: MeasureType): void { + switch (type) { + case 'heart_rate': + setHeartRateMeasure(true); + break; + case 'blood_oxygen': + setBloodOxygenMeasure('start'); + break; + case 'blood_pressure': + setBloodPressureMeasure('start'); + break; + case 'temperature': + startTemperatureMeasure(); + break; + case 'pressure': + setPressureMeasure(true); + break; + } + } + + /** 发送 SDK 停止测量指令 */ + private stopMeasureCommand(type: MeasureType): void { + switch (type) { + case 'heart_rate': + setHeartRateMeasure(false); + break; + case 'blood_oxygen': + setBloodOxygenMeasure('stop'); + break; + case 'blood_pressure': + setBloodPressureMeasure('stop'); + break; + case 'temperature': + break; // 体温是单次触发,无法停止 + case 'pressure': + setPressureMeasure(false); + break; + } + } + + /** 成功 resolve pending 测量 */ + private resolvePending(values: Record): void { + if (!this.pending) return; + const p = this.pending; + this.pending = null; + + clearTimeout(p.timer); + if (p.settleTimer) clearTimeout(p.settleTimer); + + // 停止持续测量型指标的 SDK 指令 + this.stopMeasureCommand(p.type); + + p.resolve({ + type: p.type, + values, + measuredAt: Date.now(), + }); + } + + /** 失败 reject pending 测量 */ + private rejectPending(error: Error): void { + if (!this.pending) return; + const p = this.pending; + this.pending = null; + + clearTimeout(p.timer); + if (p.settleTimer) clearTimeout(p.settleTimer); + + // 停止 SDK 指令 + this.stopMeasureCommand(p.type); + + p.reject(error); + } + + // ── 睡眠数据 ── + + /** 读取单天精准睡眠数据,返回 Promise */ + readSleepData(day: number): Promise { + if (!this.isConnected) { + return Promise.reject(new Error('设备未连接')); + } + + return new Promise((resolve) => { + this.sleepResolvers.set(day, resolve); + + // 超时保护 30s + const timer = setTimeout(() => { + this.sleepResolvers.delete(day); + this.sleepTimeouts.delete(day); + resolve(null); + }, 30_000); + this.sleepTimeouts.set(day, timer); + + // 发送 SDK 读取指令 + readPreciseSleepData(day); + }); + } + + /** 读取 3 天睡眠数据 */ + async readAllSleepData(): Promise { + const results: SleepReading[] = []; + for (let day = 0; day < 3; day++) { + const sleep = await this.readSleepData(day); + if (sleep) { + results.push(sleep); + } + } + return results; + } + + /** 处理 SDK 睡眠数据回调(type=4) */ + private handleSleepEvent(data: SdkEventData): void { + const progress = data.Progress ?? 0; + const readDay = (data as { readDay?: number }).readDay ?? 0; + + // 进度未达 100% 忽略 + if (progress < 100) return; + + const content = data.content ?? {}; + const sleep = this.parseSleepData(readDay, content as Record); + + // 通知回调 + if (sleep) { + this.onSleepData?.(readDay, sleep); + } + + // resolve 等待中的 Promise + const resolve = this.sleepResolvers.get(readDay); + if (resolve) { + const timer = this.sleepTimeouts.get(readDay); + if (timer) clearTimeout(timer); + this.sleepResolvers.delete(readDay); + this.sleepTimeouts.delete(readDay); + resolve(sleep); + } + } + + /** 从 SDK content 解析精准睡眠数据 */ + private parseSleepData(day: number, content: Record): SleepReading | null { + const total = Number(content.sleepTotalTime ?? 0); + if (total <= 0) return null; + + return { + day, + deepSleepMinutes: Number(content.deepSleepTime ?? 0), + lightSleepMinutes: Number(content.lightSleepTime ?? 0), + otherSleepMinutes: Number(content.otherSleepTime ?? 0), + totalSleepMinutes: total, + qualityScore: Number(content.sleepQuality ?? 0), + fallAsleepTime: String(content.fallAsleepTime ?? ''), + exitSleepTime: String(content.exitSleepTime ?? ''), + }; + } + + // ── 自动测量 ── + + /** 开启自动测量(心率 + 血压 + 血氧 + 体温) */ + enableAutoMeasurement(): void { + if (!this.isConnected) return; + + console.log('[veepoo-pipeline] 开启自动测量功能'); + setAutoHeartRate(true); + setAutoBloodPressure(true); + setAutoTemperature(true); + + // 读取当前自动测量配置 + readAutoTestConfig(); + } + + /** 断开连接 */ + async disconnect(): Promise { + if (this.pending) { + this.rejectPending(new Error('设备已断开')); + } + this.isConnected = false; + this.deviceId = ''; + await veepooDisconnect(); + } + + /** 获取连接状态 */ + getConnected(): boolean { + return this.isConnected; + } + + /** 获取设备 ID */ + getDeviceId(): string { + return this.deviceId; + } +} + +function delay(ms: number): Promise { + return new Promise((r) => setTimeout(r, ms)); +} + +function getMeasureTimeout(type: MeasureType): number { + const timeouts: Record = { + heart_rate: 60_000, + blood_oxygen: 60_000, + blood_pressure: 120_000, + temperature: 60_000, + pressure: 90_000, + }; + return timeouts[type]; +} diff --git a/apps/miniprogram/src/services/ble/veepoo/index.ts b/apps/miniprogram/src/services/ble/veepoo/index.ts new file mode 100644 index 0000000..74fa0f7 --- /dev/null +++ b/apps/miniprogram/src/services/ble/veepoo/index.ts @@ -0,0 +1,21 @@ +export { VeepooPipeline } from './VeepooPipeline'; +export { VeepooHistoryReader } from './VeepooHistoryReader'; +export type { + ConnectionChangeCallback, + AuthResultCallback, + MeasureEventCallback, + DailyDataCallback, +} from './VeepooPipeline'; +export type { + MeasureType, + MeasurePhase, + MeasureStatus, + MeasureResult, + MeasureConfig, + ConnectionPhase, + VeepooDeviceInfo, + HistorySyncState, + SleepReading, + AutoTestSyncState, +} from './types'; +export { MEASURE_TYPES, MEASURE_CONFIG } from './types'; diff --git a/apps/miniprogram/src/services/ble/veepoo/types.ts b/apps/miniprogram/src/services/ble/veepoo/types.ts new file mode 100644 index 0000000..5c42ec8 --- /dev/null +++ b/apps/miniprogram/src/services/ble/veepoo/types.ts @@ -0,0 +1,152 @@ +/** Veepoo 管线专用类型定义 */ + +/** 测量指标类型 */ +export type MeasureType = + | 'heart_rate' + | 'blood_oxygen' + | 'blood_pressure' + | 'temperature' + | 'pressure'; + +/** 所有支持的测量指标 */ +export const MEASURE_TYPES: readonly MeasureType[] = [ + 'heart_rate', + 'blood_oxygen', + 'blood_pressure', + 'temperature', + 'pressure', +] as const; + +/** 测量指标配置 */ +export interface MeasureConfig { + label: string; + unit: string; + icon: string; + color: string; + /** 正常范围 [min, max] */ + normalRange: [number, number]; + /** 测量超时(毫秒) */ + timeout: number; + /** 测量模式 */ + mode: 'continuous' | 'progress' | 'single'; +} + +/** 各指标配置表 */ +export const MEASURE_CONFIG: Record = { + heart_rate: { + label: '心率', + unit: 'bpm', + icon: '♥', + color: '#EF4444', + normalRange: [60, 100], + timeout: 60_000, + mode: 'continuous', + }, + blood_oxygen: { + label: '血氧', + unit: '%', + icon: 'O₂', + color: '#3B82F6', + normalRange: [95, 100], + timeout: 60_000, + mode: 'continuous', + }, + blood_pressure: { + label: '血压', + unit: 'mmHg', + icon: '↕', + color: '#8B5CF6', + normalRange: [90, 140], + timeout: 120_000, + mode: 'progress', + }, + temperature: { + label: '体温', + unit: '°C', + icon: 'T', + color: '#F59E0B', + normalRange: [36.0, 37.3], + timeout: 60_000, + mode: 'single', + }, + pressure: { + label: '压力', + unit: '', + icon: '~', + color: '#6366F1', + normalRange: [1, 40], + timeout: 90_000, + mode: 'progress', + }, +}; + +/** 连接阶段 */ +export type ConnectionPhase = + | 'idle' + | 'scanning' + | 'connecting' + | 'authenticating' + | 'ready' + | 'disconnected' + | 'error'; + +/** 测量阶段 */ +export type MeasurePhase = 'idle' | 'measuring' | 'success' | 'error'; + +/** 单个指标的测量状态 */ +export interface MeasureStatus { + phase: MeasurePhase; + progress: number; + currentValue: number | null; + result: MeasureResult | null; + error: string | null; +} + +/** 测量结果 */ +export interface MeasureResult { + type: MeasureType; + values: Record; + measuredAt: number; +} + +/** 设备信息 */ +export interface VeepooDeviceInfo { + deviceId: string; + name: string; + batteryLevel: number | null; +} + +/** 历史数据同步状态 */ +export interface HistorySyncState { + phase: 'idle' | 'reading' | 'uploading' | 'done'; + progress: number; + packagesRead: number; + lastCheckpoint: number; +} + +/** 睡眠数据(从 SDK 精准睡眠解析) */ +export interface SleepReading { + /** 读取天数(0=今天, 1=昨天, 2=前天) */ + day: number; + /** 深睡时长(分钟) */ + deepSleepMinutes: number; + /** 浅睡时长(分钟) */ + lightSleepMinutes: number; + /** 其他睡眠时长(分钟) */ + otherSleepMinutes: number; + /** 睡眠总时长(分钟) */ + totalSleepMinutes: number; + /** 睡眠质量评分(1-5 星) */ + qualityScore: number; + /** 入睡时间(时间戳字符串) */ + fallAsleepTime: string; + /** 退出睡眠时间(时间戳字符串) */ + exitSleepTime: string; +} + +/** 自动测量同步状态 */ +export interface AutoTestSyncState { + phase: 'idle' | 'reading_config' | 'configuring' | 'configured'; + enabledTypes: string[]; + intervalMinutes: number; +} diff --git a/apps/miniprogram/src/stores/veepoo.ts b/apps/miniprogram/src/stores/veepoo.ts new file mode 100644 index 0000000..60912d6 --- /dev/null +++ b/apps/miniprogram/src/stores/veepoo.ts @@ -0,0 +1,335 @@ +import { create } from 'zustand'; +import { VeepooPipeline } from '@/services/ble/veepoo/VeepooPipeline'; +import { VeepooHistoryReader } from '@/services/ble/veepoo/VeepooHistoryReader'; +import type { + MeasureType, + MeasureStatus, + MeasureResult, + ConnectionPhase, + VeepooDeviceInfo, + HistorySyncState, + SleepReading, +} from '@/services/ble/veepoo/types'; +import { MEASURE_TYPES } from '@/services/ble/veepoo/types'; +import { useAuthStore } from './auth'; + +/** 初始化每个指标的默认状态 */ +function initialMeasureStates(): Record { + const states = {} as Record; + for (const t of MEASURE_TYPES) { + states[t] = { phase: 'idle', progress: 0, currentValue: null, result: null, error: null }; + } + return states; +} + +interface VeepooState { + // 连接 + connectionPhase: ConnectionPhase; + device: VeepooDeviceInfo | null; + error: string | null; + + // 测量 + activeMeasure: MeasureType | null; + measureStates: Record; + + // 历史 + historySync: HistorySyncState; + + // 睡眠 + sleepData: SleepReading[]; + sleepLoading: boolean; + + // Actions + connect: (targetName?: string) => Promise; + disconnect: () => Promise; + startMeasure: (type: MeasureType) => Promise; + cancelMeasure: () => void; + syncHistory: (patientId: string) => Promise; + readSleepData: () => Promise; + enableAutoMeasurement: () => void; + reset: () => void; +} + +let pipelineInstance: VeepooPipeline | null = null; +let historyReaderInstance: VeepooHistoryReader | null = null; + +function getPipeline(): VeepooPipeline { + if (!pipelineInstance) { + pipelineInstance = new VeepooPipeline(); + } + return pipelineInstance; +} + +function getHistoryReader(): VeepooHistoryReader { + if (!historyReaderInstance) { + historyReaderInstance = new VeepooHistoryReader(); + } + return historyReaderInstance; +} + +export const useVeepooStore = create((set, get) => ({ + connectionPhase: 'idle', + device: null, + error: null, + activeMeasure: null, + measureStates: initialMeasureStates(), + historySync: { phase: 'idle', progress: 0, packagesRead: 0, lastCheckpoint: 0 }, + sleepData: [], + sleepLoading: false, + + connect: async (targetName = 'M2') => { + console.log('[veepoo-store] connect() 开始, target:', targetName); + set({ connectionPhase: 'scanning', error: null }); + const pipeline = getPipeline(); + const historyReader = getHistoryReader(); + + // 注册全部回调(包含新增的 onSleepData) + pipeline.setCallbacks({ + onConnectionChange: (connected) => { + if (!connected) { + set({ connectionPhase: 'disconnected', device: null }); + } + }, + onAuthResult: (success) => { + if (success) { + set({ connectionPhase: 'ready' }); + } + }, + onMeasureEvent: (type, data) => { + const state = get(); + if (state.activeMeasure !== type) return; + + const value = extractDisplayValue(type, data); + set({ + measureStates: { + ...state.measureStates, + [type]: { + ...state.measureStates[type], + phase: 'measuring', + progress: (data.Progress ?? data.progress ?? 0) as number, + currentValue: value, + }, + }, + }); + }, + onDailyData: (data) => { + // 转发给 HistoryReader 处理 + historyReader.handleDailyEvent(data); + + const progress = data.Progress ?? 0; + set((s) => ({ + historySync: { ...s.historySync, progress: progress as number }, + })); + }, + onSleepData: (_day, sleep) => { + // 收集睡眠数据到 store + set((s) => ({ + sleepData: [...s.sleepData, sleep], + })); + }, + }); + + // 注册 HistoryReader 进度回调 + historyReader.setCallbacks({ + onProgress: (progress, phase) => { + set((s) => ({ + historySync: { + ...s.historySync, + phase: phase === 'uploading' ? 'uploading' : 'reading', + progress, + }, + })); + }, + }); + + try { + set({ connectionPhase: 'connecting' }); + const deviceId = await pipeline.connect(targetName); + set({ + connectionPhase: 'authenticating', + device: { deviceId, name: targetName, batteryLevel: null }, + }); + + // 认证结果由 onAuthResult 回调设置 + // 等待 ready 状态(最多 10s) + await waitForState(() => get().connectionPhase === 'ready', 10_000); + + // 认证通过后:自动同步历史 + 读取睡眠 + 开启自动测量 + const patient = useAuthStore.getState().currentPatient; + const readyState = get().connectionPhase === 'ready'; + if (patient && readyState) { + const deviceIdForReader = get().device?.deviceId ?? 'veepoo_m2'; + + // 并行执行三件事: + // 1. 同步日常历史数据(后台执行,进度通过回调更新) + get().syncHistory(patient.id); + + // 2. 读取睡眠数据 → 完成后自动上传 + get().readSleepData().then((sleepResults) => { + if (sleepResults.length > 0) { + historyReader.uploadSleepReadings(patient.id, deviceIdForReader, sleepResults); + } + }); + + // 3. 开启自动测量(心率+血压+体温) + pipeline.enableAutoMeasurement(); + } + } catch (err) { + console.error('[veepoo-store] connect 失败:', err); + set({ + connectionPhase: 'error', + error: err instanceof Error ? err.message : '连接失败', + }); + } + }, + + disconnect: async () => { + const pipeline = getPipeline(); + await pipeline.disconnect(); + set({ + connectionPhase: 'idle', + device: null, + error: null, + activeMeasure: null, + measureStates: initialMeasureStates(), + sleepData: [], + sleepLoading: false, + }); + }, + + startMeasure: async (type: MeasureType) => { + const state = get(); + if (state.activeMeasure) { + throw new Error(`正在测量 ${state.activeMeasure},请等待完成`); + } + if (state.connectionPhase !== 'ready') { + throw new Error('设备未就绪'); + } + + set({ + activeMeasure: type, + measureStates: { + ...state.measureStates, + [type]: { phase: 'measuring', progress: 0, currentValue: null, result: null, error: null }, + }, + }); + + const pipeline = getPipeline(); + try { + const result = await pipeline.startMeasure(type); + set((s) => ({ + activeMeasure: null, + measureStates: { + ...s.measureStates, + [type]: { phase: 'success', progress: 100, currentValue: null, result, error: null }, + }, + })); + return result; + } catch (err) { + const msg = err instanceof Error ? err.message : '测量失败'; + set((s) => ({ + activeMeasure: null, + measureStates: { + ...s.measureStates, + [type]: { phase: 'error', progress: 0, currentValue: null, result: null, error: msg }, + }, + })); + throw err; + } + }, + + cancelMeasure: () => { + const pipeline = getPipeline(); + pipeline.cancelMeasure(); + }, + + syncHistory: async (patientId: string) => { + const deviceId = get().device?.deviceId ?? 'veepoo_m2'; + set((s) => ({ historySync: { ...s.historySync, phase: 'reading', progress: 0 } })); + + try { + const historyReader = getHistoryReader(); + const count = await historyReader.startRead(patientId, deviceId); + set((s) => ({ + historySync: { ...s.historySync, phase: 'done', progress: 100, packagesRead: count }, + })); + console.log('[veepoo-store] 历史数据同步完成, 上传:', count, '条'); + } catch (err) { + console.error('[veepoo-store] 历史数据同步失败:', err); + set((s) => ({ historySync: { ...s.historySync, phase: 'done', progress: 100 } })); + } + }, + + readSleepData: async () => { + const pipeline = getPipeline(); + if (!pipeline.getConnected()) { + console.warn('[veepoo-store] 设备未连接,跳过睡眠数据读取'); + return []; + } + + set({ sleepLoading: true, sleepData: [] }); + try { + const sleepResults = await pipeline.readAllSleepData(); + set({ sleepData: sleepResults, sleepLoading: false }); + console.log('[veepoo-store] 睡眠数据读取完成:', sleepResults.length, '天'); + return sleepResults; + } catch (err) { + console.error('[veepoo-store] 睡眠数据读取失败:', err); + set({ sleepLoading: false }); + return []; + } + }, + + enableAutoMeasurement: () => { + const pipeline = getPipeline(); + pipeline.enableAutoMeasurement(); + }, + + reset: () => { + set({ + connectionPhase: 'idle', + device: null, + error: null, + activeMeasure: null, + measureStates: initialMeasureStates(), + historySync: { phase: 'idle', progress: 0, packagesRead: 0, lastCheckpoint: 0 }, + sleepData: [], + sleepLoading: false, + }); + }, +})); + +/** 从 SDK 事件 content 提取显示值 */ +function extractDisplayValue(type: MeasureType, content: Record): number | null { + switch (type) { + case 'heart_rate': { + const v = Number(content.heartRate); + return v >= 30 && v <= 250 ? v : null; + } + case 'blood_oxygen': { + const v = Number(content.bloodOxygen); + return v >= 70 && v <= 100 ? v : null; + } + case 'blood_pressure': + return Number(content.bloodPressureHigh) || null; + case 'temperature': + return Number(content.bodyTemperature) || null; + case 'pressure': + return Number(content.pressure) || null; + default: + return null; + } +} + +/** 轮询等待状态满足条件 */ +function waitForState(check: () => boolean, timeoutMs: number): Promise { + return new Promise((resolve, reject) => { + const start = Date.now(); + const poll = () => { + if (check()) { resolve(); return; } + if (Date.now() - start >= timeoutMs) { reject(new Error('等待超时')); return; } + setTimeout(poll, 200); + }; + poll(); + }); +} diff --git a/docs/design/veepoo-measure-prototype.html b/docs/design/veepoo-measure-prototype.html new file mode 100644 index 0000000..4f44458 --- /dev/null +++ b/docs/design/veepoo-measure-prototype.html @@ -0,0 +1,879 @@ + + + + + +Veepoo M2 手环 — 测量页 & 上传页原型 + + + + + +
+
页面 1 · 测量页(就绪态 — 心率测量中)
+
+
+ +
+ 9:41 +
+ ●●●● + WiFi + 85% +
+
+ +
+ +
+
+
+ M2 手环 + 85% +
+ +
+ + +
+ +
+
+ 心率 +
+ +
+
O₂
+ 血氧 +
+ +
+
+ 血压 +
+ +
+
T
+ 体温 +
+ +
+
~
+ 压力 +
+
+ + +
+
+ + + + +
+ 72 + bpm +
+
+ + +
+
+
+ + +
♥ 心率正常
+
+ + +
+

测量数据仅供参考,不作为医学诊断依据。如有不适请及时就医。

+
+ + +
+ + +
+
+
+
+
+ + + +
+
页面 2 · 数据上传页(结果汇总 + 上传)
+
+
+ +
+ 9:41 +
+ ●●●● + WiFi + 85% +
+
+ +
+ + + + +
+ +
+
+
心率
+
+ 72 + bpm +
+
● 正常
+
+ + +
+
+
血氧
+
+ 98 + % +
+
● 正常
+
+ + +
+
+
血压
+
+ 135 + / 88 mmHg +
+
● 偏高
+
+ + +
+
+
体温
+
未测量
+
+ + +
+
+
压力
+
未测量
+
+
+ + + +
+
+
+
+ + + +
+
页面 1 · 测量页(未连接态)
+
+
+
+ 9:41 +
+ ●●●● + WiFi + 85% +
+
+ +
+ +
+
+
+ BT +
+
+ +

+ M2 手环健康测量 +

+

+ 请确保手环已佩戴且蓝牙已开启 +

+ + +
+
+
+
+ + + +
+
页面 1 · 测量页(血压测量完成)
+
+
+
+ 9:42 +
+ ●●●● + WiFi + 85% +
+
+ +
+
+
+
+ M2 手环 + 85% +
+ +
+ + +
+
+
+ 心率 +
+
+
O₂
+ 血氧 +
+
+
+ 血压 +
+
+
T
+ 体温 +
+
+
~
+ 压力 +
+
+ + +
+
+ + + + +
+ 135/88 + mmHg +
+
+ + +
↕ 血压偏高,建议关注
+
+ +
+

测量数据仅供参考,不作为医学诊断依据。如有不适请及时就医。

+
+ +
+ + +
+
+
+
+
+ + + + + diff --git a/docs/design/veepoo-prototype-preview.png b/docs/design/veepoo-prototype-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..be4cd1c002984d48d34c9bcbd8ee1c804e24cd72 GIT binary patch literal 122330 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 h00006Nkl 最后更新: 2026-05-30 | 数据截止: feat/media-library-banner 分支(Veepoo M2 BLE 实时测量 + SDK 对接全流程修复) +> 最后更新: 2026-05-31 | 数据截止: feat/media-library-banner 分支(Veepoo M2 BLE 管线扩展:精准睡眠 + 自动测量 + UI 重构) | 指标 | 值 | |------|-----| @@ -18,7 +18,7 @@ | erp-ai 实体 | 20 个 Entity(95 文件,4 AI Provider,chat_handler 支持 FC/Ollama fallback) | | 全系统 Entity | **115 个**(58 health + 20 ai + 33 基础 + 4 core) | | Web 前端 | 316 个 TS/TSX 文件(54 活跃路由,83 API 模块,108 页面) | -| 微信小程序 | Taro 4.2 + React 18,185 个 TS/TSX 文件 / 62 页面(15 主包 + 47 分包) / 4 TabBar + 医生端独立分包,34 组件(ui 21 + patterns 4 + 独立 9) / 49 service 文件 / 5 Zustand store / 12 hooks,统一组件库 + CSS 变量主题(103 SCSS 全量接入 `var(--tk-*)`,字号 token 对齐原型统计,医生端 `.doctor-mode` 靛蓝覆盖,登录页账号密码+微信一键登录);**Phase 2+3 完成**:Token 构建时生成 + Canvas 适老 + PII 清理 + 缓存加密 + any 清零 + 大文件拆分(3→6) + 触觉反馈 + 导航状态保持 + 独立分包 + CI 集成 + HMAC 请求签名;**并发安全**:长轮询独立通道 `requestUnlimited` + ConcurrencyLimiter(12) + safeNavigateTo 全局页栈保护 + reLaunch 去重;**Veepoo M2 BLE 管线**:独立管线(VeepooBridge 14 API + VeepooPipeline 事件路由 + VeepooStore 状态管理)+ **原生分包页面**(`pkg-veepoo` 原生 JS+WXML,脱离 Taro 直接调用 SDK,绕过框架兼容性限制)+ 实时测量页面(心率/血氧/血压/体温/压力 5 指标,圆环仪表盘 + 长者模式适配)+ 3 天历史数据同步(VeepooHistoryReader 分批上传 + 断点续传);**preloadRule 已移除 pkg-health** 防止 380KB SDK 预加载导致首页 DevTools 卡死;**构建优化**:`lazyCodeLoading: requiredComponents` 仅生产构建启用(dev 下已知 DevTools 卡死 bug),`addChunkPages` 仅 TabBar 页注入 common chunk,主包 dev 892KB / prod 766KB;**五维度分析评分 6.7/10**(架构7.25/安全6.0/UX7.4/工程6.2) | +| 微信小程序 | Taro 4.2 + React 18,185 个 TS/TSX 文件 / 62 页面(15 主包 + 47 分包) / 4 TabBar + 医生端独立分包,34 组件(ui 21 + patterns 4 + 独立 9) / 49 service 文件 / 5 Zustand store / 12 hooks,统一组件库 + CSS 变量主题(103 SCSS 全量接入 `var(--tk-*)`,字号 token 对齐原型统计,医生端 `.doctor-mode` 靛蓝覆盖,登录页账号密码+微信一键登录);**Phase 2+3 完成**:Token 构建时生成 + Canvas 适老 + PII 清理 + 缓存加密 + any 清零 + 大文件拆分(3→6) + 触觉反馈 + 导航状态保持 + 独立分包 + CI 集成 + HMAC 请求签名;**并发安全**:长轮询独立通道 `requestUnlimited` + ConcurrencyLimiter(12) + safeNavigateTo 全局页栈保护 + reLaunch 去重;**Veepoo M2 BLE 管线**:独立管线(VeepooBridge 24 API(含精准睡眠/自动测量/开关设置/体温自动数据)+ VeepooPipeline 事件路由(type=1/4/5/6/18/31/51/54/58)+ VeepooHistoryReader 日常+睡眠上传 + VeepooStore 状态管理(含 sleepData/sleepLoading))+ **原生分包页面**(`pkg-veepoo` 原生 JS+WXML,脱离 Taro 直接调用 SDK,绕过框架兼容性限制)+ 实时测量页面(心率/血氧/血压/体温/压力 5 指标,圆环仪表盘 + 长者模式适配)+ 3 天历史数据同步(VeepooHistoryReader 分批上传 + 断点续传)+ **精准睡眠数据自动读取**(认证后自动读取 3 天睡眠:深睡/浅睡/总时长/质量评分,通过 Storage 回传 Taro 页面)+ **自动测量功能**(认证后自动开启心率/血压/体温自动监测);**UI 重构**:测量页药丸式选择器 + SVG 圆环仪表盘 + 健康评估标签;数据上传页 2 列结果卡片网格 + 彩色条标识 + 睡眠数据卡片(★ 评分 + 总时长);**preloadRule 已移除 pkg-health** 防止 380KB SDK 预加载导致首页 DevTools 卡死;**构建优化**:`lazyCodeLoading: requiredComponents` 仅生产构建启用(dev 下已知 DevTools 卡死 bug),`addChunkPages` 仅 TabBar 页注入 common chunk,主包 dev 892KB / prod 766KB;**五维度分析评分 6.7/10**(架构7.25/安全6.0/UX7.4/工程6.2) | | 前端测试 | Web 62 单元测试文件(~693 断言) + 17 E2E spec(13 Web + 4 MP,~64 断言);小程序 12 单元测试文件(127 断言) + 4 E2E spec(~16 断言),覆盖率 ~6% | | 后端测试 | **1030 个函数**(839 同步 + 191 异步),96 个文件含测试 | | 事件系统 | 31 事件类型(health)/ 51 全系统 / 82 发布点 / 15 消费者模块 / Outbox + LISTEN/NOTIFY | @@ -153,6 +153,7 @@ | veepoo-measure 页面空白(useRef is not defined) | [[miniprogram]] 原生页面桥接 | TSX 文件使用 `useRef` 但仅 `import React from 'react'` 未解构导入 | **已修复:** 改为 `import React, { useRef } from 'react'`(2026-05-30) | | M2 设备扫描不到(名称匹配过严) | [[miniprogram]] 原生页面扫描 | 过滤条件 `name.indexOf('M2')` 过严,设备可能广播为 VPM/VEEPOO | **已修复:** 放宽匹配 M2/VPM/VEEPOO 三种前缀(2026-05-30) | | M2 设备认证超时(3 层根因) | [[miniprogram]] 原生页面认证 | **根因链**:①连接回调 `errno:0` 在第 1 次回调就匹配,认证在特征值订阅前发送 → 修复为只匹配 `connection:true`;②`veepooWeiXinSDKNotifyMonitorValueChange` 在 `onLoad` 注册时内部调用 `wx.notifyBLECharacteristicValueChange`,适配器未初始化 → `not init` 错误,改到 `connection:true` 后注册;③认证结果字段检查错误:代码检查 `VPDevicepassword`(值="0000")而非 `VPDeviceAck`(值="successfulVerification") | **已修复:** 三层修复 — connection:true 唯一匹配 + 监听器时序 + VPDeviceAck 字段(2026-05-30) | +| Veepoo 上传按钮无响应(无日志无报错) | [[miniprogram]] veepoo-measure | `handleUpload` 中 `if (!patient) return;` 静默退出,`currentPatient` 从 auth store 恢复可能为 null(原生页返回后) | **已修复:** patientId 增加 URL 参数 fallback + 每个 early return 添加 console.warn + Taro.showToast 用户提示 + 上传按钮 disabled/loading 状态(2026-05-31) | ## 模块导航