- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等) - 新增医生端分包原型(核心 + 临床两个分包) - 新增 AI 客服对话页原型 - 新增 MP UI 优化指南文档 - 更新 wiki 基础设施和小程序文档
330 lines
16 KiB
HTML
330 lines
16 KiB
HTML
<!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"></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: 600px; text-align: center; line-height: 1.8; }
|
|
.screens { display: flex; gap: 48px; 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; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="page-title">HMS 小程序 · 在线咨询</div>
|
|
<div class="note">温润东方风设计系统 — 咨询列表页 + 咨询详情(聊天)页。左侧为咨询列表,右侧为与王医生的聊天详情。</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 = 393, height = 852, 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 ConsultationList() {
|
|
const doctors = [
|
|
{ name: '王医生', dept: '心内科', avatar: '王', avatarBg: '#C4623A', lastMsg: '您的检查报告已出,建议...', time: '10分钟前', unread: 2, opacity: 1 },
|
|
{ name: '李医生', dept: '肾内科', avatar: '李', avatarBg: '#5B7A5E', lastMsg: '复查结果整体平稳', time: '昨天', unread: 0, opacity: 0.7 },
|
|
{ name: '张医生', dept: '全科', avatar: '张', avatarBg: '#C4873A', lastMsg: '门诊随访已完成', time: '3天前', unread: 0, opacity: 0.7 },
|
|
];
|
|
|
|
return (
|
|
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
|
|
{/* 导航栏 */}
|
|
<div style={{ padding: '16px 20px 12px', background: T.card, borderBottom: `1px solid ${T.bdL}` }}>
|
|
<div style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.tx, textAlign: 'center' }}>在线咨询</div>
|
|
</div>
|
|
|
|
{/* 搜索栏 */}
|
|
<div style={{ padding: '12px 20px 4px' }}>
|
|
<div style={{ height: 44, background: T.surface, borderRadius: T.r, display: 'flex', alignItems: 'center', padding: '0 14px', gap: 8 }}>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke={T.tx3} strokeWidth="2" strokeLinecap="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
|
<span style={{ fontSize: 14, color: T.tx3 }}>搜索医生或科室</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 咨询列表 */}
|
|
<div style={{ flex: 1, padding: '8px 20px 20px', display: 'flex', flexDirection: 'column', gap: 8 }}>
|
|
{doctors.map((doc, i) => (
|
|
<div key={i} style={{
|
|
background: T.card,
|
|
borderRadius: T.r,
|
|
padding: 16,
|
|
opacity: doc.opacity,
|
|
boxShadow: '0 1px 4px rgba(0,0,0,0.04)',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 14,
|
|
position: 'relative',
|
|
}}>
|
|
{/* 头像 */}
|
|
<div style={{
|
|
width: 48, height: 48, borderRadius: 24,
|
|
background: doc.avatarBg,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
flexShrink: 0,
|
|
}}>
|
|
<span style={{ color: '#fff', fontSize: 20, fontWeight: 600, fontFamily: T.sans }}>{doc.avatar}</span>
|
|
</div>
|
|
|
|
{/* 信息 */}
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
|
|
<span style={{ fontSize: 16, fontWeight: 600, color: T.tx }}>{doc.name}<span style={{ color: T.tx3, fontWeight: 400, fontSize: 13, marginLeft: 6 }}>· {doc.dept}</span></span>
|
|
<span style={{ fontSize: 12, color: T.tx3 }}>{doc.time}</span>
|
|
</div>
|
|
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
|
<span style={{ fontSize: 14, color: T.tx2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: 200 }}>{doc.lastMsg}</span>
|
|
{doc.unread > 0 && (
|
|
<div style={{
|
|
minWidth: 20, height: 20, borderRadius: 10,
|
|
background: T.dan, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
padding: '0 6px', flexShrink: 0,
|
|
}}>
|
|
<span style={{ color: '#fff', fontSize: 12, fontWeight: 600 }}>{doc.unread}</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
|
|
{/* 底部提示 */}
|
|
<div style={{ textAlign: 'center', padding: '20px 0 8px' }}>
|
|
<span style={{ fontSize: 12, color: T.tx3 }}>共 3 位咨询医生</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─── 咨询详情(聊天页) ───
|
|
function ChatDetail() {
|
|
const messages = [
|
|
{ from: 'doctor', text: '张先生您好,您的血常规报告已出。' },
|
|
{ from: 'me', text: '好的,有什么需要注意的吗?' },
|
|
{ from: 'doctor', text: '血红蛋白略有下降,建议补充铁剂,下月复查。' },
|
|
{ from: 'doctor', text: '我帮您开个处方,注意按时服用。' },
|
|
{ from: 'me', text: '谢谢医生!' },
|
|
];
|
|
|
|
const timeMarkers = ['今天 09:32', null, null, '09:45', null];
|
|
|
|
return (
|
|
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
|
|
{/* 导航栏 */}
|
|
<div style={{
|
|
padding: '16px 20px 12px',
|
|
background: T.card,
|
|
borderBottom: `1px solid ${T.bdL}`,
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'space-between',
|
|
}}>
|
|
{/* 返回箭头 */}
|
|
<div style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke={T.tx} strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M15 18l-6-6 6-6"/></svg>
|
|
</div>
|
|
{/* 标题 */}
|
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
|
<span style={{ fontFamily: T.serif, fontSize: 17, fontWeight: 700, color: T.tx }}>王医生·心内科</span>
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4, marginTop: 2 }}>
|
|
<div style={{ width: 6, height: 6, borderRadius: 3, background: T.acc }} />
|
|
<span style={{ fontSize: 11, color: T.acc }}>在线</span>
|
|
</div>
|
|
</div>
|
|
{/* 更多 */}
|
|
<div style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke={T.tx3} strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="5" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="12" cy="19" r="1"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 聊天区域 */}
|
|
<div style={{ flex: 1, padding: '16px 20px', display: 'flex', flexDirection: 'column', gap: 4, overflow: 'auto' }}>
|
|
{/* 日期标记 */}
|
|
<div style={{ textAlign: 'center', margin: '4px 0 12px' }}>
|
|
<span style={{ fontSize: 11, color: T.tx3, background: T.surface, padding: '3px 12px', borderRadius: 10 }}>今天 09:30</span>
|
|
</div>
|
|
|
|
{messages.map((msg, i) => (
|
|
<React.Fragment key={i}>
|
|
{/* 时间标记 */}
|
|
{timeMarkers[i] && i > 0 && (
|
|
<div style={{ textAlign: 'center', margin: '8px 0' }}>
|
|
<span style={{ fontSize: 11, color: T.tx3, background: T.surface, padding: '3px 12px', borderRadius: 10 }}>{timeMarkers[i]}</span>
|
|
</div>
|
|
)}
|
|
|
|
{msg.from === 'doctor' ? (
|
|
/* 医生消息 — 左对齐 */
|
|
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 4 }}>
|
|
<div style={{
|
|
width: 36, height: 36, borderRadius: 18,
|
|
background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
flexShrink: 0,
|
|
}}>
|
|
<span style={{ color: '#fff', fontSize: 15, fontWeight: 600 }}>王</span>
|
|
</div>
|
|
<div style={{
|
|
background: T.card,
|
|
borderRadius: '4px 16px 16px 16px',
|
|
padding: '10px 14px',
|
|
maxWidth: '72%',
|
|
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
|
|
}}>
|
|
<span style={{ fontSize: 15, color: T.tx, lineHeight: 1.6 }}>{msg.text}</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
/* 我的消息 — 右对齐 */
|
|
<div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'flex-end', gap: 10, marginBottom: 4 }}>
|
|
<div style={{
|
|
background: T.priL,
|
|
borderRadius: '16px 4px 16px 16px',
|
|
padding: '10px 14px',
|
|
maxWidth: '72%',
|
|
}}>
|
|
<span style={{ fontSize: 15, color: T.tx, lineHeight: 1.6 }}>{msg.text}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
|
|
{/* 正在输入提示 */}
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
|
|
<div style={{
|
|
width: 36, height: 36, borderRadius: 18,
|
|
background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
flexShrink: 0,
|
|
}}>
|
|
<span style={{ color: '#fff', fontSize: 15, fontWeight: 600 }}>王</span>
|
|
</div>
|
|
<div style={{
|
|
background: T.card, borderRadius: '4px 16px 16px 16px',
|
|
padding: '10px 16px', display: 'flex', gap: 4, alignItems: 'center',
|
|
}}>
|
|
<div style={{ width: 6, height: 6, borderRadius: 3, background: T.tx3, animation: 'pulse 1.4s infinite' }} />
|
|
<div style={{ width: 6, height: 6, borderRadius: 3, background: T.tx3, animation: 'pulse 1.4s infinite 0.2s' }} />
|
|
<div style={{ width: 6, height: 6, borderRadius: 3, background: T.tx3, animation: 'pulse 1.4s infinite 0.4s' }} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* 底部输入栏 */}
|
|
<div style={{
|
|
background: T.card,
|
|
borderTop: `1px solid ${T.bdL}`,
|
|
padding: '10px 16px',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: 10,
|
|
}}>
|
|
{/* 附加按钮 */}
|
|
<div style={{ width: 36, height: 36, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke={T.tx3} strokeWidth="2" strokeLinecap="round"><circle cx="12" cy="12" r="10"/><path d="M12 8v8M8 12h8"/></svg>
|
|
</div>
|
|
|
|
{/* 输入框 */}
|
|
<div style={{
|
|
flex: 1, height: 40, background: T.surface, borderRadius: 20,
|
|
display: 'flex', alignItems: 'center', padding: '0 14px',
|
|
}}>
|
|
<span style={{ fontSize: 14, color: T.tx3 }}>输入消息...</span>
|
|
</div>
|
|
|
|
{/* 发送按钮 */}
|
|
<div style={{
|
|
width: 40, height: 40, borderRadius: 20,
|
|
background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
flexShrink: 0,
|
|
}}>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round"><path d="M22 2L11 13"/><path d="M22 2L15 22L11 13L2 9L22 2Z"/></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}>
|
|
<ConsultationList />
|
|
</IosFrame>
|
|
</div>
|
|
<div className="screen-wrap">
|
|
<span className="screen-label">咨询详情 — 聊天</span>
|
|
<IosFrame time="9:45" battery={84}>
|
|
<ChatDetail />
|
|
</IosFrame>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
</script>
|
|
|
|
<style>
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 0.3; }
|
|
50% { opacity: 1; }
|
|
}
|
|
</style>
|
|
</body>
|
|
</html>
|