- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等) - 新增医生端分包原型(核心 + 临床两个分包) - 新增 AI 客服对话页原型 - 新增 MP UI 优化指南文档 - 更新 wiki 基础设施和小程序文档
399 lines
18 KiB
HTML
399 lines
18 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 小程序 — 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: 700px; 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; }
|
||
@keyframes pulse { 0%,80%,100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<div class="page-title">HMS 小程序 · AI 客服咨询</div>
|
||
<div class="note">
|
||
温润东方风设计系统 — 消息 Tab 改为 AI 客服单窗口(无列表),通知迁移至「我的」页面。<br/>
|
||
左:欢迎状态(首次进入)· 中:对话进行中 · 右:个人中心通知入口
|
||
</div>
|
||
<div id="root"></div>
|
||
|
||
<script type="text/babel">
|
||
// ─── iOS 设备框 ───
|
||
const F = {
|
||
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' },
|
||
status: { 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' },
|
||
island: { position: 'absolute', top: 12, left: '50%', transform: 'translateX(-50%)', width: 124, height: 36, background: '#000', borderRadius: 999, zIndex: 30 },
|
||
body: { position: 'absolute', top: 54, left: 0, right: 0, bottom: 34, overflow: 'auto' },
|
||
home: { 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 Phone({ children, w = 393, h = 852, time = '9:41', battery = 85, dark = false }) {
|
||
const c = dark ? '#fff' : '#000';
|
||
return (
|
||
<div style={F.wrapper}>
|
||
<div style={{ ...F.screen, width: w, height: h }}>
|
||
<div style={{ ...F.status, color: c }}>
|
||
<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={c}/><path d="M3 7.5a7 7 0 0110 0" stroke={c} strokeWidth="1.3" fill="none" strokeLinecap="round"/><path d="M1 4.5a11 11 0 0114 0" stroke={c} strokeWidth="1.3" fill="none" strokeLinecap="round" opacity="0.7"/></svg>
|
||
<div style={{ width: 26, height: 12, border: `1.5px solid ${c}`, borderRadius: 3, padding: 1, position: 'relative' }}>
|
||
<div style={{ width: `${battery}%`, height: '100%', background: c, borderRadius: 1 }} />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style={F.island} />
|
||
<div style={F.body}>{children}</div>
|
||
<div style={F.home} />
|
||
</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',
|
||
dan: '#B54A4A', danL: '#FDEAEA',
|
||
white: '#FFFFFF',
|
||
serif: "Georgia, 'Times New Roman', serif",
|
||
sans: "-apple-system, 'PingFang SC', sans-serif",
|
||
r: 16, rSm: 12, rXs: 8,
|
||
};
|
||
|
||
// ─── 占位:三屏容器 ───
|
||
function App() {
|
||
return (
|
||
<div className="screens">
|
||
{/* 左屏:欢迎状态 */}
|
||
<div className="screen-wrap">
|
||
<Phone><WelcomeChat /></Phone>
|
||
<div className="screen-label">首次进入 · 欢迎状态</div>
|
||
</div>
|
||
|
||
{/* 中屏:对话进行中 */}
|
||
<div className="screen-wrap">
|
||
<Phone><ActiveChat /></Phone>
|
||
<div className="screen-label">对话进行中</div>
|
||
</div>
|
||
|
||
{/* 右屏:个人中心通知入口 */}
|
||
<div className="screen-wrap">
|
||
<Phone><ProfileWithNotify /></Phone>
|
||
<div className="screen-label">个人中心 · 通知入口</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 共享:聊天导航栏 ───
|
||
function ChatNav() {
|
||
return (
|
||
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px 12px', background: T.card, borderBottom: `1px solid ${T.bdL}`, flexShrink: 0 }}>
|
||
{/* 返回 */}
|
||
<div style={{ width: 32, height: 32, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<span style={{ fontSize: 24, fontWeight: 300, color: T.tx }}>‹</span>
|
||
</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 }}>24小时在线</span>
|
||
</div>
|
||
</div>
|
||
{/* 占位 */}
|
||
<div style={{ width: 32 }} />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 共享:底部输入栏 ───
|
||
function ChatBar() {
|
||
return (
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', paddingBottom: 24, background: T.card, borderTop: `1px solid ${T.bdL}`, flexShrink: 0 }}>
|
||
<div style={{ width: 36, height: 36, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<span style={{ fontSize: 22, color: T.tx3, fontWeight: 300 }}>+</span>
|
||
</div>
|
||
<div style={{ flex: 1, height: 40, background: T.surface, borderRadius: 20, padding: '0 14px', display: 'flex', alignItems: 'center' }}>
|
||
<span style={{ color: T.tx3, fontSize: 14 }}>输入您的问题...</span>
|
||
</div>
|
||
<div style={{ width: 40, height: 40, borderRadius: 20, background: T.pri, display: 'flex', alignItems: 'center', justifyContent: 'center', opacity: 0.5 }}>
|
||
<span style={{ color: T.white, fontSize: 20, fontWeight: 700 }}>›</span>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 共享:AI 头像 ───
|
||
function AiAvatar({ size = 36 }) {
|
||
return (
|
||
<div style={{ width: size, height: size, borderRadius: size / 2, background: `linear-gradient(135deg, ${T.pri}, ${T.priD})`, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||
<span style={{ color: T.white, fontSize: size * 0.42, fontWeight: 600 }}>华</span>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 左屏:欢迎状态 ───
|
||
function WelcomeChat() {
|
||
const quickActions = [
|
||
{ icon: '📋', label: '查看报告' },
|
||
{ icon: '💊', label: '用药咨询' },
|
||
{ icon: '📅', label: '预约挂号' },
|
||
{ icon: '🔔', label: '健康提醒' },
|
||
];
|
||
|
||
return (
|
||
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
|
||
<ChatNav />
|
||
|
||
<div style={{ flex: 1, padding: '24px 20px', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 0 }}>
|
||
{/* AI 大头像 */}
|
||
<div style={{ width: 72, height: 72, borderRadius: 36, background: `linear-gradient(135deg, ${T.pri}, ${T.priD})`, display: 'flex', alignItems: 'center', justifyContent: 'center', boxShadow: `0 8px 24px rgba(196, 98, 58, 0.25)` }}>
|
||
<span style={{ color: T.white, fontSize: 32, fontWeight: 600, fontFamily: T.serif }}>华</span>
|
||
</div>
|
||
|
||
{/* 问候语 */}
|
||
<div style={{ marginTop: 16, textAlign: 'center' }}>
|
||
<div style={{ fontSize: 17, fontWeight: 600, color: T.tx }}>您好,我是小华</div>
|
||
<div style={{ fontSize: 13, color: T.tx3, marginTop: 6, lineHeight: 1.6 }}>您的专属健康助手,随时为您解答<br/>健康问题、预约服务、报告解读等</div>
|
||
</div>
|
||
|
||
{/* 分割线 */}
|
||
<div style={{ width: 32, height: 1, background: T.bd, margin: '20px 0 16px' }} />
|
||
|
||
{/* 快捷问题 */}
|
||
<div style={{ fontSize: 12, color: T.tx3, marginBottom: 12 }}>您可能想问</div>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, justifyContent: 'center', maxWidth: 320 }}>
|
||
{quickActions.map((a, i) => (
|
||
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '8px 14px', background: T.card, borderRadius: T.r, border: `1px solid ${T.bdL}`, fontSize: 13, color: T.tx2, cursor: 'pointer' }}>
|
||
<span style={{ fontSize: 15 }}>{a.icon}</span>
|
||
<span>{a.label}</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
<ChatBar />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 占位组件(后续步骤填充)───
|
||
// ─── 中屏:对话进行中 ───
|
||
function ActiveChat() {
|
||
const messages = [
|
||
{ from: 'ai', text: '您好!我是健康助手小华,请问有什么可以帮助您的?' },
|
||
{ from: 'user', text: '我最近体检报告出来了,想了解一下指标情况' },
|
||
{ from: 'ai', text: '好的,我来帮您解读最近的体检报告。请稍等,正在为您查询…', card: true },
|
||
{ from: 'user', text: '好的' },
|
||
{ from: 'ai', text: '已为您找到最近的体检报告(2026-05-10)。整体来看各项指标基本正常,有几个需要关注的点:\n\n1. 空腹血糖略偏高(6.2 mmol/L),建议关注饮食\n2. 维生素D偏低,可适当补充\n3. 其余指标均在正常范围内\n\n如需详细解读某项指标,请告诉我。' },
|
||
];
|
||
|
||
return (
|
||
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
|
||
<ChatNav />
|
||
|
||
<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}>
|
||
{/* 时间间隔 */}
|
||
{i === 3 && (
|
||
<div style={{ textAlign: 'center', margin: '8px 0' }}>
|
||
<span style={{ fontSize: 11, color: T.tx3, background: T.surface, padding: '3px 12px', borderRadius: 10 }}>09:35</span>
|
||
</div>
|
||
)}
|
||
|
||
{msg.from === 'ai' ? (
|
||
/* AI 消息 — 左对齐 */
|
||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 4 }}>
|
||
<AiAvatar />
|
||
<div style={{ maxWidth: 260 }}>
|
||
<div style={{
|
||
background: T.card,
|
||
borderRadius: '4px 16px 16px 16px',
|
||
padding: '10px 14px',
|
||
boxShadow: '0 1px 3px rgba(0,0,0,0.04)',
|
||
}}>
|
||
{msg.card ? (
|
||
/* 带卡片的内容 */
|
||
<div>
|
||
<span style={{ fontSize: 15, color: T.tx, lineHeight: 1.6 }}>{msg.text}</span>
|
||
{/* 报告卡片 */}
|
||
<div style={{ marginTop: 8, padding: '10px 12px', background: T.surface, borderRadius: T.rSm, borderLeft: `3px solid ${T.pri}` }}>
|
||
<div style={{ fontSize: 13, fontWeight: 600, color: T.tx }}>2026年度健康体检</div>
|
||
<div style={{ fontSize: 11, color: T.tx3, marginTop: 2 }}>2026-05-10 · 综合</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<span style={{ fontSize: 15, color: T.tx, lineHeight: 1.6, whiteSpace: 'pre-line' }}>{msg.text}</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
/* 用户消息 — 右对齐 */
|
||
<div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 4 }}>
|
||
<div style={{ maxWidth: 260, background: T.priL, borderRadius: '16px 4px 16px 16px', padding: '10px 14px' }}>
|
||
<span style={{ fontSize: 15, color: T.tx, lineHeight: 1.6 }}>{msg.text}</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</React.Fragment>
|
||
))}
|
||
|
||
{/* AI 正在输入 */}
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 4 }}>
|
||
<AiAvatar />
|
||
<div style={{ background: T.card, borderRadius: '4px 16px 16px 16px', padding: '10px 16px', boxShadow: '0 1px 3px rgba(0,0,0,0.04)' }}>
|
||
<div style={{ 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>
|
||
|
||
<ChatBar />
|
||
</div>
|
||
);
|
||
}
|
||
|
||
// ─── 右屏:个人中心 · 通知入口 ───
|
||
function ProfileWithNotify() {
|
||
const user = { name: '张三', phone: '138****6789' };
|
||
|
||
const menuGroups = [
|
||
{
|
||
label: null,
|
||
items: [
|
||
{ icon: '🔔', label: '消息通知', badge: 3, color: T.pri },
|
||
],
|
||
},
|
||
{
|
||
label: '健康管理',
|
||
items: [
|
||
{ icon: '📊', label: '健康记录', color: '#C4623A' },
|
||
{ icon: '📄', label: '我的报告', color: '#5B7A5E' },
|
||
{ icon: '🤖', label: 'AI 分析', color: '#C4873A' },
|
||
],
|
||
},
|
||
{
|
||
label: '就诊服务',
|
||
items: [
|
||
{ icon: '📅', label: '我的预约', color: '#3A6B8C' },
|
||
{ icon: '🔄', label: '我的随访', color: '#7A5B7A' },
|
||
],
|
||
},
|
||
{
|
||
label: '生活服务',
|
||
items: [
|
||
{ icon: '🎁', label: '积分商城', color: '#C4873A' },
|
||
{ icon: '🏃', label: '线下活动', color: '#5B7A5E' },
|
||
],
|
||
},
|
||
{
|
||
label: '账号',
|
||
items: [
|
||
{ icon: '👤', label: '就诊人管理', color: T.tx2 },
|
||
{ icon: '⚙️', label: '设置', color: T.tx2 },
|
||
],
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div style={{ height: '100%', background: T.bg }}>
|
||
{/* 用户卡片 */}
|
||
<div style={{ padding: '20px 20px 16px', display: 'flex', alignItems: 'center', gap: 14 }}>
|
||
<div style={{ width: 56, height: 56, borderRadius: 28, background: `linear-gradient(135deg, ${T.pri}, ${T.priD})`, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||
<span style={{ color: T.white, fontSize: 24, fontWeight: 600 }}>{user.name.charAt(0)}</span>
|
||
</div>
|
||
<div>
|
||
<div style={{ fontSize: 18, fontWeight: 600, color: T.tx }}>{user.name}</div>
|
||
<div style={{ fontSize: 13, color: T.tx3, marginTop: 2 }}>{user.phone}</div>
|
||
</div>
|
||
<div style={{ marginLeft: 'auto', color: T.tx3, fontSize: 20 }}>›</div>
|
||
</div>
|
||
|
||
{/* 统计行 */}
|
||
<div style={{ display: 'flex', gap: 10, padding: '0 20px 16px' }}>
|
||
{[
|
||
{ label: '健康积分', value: '1,280' },
|
||
{ label: '连续打卡', value: '15天' },
|
||
].map((s, i) => (
|
||
<div key={i} style={{ flex: 1, background: T.card, borderRadius: T.r, padding: '14px 16px' }}>
|
||
<div style={{ fontFamily: T.serif, fontSize: 22, fontWeight: 700, color: T.pri }}>{s.value}</div>
|
||
<div style={{ fontSize: 12, color: T.tx3, marginTop: 2 }}>{s.label}</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* 菜单列表 */}
|
||
<div style={{ padding: '0 20px' }}>
|
||
{menuGroups.map((group, gi) => (
|
||
<div key={gi} style={{ marginBottom: 12 }}>
|
||
{group.label && <div style={{ fontSize: 12, color: T.tx3, marginBottom: 6, paddingLeft: 4 }}>{group.label}</div>}
|
||
<div style={{ background: T.card, borderRadius: T.r, overflow: 'hidden' }}>
|
||
{group.items.map((item, ii) => (
|
||
<div key={ii} style={{
|
||
display: 'flex', alignItems: 'center', padding: '14px 16px',
|
||
borderBottom: ii < group.items.length - 1 ? `1px solid ${T.bdL}` : 'none',
|
||
cursor: 'pointer',
|
||
}}>
|
||
{/* 图标 */}
|
||
<div style={{
|
||
width: 32, height: 32, borderRadius: T.rXs,
|
||
background: `${item.color}15`,
|
||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
fontSize: 16, marginRight: 12,
|
||
}}>
|
||
{item.icon}
|
||
</div>
|
||
{/* 标签 */}
|
||
<span style={{ flex: 1, fontSize: 15, color: T.tx }}>{item.label}</span>
|
||
{/* 角标 */}
|
||
{item.badge && (
|
||
<div style={{
|
||
minWidth: 18, height: 18, borderRadius: 9,
|
||
background: T.dan, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||
padding: '0 5px', marginRight: 8,
|
||
}}>
|
||
<span style={{ fontSize: 11, color: T.white, fontWeight: 600 }}>{item.badge}</span>
|
||
</div>
|
||
)}
|
||
{/* 箭头 */}
|
||
<span style={{ color: T.tx3, fontSize: 16 }}>›</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
||
</script>
|
||
</body>
|
||
</html>
|