feat(mp): Veepoo M2 BLE 管线扩展 — 精准睡眠数据 + 自动测量 + UI 重构

- 新增 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 状态预览)
This commit is contained in:
iven
2026-05-31 21:48:06 +08:00
parent 6d073840aa
commit 92ffd8cecb
14 changed files with 3419 additions and 603 deletions

View File

@@ -1,5 +1,6 @@
<!--
Veepoo M2 原生小程序页面 — 连接 + 测量
设计原型: docs/design/veepoo-measure-prototype.html
完全匹配 SDK 官方 Demo 流程,不依赖 Taro
-->
@@ -26,7 +27,7 @@
</view>
<view wx:if="{{hasResults}}" class="connect-back">
<view class="btn-secondary" bindtap="handleBack">查看测量结果并返回</view>
<view class="btn-text" bindtap="handleBack">查看测量结果并返回</view>
</view>
</view>
</block>
@@ -51,82 +52,92 @@
<block wx:elif="{{phase === 'ready'}}">
<view class="measure-page">
<!-- 设备状态栏 -->
<view class="header">
<view class="header-device">
<view class="header-dot header-dot--on"></view>
<text class="header-name">{{deviceName}}</text>
<text wx:if="{{batteryLevel !== null}}" class="header-battery">{{batteryLevel}}%</text>
<view class="device-bar">
<view class="device-bar__left">
<view class="device-bar__dot"></view>
<text class="device-bar__name">{{deviceName}}</text>
<text wx:if="{{batteryLevel !== null}}" class="device-bar__battery">{{batteryLevel}}%</text>
</view>
<text class="header-disconnect" bindtap="handleDisconnect">断开</text>
<view class="device-bar__disconnect" bindtap="handleDisconnect">断开</view>
</view>
<!-- 指标选择器 — H1 修复:用 results[type] 替代 measureStates -->
<view class="selector">
<!-- 指标选择器 — 药丸式 -->
<scroll-view class="selector" scroll-x enhanced show-scrollbar="{{false}}">
<view
wx:for="{{measureTypes}}"
wx:key="type"
class="selector-item {{selectedType === item.type ? 'selector-item--active' : ''}} {{results[item.type] ? 'selector-item--done' : ''}}"
class="selector__pill {{selectedType === item.type ? 'selector__pill--active' : ''}} {{results[item.type] ? 'selector__pill--done' : ''}}"
data-type="{{item.type}}"
bindtap="handleSelectType"
>
<text class="selector-icon" style="color: {{item.color}}">{{item.icon}}</text>
<text class="selector-label">{{item.label}}</text>
<view wx:if="{{results[item.type]}}" class="selector-check" style="background-color: {{item.color}}">✓</view>
<view class="selector__icon-wrap" style="background: {{item.color}}">
<text class="selector__icon">{{item.icon}}</text>
</view>
<text class="selector__label">{{item.label}}</text>
</view>
</view>
</scroll-view>
<!-- 仪表盘区域 — C4 修复:用 data 中预计算的 selectedIcon/selectedColor -->
<view class="gauge">
<view class="gauge-circle">
<!-- 空闲 -->
<block wx:if="{{measurePhase === 'idle'}}">
<text class="gauge-icon" style="color: {{selectedColor}}">{{selectedIcon}}</text>
<text class="gauge-hint">点击下方按钮开始测量{{selectedLabel}}</text>
</block>
<!-- 测量中 -->
<block wx:elif="{{measurePhase === 'measuring'}}">
<text wx:if="{{measureDisplayValue}}" class="gauge-value" style="color: {{selectedColor}}">{{measureDisplayValue}}</text>
<text wx:else class="gauge-loading">测量中...</text>
<text wx:if="{{measureDisplayValue}}" class="gauge-unit">{{selectedUnit}}</text>
</block>
<!-- 成功 -->
<block wx:elif="{{measurePhase === 'success'}}">
<text class="gauge-value" style="color: #22C55E">{{measureDisplayValue}}</text>
<text class="gauge-unit">{{selectedUnit}}</text>
</block>
<!-- 错误 -->
<block wx:elif="{{measurePhase === 'error'}}">
<text class="gauge-err">!</text>
<text class="gauge-err-text">{{measureError}}</text>
</block>
<!-- 仪表盘区域 -->
<view class="gauge-section">
<view class="gauge {{measurePhase === 'measuring' ? 'gauge--measuring' : ''}}">
<!-- SVG 圆环 -->
<view class="gauge__ring-wrap">
<view class="gauge__ring-bg"></view>
<view class="gauge__ring-progress" style="background: conic-gradient({{selectedColor}} {{measureProgress * 3.6}}deg, #E8E2DC 0deg);"></view>
<view class="gauge__center">
<!-- 空闲 -->
<block wx:if="{{measurePhase === 'idle'}}">
<text class="gauge__icon-lg" style="color: {{selectedColor}}">{{selectedIcon}}</text>
<text class="gauge__hint">点击下方按钮开始</text>
</block>
<!-- 测量中 -->
<block wx:elif="{{measurePhase === 'measuring'}}">
<text wx:if="{{measureDisplayValue}}" class="gauge__value" style="color: {{selectedColor}}">{{measureDisplayValue}}</text>
<text wx:else class="gauge__loading">测量中...</text>
<text wx:if="{{measureDisplayValue}}" class="gauge__unit">{{selectedUnit}}</text>
</block>
<!-- 成功 -->
<block wx:elif="{{measurePhase === 'success'}}">
<text class="gauge__value" style="color: {{selectedColor}}">{{measureDisplayValue}}</text>
<text class="gauge__unit">{{selectedUnit}}</text>
</block>
<!-- 错误 -->
<block wx:elif="{{measurePhase === 'error'}}">
<text class="gauge__err">!</text>
<text class="gauge__err-text">{{measureError}}</text>
</block>
</view>
</view>
</view>
<!-- 进度条 -->
<view wx:if="{{measurePhase === 'measuring' && measureProgress > 0}}" class="gauge-progress-bar">
<view class="gauge-progress-fill" style="width: {{measureProgress}}%"></view>
<view wx:if="{{measurePhase === 'measuring' && measureProgress > 0}}" class="progress-bar">
<view class="progress-bar__fill" style="width: {{measureProgress}}%; background: {{selectedColor}};"></view>
</view>
</view>
<!-- 免责声明 -->
<view class="disclaimer">
<text class="disclaimer-text">本数据由智能手环采集,仅供健康趋势参考,不作为医诊断依据</text>
<text class="disclaimer__text">测量数据仅供参考,不作为医诊断依据。如有不适请及时就医。</text>
</view>
<!-- 操作按钮 -->
<view class="actions">
<block wx:if="{{measurePhase === 'idle'}}">
<view class="btn-primary btn-large" bindtap="handleStartMeasure">开始测量{{selectedLabel}}</view>
</block>
<block wx:elif="{{measurePhase === 'measuring'}}">
<view class="btn-secondary btn-large" bindtap="handleCancelMeasure">停止测量</view>
</block>
<block wx:elif="{{measurePhase === 'success'}}">
<view class="actions-row">
<view class="btn-secondary" bindtap="handleResetResult">重新测量</view>
<view class="btn-primary" bindtap="handleBack">完成并返回</view>
<view class="btn btn--primary" style="background: {{selectedColor}}; box-shadow: 0 4px 16px rgba(0,0,0,0.15);" bindtap="handleStartMeasure">
开始测量{{selectedLabel}}
</view>
</block>
<block wx:elif="{{measurePhase === 'measuring'}}">
<view class="btn btn--secondary" bindtap="handleCancelMeasure">停止测量</view>
<view class="btn btn--text" bindtap="handleBack">完成并查看结果</view>
</block>
<block wx:elif="{{measurePhase === 'success'}}">
<view class="btn btn--primary" style="background: {{selectedColor}}; box-shadow: 0 4px 16px rgba(0,0,0,0.15);" bindtap="handleResetResult">重新测量</view>
<view class="btn btn--secondary" bindtap="handleBack">完成并查看结果</view>
</block>
<block wx:elif="{{measurePhase === 'error'}}">
<view class="btn-primary btn-large" bindtap="handleStartMeasure">重新测量</view>
<view class="btn btn--primary" style="background: {{selectedColor}}; box-shadow: 0 4px 16px rgba(0,0,0,0.15);" bindtap="handleStartMeasure">重新测量</view>
</block>
</view>
</view>