Files
hms/docs/design/mp-redesign-home.html
iven df1d85bfde docs: T40 UI 审计报告 + wiki 更新 + Docker 配置
- T40 UI 审计计划和结果文档(docs/qa/)
- wiki 更新:miniprogram 设计系统合规审计记录 + index 关键数字更新
- 审计 V2 完整报告(docs/audits/v2/)
- 讨论记录文档(docs/discussions/)
- 设计规格和实施计划(docs/superpowers/)
- 角色测试计划和结果(docs/qa/role-test-*)
- Docker 生产部署配置
2026-05-13 23:29:42 +08:00

579 lines
34 KiB
HTML
Raw 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; text-transform: uppercase; }
.note { color: #666; font-size: 12px; max-width: 600px; text-align: center; line-height: 1.8; }
.screens { display: flex; gap: 48px; 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">设计假设:保持温润东方风设计系统,提升留白节奏与视觉层级。待办融入首页智能提醒卡片,不新增独立 Tab。</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 = 393, height = 852, time = '9:41', battery = 85 }) {
return (
<div style={iosFrameStyles.wrapper}>
<div style={{ ...iosFrameStyles.screen, width, height }}>
<div style={{ ...iosFrameStyles.statusBar, color: '#000' }}>
<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="#000"/><path d="M3 7.5a7 7 0 0110 0" stroke="#000" strokeWidth="1.3" fill="none" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke="#000" strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7"/></svg>
<div style={{ width: 26, height: 12, border: '1.5px solid #000', borderRadius: 3, padding: 1, position: 'relative' }}>
<div style={{ width: `${battery}%`, height: '100%', background: '#000', 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,
};
// ─── 首页:设计方案 A当前风格优化 ───
function HomeA() {
return (
<div style={{ height: '100%', background: T.bg, padding: '20px 20px 100px', overflowY: 'auto' }}>
{/* 问候区 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 24 }}>
<div>
<div style={{ fontSize: 26, fontWeight: 700, color: T.tx, fontFamily: T.serif }}>上午好张三</div>
<div style={{ fontSize: 14, color: T.tx3, marginTop: 4 }}>5月8日 周四</div>
</div>
<div style={{ width: 44, height: 44, borderRadius: 22, background: T.priL, display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
<span style={{ fontSize: 18, color: T.priD }}></span>
<div style={{ position: 'absolute', top: 2, right: 2, width: 8, height: 8, borderRadius: 4, background: T.dan }} />
</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={{ display: 'flex', alignItems: 'center', gap: 16 }}>
{/* 进度环 */}
<div style={{ width: 64, height: 64, position: 'relative', flexShrink: 0 }}>
<svg width="64" height="64" viewBox="0 0 64 64">
<circle cx="32" cy="32" r="28" fill="none" stroke={T.bd} strokeWidth="4" />
<circle cx="32" cy="32" r="28" fill="none" stroke={T.pri} strokeWidth="4" strokeDasharray={`${0.75 * 176} ${0.25 * 176}`} strokeDashoffset="0" strokeLinecap="round" transform="rotate(-90 32 32)" />
</svg>
<div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: T.pri }}>3/4</div>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 16, fontWeight: 600, color: T.tx, marginBottom: 8 }}>今日已记录 3 项体征</div>
<div style={{ display: 'flex', gap: 6 }}>
{['血压 ✓','心率 ✓','血糖 ✓','体重'].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>
</div>
{/* 体征 2x2 */}
<div style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx, marginBottom: 12 }}>今日体征</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 20 }}>
{[
{ 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' },
].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: 30, fontWeight: 700, color: T.tx, lineHeight: 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 style={{ background: `linear-gradient(135deg, ${T.pri} 0%, ${T.priD} 100%)`, borderRadius: T.r, padding: 18, marginBottom: 16, color: '#fff' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
<span style={{ fontSize: 15, fontWeight: 600 }}>智能提醒</span>
<span style={{ fontSize: 12, opacity: 0.7 }}>2 条待处理</span>
</div>
{[
{ text: '血压连续 3 日偏高,建议预约复查', type: 'AI 建议' },
{ text: '明日 09:00 有预约 — 李医生门诊', type: '预约' },
].map((r, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 0', borderTop: i === 1 ? '1px solid rgba(255,255,255,0.15)' : 'none' }}>
<span style={{ fontSize: 10, padding: '2px 6px', borderRadius: 4, background: 'rgba(255,255,255,0.2)', fontWeight: 500 }}>{r.type}</span>
<span style={{ fontSize: 13, flex: 1 }}>{r.text}</span>
<span style={{ opacity: 0.5 }}></span>
</div>
))}
</div>
{/* 快捷操作 */}
<div style={{ display: 'flex', gap: 10 }}>
<div style={{ flex: 1, height: 52, borderRadius: 14, background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 17, fontWeight: 600 }}>记录体征</div>
<div style={{ flex: 1, height: 52, borderRadius: 14, background: 'transparent', border: `2px solid ${T.pri}`, display: 'flex', alignItems: 'center', justifyContent: 'center', color: T.pri, fontSize: 17, fontWeight: 600 }}>预约挂号</div>
</div>
{/* 底部 TabBar 占位 */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 80, background: '#fff', borderTop: `1px solid ${T.bdL}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around', paddingBottom: 10 }}>
{['首页','健康','消息','我的'].map((t, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, color: i === 0 ? T.pri : T.tx3, fontSize: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: 6, background: i === 0 ? T.pri : T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center', color: i === 0 ? '#fff' : T.tx3, fontSize: 12 }} />
<span>{t}</span>
</div>
))}
</div>
</div>
);
}
// ─── 健康页 ───
function HealthA() {
const [tab, setTab] = React.useState(0);
const vitalTabs = ['血压', '心率', '血糖', '体重'];
const trendData = [132, 128, 135, 130, 138, 126, 130];
const days = ['一','二','三','四','五','六','日'];
const maxV = Math.max(...trendData);
const threshold = 140;
return (
<div style={{ height: '100%', background: T.bg, overflowY: 'auto' }}>
<div style={{ padding: '20px 20px 100px' }}>
{/* 页头 */}
<div style={{ fontFamily: T.serif, fontSize: 26, fontWeight: 700, color: T.tx, marginBottom: 20 }}>健康数据</div>
{/* AI 建议卡片 — 温暖提示风格 */}
<div style={{ background: T.accL, borderRadius: T.r, padding: 16, marginBottom: 20, borderLeft: `4px solid ${T.acc}` }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<span style={{ fontSize: 14, fontWeight: 600, color: T.acc }}>AI 健康建议</span>
<span style={{ fontSize: 12, color: T.acc, opacity: 0.7 }}>1 条待查看</span>
</div>
<div style={{ fontSize: 13, color: T.tx2, lineHeight: 1.6 }}> 7 日收缩压呈上升趋势建议关注饮食并预约复查</div>
</div>
{/* 类型 Tab */}
<div style={{ display: 'flex', gap: 8, marginBottom: 20 }}>
{vitalTabs.map((t, i) => (
<div key={i} onClick={() => setTab(i)} style={{
flex: 1, height: 44, borderRadius: T.rSm,
background: tab === i ? T.pri : T.surface,
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', transition: 'all 0.2s',
boxShadow: tab === i ? '0 2px 8px rgba(196,98,58,0.25)' : 'none',
}}>
<span style={{ fontSize: 15, fontWeight: 600, color: tab === i ? '#fff' : T.tx2 }}>{t}</span>
</div>
))}
</div>
{/* 录入区 */}
<div style={{ background: T.card, borderRadius: T.r, padding: 20, boxShadow: '0 2px 12px rgba(45,42,38,0.06)', marginBottom: 20 }}>
{tab === 0 && (
<div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6 }}>收缩压高压</div>
<div style={{ height: 56, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm, display: 'flex', alignItems: 'center', padding: '0 16px' }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx }}>130</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>mmHg</span>
</div>
</div>
<div style={{ marginBottom: 16 }}>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6 }}>舒张压低压</div>
<div style={{ height: 56, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm, display: 'flex', alignItems: 'center', padding: '0 16px' }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx }}>85</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>mmHg</span>
</div>
</div>
<div style={{ fontSize: 12, color: T.tx3, lineHeight: 1.6 }}>参考范围收缩压 90-140 / 舒张压 60-90 mmHg</div>
</div>
)}
{tab === 1 && (
<div>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6 }}>心率</div>
<div style={{ height: 56, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm, display: 'flex', alignItems: 'center', padding: '0 16px' }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx }}>72</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>bpm</span>
</div>
<div style={{ fontSize: 12, color: T.tx3, marginTop: 8 }}>参考范围60-100 bpm</div>
</div>
)}
{tab === 2 && (
<div>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6 }}>血糖值</div>
<div style={{ height: 56, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm, display: 'flex', alignItems: 'center', padding: '0 16px' }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx }}>5.6</span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>mmol/L</span>
</div>
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
<div style={{ flex: 1, height: 40, borderRadius: T.rSm, background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: '#fff' }}>空腹</span>
</div>
<div style={{ flex: 1, height: 40, borderRadius: T.rSm, background: T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ fontSize: 14, fontWeight: 600, color: T.tx2 }}>餐后 2h</span>
</div>
</div>
</div>
)}
{tab === 3 && (
<div>
<div style={{ fontSize: 13, color: T.tx3, marginBottom: 6 }}>体重</div>
<div style={{ height: 56, background: T.bg, border: `2px solid ${T.bd}`, borderRadius: T.rSm, display: 'flex', alignItems: 'center', padding: '0 16px' }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.tx, opacity: 0.3 }}></span>
<span style={{ fontSize: 13, color: T.tx3, marginLeft: 6 }}>kg</span>
</div>
</div>
)}
{/* 保存按钮 */}
<div style={{ height: 52, borderRadius: 14, background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 17, fontWeight: 600, marginTop: 20, boxShadow: '0 2px 8px rgba(196,98,58,0.25)' }}>保存</div>
</div>
{/* 趋势图 */}
<div style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx, marginBottom: 12 }}> 7 天趋势</div>
<div style={{ background: T.card, borderRadius: T.r, padding: 20, boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
{/* 阈值线 + 柱状图 */}
<div style={{ position: 'relative', height: 140, background: T.bg, borderRadius: T.rSm, padding: '12px 8px', display: 'flex', alignItems: 'flex-end', gap: 0 }}>
{/* 阈值标线 */}
<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: 28, 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: 10 }}>
<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={{ background: T.card, borderRadius: T.r, padding: 16, marginTop: 16, boxShadow: '0 1px 4px rgba(45,42,38,0.04)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 15, color: T.tx, fontWeight: 500 }}>最新健康资讯</span>
<span style={{ color: T.tx3 }}></span>
</div>
</div>
{/* TabBar */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 80, background: '#fff', borderTop: `1px solid ${T.bdL}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around', paddingBottom: 10 }}>
{['首页','健康','消息','我的'].map((t, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, color: i === 1 ? T.pri : T.tx3, fontSize: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: 6, background: i === 1 ? T.pri : T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center', color: i === 1 ? '#fff' : T.tx3, fontSize: 12 }} />
<span>{t}</span>
</div>
))}
</div>
</div>
);
}
// ─── 消息页 ───
function MessagesA() {
const [tab, setTab] = React.useState(0);
const tabs = ['咨询', '通知'];
const consultations = [
{ id: 1, type: 'online', doctor: '王医生 · 心内科', preview: '您的检查报告已出,建议...', time: '10 分钟前', unread: 2 },
{ id: 2, type: 'online', doctor: '李医生 · 肾内科', preview: '复查结果整体平稳', time: '昨天', unread: 0 },
{ id: 3, type: 'offline', doctor: '张医生 · 全科', preview: '门诊随访已完成', time: '3 天前', unread: 0 },
];
const notifications = [
{ id: 1, title: '预约确认', desc: '明日 09:00 李医生门诊已确认', time: '2 小时前', type: 'appointment', read: false },
{ id: 2, title: '体征异常提醒', desc: '今日收缩压偏高138 mmHg', time: '今天 08:30', type: 'alert', read: false },
{ id: 3, title: '随访到期', desc: '肾功能复查随访将于 5/12 到期', time: '昨天', type: 'followup', read: true },
{ id: 4, title: '签到成功', desc: '连续打卡 7 天,获得 50 积分', time: '昨天', type: 'points', read: true },
{ id: 5, title: '报告已生成', desc: '您的 5 月体检报告已生成', time: '3 天前', type: 'report', read: true },
];
const typeIcon = { appointment: '约', alert: '警', followup: '随', points: '分', report: '报' };
const typeBg = { appointment: T.priL, alert: T.wrnL, followup: T.accL, points: T.priL, report: T.accL };
const typeColor = { appointment: T.pri, alert: T.wrn, followup: T.acc, points: T.pri, report: T.acc };
return (
<div style={{ height: '100%', background: T.bg, overflowY: 'auto' }}>
<div style={{ padding: '20px 20px 100px' }}>
{/* 页头 */}
<div style={{ fontFamily: T.serif, fontSize: 26, fontWeight: 700, color: T.tx, marginBottom: 20 }}>消息</div>
{/* Tab */}
<div style={{ display: 'flex', gap: 0, marginBottom: 0, background: T.surface, borderRadius: T.rSm, padding: 3 }}>
{tabs.map((t, i) => (
<div key={i} onClick={() => setTab(i)} style={{
flex: 1, height: 40, borderRadius: T.rXs,
background: tab === i ? T.card : 'transparent',
display: 'flex', alignItems: 'center', justifyContent: 'center',
cursor: 'pointer', boxShadow: tab === i ? '0 1px 4px rgba(45,42,38,0.06)' : 'none',
}}>
<span style={{ fontSize: 15, fontWeight: 600, color: tab === i ? T.tx : T.tx3 }}>{t}</span>
{i === 0 && consultations.filter(c => c.unread > 0).length > 0 && (
<span style={{ marginLeft: 6, minWidth: 16, height: 16, borderRadius: 8, background: T.dan, color: '#fff', fontSize: 10, fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 4px' }}>2</span>
)}
</div>
))}
</div>
<div style={{ height: 12 }} />
{/* 咨询列表 */}
{tab === 0 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{consultations.map((c) => (
<div key={c.id} style={{ background: T.card, borderRadius: T.r, padding: 16, boxShadow: '0 1px 4px rgba(45,42,38,0.04)', display: 'flex', gap: 12, alignItems: 'center', opacity: c.unread > 0 ? 1 : 0.7 }}>
{/* 头像 */}
<div style={{ width: 44, height: 44, borderRadius: 22, background: c.unread > 0 ? T.priL : T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<span style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: c.unread > 0 ? T.pri : T.tx3 }}>{c.doctor.charAt(0)}</span>
</div>
{/* 内容 */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
<span style={{ fontSize: 15, fontWeight: 600, color: T.tx }}>{c.doctor}</span>
<span style={{ fontSize: 12, color: T.tx3 }}>{c.time}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<span style={{ fontSize: 13, color: T.tx2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1, marginRight: 8 }}>{c.preview}</span>
{c.unread > 0 && (
<span style={{ minWidth: 18, height: 18, borderRadius: 9, background: T.dan, color: '#fff', fontSize: 11, fontWeight: 600, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '0 4px', flexShrink: 0 }}>{c.unread}</span>
)}
</div>
</div>
</div>
))}
</div>
)}
{/* 通知列表 */}
{tab === 1 && (
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{notifications.map((n) => (
<div key={n.id} style={{ background: T.card, borderRadius: T.r, padding: 16, boxShadow: '0 1px 4px rgba(45,42,38,0.04)', display: 'flex', gap: 12, alignItems: 'flex-start', opacity: n.read ? 0.65 : 1 }}>
{/* 类型图标 */}
<div style={{ width: 36, height: 36, borderRadius: T.rSm, background: typeBg[n.type], display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<span style={{ fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: typeColor[n.type] }}>{typeIcon[n.type]}</span>
</div>
{/* 内容 */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }}>
<span style={{ fontSize: 15, fontWeight: n.read ? 400 : 600, color: T.tx }}>{n.title}</span>
<span style={{ fontSize: 12, color: T.tx3, flexShrink: 0, marginLeft: 8 }}>{n.time}</span>
</div>
<span style={{ fontSize: 13, color: T.tx2, lineHeight: 1.5, display: 'block' }}>{n.desc}</span>
</div>
{/* 未读点 */}
{!n.read && <div style={{ width: 8, height: 8, borderRadius: 4, background: T.pri, flexShrink: 0, marginTop: 6 }} />}
</div>
))}
</div>
)}
</div>
{/* TabBar */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 80, background: '#fff', borderTop: `1px solid ${T.bdL}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around', paddingBottom: 10 }}>
{['首页','健康','消息','我的'].map((t, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, color: i === 2 ? T.pri : T.tx3, fontSize: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: 6, background: i === 2 ? T.pri : T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center', color: i === 2 ? '#fff' : T.tx3, fontSize: 12, position: 'relative' }}>
{i === 2 && <div style={{ position: 'absolute', top: -2, right: -2, width: 8, height: 8, borderRadius: 4, background: T.dan, border: '1.5px solid #fff' }} />}
</div>
<span>{t}</span>
</div>
))}
</div>
</div>
);
}
// ─── 个人中心页 ───
function ProfileA() {
const menuGroups = [
{
title: '健康管理',
items: [
{ label: '健康记录', icon: '健', bg: T.priL, color: T.pri },
{ label: '我的报告', icon: '报', bg: T.accL, color: T.acc },
{ label: 'AI 分析', icon: '智', bg: T.priL, color: T.pri },
{ label: '诊断记录', icon: '诊', bg: T.accL, color: T.acc },
{ label: '用药记录', icon: '药', bg: T.priL, color: T.pri },
],
},
{
title: '就诊服务',
items: [
{ label: '我的预约', icon: '约', bg: T.priL, color: T.pri },
{ label: '我的随访', icon: '随', bg: T.accL, color: T.acc },
{ label: '在线咨询', icon: '问', bg: T.priL, color: T.pri },
],
},
{
title: '透析管理',
items: [
{ label: '透析记录', icon: '透', bg: T.priL, color: T.pri },
{ label: '透析处方', icon: '方', bg: T.accL, color: T.acc },
{ label: '知情同意', icon: '知', bg: T.priL, color: T.pri },
],
},
{
title: '生活服务',
items: [
{ label: '积分商城', icon: '礼', bg: T.priL, color: T.pri },
{ label: '线下活动', icon: '活', bg: T.accL, color: T.acc },
],
},
{
title: '账号',
items: [
{ label: '就诊人管理', icon: '家', bg: T.priL, color: T.pri },
{ label: '设备同步', icon: '设', bg: T.surface, color: T.tx3 },
{ label: '设置', icon: '齿', bg: T.surface, color: T.tx3 },
],
},
];
return (
<div style={{ height: '100%', background: T.bg, overflowY: 'auto' }}>
<div style={{ padding: '20px 20px 100px' }}>
{/* 用户卡片 */}
<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: 16 }}>
<div style={{ width: 60, height: 60, borderRadius: 30, background: `linear-gradient(135deg, ${T.priL} 0%, ${T.pri} 100%)`, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<span style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: '#fff' }}></span>
</div>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 22, fontWeight: 700, color: T.tx, fontFamily: T.serif, marginBottom: 2 }}>张三</div>
<div style={{ fontSize: 14, color: T.tx3 }}>138****1234</div>
</div>
<span style={{ color: T.tx3, fontSize: 16 }}></span>
</div>
{/* 积分 + 打卡 */}
<div style={{ display: 'flex', gap: 10, marginBottom: 24 }}>
<div style={{ flex: 1, background: T.card, borderRadius: T.r, padding: 16, textAlign: 'center', boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
<div style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.pri, display: 'block' }}>1,280</div>
<div style={{ fontSize: 13, color: T.tx3, marginTop: 2 }}>健康积分</div>
</div>
<div style={{ flex: 1, background: T.card, borderRadius: T.r, padding: 16, textAlign: 'center', boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
<div style={{ fontFamily: T.serif, fontSize: 28, fontWeight: 700, color: T.acc, display: 'block' }}>7<span style={{ fontSize: 16, fontWeight: 400 }}></span></div>
<div style={{ fontSize: 13, color: T.tx3, marginTop: 2 }}>连续打卡</div>
</div>
</div>
{/* 分组菜单 */}
{menuGroups.map((group, gi) => (
<div key={gi} style={{ marginBottom: 14 }}>
<div style={{ fontSize: 14, fontWeight: 600, color: T.tx2, marginBottom: 8, paddingLeft: 4 }}>{group.title}</div>
<div style={{ background: T.card, borderRadius: T.r, overflow: 'hidden', boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
{group.items.map((item, ii) => (
<div key={ii} style={{
display: 'flex', alignItems: 'center', gap: 14, padding: '14px 16px',
borderBottom: ii < group.items.length - 1 ? `1px solid ${T.bdL}` : 'none',
}}>
<div style={{ width: 36, height: 36, borderRadius: T.rSm, background: item.bg, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<span style={{ fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: item.color }}>{item.icon}</span>
</div>
<span style={{ flex: 1, fontSize: 15, color: T.tx }}>{item.label}</span>
<span style={{ color: T.tx3, fontSize: 14 }}></span>
</div>
))}
</div>
</div>
))}
{/* 退出 */}
<div style={{ textAlign: 'center', padding: '16px 0' }}>
<span style={{ fontSize: 14, color: T.tx3 }}>退出登录</span>
</div>
</div>
{/* TabBar */}
<div style={{ position: 'absolute', bottom: 0, left: 0, right: 0, height: 80, background: '#fff', borderTop: `1px solid ${T.bdL}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around', paddingBottom: 10 }}>
{['首页','健康','消息','我的'].map((t, i) => (
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, color: i === 3 ? T.pri : T.tx3, fontSize: 10 }}>
<div style={{ width: 24, height: 24, borderRadius: 6, background: i === 3 ? T.pri : T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center', color: i === 3 ? '#fff' : T.tx3, fontSize: 12 }} />
<span>{t}</span>
</div>
))}
</div>
</div>
);
}
// ─── 渲染 ───
function App() {
return (
<div className="screens">
<div className="screen-wrap">
<span className="screen-label">首页</span>
<IosFrame time="9:41" battery={85}>
<HomeA />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">健康数据</span>
<IosFrame time="9:41" battery={85}>
<HealthA />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">消息</span>
<IosFrame time="9:41" battery={85}>
<MessagesA />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">我的</span>
<IosFrame time="9:41" battery={85}>
<ProfileA />
</IosFrame>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>