chore: 设计规格文档 + 销售数据 + 脚本工具 + 根目录 monorepo 配置
- docs/: 设计规格、讨论记录、销售数据、健康管理文档 - scripts/: 辅助脚本 - package.json + pnpm-lock.yaml: monorepo 根配置
This commit is contained in:
317
docs/design/miniprogram/design-system.html
Normal file
317
docs/design/miniprogram/design-system.html
Normal file
@@ -0,0 +1,317 @@
|
||||
<!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" 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 rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<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 { font-family: -apple-system, 'PingFang SC', 'Helvetica Neue', sans-serif; background: #F5F0EB; color: #2D2A26; line-height: 1.6; }
|
||||
:root {
|
||||
--bg: #F5F0EB;
|
||||
--surface: #FFFFFF;
|
||||
--surface-alt: #EDE8E2;
|
||||
--text-primary: #2D2A26;
|
||||
--text-secondary: #7A756E;
|
||||
--text-tertiary: #A8A29E;
|
||||
--accent: #C4623A;
|
||||
--accent-light: #F0DDD4;
|
||||
--accent-dark: #8B3E1F;
|
||||
--success: #5B7A5E;
|
||||
--success-light: #E8F0E8;
|
||||
--warning: #C4873A;
|
||||
--warning-light: #FFF3E0;
|
||||
--danger: #B54A4A;
|
||||
--danger-light: #FDEAEA;
|
||||
--border: #E8E2DC;
|
||||
--border-light: #F0EBE5;
|
||||
--shadow: 0 1px 4px rgba(45,42,38,0.06);
|
||||
--shadow-md: 0 4px 16px rgba(45,42,38,0.08);
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--radius-lg: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="text/babel">
|
||||
const dsStyles = {
|
||||
page: { maxWidth: 1200, margin: '0 auto', padding: '60px 40px' },
|
||||
title: { fontFamily: '"Noto Serif SC", serif', fontSize: 36, fontWeight: 700, color: '#2D2A26', marginBottom: 8 },
|
||||
subtitle: { fontSize: 16, color: '#7A756E', marginBottom: 48, letterSpacing: '0.02em' },
|
||||
section: { marginBottom: 56 },
|
||||
sectionTitle: { fontFamily: '"Noto Serif SC", serif', fontSize: 22, fontWeight: 600, color: '#2D2A26', marginBottom: 24, paddingBottom: 12, borderBottom: '1px solid #E8E2DC' },
|
||||
row: { display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 16 },
|
||||
card: { background: '#fff', borderRadius: 12, padding: 24, boxShadow: '0 1px 4px rgba(45,42,38,0.06)' },
|
||||
};
|
||||
|
||||
/* ─── 色彩系统 ─── */
|
||||
const ColorSwatch = ({ name, hex, desc, light }) => (
|
||||
<div style={{ flex: '0 0 160px' }}>
|
||||
<div style={{
|
||||
height: 80, borderRadius: 12, background: hex,
|
||||
border: light ? '1px solid #E8E2DC' : 'none',
|
||||
marginBottom: 8, boxShadow: hex === '#FFFFFF' ? '0 1px 4px rgba(0,0,0,0.08)' : 'none',
|
||||
}} />
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26' }}>{name}</div>
|
||||
<div style={{ fontSize: 12, color: '#7A756E', fontFamily: 'monospace' }}>{hex}</div>
|
||||
{desc && <div style={{ fontSize: 11, color: '#A8A29E', marginTop: 2 }}>{desc}</div>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const ColorSection = () => {
|
||||
const colors = [
|
||||
{ name: '背景', hex: '#F5F0EB', desc: 'warm cream', light: true },
|
||||
{ name: '卡片', hex: '#FFFFFF', desc: 'surface', light: true },
|
||||
{ name: '辅助底', hex: '#EDE8E2', desc: 'surface-alt', light: true },
|
||||
{ name: '主文字', hex: '#2D2A26', desc: 'warm black' },
|
||||
{ name: '次文字', hex: '#7A756E', desc: 'warm gray' },
|
||||
{ name: '淡文字', hex: '#A8A29E', desc: 'tertiary' },
|
||||
{ name: '边框', hex: '#E8E2DC', desc: 'border', light: true },
|
||||
];
|
||||
const accent = [
|
||||
{ name: '强调色', hex: '#C4623A', desc: 'terracotta' },
|
||||
{ name: '强调浅', hex: '#F0DDD4', desc: 'accent-light', light: true },
|
||||
{ name: '强调深', hex: '#8B3E1F', desc: 'accent-dark' },
|
||||
];
|
||||
const status = [
|
||||
{ name: '成功', hex: '#5B7A5E', desc: 'sage green' },
|
||||
{ name: '警告', hex: '#C4873A', desc: 'warm amber' },
|
||||
{ name: '危险', hex: '#B54A4A', desc: 'muted red' },
|
||||
{ name: '成功浅', hex: '#E8F0E8', desc: 'success-light', light: true },
|
||||
{ name: '警告浅', hex: '#FFF3E0', desc: 'warning-light', light: true },
|
||||
{ name: '危险浅', hex: '#FDEAEA', desc: 'danger-light', light: true },
|
||||
];
|
||||
return (
|
||||
<div style={dsStyles.section}>
|
||||
<div style={dsStyles.sectionTitle}>色彩</div>
|
||||
<div style={{ fontSize: 13, color: '#7A756E', marginBottom: 16 }}>
|
||||
温润米底 + 赤土橙贯穿全场。单一 accent 不多色。色值从 oklch 定义保证和谐。
|
||||
</div>
|
||||
<div style={dsStyles.row}>
|
||||
{colors.map(c => <ColorSwatch key={c.hex} {...c} />)}
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, color: '#2D2A26', margin: '20px 0 12px' }}>强调色</div>
|
||||
<div style={dsStyles.row}>
|
||||
{accent.map(c => <ColorSwatch key={c.hex} {...c} />)}
|
||||
</div>
|
||||
<div style={{ fontSize: 14, fontWeight: 600, color: '#2D2A26', margin: '20px 0 12px' }}>状态色</div>
|
||||
<div style={dsStyles.row}>
|
||||
{status.map(c => <ColorSwatch key={c.hex} {...c} />)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ─── 字体系统 ─── */
|
||||
const TypographySection = () => {
|
||||
const sizes = [
|
||||
{ name: '大标题', style: { fontFamily: '"Noto Serif SC", serif', fontSize: 28, fontWeight: 700 } },
|
||||
{ name: '标题', style: { fontFamily: '"Noto Serif SC", serif', fontSize: 22, fontWeight: 600 } },
|
||||
{ name: '小标题', style: { fontFamily: '"Noto Serif SC", serif', fontSize: 17, fontWeight: 600 } },
|
||||
{ name: '正文', style: { fontFamily: '-apple-system, "PingFang SC", sans-serif', fontSize: 15, fontWeight: 400 } },
|
||||
{ name: '辅助文字', style: { fontFamily: '-apple-system, "PingFang SC", sans-serif', fontSize: 13, fontWeight: 400, color: '#7A756E' } },
|
||||
{ name: '数据大号', style: { fontFamily: '"Noto Serif SC", serif', fontSize: 36, fontWeight: 700, color: '#C4623A' } },
|
||||
{ name: '数据中号', style: { fontFamily: '"Noto Serif SC", serif', fontSize: 24, fontWeight: 600 } },
|
||||
{ name: '数据单位', style: { fontFamily: '-apple-system, "PingFang SC", sans-serif', fontSize: 13, fontWeight: 400, color: '#A8A29E' } },
|
||||
];
|
||||
return (
|
||||
<div style={dsStyles.section}>
|
||||
<div style={dsStyles.sectionTitle}>字体</div>
|
||||
<div style={{ fontSize: 13, color: '#7A756E', marginBottom: 20 }}>
|
||||
衬线 display (Noto Serif SC) 用于标题和数据数字,系统无衬线用于正文。字重对比鲜明。
|
||||
</div>
|
||||
<div style={{ background: '#fff', borderRadius: 12, padding: 28, boxShadow: '0 1px 4px rgba(45,42,38,0.06)' }}>
|
||||
{sizes.map(s => (
|
||||
<div key={s.name} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', padding: '12px 0', borderBottom: '1px solid #F0EBE5' }}>
|
||||
<div style={s.style}>{s.name}</div>
|
||||
<div style={{ fontSize: 12, color: '#A8A29E', fontFamily: 'monospace' }}>
|
||||
{s.style.fontSize}px / {s.style.fontWeight} {s.style.fontFamily?.includes('Serif') ? '· serif' : '· sans'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ─── 间距与圆角 ─── */
|
||||
const SpacingSection = () => {
|
||||
const spacings = [4, 8, 12, 16, 20, 24, 32, 40, 48];
|
||||
const radii = [
|
||||
{ name: 'sm', value: 8 },
|
||||
{ name: 'md', value: 12 },
|
||||
{ name: 'lg', value: 16 },
|
||||
{ name: 'xl', value: 20 },
|
||||
{ name: 'pill', value: 999 },
|
||||
];
|
||||
return (
|
||||
<div style={dsStyles.section}>
|
||||
<div style={dsStyles.sectionTitle}>间距与圆角</div>
|
||||
<div style={{ display: 'flex', gap: 32, flexWrap: 'wrap' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 13, color: '#7A756E', marginBottom: 12 }}>间距基准 4px</div>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 8 }}>
|
||||
{spacings.map(s => (
|
||||
<div key={s} style={{ textAlign: 'center' }}>
|
||||
<div style={{ width: s, height: s, background: '#C4623A', borderRadius: 2, opacity: 0.6 }} />
|
||||
<div style={{ fontSize: 10, color: '#A8A29E', marginTop: 4 }}>{s}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 13, color: '#7A756E', marginBottom: 12 }}>圆角</div>
|
||||
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||
{radii.map(r => (
|
||||
<div key={r.name} style={{ textAlign: 'center' }}>
|
||||
<div style={{ width: 48, height: 48, border: '2px solid #C4623A', borderRadius: r.value === 999 ? 24 : r.value }} />
|
||||
<div style={{ fontSize: 10, color: '#A8A29E', marginTop: 4 }}>{r.name} ({r.value})</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ─── 组件库 ─── */
|
||||
const ComponentSection = () => {
|
||||
const buttonStyles = {
|
||||
row: { display: 'flex', gap: 12, alignItems: 'center', marginBottom: 16, flexWrap: 'wrap' },
|
||||
primary: { padding: '10px 28px', background: '#C4623A', color: '#fff', border: 'none', borderRadius: 12, fontSize: 15, fontWeight: 600, cursor: 'pointer' },
|
||||
secondary: { padding: '10px 28px', background: 'transparent', color: '#C4623A', border: '1.5px solid #C4623A', borderRadius: 12, fontSize: 15, fontWeight: 600, cursor: 'pointer' },
|
||||
ghost: { padding: '10px 28px', background: 'transparent', color: '#7A756E', border: '1.5px solid #E8E2DC', borderRadius: 12, fontSize: 15, fontWeight: 500, cursor: 'pointer' },
|
||||
disabled: { padding: '10px 28px', background: '#EDE8E2', color: '#A8A29E', border: 'none', borderRadius: 12, fontSize: 15, fontWeight: 500 },
|
||||
small: { padding: '6px 16px', background: '#C4623A', color: '#fff', border: 'none', borderRadius: 8, fontSize: 13, fontWeight: 600 },
|
||||
};
|
||||
|
||||
const tagStyles = {
|
||||
success: { display: 'inline-block', padding: '3px 10px', borderRadius: 6, fontSize: 12, fontWeight: 500, background: '#E8F0E8', color: '#5B7A5E' },
|
||||
warning: { display: 'inline-block', padding: '3px 10px', borderRadius: 6, fontSize: 12, fontWeight: 500, background: '#FFF3E0', color: '#C4873A' },
|
||||
danger: { display: 'inline-block', padding: '3px 10px', borderRadius: 6, fontSize: 12, fontWeight: 500, background: '#FDEAEA', color: '#B54A4A' },
|
||||
default: { display: 'inline-block', padding: '3px 10px', borderRadius: 6, fontSize: 12, fontWeight: 500, background: '#F0EBE5', color: '#7A756E' },
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={dsStyles.section}>
|
||||
<div style={dsStyles.sectionTitle}>组件</div>
|
||||
<div style={{ display: 'flex', gap: 24, flexDirection: 'column' }}>
|
||||
|
||||
{/* 按钮 */}
|
||||
<div style={dsStyles.card}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26', marginBottom: 12 }}>按钮</div>
|
||||
<div style={buttonStyles.row}>
|
||||
<button style={buttonStyles.primary}>主要操作</button>
|
||||
<button style={buttonStyles.secondary}>次要操作</button>
|
||||
<button style={buttonStyles.ghost}>取消</button>
|
||||
<button style={buttonStyles.disabled}>禁用</button>
|
||||
<button style={buttonStyles.small}>小按钮</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 标签 */}
|
||||
<div style={dsStyles.card}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26', marginBottom: 12 }}>状态标签</div>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<span style={tagStyles.success}>正常</span>
|
||||
<span style={tagStyles.warning}>偏高</span>
|
||||
<span style={tagStyles.danger}>异常</span>
|
||||
<span style={tagStyles.default}>待处理</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据卡片 */}
|
||||
<div style={dsStyles.card}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26', marginBottom: 12 }}>健康数据卡片</div>
|
||||
<div style={{ display: 'flex', gap: 12 }}>
|
||||
{[
|
||||
{ label: '血压', value: '120/80', unit: 'mmHg', status: '正常', statusType: 'success' },
|
||||
{ label: '心率', value: '72', unit: 'bpm', status: '正常', statusType: 'success' },
|
||||
{ label: '血糖', value: '6.8', unit: 'mmol/L', status: '偏高', statusType: 'warning' },
|
||||
].map(item => (
|
||||
<div key={item.label} style={{ flex: 1, background: '#F5F0EB', borderRadius: 12, padding: 16, textAlign: 'center' }}>
|
||||
<div style={{ fontSize: 13, color: '#7A756E', marginBottom: 8 }}>{item.label}</div>
|
||||
<div style={{ fontFamily: '"Noto Serif SC", serif', fontSize: 28, fontWeight: 700, color: '#2D2A26' }}>{item.value}</div>
|
||||
<div style={{ fontSize: 12, color: '#A8A29E', margin: '4px 0 8px' }}>{item.unit}</div>
|
||||
<span style={tagStyles[item.statusType]}>{item.status}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 列表项 */}
|
||||
<div style={dsStyles.card}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26', marginBottom: 12 }}>列表项</div>
|
||||
<div style={{ borderRadius: 12, border: '1px solid #E8E2DC', overflow: 'hidden' }}>
|
||||
{[
|
||||
{ title: '预约:2026-04-28 上午', sub: '王医生 · 心内科 · 待确认' },
|
||||
{ title: '随访:血压监测', sub: '每日记录血压数据 · 截止 05-01' },
|
||||
{ title: '报告:血常规检验', sub: '2026-04-25 · 已出结果' },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{
|
||||
display: 'flex', alignItems: 'center', padding: '16px 20px',
|
||||
borderBottom: i < 2 ? '1px solid #F0EBE5' : 'none',
|
||||
}}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<div style={{ fontSize: 15, fontWeight: 500, color: '#2D2A26', marginBottom: 2 }}>{item.title}</div>
|
||||
<div style={{ fontSize: 13, color: '#7A756E' }}>{item.sub}</div>
|
||||
</div>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" style={{ color: '#A8A29E', flexShrink: 0 }}>
|
||||
<path d="M6 4l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 图标风格 */}
|
||||
<div style={dsStyles.card}>
|
||||
<div style={{ fontSize: 13, fontWeight: 600, color: '#2D2A26', marginBottom: 12 }}>图标风格 · 线性 1.5px</div>
|
||||
<div style={{ display: 'flex', gap: 24 }}>
|
||||
{[
|
||||
{ label: '首页', path: 'M8 2l6 5v7a1 1 0 01-1 1H3a1 1 0 01-1-1V7l6-5z' },
|
||||
{ label: '心率', path: 'M2 8h3l2-4 3 8 2-4h2' },
|
||||
{ label: '预约', path: 'M3 4h10v9a1 1 0 01-1 1H4a1 1 0 01-1-1V4zM3 4V2M13 4V2M6 7h4M6 10h2' },
|
||||
{ label: '消息', path: 'M2 4a1 1 0 011-1h10a1 1 0 011 1v6a1 1 0 01-1 1H5l-3 3V4z' },
|
||||
{ label: '我的', path: 'M8 8a3 3 0 100-6 3 3 0 000 6zM3 14a5 5 0 0110 0' },
|
||||
].map(ico => (
|
||||
<div key={ico.label} style={{ textAlign: 'center' }}>
|
||||
<svg width="24" height="24" viewBox="0 0 16 16" fill="none" stroke="#7A756E" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d={ico.path} />
|
||||
</svg>
|
||||
<div style={{ fontSize: 11, color: '#A8A29E', marginTop: 4 }}>{ico.label}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/* ─── 渲染 ─── */
|
||||
const App = () => (
|
||||
<div style={dsStyles.page}>
|
||||
<div style={dsStyles.title}>HMS 健康管理 · 设计系统</div>
|
||||
<div style={dsStyles.subtitle}>温润东方风 — Kenya Hara 式克制留白 · 赤土橙 #C4623A 贯穿全场</div>
|
||||
<ColorSection />
|
||||
<TypographySection />
|
||||
<SpacingSection />
|
||||
<ComponentSection />
|
||||
</div>
|
||||
);
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
346
docs/design/miniprogram/preview.html
Normal file
346
docs/design/miniprogram/preview.html
Normal file
@@ -0,0 +1,346 @@
|
||||
<!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>
|
||||
319
docs/design/web/preview.html
Normal file
319
docs/design/web/preview.html
Normal file
@@ -0,0 +1,319 @@
|
||||
<!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>
|
||||
Reference in New Issue
Block a user