Files
hms/docs/design/mp-04-article-report.html
iven aa27c5174c docs(mp): 新增小程序全页面 HTML 原型 + UI 优化指南
- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等)
- 新增医生端分包原型(核心 + 临床两个分包)
- 新增 AI 客服对话页原型
- 新增 MP UI 优化指南文档
- 更新 wiki 基础设施和小程序文档
2026-05-17 00:51:07 +08:00

431 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HMS 小程序 — 文章与AI报告</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #1a1a1a; font-family: -apple-system, 'PingFang SC', sans-serif; display: flex; flex-direction: column; align-items: center; padding: 40px 20px; gap: 24px; }
.page-title { color: #999; font-size: 13px; letter-spacing: 0.15em; text-transform: uppercase; }
.note { color: #666; font-size: 12px; max-width: 900px; text-align: center; line-height: 1.8; }
.screens { display: flex; gap: 36px; flex-wrap: wrap; justify-content: center; align-items: flex-start; }
.screen-wrap { display: flex; flex-direction: column; align-items: center; gap: 12px; }
.screen-label { color: #888; font-size: 12px; font-style: italic; }
::-webkit-scrollbar { width: 0; height: 0; }
</style>
</head>
<body>
<div class="page-title">HMS 小程序 · 文章与 AI 报告</div>
<div class="note">温润东方风设计系统 — 文章列表/详情 + AI 报告列表/详情,四屏并排展示。内容为王,阅读体验优先。</div>
<div id="root"></div>
<script type="text/babel">
// ─── iOS 设备框 ───
const iosFrameStyles = {
wrapper: { display: 'inline-block', padding: 12, background: '#000', borderRadius: 60, boxShadow: '0 0 0 2px #1f2937, 0 20px 60px rgba(0,0,0,0.3)', position: 'relative' },
screen: { position: 'relative', borderRadius: 48, overflow: 'hidden', background: '#fff' },
statusBar: { position: 'absolute', top: 0, left: 0, right: 0, height: 54, display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0 32px', fontSize: 16, fontWeight: 600, fontFamily: '-apple-system, "SF Pro Text", sans-serif', zIndex: 20, pointerEvents: 'none' },
dynamicIsland: { position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)', width: 124, height: 36, background: '#000', borderRadius: 999, zIndex: 30 },
content: { position: 'absolute', top: 54, left: 0, right: 0, bottom: 34, overflow: 'auto' },
homeIndicator: { position: 'absolute', bottom: 10, left: '50%', transform: 'translateX(-50%)', width: 140, height: 5, background: 'rgba(0,0,0,0.3)', borderRadius: 999, zIndex: 10 },
};
function IosFrame({ children, width = 370, height = 800, time = '9:41', battery = 85, darkStatus = false }) {
const statusColor = darkStatus ? '#fff' : '#000';
return (
<div style={iosFrameStyles.wrapper}>
<div style={{ ...iosFrameStyles.screen, width, height }}>
<div style={{ ...iosFrameStyles.statusBar, color: statusColor }}>
<span>{time}</span>
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<svg width="16" height="12" viewBox="0 0 16 12" fill="none"><path d="M8 11.5a1 1 0 100-2 1 1 0 000 2z" fill={statusColor}/><path d="M3 7.5a7 7 0 0110 0" stroke={statusColor} strokeWidth="1.3" fill="none" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke={statusColor} strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7"/></svg>
<div style={{ width: 26, height: 12, border: `1.5px solid ${statusColor}`, borderRadius: 3, padding: 1, position: 'relative' }}>
<div style={{ width: `${battery}%`, height: '100%', background: statusColor, borderRadius: 1 }} />
</div>
</div>
</div>
<div style={iosFrameStyles.dynamicIsland} />
<div style={iosFrameStyles.content}>{children}</div>
<div style={iosFrameStyles.homeIndicator} />
</div>
</div>
);
}
// ─── 设计 Token ───
const T = {
pri: '#C4623A', priL: '#F0DDD4', priD: '#8B3E1F',
bg: '#F5F0EB', card: '#FFFFFF', surface: '#EDE8E2',
tx: '#2D2A26', tx2: '#5A554F', tx3: '#78716C',
bd: '#E8E2DC', bdL: '#F0EBE5',
acc: '#5B7A5E', accL: '#E8F0E8',
wrn: '#C4873A', wrnL: '#FFF3E0',
dan: '#B54A4A', danL: '#FDEAEA',
serif: "Georgia, 'Times New Roman', serif",
sans: "-apple-system, 'PingFang SC', sans-serif",
r: 16, rSm: 12, rXs: 8,
};
// ─── 通用导航栏 ───
function NavBar({ title }) {
return (
<div style={{
height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center',
borderBottom: `1px solid ${T.bdL}`, position: 'relative', background: T.bg,
flexShrink: 0
}}>
<svg style={{ position: 'absolute', left: 16, top: '50%', transform: 'translateY(-50%)' }} width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M15 19l-7-7 7-7" stroke={T.tx} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx }}>{title}</span>
</div>
);
}
// ─── 屏幕1文章列表 ───
function ArticleList() {
const categories = ['全部', '高血压', '糖尿病', '肾病', '透析'];
const articles = [
{ title: '高血压患者日常饮食指南', source: '健康时报', date: '5月8日', summary: '科学饮食是控制血压的关键,低盐、低脂、高纤维...' },
{ title: '血液透析的日常注意事项', source: '肾病频道', date: '5月7日', summary: '透析患者需要特别注意水分控制与营养补充...' },
{ title: '糖尿病患者如何科学运动', source: '医学前沿', date: '5月5日', summary: '规律运动有助控制血糖,但需注意运动强度...' },
{ title: '肾脏健康的十个信号', source: '健康管理', date: '5月3日', summary: '早期发现肾脏问题至关重要,关注这些预警信号...' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="健康资讯" />
{/* 分类标签 */}
<div style={{
display: 'flex', gap: 8, padding: '12px 16px', flexShrink: 0,
overflowX: 'auto', background: T.bg
}}>
{categories.map((cat, i) => (
<div key={cat} style={{
padding: '8px 18px', borderRadius: 20, flexShrink: 0, fontSize: 14,
fontWeight: i === 0 ? 600 : 400,
background: i === 0 ? T.pri : T.surface,
color: i === 0 ? '#fff' : T.tx2,
fontFamily: T.sans,
boxShadow: i === 0 ? `0 2px 8px rgba(196,98,58,0.2)` : 'none',
transition: 'all 0.2s',
}}>{cat}</div>
))}
</div>
{/* 文章列表 */}
<div style={{ flex: 1, overflow: 'auto', padding: '0 16px 16px' }}>
{articles.map((a, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.r, padding: 14, marginBottom: 12,
display: 'flex', gap: 14,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
}}>
{/* 缩略图占位 */}
<div style={{
width: 80, height: 80, borderRadius: T.rXs, flexShrink: 0,
background: T.surface, display: 'flex', alignItems: 'center', justifyContent: 'center',
}}>
<span style={{ fontSize: 12, color: T.tx3 }}>配图</span>
</div>
{/* 文字区 */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', justifyContent: 'space-between', minWidth: 0 }}>
<div>
<div style={{
fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: T.tx,
lineHeight: 1.35, marginBottom: 4,
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
}}>{a.title}</div>
<div style={{
fontSize: 13, color: T.tx2, lineHeight: 1.4,
whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
}}>{a.summary}</div>
</div>
<div style={{ display: 'flex', gap: 12, fontSize: 12, color: T.tx3 }}>
<span>{a.source}</span>
<span>{a.date}</span>
</div>
</div>
</div>
))}
</div>
</div>
);
}
// ─── 屏幕2文章详情 ───
function ArticleDetail() {
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column', position: 'relative' }}>
<NavBar title="文章详情" />
<div style={{ flex: 1, overflow: 'auto', padding: '20px 16px 80px' }}>
{/* 文章标题 */}
<div style={{
fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.tx,
lineHeight: 1.4, marginBottom: 12,
}}>高血压患者日常饮食指南</div>
{/* 元信息 */}
<div style={{ display: 'flex', gap: 16, fontSize: 13, color: T.tx3, marginBottom: 16 }}>
<span>健康时报</span>
<span>2026年5月8日</span>
<span>1.2k 阅读</span>
</div>
{/* 分隔线 */}
<div style={{ height: 1, background: T.bdL, marginBottom: 20 }} />
{/* 正文 */}
<div style={{ fontSize: 15, color: T.tx2, lineHeight: 1.8, fontFamily: T.sans }}>
<p style={{ marginBottom: 16, textIndent: '2em' }}>
高血压是一种常见的慢性疾病合理的饮食习惯对于控制血压至关重要研究表明低盐低脂高纤维的饮食模式能够有效降低血压水平减少心血管疾病的发生风险
</p>
<p style={{ marginBottom: 16, textIndent: '2em' }}>
建议每日食盐摄入量控制在5克以内多食用新鲜蔬果全谷物和优质蛋白避免高钠加工食品如腌制品方便面等同时适量补充富含钾钙的食物如香蕉菠菜和牛奶
</p>
<p style={{ textIndent: '2em' }}>
除了饮食调节还应注意控制总热量摄入维持健康体重每周进行至少150分钟的中等强度有氧运动如快走游泳等定期监测血压遵医嘱服药...
</p>
</div>
</div>
{/* 底部浮动栏 */}
<div style={{
position: 'absolute', bottom: 34, left: 0, right: 0, height: 60,
background: T.card, borderTop: `1px solid ${T.bdL}`,
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 32,
padding: '0 16px',
}}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
<path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" stroke={T.tx3} strokeWidth="1.8" fill="none"/>
</svg>
<span style={{ fontSize: 11, color: T.tx3 }}>收藏</span>
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
<svg width="22" height="22" viewBox="0 0 24 24" fill="none">
<path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8" stroke={T.tx3} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
<polyline points="16,6 12,2 8,6" stroke={T.tx3} strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
<line x1="12" y1="2" x2="12" y2="15" stroke={T.tx3} strokeWidth="1.8" strokeLinecap="round"/>
</svg>
<span style={{ fontSize: 11, color: T.tx3 }}>分享</span>
</div>
</div>
</div>
);
}
// ─── 屏幕3AI报告列表 ───
function AIReportList() {
const reports = [
{ type: '综合评估', typeColor: T.acc, typeBg: T.accL, title: '5月健康综合评估', date: '5月8日', summary: '整体健康状况良好,血压需持续关注,建议保持低盐饮食...' },
{ type: '化验解读', typeColor: T.pri, typeBg: T.priL, title: '血常规化验解读', date: '5月6日', summary: '血红蛋白略低,建议补充铁剂,注意营养均衡...' },
{ type: '趋势分析', typeColor: T.wrn, typeBg: T.wrnL, title: '近30天血压趋势', date: '5月1日', summary: '收缩压呈下降趋势,控制效果良好,请继续保持...' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
<NavBar title="AI 健康报告" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px' }}>
{reports.map((r, i) => (
<div key={i} style={{
background: T.card, borderRadius: T.r, padding: 16, marginBottom: 12,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
position: 'relative',
}}>
{/* 类型标签 */}
<div style={{
display: 'inline-block', padding: '3px 10px', borderRadius: 6,
background: r.typeBg, color: r.typeColor, fontSize: 12, fontWeight: 600,
marginBottom: 10,
}}>{r.type}</div>
{/* 标题 */}
<div style={{
fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: T.tx,
marginBottom: 6, lineHeight: 1.35,
}}>{r.title}</div>
{/* 日期 */}
<div style={{ fontSize: 12, color: T.tx3, marginBottom: 8 }}>{r.date}</div>
{/* 摘要 */}
<div style={{
fontSize: 13, color: T.tx2, lineHeight: 1.5,
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
}}>{r.summary}</div>
{/* 右箭头 */}
<svg style={{ position: 'absolute', right: 16, top: '50%', transform: 'translateY(-50%)' }} width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M9 5l7 7-7 7" stroke={T.tx3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
))}
</div>
</div>
);
}
// ─── 屏幕4AI报告详情 ───
function AIReportDetail() {
const score = 85;
const circumference = 2 * Math.PI * 52;
const offset = circumference - (score / 100) * circumference;
const indicators = [
{ label: '血压', value: '130/85 mmHg', status: '偏高', statusType: 'warn' },
{ label: '血糖', value: '5.6 mmol/L', status: '正常', statusType: 'good' },
{ label: '血红蛋白', value: '110 g/L', status: '偏低', statusType: 'warn' },
{ label: '心率', value: '72 bpm', status: '正常', statusType: 'good' },
];
return (
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column', position: 'relative' }}>
<NavBar title="报告详情" />
<div style={{ flex: 1, overflow: 'auto', padding: '16px 16px 80px' }}>
{/* 报告头部 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 20, marginBottom: 12,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)', textAlign: 'center',
}}>
<div style={{
display: 'inline-block', padding: '4px 14px', borderRadius: 6,
background: T.accL, color: T.acc, fontSize: 13, fontWeight: 600,
marginBottom: 12,
}}>综合评估</div>
<div style={{
fontFamily: T.serif, fontSize: 20, fontWeight: 700, color: T.tx,
marginBottom: 4,
}}>5月健康综合评估</div>
<div style={{ fontSize: 13, color: T.tx3 }}>2026年5月8日</div>
</div>
{/* 综合评分环 */}
<div style={{
background: T.card, borderRadius: T.r, padding: '24px 20px', marginBottom: 12,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)', display: 'flex', flexDirection: 'column',
alignItems: 'center',
}}>
<svg width="140" height="140" viewBox="0 0 140 140" style={{ marginBottom: 12 }}>
{/* 背景圆 */}
<circle cx="70" cy="70" r="52" fill="none" stroke={T.bdL} strokeWidth="10" />
{/* 进度圆 */}
<circle cx="70" cy="70" r="52" fill="none"
stroke={T.acc} strokeWidth="10" strokeLinecap="round"
strokeDasharray={circumference} strokeDashoffset={offset}
transform="rotate(-90 70 70)"
style={{ transition: 'stroke-dashoffset 1s ease' }}
/>
{/* 中心文字 */}
<text x="70" y="64" textAnchor="middle" fontFamily={T.serif} fontSize="36" fontWeight="700" fill={T.tx}>{score}</text>
<text x="70" y="86" textAnchor="middle" fontFamily={T.sans} fontSize="14" fill={T.tx3}>/100</text>
</svg>
<div style={{
fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.acc,
}}>良好</div>
</div>
{/* 分析结论 */}
<div style={{
background: T.accL, borderRadius: T.r, padding: '14px 16px', marginBottom: 12,
borderLeft: `4px solid ${T.acc}`,
}}>
<div style={{ fontFamily: T.serif, fontSize: 14, fontWeight: 700, color: T.acc, marginBottom: 8 }}>分析结论</div>
<div style={{ fontSize: 14, color: T.tx2, lineHeight: 1.7 }}>
<div style={{ marginBottom: 6, display: 'flex', gap: 6 }}>
<span style={{ color: T.acc, fontWeight: 700 }}>&bull;</span>
<span>血压控制良好建议继续保持低盐饮食</span>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<span style={{ color: T.acc, fontWeight: 700 }}>&bull;</span>
<span>血红蛋白略有下降建议补充铁剂</span>
</div>
</div>
</div>
{/* 详细指标 */}
<div style={{
background: T.card, borderRadius: T.r, padding: 16, marginBottom: 12,
boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
}}>
<div style={{ fontFamily: T.serif, fontSize: 16, fontWeight: 700, color: T.tx, marginBottom: 14 }}>详细指标</div>
{indicators.map((ind, i) => (
<div key={i} style={{
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
padding: '12px 0',
borderBottom: i < indicators.length - 1 ? `1px solid ${T.bdL}` : 'none',
}}>
<span style={{ fontSize: 15, color: T.tx, fontWeight: 500, minWidth: 72 }}>{ind.label}</span>
<span style={{ fontSize: 14, color: T.tx2, flex: 1, textAlign: 'center' }}>{ind.value}</span>
<div style={{
padding: '3px 10px', borderRadius: 6, fontSize: 12, fontWeight: 600,
background: ind.statusType === 'good' ? T.accL : T.wrnL,
color: ind.statusType === 'good' ? T.acc : T.wrn,
}}>{ind.status}</div>
</div>
))}
</div>
</div>
{/* 底部按钮 */}
<div style={{
position: 'absolute', bottom: 34, left: 0, right: 0,
padding: '12px 16px', background: T.bg,
}}>
<div style={{
height: 50, borderRadius: T.r, background: T.pri,
display: 'flex', alignItems: 'center', justifyContent: 'center',
color: '#fff', fontSize: 16, fontWeight: 600, fontFamily: T.sans,
boxShadow: `0 4px 16px rgba(196,98,58,0.3)`,
gap: 8,
}}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none">
<path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
<polyline points="16,6 12,2 8,6" stroke="#fff" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"/>
<line x1="12" y1="2" x2="12" y2="15" stroke="#fff" strokeWidth="1.8" strokeLinecap="round"/>
</svg>
分享给医生
</div>
</div>
</div>
);
}
// ─── 渲染 ───
function App() {
return (
<div className="screens">
<div className="screen-wrap">
<span className="screen-label">文章列表</span>
<IosFrame time="9:41" battery={85}>
<ArticleList />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">文章详情</span>
<IosFrame time="9:42" battery={84}>
<ArticleDetail />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">AI 报告列表</span>
<IosFrame time="9:43" battery={83}>
<AIReportList />
</IosFrame>
</div>
<div className="screen-wrap">
<span className="screen-label">AI 报告详情</span>
<IosFrame time="9:44" battery={82}>
<AIReportDetail />
</IosFrame>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body>
</html>