- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等) - 新增医生端分包原型(核心 + 临床两个分包) - 新增 AI 客服对话页原型 - 新增 MP UI 优化指南文档 - 更新 wiki 基础设施和小程序文档
665 lines
30 KiB
HTML
665 lines
30 KiB
HTML
<!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>
|