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

@@ -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);
}
},
});

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>

View File

@@ -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; }