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

399 lines
18 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: 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>