docs(mp): 新增小程序全页面 HTML 原型 + UI 优化指南

- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等)
- 新增医生端分包原型(核心 + 临床两个分包)
- 新增 AI 客服对话页原型
- 新增 MP UI 优化指南文档
- 更新 wiki 基础设施和小程序文档
This commit is contained in:
iven
2026-05-17 00:51:07 +08:00
parent 710b2e2423
commit aa27c5174c
93 changed files with 15506 additions and 70 deletions

View File

@@ -0,0 +1,664 @@
<!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; }
</style>
</head>
<body>
<div class="page-title">HMS 小程序 · 健康分包</div>
<div class="note">告警列表 + 日常监测 + 设备同步 + 体征录入 + 趋势分析 — 五屏并排展示健康分包核心功能</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 statusColor = darkStatus ? '#fff' : '#000';
return (
<div style={iosFrameStyles.wrapper}>
<div style={{ ...iosFrameStyles.screen, width, height }}>
<div style={{ ...iosFrameStyles.statusBar, color: statusColor }}>
<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={statusColor}/><path d="M3 7.5a7 7 0 0110 0" stroke={statusColor} strokeWidth="1.3" fill="none" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke={statusColor} strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7"/></svg>
<div style={{ width: 26, height: 12, border: `1.5px solid ${statusColor}`, borderRadius: 3, padding: 1, position: 'relative' }}>
<div style={{ width: `${battery}%`, height: '100%', background: statusColor, 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,
};
// ─── 通用导航栏 ───
function NavBar({ title }) {
return (
<div style={{
height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center',
borderBottom: `1px solid ${T.bdL}`, background: 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={T.tx} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx }}>{title}</span>
</div>
);
}
// ─── 屏幕一:告警列表 ───
function AlertList() {
const alerts = [
{ level: 'HIGH', desc: '收缩压持续偏高(>140mmHg', time: '今天 08:30', bg: T.danL, color: T.dan, label: '高' },
{ level: 'MEDIUM', desc: '血糖餐后值偏高8.2mmol/L', time: '昨天 19:00', bg: T.wrnL, color: T.wrn, label: '中' },
{ level: 'LOW', desc: '体重较上周增加 0.5kg', time: '3天前', bg: T.accL, color: T.acc, label: '低' },
{ level: 'LOW', desc: '心率偏低55bpm', time: '5天前', bg: T.accL, color: T.acc, label: '低' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="健康告警" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 100px' }}>
{/* 统计概览 */}
<div style={{
display: 'flex', gap: 10, marginBottom: 16,
}}>
<div style={{ flex: 1, background: T.danL, borderRadius: T.rSm, padding: '12px 14px', textAlign: 'center' }}>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.dan }}>1</div>
<div style={{ fontSize: 12, color: T.dan, marginTop: 2 }}>高级</div>
</div>
<div style={{ flex: 1, background: T.wrnL, borderRadius: T.rSm, padding: '12px 14px', textAlign: 'center' }}>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.wrn }}>1</div>
<div style={{ fontSize: 12, color: T.wrn, marginTop: 2 }}>中级</div>
</div>
<div style={{ flex: 1, background: T.accL, borderRadius: T.rSm, padding: '12px 14px', textAlign: 'center' }}>
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.acc }}>2</div>
<div style={{ fontSize: 12, color: T.acc, marginTop: 2 }}>低级</div>
</div>
</div>
{/* 告警卡片列表 */}
{alerts.map((a, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.r, padding: '16px 18px',
marginBottom: 10, boxShadow: '0 2px 12px rgba(45,42,38,0.06)',
display: 'flex', alignItems: 'center', gap: 14,
}}>
{/* 严重程度标签 */}
<div style={{
minWidth: 40, height: 40, borderRadius: T.rSm,
background: a.bg, display: 'flex', alignItems: 'center', justifyContent: 'center',
flexShrink: 0,
}}>
<span style={{ fontSize: 14, fontWeight: 700, color: a.color }}>{a.label}</span>
</div>
{/* 内容 */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ fontSize: 15, fontWeight: 500, color: T.tx, lineHeight: 1.4, marginBottom: 4 }}>
{a.desc}
</div>
<div style={{ fontSize: 12, color: T.tx3 }}>{a.time}</div>
</div>
{/* 箭头 */}
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0 }}>
<path d="M9 5l7 7-7 7" stroke={T.tx3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
))}
</div>
</div>
);
}
// ─── 屏幕二:日常监测 ───
function DailyMonitoring() {
const vitals = [
{ label: '血压', value: '130/85', unit: 'mmHg', status: '偏高', statusType: 'wrn' },
{ label: '心率', value: '72', unit: 'bpm', status: '正常', statusType: 'acc' },
{ label: '血糖', value: '5.6', unit: 'mmol/L', status: '正常', statusType: 'acc' },
{ label: '体重', value: '—', unit: 'kg', status: '未记录', statusType: 'empty' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="日常监测" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 100px' }}>
{/* 日期选择横条 */}
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
background: T.card, borderRadius: T.r, padding: '12px 16px',
marginBottom: 16, boxShadow: '0 2px 12px rgba(45,42,38,0.06)',
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M15 19l-7-7 7-7" stroke={T.tx3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: 17, fontWeight: 600, color: T.tx, fontFamily: T.serif }}>5月8日</div>
<div style={{ fontSize: 13, color: T.tx3, marginTop: 2 }}>周四</div>
</div>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M9 5l7 7-7 7" stroke={T.tx3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
{/* 今日概览卡片 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 20,
boxShadow: '0 2px 12px rgba(45,42,38,0.06)', marginBottom: 16,
display: 'flex', alignItems: 'center', gap: 18,
}}>
{/* 进度环 */}
<div style={{ width: 68, height: 68, position: 'relative', flexShrink: 0 }}>
<svg width="68" height="68" viewBox="0 0 68 68">
<circle cx="34" cy="34" r="29" fill="none" stroke={T.bd} strokeWidth="4.5" />
<circle cx="34" cy="34" r="29" fill="none" stroke={T.pri} strokeWidth="4.5"
strokeDasharray={`${0.75 * 2 * Math.PI * 29} ${0.25 * 2 * Math.PI * 29}`}
strokeDashoffset="0" strokeLinecap="round" transform="rotate(-90 34 34)" />
</svg>
<div style={{
position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
fontFamily: T.serif, fontSize: 17, fontWeight: 700, color: T.pri,
}}>3/4</div>
</div>
<div>
<div style={{ fontSize: 16, fontWeight: 600, color: T.tx, marginBottom: 6 }}>今日已记录 3 项体征</div>
<div style={{ display: 'flex', gap: 5, flexWrap: 'wrap' }}>
{['血压 ✓','心率 ✓','血糖 ✓','体重'].map((t, i) => (
<span key={i} style={{
fontSize: 11, padding: '3px 8px', borderRadius: 999,
background: i < 3 ? T.accL : T.surface,
color: i < 3 ? T.acc : T.tx3, fontWeight: 500,
}}>{t}</span>
))}
</div>
</div>
</div>
{/* 体征 2x2 网格 */}
<div style={{ fontFamily: T.serif, fontSize: 17, fontWeight: 700, color: T.tx, marginBottom: 10 }}>今日体征</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
{vitals.map((v, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.r, padding: '14px 16px',
boxShadow: '0 1px 4px rgba(45,42,38,0.04)',
}}>
<div style={{ fontSize: 13, color: T.tx2, marginBottom: 6 }}>{v.label}</div>
<div style={{ display: 'flex', alignItems: 'baseline', marginBottom: 6 }}>
<span style={{
fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx, lineHeight: 1,
opacity: v.statusType === 'empty' ? 0.3 : 1,
}}>{v.value}</span>
<span style={{ fontSize: 12, color: T.tx3, marginLeft: 3 }}>{v.unit}</span>
</div>
<span style={{
fontSize: 11, padding: '2px 8px', borderRadius: 999, fontWeight: 500,
background: v.statusType === 'acc' ? T.accL : v.statusType === 'wrn' ? T.wrnL : T.surface,
color: v.statusType === 'acc' ? T.acc : v.statusType === 'wrn' ? T.wrn : T.tx3,
}}>{v.status}</span>
</div>
))}
</div>
</div>
</div>
);
}
// ─── 屏幕三:设备同步 ───
function DeviceSync() {
const devices = [
{ name: 'BCM 血压计', connected: true, lastSync: '5分钟前' },
{ name: '欧姆龙 血糖仪', connected: false, lastSync: null },
];
// 生成心率波形 SVG path
const wavePoints = [];
for (let x = 0; x <= 300; x += 2) {
let y = 40;
// 模拟心电图 QRS 波形
const cycle = x % 80;
if (cycle >= 30 && cycle < 35) y = 40 - 8;
else if (cycle >= 35 && cycle < 40) y = 40 + 28;
else if (cycle >= 40 && cycle < 45) y = 40 - 18;
else if (cycle >= 45 && cycle < 50) y = 40 + 5;
else y = 40 + Math.sin(x * 0.05) * 2;
wavePoints.push(`${x === 0 ? 'M' : 'L'}${x},${y}`);
}
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="设备同步" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 100px' }}>
{/* 已连接设备卡片 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 18,
boxShadow: '0 2px 12px rgba(45,42,38,0.06)', marginBottom: 16,
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12 }}>
<span style={{ fontSize: 15, fontWeight: 600, color: T.tx }}>当前设备</span>
<span style={{
display: 'inline-block', padding: '3px 10px', borderRadius: 20,
background: T.accL, color: T.acc, fontSize: 12, fontWeight: 600,
}}>已连接</span>
</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{
width: 48, height: 48, borderRadius: T.rSm, background: T.accL,
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<rect x="2" y="6" width="20" height="12" rx="2" stroke={T.acc} strokeWidth="1.5"/>
<path d="M6 10h2M10 10h2M6 14h8" stroke={T.acc} strokeWidth="1.5" strokeLinecap="round"/>
</svg>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 16, fontWeight: 600, color: T.tx }}>BCM 血压计</div>
<div style={{ fontSize: 13, color: T.tx3, marginTop: 2 }}>最后同步5分钟前</div>
</div>
</div>
</div>
{/* 可用设备列表 */}
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx2, marginBottom: 8, paddingLeft: 2 }}>可用设备</div>
<div style={{ background: T.card, borderRadius: T.r, overflow: 'hidden', boxShadow: '0 1px 4px rgba(45,42,38,0.04)', marginBottom: 20 }}>
{devices.map((d, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px',
borderBottom: i < devices.length - 1 ? `1px solid ${T.bdL}` : 'none',
}}>
<div style={{
width: 40, height: 40, borderRadius: T.rSm,
background: d.connected ? T.accL : T.surface,
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<rect x="2" y="6" width="20" height="12" rx="2" stroke={d.connected ? T.acc : T.tx3} strokeWidth="1.5"/>
<path d="M6 10h2M10 10h2M6 14h8" stroke={d.connected ? T.acc : T.tx3} strokeWidth="1.5" strokeLinecap="round"/>
</svg>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 15, fontWeight: 500, color: T.tx }}>{d.name}</div>
</div>
<span style={{
fontSize: 13, color: d.connected ? T.acc : T.tx3, fontWeight: 500,
}}>{d.connected ? '已连接' : '未连接'}</span>
</div>
))}
</div>
{/* 实时数据区 — 心率波形 */}
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx2, marginBottom: 8, paddingLeft: 2 }}>实时数据</div>
<div style={{
background: T.card, borderRadius: T.r, padding: 18,
boxShadow: '0 1px 4px rgba(45,42,38,0.04)', marginBottom: 20,
}}>
<div style={{
background: T.bg, borderRadius: T.rSm, padding: '12px 8px',
height: 90, display: 'flex', alignItems: 'center', justifyContent: 'center',
marginBottom: 12, overflow: 'hidden',
}}>
<svg width="100%" height="80" viewBox="0 0 300 80" preserveAspectRatio="none" style={{ overflow: 'visible' }}>
<path d={wavePoints.join(' ')} fill="none" stroke={T.pri} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div style={{ textAlign: 'center' }}>
<span style={{ fontFamily: T.serif, fontSize: 36, fontWeight: 700, color: T.tx }}>72</span>
<span style={{ fontSize: 14, color: T.tx3, marginLeft: 4 }}>bpm</span>
</div>
</div>
{/* 同步数据按钮 */}
<div style={{
height: 52, borderRadius: 16, background: T.pri,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 17, fontWeight: 600,
boxShadow: `0 4px 16px rgba(196,98,58,0.3)`,
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" style={{ marginRight: 8 }}>
<path d="M21.5 2v6h-6M2.5 22v-6h6M2 11.5a10 10 0 0118.8-4.3M22 12.5a10 10 0 01-18.8 4.3"
stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
同步数据
</div>
</div>
</div>
);
}
// ─── 屏幕四:体征录入 ───
function VitalInput() {
const types = ['血压', '心率', '血糖', '体重'];
const activeType = 0;
const periods = ['早晨', '晚上'];
const activePeriod = 0;
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="记录体征" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 120px' }}>
{/* 体征类型选择 */}
<div style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
{types.map((t, i) => (
<div key={i} style={{
flex: 1, height: 40, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 14, fontWeight: 600,
background: i === activeType ? T.pri : T.surface,
color: i === activeType ? '#fff' : T.tx2,
}}>{t}</div>
))}
</div>
{/* 血压录入表单 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 20,
boxShadow: '0 2px 12px rgba(45,42,38,0.06)', marginBottom: 16,
}}>
{/* 收缩压 */}
<div style={{ marginBottom: 18 }}>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6, fontWeight: 500 }}>收缩压高压</div>
<div style={{
height: 60, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', padding: '0 16px',
}}>
<span style={{ fontFamily: T.serif, fontSize: 30, fontWeight: 700, color: T.tx }}>130</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>mmHg</span>
</div>
</div>
{/* 舒张压 */}
<div style={{ marginBottom: 18 }}>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6, fontWeight: 500 }}>舒张压低压</div>
<div style={{
height: 60, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', padding: '0 16px',
}}>
<span style={{ fontFamily: T.serif, fontSize: 30, fontWeight: 700, color: T.tx }}>85</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>mmHg</span>
</div>
</div>
{/* 测量时段 */}
<div style={{ marginBottom: 18 }}>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6, fontWeight: 500 }}>测量时段</div>
<div style={{ display: 'flex', gap: 10 }}>
{periods.map((p, i) => (
<div key={i} style={{
flex: 1, height: 44, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 15, fontWeight: 600,
background: i === activePeriod ? T.pri : T.surface,
color: i === activePeriod ? '#fff' : T.tx2,
}}>{p}</div>
))}
</div>
</div>
{/* 备注 */}
<div>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6, fontWeight: 500 }}>备注</div>
<div style={{
minHeight: 72, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm,
padding: '12px 16px', fontSize: 15, color: T.tx3, lineHeight: 1.6,
}}>
添加备注选填
</div>
</div>
</div>
{/* 参考范围提示 */}
<div style={{
background: T.surface, borderRadius: T.rSm, padding: '12px 16px',
display: 'flex', alignItems: 'flex-start', gap: 8,
}}>
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" style={{ flexShrink: 0, marginTop: 1 }}>
<circle cx="12" cy="12" r="10" stroke={T.tx3} strokeWidth="1.5"/>
<path d="M12 16v-4M12 8h.01" stroke={T.tx3} strokeWidth="2" strokeLinecap="round"/>
</svg>
<div style={{ fontSize: 12, color: T.tx3, lineHeight: 1.7 }}>
参考范围收缩压 90-140 / 舒张压 60-90 mmHg
</div>
</div>
</div>
{/* 底部保存按钮 */}
<div style={{
position: 'absolute', bottom: 34, left: 0, right: 0,
padding: '12px 16px', background: T.bg,
borderTop: `1px solid ${T.bdL}`,
}}>
<div style={{
height: 52, borderRadius: 16, background: T.pri,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 17, fontWeight: 600,
boxShadow: `0 4px 16px rgba(196,98,58,0.3)`,
}}>
保存
</div>
</div>
</div>
);
}
// ─── 屏幕五:趋势分析 ───
function TrendAnalysis() {
const timeRanges = ['7天', '30天', '90天'];
const activeRange = 0;
const trendData = [132, 128, 135, 130, 138, 126, 130];
const days = ['一','二','三','四','五','六','日'];
const maxV = Math.max(...trendData);
const threshold = 140;
const stats = [
{ label: '平均值', value: '130', unit: 'mmHg' },
{ label: '最高值', value: '138', unit: 'mmHg' },
{ label: '最低值', value: '126', unit: 'mmHg' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="趋势分析" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 100px' }}>
{/* 时间段选择 */}
<div style={{ display: 'flex', gap: 8, marginBottom: 16 }}>
{timeRanges.map((t, i) => (
<div key={i} style={{
flex: 1, height: 40, borderRadius: T.rSm,
display: 'flex', alignItems: 'center', justifyContent: 'center',
fontSize: 14, fontWeight: 600,
background: i === activeRange ? T.pri : T.surface,
color: i === activeRange ? '#fff' : T.tx2,
}}>{t}</div>
))}
</div>
{/* 趋势图 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 18,
boxShadow: '0 2px 12px rgba(45,42,38,0.06)', marginBottom: 16,
}}>
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx, marginBottom: 14 }}>收缩压趋势</div>
{/* 柱状图 */}
<div style={{
position: 'relative', height: 140, background: T.bg, borderRadius: T.rSm,
padding: '12px 8px', display: 'flex', alignItems: 'flex-end',
}}>
{/* 阈值标线 */}
<div style={{
position: 'absolute', left: 8, right: 8,
bottom: `${12 + (threshold / maxV) * 100}px`,
borderTop: `1.5px dashed ${T.wrn}`, opacity: 0.6,
}} />
<div style={{
position: 'absolute', right: 12,
bottom: `${18 + (threshold / maxV) * 100}px`,
fontSize: 10, color: T.wrn, opacity: 0.7,
}}>140</div>
{trendData.map((v, i) => {
const hPct = Math.max(10, (v / maxV) * 100);
const isWarn = v >= threshold;
return (
<div key={i} style={{
flex: 1, display: 'flex', flexDirection: 'column',
alignItems: 'center', height: '100%', justifyContent: 'flex-end',
}}>
<div style={{
width: 26, borderRadius: '6px 6px 0 0', minHeight: 8,
height: `${hPct}%`, background: isWarn ? T.wrn : T.pri,
opacity: isWarn ? 1 : 0.7, transition: 'height 0.3s',
}} />
<span style={{ fontSize: 11, color: T.tx3, marginTop: 6 }}>{days[i]}</span>
</div>
);
})}
</div>
{/* 图例 */}
<div style={{ display: 'flex', justifyContent: 'center', gap: 16, marginTop: 12 }}>
<span style={{ fontSize: 11, color: T.tx3 }}>
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: 2, background: T.pri, marginRight: 4, verticalAlign: 'middle' }} />正常
</span>
<span style={{ fontSize: 11, color: T.tx3 }}>
<span style={{ display: 'inline-block', width: 10, height: 10, borderRadius: 2, background: T.wrn, marginRight: 4, verticalAlign: 'middle' }} />偏高
</span>
<span style={{ fontSize: 11, color: T.tx3 }}>
<span style={{ display: 'inline-block', width: 10, height: 0, borderTop: '1.5px dashed ' + T.wrn, marginRight: 4, verticalAlign: 'middle' }} />阈值
</span>
</div>
</div>
{/* 统计摘要卡片 */}
<div style={{
display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 10, marginBottom: 16,
}}>
{stats.map((s, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.rSm, padding: '14px 12px',
textAlign: 'center', boxShadow: '0 1px 4px rgba(45,42,38,0.04)',
}}>
<div style={{ fontSize: 12, color: T.tx3, marginBottom: 4 }}>{s.label}</div>
<div>
<span style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.tx }}>{s.value}</span>
<span style={{ fontSize: 11, color: T.tx3, marginLeft: 2 }}>{s.unit}</span>
</div>
</div>
))}
</div>
{/* 趋势指示 */}
<div style={{
background: T.accL, borderRadius: T.r, padding: '16px 18px',
display: 'flex', alignItems: 'center', gap: 12,
}}>
<div style={{
width: 40, height: 40, borderRadius: 20, background: T.acc,
display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
}}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none">
<path d="M12 19V5M5 12l7-7 7 7" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
<div>
<div style={{ fontSize: 15, fontWeight: 600, color: T.acc, marginBottom: 2 }}>
趋势下降
</div>
<div style={{ fontSize: 13, color: T.acc, opacity: 0.8 }}>
7 日收缩压呈下降趋势保持良好
</div>
</div>
</div>
</div>
</div>
);
}
// ─── 渲染 ───
function App() {
return (
<div className="screens">
<div className="screen-wrap">
<span className="screen-label">告警列表</span>
<IosFrame time="9:41" battery={85}>
<AlertList />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">日常监测</span>
<IosFrame time="9:42" battery={84}>
<DailyMonitoring />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">设备同步</span>
<IosFrame time="9:43" battery={83}>
<DeviceSync />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">体征录入</span>
<IosFrame time="9:44" battery={82}>
<VitalInput />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">趋势分析</span>
<IosFrame time="9:45" battery={81}>
<TrendAnalysis />
</IosFrame>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>