- docs/: 设计规格、讨论记录、销售数据、健康管理文档 - scripts/: 辅助脚本 - package.json + pnpm-lock.yaml: monorepo 根配置
347 lines
18 KiB
HTML
347 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<title>HMS 小程序设计预览 · 温润东方风</title>
|
||
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
|
||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
|
||
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
||
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||
body { background: #E8E2DC; font-family: -apple-system, 'PingFang SC', sans-serif; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="root"></div>
|
||
<script type="text/babel">
|
||
/* ─── Design Tokens ─── */
|
||
const C = {
|
||
bg: '#F5F0EB', surface: '#FFFFFF', surfaceAlt: '#EDE8E2',
|
||
tx1: '#2D2A26', tx2: '#7A756E', tx3: '#A8A29E',
|
||
accent: '#C4623A', accentLt: '#F0DDD4', accentDk: '#8B3E1F',
|
||
ok: '#5B7A5E', okLt: '#E8F0E8',
|
||
warn: '#C4873A', warnLt: '#FFF3E0',
|
||
err: '#B54A4A', errLt: '#FDEAEA',
|
||
bd: '#E8E2DC', bdLt: '#F0EBE5',
|
||
};
|
||
const R = { sm: 8, md: 12, lg: 16 };
|
||
const serif = '"Noto Serif SC", serif';
|
||
|
||
/* ─── iOS Frame ─── */
|
||
function IosFrame({ children, darkMode }) {
|
||
const bg = darkMode ? '#1A1816' : C.bg;
|
||
const tc = darkMode ? '#E8E2DC' : C.tx1;
|
||
return (
|
||
<div style={{ display: 'inline-block', padding: 10, background: '#1A1816', borderRadius: 48, boxShadow: '0 0 0 1px #333, 0 20px 60px rgba(0,0,0,0.25)' }}>
|
||
<div style={{ width: 393, height: 852, borderRadius: 40, overflow: 'hidden', background: bg, position: 'relative' }}>
|
||
{/* Dynamic Island */}
|
||
<div style={{ position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)', width: 124, height: 36, background: '#000', borderRadius: 999, zIndex: 30 }} />
|
||
{/* Status Bar */}
|
||
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 54, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 32px', fontSize: 15, fontWeight: 600, color: tc, zIndex: 20, pointerEvents: 'none' }}>
|
||
<span>9:41</span>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 2, height: 12 }}>
|
||
{[4,6,9,11].map(h => <div key={h} style={{ width: 3, height: h, background: tc, borderRadius: 1, opacity: 0.8 }} />)}
|
||
</div>
|
||
<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={tc}/><path d="M3 7.5a7 7 0 0110 0" stroke={tc} strokeWidth="1.3" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke={tc} strokeWidth="1.3" strokeLinecap="round" opacity="0.7"/></svg>
|
||
<div style={{ width: 26, height: 12, border: `1.5px solid ${tc}`, borderRadius: 3, padding: 1, opacity: 0.8 }}>
|
||
<div style={{ width: '80%', height: '100%', background: tc, borderRadius: 1 }} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{/* Content */}
|
||
<div style={{ position: 'absolute', top: 54, left: 0, right: 0, bottom: 34, overflow: 'auto' }}>
|
||
{children}
|
||
</div>
|
||
{/* Home Indicator */}
|
||
<div style={{ position: 'absolute', bottom: 10, left: '50%', transform: 'translateX(-50%)', width: 140, height: 5, background: darkMode ? 'rgba(255,255,255,0.3)' : 'rgba(0,0,0,0.2)', borderRadius: 999, zIndex: 10 }} />
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Shared Components ─── */
|
||
function Tag({ type, children }) {
|
||
const map = { ok: [C.ok, C.okLt], warn: [C.warn, C.warnLt], err: [C.err, C.errLt], default: [C.tx2, C.bdLt] };
|
||
const [fg, bg] = map[type] || map.default;
|
||
return <span style={{ display: 'inline-block', padding: '2px 8px', borderRadius: 6, fontSize: 11, fontWeight: 500, background: bg, color: fg }}>{children}</span>;
|
||
}
|
||
|
||
function SectionTitle({ children }) {
|
||
return <div style={{ fontFamily: serif, fontSize: 18, fontWeight: 600, color: C.tx1, marginBottom: 14 }}>{children}</div>;
|
||
}
|
||
|
||
function ListItem({ title, sub, right }) {
|
||
return (
|
||
<div style={{ display: 'flex', alignItems: 'center', padding: '14px 0', borderBottom: `1px solid ${C.bdLt}` }}>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ fontSize: 15, fontWeight: 500, color: C.tx1, marginBottom: 2 }}>{title}</div>
|
||
<div style={{ fontSize: 12, color: C.tx2 }}>{sub}</div>
|
||
</div>
|
||
{right}
|
||
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" style={{ color: C.tx3, marginLeft: 8, flexShrink: 0 }}>
|
||
<path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||
</svg>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Patient TabBar ─── */
|
||
const patientTabs = [
|
||
{ label: '首页', d: 'M4 3h8v8a1 1 0 01-1 1H5a1 1 0 01-1-1V3zM4 3V1M12 3V1' },
|
||
{ label: '健康', d: 'M2 6h2l1.5-3 2.5 6 1.5-3H8' },
|
||
{ label: '咨询', d: 'M2 3h12v8a1 1 0 01-1 1H5l-3 3V3z' },
|
||
{ label: '商城', d: 'M2 4h12l-1 8H3L2 4zM5 4V3a3 3 0 016 0v1' },
|
||
{ label: '我的', d: 'M8 7a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM3 14a5 5 0 0110 0' },
|
||
];
|
||
function PatientTabBar({ active }) {
|
||
return (
|
||
<div style={{ position: 'absolute', bottom: 34, left: 0, right: 0, height: 56, background: C.surface, borderTop: `1px solid ${C.bd}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around' }}>
|
||
{patientTabs.map((t, i) => (
|
||
<div key={t.label} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3 }}>
|
||
<svg width="22" height="22" viewBox="0 0 16 16" fill="none" stroke={i === active ? C.accent : C.tx3} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d={t.d}/></svg>
|
||
<span style={{ fontSize: 10, color: i === active ? C.accent : C.tx3, fontWeight: i === active ? 600 : 400 }}>{t.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Doctor TabBar ─── */
|
||
const doctorTabs = [
|
||
{ label: '工作台', d: 'M3 2h4v5H3V2zM9 2h4v5H9V2zM3 9h4v5H3V9zM9 9h4v5H9V9z' },
|
||
{ label: '患者', d: 'M8 7a2.5 2.5 0 100-5 2.5 2.5 0 000 5zM3 14a5 5 0 0110 0' },
|
||
{ label: '咨询', d: 'M2 3h12v8a1 1 0 01-1 1H5l-3 3V3z' },
|
||
{ label: '随访', d: 'M4 3h8v8a1 1 0 01-1 1H5a1 1 0 01-1-1V3z' },
|
||
{ label: '报告', d: 'M3 2h10v12H3V2zM5 5h6M5 8h4' },
|
||
];
|
||
function DoctorTabBar({ active }) {
|
||
return (
|
||
<div style={{ position: 'absolute', bottom: 34, left: 0, right: 0, height: 56, background: C.surface, borderTop: `1px solid ${C.bd}`, display: 'flex', alignItems: 'center', justifyContent: 'space-around' }}>
|
||
{doctorTabs.map((t, i) => (
|
||
<div key={t.label} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 3 }}>
|
||
<svg width="22" height="22" viewBox="0 0 16 16" fill="none" stroke={i === active ? C.accent : C.tx3} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d={t.d}/></svg>
|
||
<span style={{ fontSize: 10, color: i === active ? C.accent : C.tx3, fontWeight: i === active ? 600 : 400 }}>{t.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════
|
||
患者端首页
|
||
═══════════════════════════════════════════════ */
|
||
function PatientHome() {
|
||
const healthItems = [
|
||
{ label: '血压', value: '120/80', unit: 'mmHg', status: '正常', type: 'ok' },
|
||
{ label: '心率', value: '72', unit: 'bpm', status: '正常', type: 'ok' },
|
||
{ label: '血糖', value: '6.8', unit: 'mmol/L', status: '偏高', type: 'warn' },
|
||
{ label: '体重', value: '68.5', unit: 'kg', status: '', type: 'default' },
|
||
];
|
||
const quickServices = [
|
||
{ label: '预约挂号', d: 'M4 3h8v8a1 1 0 01-1 1H5a1 1 0 01-1-1V3zM4 3V1M12 3V1M6 7h4' },
|
||
{ label: '健康录入', d: 'M2 6h2l1.5-3 2.5 6 1.5-3H8' },
|
||
{ label: '健康趋势', d: 'M2 12l4-5 3 3 5-7' },
|
||
{ label: '资讯文章', d: 'M3 2h10v12H3V2zM5 5h6M5 8h4' },
|
||
{ label: 'AI 报告', d: 'M8 2l6 5-6 5-6-5 6-5z' },
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
{/* 问候区 */}
|
||
<div style={{ background: `linear-gradient(135deg, ${C.accent} 0%, ${C.accentDk} 100%)`, padding: '20px 24px 48px', color: '#fff' }}>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
<div>
|
||
<div style={{ fontSize: 15, opacity: 0.85 }}>上午好</div>
|
||
<div style={{ fontFamily: serif, fontSize: 24, fontWeight: 700 }}>张明远</div>
|
||
</div>
|
||
<div style={{ fontSize: 13, opacity: 0.7 }}>2026年4月27日</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 今日健康卡片 */}
|
||
<div style={{ margin: '-28px 16px 16px', background: C.surface, borderRadius: R.md, padding: 20, boxShadow: '0 2px 12px rgba(45,42,38,0.08)' }}>
|
||
<SectionTitle>今日健康</SectionTitle>
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
|
||
{healthItems.map(h => (
|
||
<div key={h.label} style={{ background: C.bg, borderRadius: R.sm, padding: 14, textAlign: 'center' }}>
|
||
<div style={{ fontSize: 12, color: C.tx2, marginBottom: 6 }}>{h.label}</div>
|
||
<div style={{ fontFamily: serif, fontSize: 26, fontWeight: 700, color: C.tx1 }}>{h.value}</div>
|
||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: 4, marginTop: 4 }}>
|
||
<span style={{ fontSize: 11, color: C.tx3 }}>{h.unit}</span>
|
||
{h.status && <Tag type={h.type}>{h.status}</Tag>}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 快捷服务 */}
|
||
<div style={{ margin: '0 16px 16px' }}>
|
||
<SectionTitle>快捷服务</SectionTitle>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
|
||
{quickServices.map(s => (
|
||
<div key={s.label} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, flex: 1 }}>
|
||
<div style={{ width: 44, height: 44, borderRadius: 12, background: C.accentLt, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" stroke={C.accent} strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d={s.d}/></svg>
|
||
</div>
|
||
<span style={{ fontSize: 11, color: C.tx2 }}>{s.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 待办事项 */}
|
||
<div style={{ margin: '0 16px', paddingBottom: 80 }}>
|
||
<SectionTitle>待办事项</SectionTitle>
|
||
<div style={{ background: C.surface, borderRadius: R.md, overflow: 'hidden', boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
|
||
<div style={{ padding: '0 16px' }}>
|
||
<ListItem title="预约:2026-04-28 上午" sub="王医生 · 心内科 · 待确认" right={<Tag type="warn">待确认</Tag>} />
|
||
<ListItem title="随访:血压监测" sub="每日记录血压数据 · 截止 05-01" right={<Tag type="default">进行中</Tag>} />
|
||
<ListItem title="报告:血常规检验" sub="2026-04-25 · 已出结果" right={<Tag type="ok">已完成</Tag>} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<PatientTabBar active={0} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ═══════════════════════════════════════════════
|
||
医护端首页
|
||
═══════════════════════════════════════════════ */
|
||
function DoctorHome() {
|
||
const stats = [
|
||
{ label: '待诊', value: '8', color: C.accent },
|
||
{ label: '已诊', value: '12', color: C.ok },
|
||
{ label: '随访', value: '5', color: C.warn },
|
||
{ label: '报告', value: '3', color: C.tx2 },
|
||
];
|
||
const quickActions = [
|
||
{ label: '患者查询', d: 'M11 7a4 4 0 11-8 0 4 4 0 018 0zM2 14a7 7 0 0112 0' },
|
||
{ label: '新增随访', d: 'M8 3v10M3 8h10' },
|
||
{ label: '写报告', d: 'M3 2h10v12H3V2zM5 5h6M5 8h4M5 11h2' },
|
||
];
|
||
const patients = [
|
||
{ name: '李小红', age: 56, dept: '心内科', time: '09:30', status: '候诊中', type: 'warn' },
|
||
{ name: '王建国', age: 63, dept: '内分泌', time: '10:00', status: '检查中', type: 'default' },
|
||
{ name: '陈美玲', age: 45, dept: '心内科', time: '10:30', status: '已诊', type: 'ok' },
|
||
{ name: '赵大伟', age: 71, dept: '肾内科', time: '11:00', status: '候诊中', type: 'warn' },
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
{/* 头部 */}
|
||
<div style={{ background: `linear-gradient(135deg, ${C.accent} 0%, ${C.accentDk} 100%)`, padding: '20px 24px 36px', color: '#fff' }}>
|
||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20 }}>
|
||
<div>
|
||
<div style={{ fontSize: 13, opacity: 0.8 }}>李明医生 · 心内科</div>
|
||
<div style={{ fontFamily: serif, fontSize: 22, fontWeight: 700 }}>工作台</div>
|
||
</div>
|
||
<div style={{ width: 40, height: 40, borderRadius: 20, background: 'rgba(255,255,255,0.2)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" stroke="#fff" strokeWidth="1.5" strokeLinecap="round"><path d="M2 8h12M8 2v12"/></svg>
|
||
</div>
|
||
</div>
|
||
{/* 统计 */}
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
{stats.map(s => (
|
||
<div key={s.label} style={{ flex: 1, background: 'rgba(255,255,255,0.15)', borderRadius: R.sm, padding: '10px 0', textAlign: 'center', backdropFilter: 'blur(4px)' }}>
|
||
<div style={{ fontFamily: serif, fontSize: 24, fontWeight: 700, color: '#fff' }}>{s.value}</div>
|
||
<div style={{ fontSize: 11, color: 'rgba(255,255,255,0.8)' }}>{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 快捷操作 */}
|
||
<div style={{ margin: '16px 16px 12px', display: 'flex', gap: 10 }}>
|
||
{quickActions.map(a => (
|
||
<div key={a.label} style={{ flex: 1, background: C.surface, borderRadius: R.md, padding: '14px 0', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
|
||
<svg width="20" height="20" viewBox="0 0 16 16" fill="none" stroke={C.accent} strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round"><path d={a.d}/></svg>
|
||
<span style={{ fontSize: 12, color: C.tx2 }}>{a.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 今日排班 */}
|
||
<div style={{ margin: '0 16px 12px', background: C.surface, borderRadius: R.md, padding: 16, boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
|
||
<SectionTitle>今日排班</SectionTitle>
|
||
<div style={{ display: 'flex', gap: 8 }}>
|
||
<div style={{ flex: 1, background: C.accentLt, borderRadius: R.sm, padding: '10px 12px' }}>
|
||
<div style={{ fontSize: 12, color: C.accent, fontWeight: 600 }}>上午</div>
|
||
<div style={{ fontSize: 13, color: C.tx1, marginTop: 2 }}>08:00 - 12:00</div>
|
||
<div style={{ fontSize: 11, color: C.tx2, marginTop: 2 }}>心内科门诊 · 8 位患者</div>
|
||
</div>
|
||
<div style={{ flex: 1, background: C.bdLt, borderRadius: R.sm, padding: '10px 12px' }}>
|
||
<div style={{ fontSize: 12, color: C.tx2, fontWeight: 600 }}>下午</div>
|
||
<div style={{ fontSize: 13, color: C.tx1, marginTop: 2 }}>14:00 - 17:00</div>
|
||
<div style={{ fontSize: 11, color: C.tx2, marginTop: 2 }}>心内科门诊 · 4 位患者</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 候诊列表 */}
|
||
<div style={{ margin: '0 16px', paddingBottom: 80 }}>
|
||
<SectionTitle>候诊队列</SectionTitle>
|
||
<div style={{ background: C.surface, borderRadius: R.md, overflow: 'hidden', boxShadow: '0 1px 4px rgba(45,42,38,0.04)' }}>
|
||
<div style={{ padding: '0 16px' }}>
|
||
{patients.map((p, i) => (
|
||
<div key={p.name} style={{ display: 'flex', alignItems: 'center', padding: '12px 0', borderBottom: i < patients.length - 1 ? `1px solid ${C.bdLt}` : 'none' }}>
|
||
<div style={{ width: 36, height: 36, borderRadius: 18, background: i === 0 ? C.accentLt : C.bdLt, display: 'flex', alignItems: 'center', justifyContent: 'center', marginRight: 12, flexShrink: 0 }}>
|
||
<span style={{ fontFamily: serif, fontSize: 14, fontWeight: 600, color: i === 0 ? C.accent : C.tx2 }}>{p.name[0]}</span>
|
||
</div>
|
||
<div style={{ flex: 1 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
|
||
<span style={{ fontSize: 14, fontWeight: 500, color: C.tx1 }}>{p.name}</span>
|
||
<span style={{ fontSize: 11, color: C.tx3 }}>{p.age}岁 · {p.dept}</span>
|
||
</div>
|
||
<div style={{ fontSize: 12, color: C.tx2, marginTop: 2 }}>预约 {p.time}</div>
|
||
</div>
|
||
<Tag type={p.type}>{p.status}</Tag>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<DoctorTabBar active={0} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ─── Page Layout ─── */
|
||
function Page() {
|
||
return (
|
||
<div style={{ padding: 48, display: 'flex', gap: 64, justifyContent: 'center', flexWrap: 'wrap', minHeight: '100vh', alignItems: 'flex-start' }}>
|
||
{/* 患者端首页 */}
|
||
<div>
|
||
<div style={{ textAlign: 'center', marginBottom: 16 }}>
|
||
<div style={{ fontFamily: serif, fontSize: 20, fontWeight: 700, color: C.tx1 }}>患者端 · 首页</div>
|
||
<div style={{ fontSize: 13, color: C.tx2, marginTop: 4 }}>pages/index/index</div>
|
||
</div>
|
||
<IosFrame>
|
||
<PatientHome />
|
||
</IosFrame>
|
||
</div>
|
||
|
||
{/* 医护端首页 */}
|
||
<div>
|
||
<div style={{ textAlign: 'center', marginBottom: 16 }}>
|
||
<div style={{ fontFamily: serif, fontSize: 20, fontWeight: 700, color: C.tx1 }}>医护端 · 工作台</div>
|
||
<div style={{ fontSize: 13, color: C.tx2, marginTop: 4 }}>pages/doctor/index</div>
|
||
</div>
|
||
<IosFrame>
|
||
<DoctorHome />
|
||
</IosFrame>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(<Page />);
|
||
</script>
|
||
</body>
|
||
</html>
|