Files
hms/docs/design/miniprogram/preview.html
iven 1265935fa3 chore: 设计规格文档 + 销售数据 + 脚本工具 + 根目录 monorepo 配置
- docs/: 设计规格、讨论记录、销售数据、健康管理文档
- scripts/: 辅助脚本
- package.json + pnpm-lock.yaml: monorepo 根配置
2026-04-28 00:20:37 +08:00

347 lines
18 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">
<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>