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

320 lines
17 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 Web 端重设计 · 温润东方风</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+Sans+SC:wght@400;500;600;700&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: 'Noto Sans SC', -apple-system, sans-serif; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
/* ─── Design Tokens从小程序端映射 ─── */
const C = {
primary: '#C4623A', primaryHover: '#B55A33', primaryActive: '#8B3E1F',
primaryLight: '#F0DDD4', primaryBg: '#FAF5F0',
bg: '#F5F0EB', surface: '#FFFFFF', surfaceAlt: '#EDE8E2',
tx1: '#2D2A26', tx2: '#7A756E', tx3: '#A8A29E', txInv: '#FFFFFF',
ok: '#5B7A5E', okBg: '#E8F0E8',
warn: '#C4873A', warnBg: '#FFF3E0',
err: '#B54A4A', errBg: '#FDEAEA',
bd: '#E8E2DC', bdLt: '#F0EBE5',
shadow: 'rgba(45,42,38,0.08)',
};
const serif = '"Noto Serif SC", serif';
const sans = '"Noto Sans SC", -apple-system, sans-serif';
/* ─── 浏览器窗口框 ─── */
function BrowserFrame({ title, children }) {
return (
<div style={{ background: '#1A1816', borderRadius: 12, overflow: 'hidden', boxShadow: '0 20px 60px rgba(0,0,0,0.2)' }}>
<div style={{ height: 38, background: '#2D2A26', display: 'flex', alignItems: 'center', padding: '0 14px', gap: 8 }}>
{['#E85C4A','#E8B44A','#5BB85B'].map(c => <div key={c} style={{ width: 12, height: 12, borderRadius: 6, background: c }} />)}
<div style={{ flex: 1, marginLeft: 16, background: 'rgba(255,255,255,0.08)', borderRadius: 6, height: 24, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, color: '#A8A29E' }}>{title}</div>
</div>
<div style={{ overflow: 'auto', background: C.bg }}>{children}</div>
</div>
);
}
/* ─── Dashboard 页面 ─── */
function DashboardPage() {
const stats = [
{ label: '今日预约', value: '24', trend: '+12%', icon: '📅', color: C.primary },
{ label: '活跃患者', value: '1,847', trend: '+3.2%', icon: '👥', color: C.ok },
{ label: '待随访', value: '38', trend: '-5%', icon: '📋', color: C.warn },
{ label: 'AI 报告', value: '156', trend: '+28%', icon: '🤖', color: '#7A6E5E' },
];
const tasks = [
{ title: '王建国 · 血压异常预警', meta: '心内科 · 10分钟前', priority: 'high' },
{ title: '李小红 · 随访到期提醒', meta: '内分泌 · 今日截止', priority: 'medium' },
{ title: '批量体检报告待审核', meta: '共12份 · 队列处理中', priority: 'low' },
];
const quickActions = [
{ label: '新增患者', desc: '建档登记' },
{ label: '预约挂号', desc: '排班管理' },
{ label: '健康录入', desc: '体征数据' },
{ label: 'AI 分析', desc: '智能报告' },
];
return (
<div style={{ padding: 24 }}>
{/* 页面标题 */}
<div style={{ marginBottom: 24 }}>
<div style={{ fontFamily: serif, fontSize: 24, fontWeight: 700, color: C.tx1 }}>工作台</div>
<div style={{ fontSize: 13, color: C.tx2, marginTop: 4 }}>2026年4月27日 · 欢迎回来李明医生</div>
</div>
{/* 统计卡片 */}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16, marginBottom: 24 }}>
{stats.map(s => (
<div key={s.label} style={{ background: C.surface, borderRadius: 12, padding: '20px 20px 16px', border: `1px solid ${C.bdLt}`, position: 'relative', overflow: 'hidden', cursor: 'pointer', transition: 'box-shadow 0.15s', boxShadow: `0 1px 3px ${C.shadow}` }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, height: 3, background: s.color }} />
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start' }}>
<div>
<div style={{ fontSize: 13, color: C.tx2, marginBottom: 8 }}>{s.label}</div>
<div style={{ fontFamily: serif, fontSize: 32, fontWeight: 700, color: C.tx1, letterSpacing: -1 }}>{s.value}</div>
</div>
<div style={{ fontSize: 28, opacity: 0.7 }}>{s.icon}</div>
</div>
<div style={{ fontSize: 12, color: s.trend.startsWith('+') ? C.ok : s.trend.startsWith('-') ? C.tx3 : C.tx2, marginTop: 8 }}>
{s.trend} 较上周
</div>
</div>
))}
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 16 }}>
{/* 待办事项 */}
<div style={{ background: C.surface, borderRadius: 12, padding: 20, border: `1px solid ${C.bdLt}`, boxShadow: `0 1px 3px ${C.shadow}` }}>
<div style={{ fontFamily: serif, fontSize: 16, fontWeight: 600, color: C.tx1, marginBottom: 16 }}>待办事项</div>
<div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
{tasks.map(t => {
const colors = { high: [C.err, C.errBg], medium: [C.warn, C.warnBg], low: [C.tx2, C.surfaceAlt] };
const [tc, tb] = colors[t.priority];
return (
<div key={t.title} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px', borderRadius: 10, background: C.bg, cursor: 'pointer', transition: 'background 0.15s', borderLeft: `3px solid ${tc}` }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 14, fontWeight: 500, color: C.tx1 }}>{t.title}</div>
<div style={{ fontSize: 12, color: C.tx3, marginTop: 2 }}>{t.meta}</div>
</div>
</div>
);
})}
</div>
</div>
{/* 快捷操作 */}
<div style={{ background: C.surface, borderRadius: 12, padding: 20, border: `1px solid ${C.bdLt}`, boxShadow: `0 1px 3px ${C.shadow}` }}>
<div style={{ fontFamily: serif, fontSize: 16, fontWeight: 600, color: C.tx1, marginBottom: 16 }}>快捷操作</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
{quickActions.map(a => (
<div key={a.label} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '14px 16px', borderRadius: 10, background: C.bg, cursor: 'pointer', transition: 'all 0.15s', border: `1px solid ${C.bdLt}` }}>
<div style={{ width: 40, height: 40, borderRadius: 10, background: C.primaryLight, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
<div style={{ width: 18, height: 18, borderRadius: 4, background: C.primary }} />
</div>
<div>
<div style={{ fontSize: 14, fontWeight: 500, color: C.tx1 }}>{a.label}</div>
<div style={{ fontSize: 12, color: C.tx3 }}>{a.desc}</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
);
}
/* ─── 患者列表页 ─── */
function PatientListPage() {
const patients = [
{ name: '李小红', age: 56, dept: '心内科', status: '治疗中', phone: '138****2345', lastVisit: '2026-04-25' },
{ name: '王建国', age: 63, dept: '内分泌', status: '随访中', phone: '139****8901', lastVisit: '2026-04-24' },
{ name: '陈美玲', age: 45, dept: '心内科', status: '待诊', phone: '136****5678', lastVisit: '2026-04-23' },
{ name: '赵大伟', age: 71, dept: '肾内科', status: '治疗中', phone: '137****3456', lastVisit: '2026-04-22' },
{ name: '孙丽华', age: 58, dept: '内分泌', status: '已完成', phone: '135****7890', lastVisit: '2026-04-21' },
];
const statusMap = { '治疗中': [C.primary, C.primaryLight], '随访中': [C.ok, C.okBg], '待诊': [C.warn, C.warnBg], '已完成': [C.tx3, C.surfaceAlt] };
const columns = ['姓名', '年龄', '科室', '联系电话', '最近就诊', '状态', '操作'];
return (
<div style={{ padding: 24 }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 20, paddingBottom: 16, borderBottom: `1px solid ${C.bd}` }}>
<div>
<div style={{ fontFamily: serif, fontSize: 20, fontWeight: 700, color: C.tx1 }}>患者管理</div>
<div style={{ fontSize: 13, color: C.tx3, marginTop: 2 }}> 1,847 位患者</div>
</div>
<div style={{ display: 'flex', gap: 8 }}>
<div style={{ padding: '8px 16px', borderRadius: 8, border: `1px solid ${C.bd}`, background: C.surface, fontSize: 13, color: C.tx2, display: 'flex', alignItems: 'center', gap: 6 }}>
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke={C.tx2} strokeWidth="1.5"><circle cx="7" cy="7" r="4"/><path d="M14 14l-3-3"/></svg>
搜索患者...
</div>
<div style={{ padding: '8px 16px', borderRadius: 8, background: C.primary, color: '#fff', fontSize: 13, fontWeight: 500, cursor: 'pointer' }}>+ 新增患者</div>
</div>
</div>
{/* 表格 */}
<div style={{ background: C.surface, borderRadius: 12, overflow: 'hidden', border: `1px solid ${C.bd}`, boxShadow: `0 1px 3px ${C.shadow}` }}>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ background: C.surfaceAlt }}>
{columns.map(col => <th key={col} style={{ padding: '12px 16px', fontSize: 12, fontWeight: 600, color: C.tx2, textAlign: 'left', borderBottom: `1px solid ${C.bd}` }}>{col}</th>)}
</tr>
</thead>
<tbody>
{patients.map((p, i) => {
const [sc, sb] = statusMap[p.status] || statusMap['已完成'];
return (
<tr key={p.name} style={{ borderBottom: i < patients.length - 1 ? `1px solid ${C.bdLt}` : 'none', transition: 'background 0.1s' }}>
<td style={{ padding: '14px 16px', fontSize: 14, fontWeight: 500, color: C.tx1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<div style={{ width: 32, height: 32, borderRadius: 16, background: i === 0 ? C.primaryLight : C.surfaceAlt, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span style={{ fontFamily: serif, fontSize: 13, fontWeight: 600, color: i === 0 ? C.primary : C.tx2 }}>{p.name[0]}</span>
</div>
{p.name}
</div>
</td>
<td style={{ padding: '14px 16px', fontSize: 13, color: C.tx2 }}>{p.age}</td>
<td style={{ padding: '14px 16px', fontSize: 13, color: C.tx2 }}>{p.dept}</td>
<td style={{ padding: '14px 16px', fontSize: 13, color: C.tx2 }}>{p.phone}</td>
<td style={{ padding: '14px 16px', fontSize: 13, color: C.tx3 }}>{p.lastVisit}</td>
<td style={{ padding: '14px 16px' }}>
<span style={{ display: 'inline-block', padding: '2px 10px', borderRadius: 6, fontSize: 12, fontWeight: 500, background: sb, color: sc }}>{p.status}</span>
</td>
<td style={{ padding: '14px 16px', fontSize: 13, color: C.primary, cursor: 'pointer' }}>查看详情</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
/* ─── 侧边栏 ─── */
function Sidebar({ active = 'dashboard' }) {
const menuItems = [
{ group: '概览', items: [{ key: 'dashboard', label: '工作台' }] },
{ group: '健康管理', items: [
{ key: 'patients', label: '患者管理' },
{ key: 'appointments', label: '预约排班' },
{ key: 'followup', label: '随访管理' },
{ key: 'consultation', label: '咨询管理' },
{ key: 'articles', label: '内容管理' },
]},
{ group: '数据中心', items: [
{ key: 'ai', label: 'AI 分析' },
{ key: 'statistics', label: '统计报表' },
{ key: 'alerts', label: '预警管理' },
]},
{ group: '系统', items: [
{ key: 'users', label: '用户权限' },
{ key: 'settings', label: '系统设置' },
]},
];
return (
<div style={{ width: 240, height: '100%', background: C.surface, borderRight: `1px solid ${C.bd}`, display: 'flex', flexDirection: 'column', flexShrink: 0 }}>
{/* Logo */}
<div style={{ height: 56, display: 'flex', alignItems: 'center', padding: '0 20px', borderBottom: `1px solid ${C.bd}` }}>
<div style={{ width: 28, height: 28, borderRadius: 6, background: C.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 13, fontWeight: 800 }}>H</div>
<span style={{ marginLeft: 10, fontFamily: serif, fontSize: 15, fontWeight: 700, color: C.tx1 }}>HMS 健康</span>
</div>
{/* 菜单 */}
<div style={{ flex: 1, padding: '8px 0', overflow: 'auto' }}>
{menuItems.map(g => (
<div key={g.group}>
<div style={{ padding: '16px 20px 6px', fontSize: 11, fontWeight: 600, color: C.tx3, letterSpacing: '0.5px', textTransform: 'uppercase' }}>{g.group}</div>
{g.items.map(item => (
<div key={item.key} style={{
display: 'flex', alignItems: 'center', height: 36, margin: '1px 8px', padding: '0 12px',
borderRadius: 8, cursor: 'pointer', fontSize: 14,
background: active === item.key ? C.primaryLight : 'transparent',
color: active === item.key ? C.primary : C.tx2,
fontWeight: active === item.key ? 500 : 400,
transition: 'all 0.15s',
}}>{item.label}</div>
))}
</div>
))}
</div>
</div>
);
}
/* ─── 顶部栏 ─── */
function Header({ title }) {
return (
<div style={{ height: 56, padding: '0 24px', background: C.surface, borderBottom: `1px solid ${C.bd}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<div style={{ fontSize: 15, fontWeight: 600, color: C.tx1 }}>{title}</div>
<div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
<div style={{ width: 32, height: 32, borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer', color: C.tx2 }}>
<svg width="18" height="18" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.5"><path d="M8 2a5 5 0 015 5c0 4-5 7-5 7S3 11 3 7a5 5 0 015-5z"/></svg>
</div>
<div style={{ width: 32, height: 32, borderRadius: 16, background: C.primary, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: 13, fontWeight: 600 }}></div>
</div>
</div>
);
}
/* ─── 完整布局 ─── */
function FullLayout({ children, title }) {
return (
<div style={{ display: 'flex', height: '100vh' }}>
<Sidebar />
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<Header title={title} />
<div style={{ flex: 1, overflow: 'auto', background: C.bg }}>{children}</div>
</div>
</div>
);
}
/* ─── 页面路由 ─── */
function App() {
const [page, setPage] = React.useState('both');
return (
<div style={{ padding: 40, display: 'flex', gap: 48, justifyContent: 'center', flexWrap: 'wrap', minHeight: '100vh', alignItems: 'flex-start' }}>
{/* Dashboard */}
<div>
<div style={{ textAlign: 'center', marginBottom: 16 }}>
<div style={{ fontFamily: serif, fontSize: 18, fontWeight: 700, color: C.tx1 }}>Dashboard · 工作台</div>
<div style={{ fontSize: 12, color: C.tx2, marginTop: 4 }}>Home.tsx 新设计</div>
</div>
<BrowserFrame title="localhost:5174/dashboard">
<div style={{ width: 1100, height: 700 }}>
<FullLayout title="工作台"><DashboardPage /></FullLayout>
</div>
</BrowserFrame>
</div>
{/* 患者列表 */}
<div>
<div style={{ textAlign: 'center', marginBottom: 16 }}>
<div style={{ fontFamily: serif, fontSize: 18, fontWeight: 700, color: C.tx1 }}>患者管理 · 列表</div>
<div style={{ fontSize: 12, color: C.tx2, marginTop: 4 }}>PatientList.tsx 新设计</div>
</div>
<BrowserFrame title="localhost:5174/health/patients">
<div style={{ width: 1100, height: 700 }}>
<FullLayout title="患者管理"><PatientListPage /></FullLayout>
</div>
</BrowserFrame>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>