Files
hms/docs/design/mp-device-sync-redesign.html
iven 1e59007bd5 fix(mp): DevTools 卡死 + 主包 2MB→766KB + 代码质量 4 项全通过
根因:主包 2MB 全量组件注入导致 DevTools 渲染引擎内存渐增,
叠加离线时固定 3s 抑制期后的请求洪泛。

修复:
- app.config.ts 添加 lazyCodeLoading: requiredComponents
  主包 2.0MB→766KB,taro.js 526→131KB,vendors.js 230→28KB
- request.ts 离线抑制改为指数退避(3s→6s→12s→30s cap)
  后端不可达时自动延长抑制,防止请求风暴
- SegmentTabs Tab 接口改为 readonly,修复 TS 编译错误
- AbortController polyfill 补齐小程序运行时缺失
- 健康首页/设备同步/健康档案/报告/设置页 UI 重构
- 文章页公开端点适配游客访问
- 健康首页 Swiper 间隔优化 4s→5s,动画 500→300ms
2026-05-24 11:32:40 +08:00

754 lines
32 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HMS 小程序 — 设备同步(重新设计)</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #1a1a1a; font-family: -apple-system, 'PingFang SC', sans-serif; display: flex; flex-direction: column; align-items: center; padding: 40px 20px; gap: 24px; }
.page-title { color: #999; font-size: 13px; letter-spacing: 0.15em; }
.note { color: #666; font-size: 12px; max-width: 1200px; text-align: center; line-height: 1.8; }
.screens { display: flex; gap: 32px; flex-wrap: wrap; justify-content: center; align-items: flex-start; }
.screen-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.screen-label { color: #888; font-size: 12px; font-style: italic; }
/* 蓝牙脉冲动画 */
@keyframes pulse-ring {
0% { transform: scale(0.8); opacity: 0.6; }
50% { transform: scale(1.3); opacity: 0; }
100% { transform: scale(0.8); opacity: 0; }
}
@keyframes pulse-dot {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.1); }
}
@keyframes connect-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes fade-in-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body>
<div class="page-title">HMS 小程序 · 设备同步(重新设计)</div>
<div class="note">7 个状态屏幕:空闲 → 扫描中 → 设备列表 → 连接中 → 已连接(实时数据)→ 同步完成 → 错误状态</div>
<div id="root"></div>
<script type="text/babel">
// ─── iOS 设备框 ───
const iosFrameStyles = {
wrapper: { display: 'inline-block', padding: 12, background: '#000', borderRadius: 60, boxShadow: '0 0 0 2px #1f2937, 0 20px 60px rgba(0,0,0,0.3)', position: 'relative' },
screen: { position: 'relative', borderRadius: 48, overflow: 'hidden', background: '#fff' },
statusBar: { position: 'absolute', top: 0, left: 0, right: 0, height: 54, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 32px', fontSize: 16, fontWeight: 600, fontFamily: '-apple-system, "SF Pro Text", sans-serif', zIndex: 20, pointerEvents: 'none' },
dynamicIsland: { position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)', width: 124, height: 36, background: '#000', borderRadius: 999, zIndex: 30 },
content: { position: 'absolute', top: 54, left: 0, right: 0, bottom: 34, overflow: 'auto' },
homeIndicator: { position: 'absolute', bottom: 10, left: '50%', transform: 'translateX(-50%)', width: 140, height: 5, background: 'rgba(0,0,0,0.3)', borderRadius: 999, zIndex: 10 },
};
function IosFrame({ children, width = 360, height = 780, time = '9:41', battery = 85, darkStatus = false }) {
const c = darkStatus ? '#fff' : '#000';
return (
<div style={iosFrameStyles.wrapper}>
<div style={{ ...iosFrameStyles.screen, width, height }}>
<div style={{ ...iosFrameStyles.statusBar, color: c }}>
<span>{time}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill={c}/><path d="M3 7.5a7 7 0 0110 0" stroke={c} strokeWidth="1.3" fill="none" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke={c} strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7"/></svg>
<div style={{ width: 26, height: 12, border: `1.5px solid ${c}`, borderRadius: 3, padding: 1, position: 'relative' }}>
<div style={{ width: `${battery}%`, height: '100%', background: c, borderRadius: 1 }} />
</div>
</div>
</div>
<div style={iosFrameStyles.dynamicIsland} />
<div style={iosFrameStyles.content}>{children}</div>
<div style={iosFrameStyles.homeIndicator} />
</div>
</div>
);
}
// ─── 设计 Token ───
const T = {
pri: '#C4623A', priL: '#F0DDD4', priD: '#8B3E1F',
bg: '#F5F0EB', card: '#FFFFFF', surface: '#EDE8E2',
tx: '#2D2A26', tx2: '#5A554F', tx3: '#78716C',
bd: '#E8E2DC', bdL: '#F0EBE5',
acc: '#5B7A5E', accL: '#E8F0E8',
wrn: '#C4873A', wrnL: '#FFF3E0',
dan: '#B54A4A', danL: '#FDEAEA',
serif: "Georgia, 'Times New Roman', serif",
sans: "-apple-system, 'PingFang SC', sans-serif",
r: 16, rSm: 12, rXs: 8,
};
// ─── SVG 图标 ───
function BluetoothIcon({ size = 24, color = T.pri }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M6 7l8 8-4 4V3l4 4-8 8" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}
function HeartIcon({ size = 20, color = T.dan }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z" fill={color} opacity="0.15"/>
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z" stroke={color} strokeWidth="1.5" fill="none"/>
</svg>
);
}
function CheckIcon({ size = 32, color = T.acc }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill={color} opacity="0.12"/>
<path d="M8 12.5l2.5 2.5 5.5-5.5" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
);
}
function ErrorIcon({ size = 32, color = T.dan }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="11" fill={color} opacity="0.12"/>
<path d="M15 9l-6 6M9 9l6 6" stroke={color} strokeWidth="2" strokeLinecap="round"/>
</svg>
);
}
// ─── 信号强度条 ───
function SignalBars({ level = 3 }) {
const bars = [4, 7, 10, 13];
return (
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: 16 }}>
{bars.map((h, i) => (
<div key={i} style={{
width: 3, height: h, borderRadius: 1,
background: i < level ? T.acc : T.bd,
}} />
))}
</div>
);
}
// ─── 导航栏 ───
function NavBar({ title, dark = false }) {
return (
<div style={{
height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center',
background: dark ? T.pri : T.bg, position: 'relative',
}}>
<svg style={{ position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)' }}
width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15 19l-7-7 7-7" stroke={dark ? '#fff' : T.tx} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: dark ? '#fff' : T.tx }}>{title}</span>
</div>
);
}
// ─── 设备类型标签 ───
function DeviceTypeTag({ icon, label }) {
return (
<div style={{
display: 'flex', alignItems: 'center', gap: 6,
background: T.card, border: `1px solid ${T.bdL}`,
borderRadius: T.rXs, padding: '8px 12px',
}}>
{icon}
<span style={{ fontSize: 13, color: T.tx2 }}>{label}</span>
</div>
);
}
// ─── 屏幕一:空闲态 ───
function IdleScreen() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
{/* Hero 区域 */}
<div style={{
background: `linear-gradient(135deg, ${T.pri} 0%, ${T.priD} 100%)`,
padding: '32px 20px 28px',
display: 'flex', flexDirection: 'column', alignItems: 'center',
}}>
{/* 蓝牙设备插图 */}
<div style={{
width: 72, height: 72, borderRadius: '50%',
background: 'rgba(255,255,255,0.15)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 16,
}}>
<BluetoothIcon size={36} color="#fff" />
</div>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: '#fff', marginBottom: 6 }}>
智能设备同步
</div>
<div style={{ fontSize: 14, color: 'rgba(255,255,255,0.75)', textAlign: 'center', lineHeight: 1.5 }}>
连接蓝牙设备自动采集健康数据
</div>
</div>
<div style={{ flex: 1, padding: '16px 16px 100px', display: 'flex', flexDirection: 'column', gap: 16 }}>
{/* 支持的设备类型 */}
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx, marginBottom: 10, paddingLeft: 2 }}>
支持的设备
</div>
<div style={{ display: 'flex', gap: 8 }}>
<DeviceTypeTag icon={<HeartIcon size={16} color={T.dan} />} label="心率手环" />
<DeviceTypeTag icon={
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M12 21V3M8 6l4-3 4 3M8 18l4 3 4-3" stroke={T.pri} strokeWidth="1.5" strokeLinecap="round"/>
<circle cx="12" cy="12" r="3" stroke={T.pri} strokeWidth="1.5"/>
</svg>
} label="血压计" />
<DeviceTypeTag icon={
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M12 2v6M12 22v-4M4.93 4.93l4.24 4.24M14.83 14.83l4.24 4.24M2 12h6M16 12h6" stroke={T.wrn} strokeWidth="1.5" strokeLinecap="round"/>
<circle cx="12" cy="12" r="4" stroke={T.wrn} strokeWidth="1.5"/>
</svg>
} label="血糖仪" />
</div>
</div>
{/* 上次同步信息 */}
<div style={{
background: T.card, borderRadius: T.rSm,
padding: '14px 16px', display: 'flex', justifyContent: 'space-between', alignItems: 'center',
border: `1px solid ${T.bdL}`,
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 36, height: 36, borderRadius: '50%', background: T.accL, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<CheckIcon size={20} color={T.acc} />
</div>
<div>
<div style={{ fontSize: 14, fontWeight: 500, color: T.tx }}>上次同步</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 2 }}>今天 08:30</div>
</div>
</div>
<div style={{
background: T.accL, borderRadius: T.rXs,
padding: '4px 10px', fontSize: 12, color: T.acc, fontWeight: 500,
}}>
12 条数据
</div>
</div>
{/* 待上传提示 */}
<div style={{
background: T.wrnL, borderRadius: T.rSm,
padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 10,
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M12 9v4M12 17h.01M12 2L2 22h20L12 2z" stroke={T.wrn} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ fontSize: 13, color: T.wrn, fontWeight: 500 }}>3 条数据待上传</span>
</div>
{/* 扫描按钮 */}
<div style={{
background: T.pri, borderRadius: T.rSm,
padding: '16px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
boxShadow: `0 4px 16px rgba(196, 98, 58, 0.3)`,
marginTop: 'auto',
}}>
<BluetoothIcon size={20} color="#fff" />
<span style={{ color: '#fff', fontSize: 17, fontWeight: 600 }}>扫描附近设备</span>
</div>
</div>
</div>
);
}
// ─── 屏幕二:扫描中 ───
function ScanningScreen() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
{/* 脉冲圆环 */}
<div style={{ position: 'relative', width: 140, height: 140, marginBottom: 32 }}>
<div style={{
position: 'absolute', top: 0, left: 0, width: 140, height: 140,
borderRadius: '50%', border: `2px solid ${T.priL}`,
animation: 'pulse-ring 2s ease-out infinite',
}} />
<div style={{
position: 'absolute', top: 15, left: 15, width: 110, height: 110,
borderRadius: '50%', border: `2px solid ${T.priL}`,
animation: 'pulse-ring 2s ease-out infinite 0.5s',
}} />
<div style={{
position: 'absolute', top: 30, left: 30, width: 80, height: 80,
borderRadius: '50%', background: T.priL,
display: 'flex', alignItems: 'center', justifyContent: 'center',
animation: 'pulse-dot 2s ease-in-out infinite',
}}>
<BluetoothIcon size={36} color={T.pri} />
</div>
</div>
<div style={{ fontFamily: T.serif, fontSize: 20, fontWeight: 700, color: T.tx, marginBottom: 8, textAlign: 'center' }}>
正在搜索设备...
</div>
<div style={{ fontSize: 14, color: T.tx3, textAlign: 'center', lineHeight: 1.6 }}>
请确保设备已开启蓝牙并靠近手机
</div>
{/* 进度条 */}
<div style={{
width: 180, height: 3, borderRadius: 2, background: T.bdL,
marginTop: 24, overflow: 'hidden',
}}>
<div style={{
width: '60%', height: '100%', borderRadius: 2,
background: `linear-gradient(90deg, ${T.priL}, ${T.pri})`,
}} />
</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 8 }}>已用时 6 </div>
</div>
</div>
);
}
// ─── 屏幕三:设备列表 ───
function DeviceListScreen() {
const devices = [
{ name: 'Mi Band 8', type: '小米手环适配器', signal: 4, color: T.pri },
{ name: 'AND UA-651', type: '血压计适配器', signal: 3, color: T.pri },
{ name: 'Accu-Chek', type: '血糖仪适配器', signal: 2, color: T.wrn },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, padding: '16px 16px 100px' }}>
{/* 结果标题 */}
<div style={{
display: 'flex', justifyContent: 'space-between', alignItems: 'center',
marginBottom: 16,
}}>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx }}>发现 {devices.length} 台设备</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 2 }}>点击设备名称开始连接</div>
</div>
<div style={{
fontSize: 13, color: T.pri, fontWeight: 500, cursor: 'pointer',
display: 'flex', alignItems: 'center', gap: 4,
}}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none">
<path d="M23 4v6h-6M1 20v-6h6" stroke={T.pri} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" stroke={T.pri} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
重新扫描
</div>
</div>
{/* 设备卡片 */}
{devices.map((d, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.rSm,
padding: '16px', marginBottom: 10,
border: `1px solid ${T.bdL}`,
display: 'flex', alignItems: 'center', gap: 14,
}}>
{/* 设备图标 */}
<div style={{
width: 44, height: 44, borderRadius: T.rSm,
background: T.priL, display: 'flex', alignItems: 'center', justifyContent: 'center',
flexShrink: 0,
}}>
<BluetoothIcon size={22} color={T.pri} />
</div>
{/* 设备信息 */}
<div style={{ flex: 1 }}>
<div style={{ fontSize: 16, fontWeight: 600, color: T.tx }}>{d.name}</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 3 }}>{d.type}</div>
</div>
{/* 信号 + 箭头 */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<SignalBars level={d.signal} />
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<path d="M9 18l6-6-6-6" stroke={T.tx3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</div>
))}
{/* 未发现设备提示 */}
<div style={{
marginTop: 16, background: T.card, borderRadius: T.rSm,
padding: '14px 16px', border: `1px dashed ${T.bd}`,
display: 'flex', alignItems: 'center', gap: 10,
}}>
<div style={{
width: 32, height: 32, borderRadius: '50%', background: T.surface,
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<circle cx="11" cy="11" r="8" stroke={T.tx3} strokeWidth="1.5"/>
<path d="M21 21l-4.35-4.35" stroke={T.tx3} strokeWidth="1.5" strokeLinecap="round"/>
</svg>
</div>
<div>
<div style={{ fontSize: 13, color: T.tx2 }}>没有找到你的设备</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 2 }}>确保设备已开机且蓝牙已开启</div>
</div>
</div>
</div>
</div>
);
}
// ─── 屏幕四:连接中 ───
function ConnectingScreen() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 40 }}>
{/* 连接动画 */}
<div style={{ position: 'relative', width: 100, height: 100, marginBottom: 28 }}>
{/* 旋转环 */}
<div style={{
position: 'absolute', top: 0, left: 0, width: 100, height: 100,
borderRadius: '50%', border: `3px solid ${T.bdL}`,
borderTopColor: T.pri,
animation: 'connect-spin 1s linear infinite',
}} />
{/* 中心图标 */}
<div style={{
position: 'absolute', top: 20, left: 20, width: 60, height: 60,
borderRadius: '50%', background: T.priL,
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<BluetoothIcon size={28} color={T.pri} />
</div>
</div>
<div style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx, marginBottom: 6, textAlign: 'center' }}>
正在连接 Mi Band 8
</div>
<div style={{ fontSize: 14, color: T.tx3, textAlign: 'center' }}>
正在进行蓝牙配对...
</div>
{/* 步骤指示 */}
<div style={{
display: 'flex', alignItems: 'center', gap: 8, marginTop: 24,
}}>
<div style={{ width: 8, height: 8, borderRadius: '50%', background: T.acc }} />
<span style={{ fontSize: 12, color: T.tx3 }}>发现设备</span>
<div style={{ width: 24, height: 1, background: T.pri }} />
<div style={{ width: 8, height: 8, borderRadius: '50%', background: T.pri, animation: 'pulse-dot 1s ease-in-out infinite' }} />
<span style={{ fontSize: 12, color: T.pri, fontWeight: 500 }}>连接中</span>
<div style={{ width: 24, height: 1, background: T.bd }} />
<div style={{ width: 8, height: 8, borderRadius: '50%', background: T.bd }} />
<span style={{ fontSize: 12, color: T.tx3 }}>同步数据</span>
</div>
</div>
</div>
);
}
// ─── 屏幕五:已连接 + 实时数据 ───
function ConnectedScreen() {
const readings = [
{ type: '心率', value: '72', unit: 'bpm', color: T.dan, time: '刚刚' },
{ type: '收缩压', value: '128', unit: 'mmHg', color: T.pri, time: '2分钟前' },
{ type: '舒张压', value: '82', unit: 'mmHg', color: T.pri, time: '2分钟前' },
{ type: '心率', value: '68', unit: 'bpm', color: T.dan, time: '5分钟前' },
{ type: '心率', value: '74', unit: 'bpm', color: T.dan, time: '8分钟前' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, padding: '16px 16px 100px', overflow: 'auto' }}>
{/* 连接状态卡片 */}
<div style={{
background: `linear-gradient(135deg, ${T.acc} 0%, #4A6B4E 100%)`,
borderRadius: T.r, padding: '16px',
display: 'flex', alignItems: 'center', gap: 12,
marginBottom: 16,
}}>
<div style={{
width: 40, height: 40, borderRadius: '50%',
background: 'rgba(255,255,255,0.2)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<BluetoothIcon size={22} color="#fff" />
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 16, fontWeight: 600, color: '#fff' }}>Mi Band 8</div>
<div style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', marginTop: 2 }}>已连接 · 正在采集数据</div>
</div>
<div style={{
background: 'rgba(255,255,255,0.2)', borderRadius: T.rXs,
padding: '4px 10px', fontSize: 12, color: '#fff',
}}>
实时
</div>
</div>
{/* 最新读数高亮 */}
<div style={{
background: T.card, borderRadius: T.r, padding: '20px',
display: 'flex', alignItems: 'center', gap: 16,
marginBottom: 16, boxShadow: '0 2px 12px rgba(45,42,38,0.08)',
}}>
<div style={{
width: 52, height: 52, borderRadius: T.rSm,
background: `${T.dan}10`, display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<HeartIcon size={28} color={T.dan} />
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, color: T.tx3 }}>心率 · 刚刚</div>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 4 }}>
<span style={{ fontFamily: T.serif, fontSize: 36, fontWeight: 700, color: T.tx }}>72</span>
<span style={{ fontSize: 14, color: T.tx3 }}>bpm</span>
</div>
</div>
</div>
{/* 历史读数列表 */}
<div style={{
fontSize: 14, fontWeight: 600, color: T.tx, marginBottom: 10, paddingLeft: 2,
}}>
历史读数
</div>
<div style={{
background: T.card, borderRadius: T.rSm, overflow: 'hidden',
boxShadow: '0 1px 4px rgba(45,42,38,0.06)',
}}>
{readings.slice(1).map((r, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'center', padding: '12px 16px',
borderBottom: i < readings.length - 2 ? `1px solid ${T.bdL}` : 'none',
}}>
<div style={{ width: 90, fontSize: 14, color: T.tx2 }}>{r.type}</div>
<div style={{ flex: 1, display: 'flex', alignItems: 'baseline', gap: 3 }}>
<span style={{ fontFamily: T.serif, fontSize: 20, fontWeight: 700, color: T.tx }}>{r.value}</span>
<span style={{ fontSize: 12, color: T.tx3 }}>{r.unit}</span>
</div>
<div style={{ fontSize: 12, color: T.tx3 }}>{r.time}</div>
</div>
))}
</div>
<div style={{
textAlign: 'center', marginTop: 12, fontSize: 12, color: T.tx3,
}}>
已采集 {readings.length} 条数据
</div>
{/* 操作按钮 */}
<div style={{ display: 'flex', gap: 10, marginTop: 20 }}>
<div style={{
flex: 1, background: T.pri, borderRadius: T.rSm,
padding: '14px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6,
boxShadow: `0 4px 16px rgba(196, 98, 58, 0.3)`,
}}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ color: '#fff', fontSize: 16, fontWeight: 600 }}>上传数据</span>
</div>
<div style={{
width: 52, background: T.danL, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M18 6L6 18M6 6l12 12" stroke={T.dan} strokeWidth="2" strokeLinecap="round"/>
</svg>
</div>
</div>
</div>
</div>
);
}
// ─── 屏幕六:同步完成 ───
function DoneScreen() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '24px 20px 100px' }}>
{/* 成功图标 */}
<div style={{
width: 80, height: 80, borderRadius: '50%', background: T.accL,
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 24,
}}>
<CheckIcon size={44} color={T.acc} />
</div>
<div style={{ fontFamily: T.serif, fontSize: 24, fontWeight: 700, color: T.tx, marginBottom: 8 }}>
同步完成
</div>
<div style={{ fontSize: 15, color: T.tx3, textAlign: 'center', lineHeight: 1.6 }}>
数据已安全上传至健康管理平台
</div>
{/* 统计卡片 */}
<div style={{
display: 'flex', gap: 12, marginTop: 24, width: '100%',
}}>
<div style={{
flex: 1, background: T.card, borderRadius: T.rSm, padding: '16px',
textAlign: 'center', border: `1px solid ${T.bdL}`,
}}>
<div style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.pri }}>5</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 4 }}>上传条数</div>
</div>
<div style={{
flex: 1, background: T.card, borderRadius: T.rSm, padding: '16px',
textAlign: 'center', border: `1px solid ${T.bdL}`,
}}>
<div style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx }}>3</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 4 }}>数据类型</div>
</div>
<div style={{
flex: 1, background: T.card, borderRadius: T.rSm, padding: '16px',
textAlign: 'center', border: `1px solid ${T.bdL}`,
}}>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.acc }}>100%</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 4 }}>成功率</div>
</div>
</div>
{/* 完成按钮 */}
<div style={{
width: '100%', background: T.pri, borderRadius: T.rSm,
padding: '16px', display: 'flex', alignItems: 'center', justifyContent: 'center',
boxShadow: `0 4px 16px rgba(196, 98, 58, 0.3)`,
marginTop: 32,
}}>
<span style={{ color: '#fff', fontSize: 17, fontWeight: 600 }}>完成</span>
</div>
</div>
</div>
);
}
// ─── 屏幕七:错误状态 ───
function ErrorScreen() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" dark />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '24px 20px 100px' }}>
{/* 错误图标 */}
<div style={{
width: 80, height: 80, borderRadius: '50%', background: T.danL,
display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 24,
}}>
<ErrorIcon size={44} color={T.dan} />
</div>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.tx, marginBottom: 8 }}>
连接失败
</div>
<div style={{ fontSize: 14, color: T.tx3, textAlign: 'center', lineHeight: 1.6, maxWidth: 260 }}>
无法连接到 Mi Band 8请检查设备是否在范围内并重试
</div>
{/* 错误详情卡片 */}
<div style={{
width: '100%', background: T.card, borderRadius: T.rSm,
padding: '16px', marginTop: 24, border: `1px solid ${T.bdL}`,
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke={T.tx3} strokeWidth="1.5"/>
<path d="M12 16v.01M12 8v4" stroke={T.tx3} strokeWidth="1.5" strokeLinecap="round"/>
</svg>
<span style={{ fontSize: 13, fontWeight: 500, color: T.tx }}>错误详情</span>
</div>
<div style={{ fontSize: 13, color: T.tx3, lineHeight: 1.7 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
<span>错误码</span><span style={{ color: T.tx }}>BLE_TIMEOUT</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 4 }}>
<span>设备</span><span style={{ color: T.tx }}>Mi Band 8</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>时间</span><span style={{ color: T.tx }}>09:15:32</span>
</div>
</div>
</div>
{/* 重试按钮 */}
<div style={{
width: '100%', background: T.pri, borderRadius: T.rSm,
padding: '16px', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
boxShadow: `0 4px 16px rgba(196, 98, 58, 0.3)`,
marginTop: 24,
}}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M23 4v6h-6M1 20v-6h6" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ color: '#fff', fontSize: 17, fontWeight: 600 }}>重新扫描</span>
</div>
{/* 返回按钮 */}
<div style={{
width: '100%', borderRadius: T.rSm,
padding: '14px', display: 'flex', alignItems: 'center', justifyContent: 'center',
marginTop: 10, border: `1px solid ${T.bd}`,
}}>
<span style={{ color: T.tx2, fontSize: 16, fontWeight: 500 }}>返回</span>
</div>
</div>
</div>
);
}
// ─── 主渲染 ───
const screens = [
{ label: '空闲态', Component: IdleScreen },
{ label: '扫描中', Component: ScanningScreen },
{ label: '设备列表', Component: DeviceListScreen },
{ label: '连接中', Component: ConnectingScreen },
{ label: '已连接', Component: ConnectedScreen },
{ label: '同步完成', Component: DoneScreen },
{ label: '错误状态', Component: ErrorScreen },
];
function App() {
return (
<div className="screens">
{screens.map(({ label, Component }) => (
<div className="screen-wrap" key={label}>
<div className="screen-label">{label}</div>
<IosFrame width={360} height={780}>
<Component />
</IosFrame>
</div>
))}
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>