- 新增 12 个核心页面原型(登录/首页/咨询/预约/商城/健康等) - 新增医生端分包原型(核心 + 临床两个分包) - 新增 AI 客服对话页原型 - 新增 MP UI 优化指南文档 - 更新 wiki 基础设施和小程序文档
329 lines
15 KiB
HTML
329 lines
15 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 IconCheckin({ size = 24, color = '#fff' }) {
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M9 11l3 3L22 4"/>
|
|
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function IconTask({ size = 24, color = '#fff' }) {
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
|
<path d="M9 12l2 2 4-4"/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
function IconHistory({ size = 24, color = '#fff' }) {
|
|
return (
|
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="12" cy="12" r="10"/>
|
|
<polyline points="12 6 12 12 16 14"/>
|
|
</svg>
|
|
);
|
|
}
|
|
|
|
// ─── 商品占位图 SVG ───
|
|
function ProductPlaceholder({ label }) {
|
|
const iconMap = {
|
|
'健康体检套餐': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
|
</svg>
|
|
),
|
|
'血压计': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>
|
|
</svg>
|
|
),
|
|
'维生素D3': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="8" y="2" width="8" height="4" rx="1"/>
|
|
<path d="M6 6h12v14a2 2 0 01-2 2H8a2 2 0 01-2-2V6z"/>
|
|
<path d="M10 11h4"/>
|
|
</svg>
|
|
),
|
|
'健康食谱书': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M4 19.5A2.5 2.5 0 016.5 17H20"/>
|
|
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z"/>
|
|
<path d="M8 7h8M8 11h6"/>
|
|
</svg>
|
|
),
|
|
'运动手环': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<rect x="7" y="2" width="10" height="20" rx="5"/>
|
|
<path d="M12 8v4l2 2"/>
|
|
</svg>
|
|
),
|
|
'保温杯': (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M8 2h8v3H8z"/>
|
|
<path d="M7 5h10l-1 15H8L7 5z"/>
|
|
<path d="M10 9h4"/>
|
|
</svg>
|
|
),
|
|
};
|
|
return (
|
|
<div style={{ width: '100%', aspectRatio: '1', background: T.surface, borderRadius: T.rSm, display: 'flex', alignItems: 'center', justifyContent: 'center', position: 'relative' }}>
|
|
{iconMap[label] || (
|
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="#C4623A" strokeWidth="1.5"><rect x="3" y="3" width="18" height="18" rx="3"/></svg>
|
|
)}
|
|
<div style={{ position: 'absolute', bottom: 6, left: 0, right: 0, textAlign: 'center', fontSize: 9, color: T.tx3, fontWeight: 500 }}>{label}</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─── 积分商城主页 ───
|
|
function MallPage() {
|
|
const categories = ['全部', '健康', '生活', '食品'];
|
|
const [activeCategory, setActiveCategory] = React.useState(0);
|
|
|
|
const quickActions = [
|
|
{ icon: <IconCheckin size={22} color="#fff" />, label: '签到打卡', bg: T.acc, shadow: 'rgba(91,122,94,0.3)' },
|
|
{ icon: <IconTask size={22} color="#fff" />, label: '积分任务', bg: T.pri, shadow: 'rgba(196,98,58,0.3)' },
|
|
{ icon: <IconHistory size={22} color="#fff" />, label: '兑换记录', bg: T.wrn, shadow: 'rgba(196,135,58,0.3)' },
|
|
];
|
|
|
|
const products = [
|
|
{ name: '健康体检套餐', points: 800, price: 299, tag: '热门' },
|
|
{ name: '血压计', points: 1200, price: 199 },
|
|
{ name: '维生素D3', points: 300, price: 89 },
|
|
{ name: '健康食谱书', points: 500, price: 68, tag: '新品' },
|
|
{ name: '运动手环', points: 2000, price: 399 },
|
|
{ name: '保温杯', points: 600, price: 128, tag: '新品' },
|
|
];
|
|
|
|
return (
|
|
<div style={{ height: '100%', background: T.bg, display: 'flex', flexDirection: 'column' }}>
|
|
{/* ── 导航栏 ── */}
|
|
<div style={{ padding: '12px 20px 8px', display: 'flex', alignItems: 'center', gap: 8, background: T.bg }}>
|
|
<svg width="24" height="24" 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 style={{ flex: 1, textAlign: 'center', fontFamily: T.serif, fontSize: 26, fontWeight: 700, color: T.tx, paddingRight: 24 }}>积分商城</div>
|
|
</div>
|
|
|
|
{/* ── 可滚动内容区 ── */}
|
|
<div style={{ flex: 1, overflow: 'auto', padding: '8px 20px 24px' }}>
|
|
{/* ── 积分卡片 ── */}
|
|
<div style={{
|
|
background: `linear-gradient(135deg, ${T.pri} 0%, ${T.priD} 100%)`,
|
|
borderRadius: T.r,
|
|
padding: '24px 24px 20px',
|
|
marginBottom: 20,
|
|
boxShadow: `0 8px 24px rgba(196,98,58,0.25)`,
|
|
position: 'relative',
|
|
overflow: 'hidden',
|
|
}}>
|
|
{/* 装饰圆 */}
|
|
<div style={{ position: 'absolute', top: -20, right: -20, width: 100, height: 100, borderRadius: 50, background: 'rgba(255,255,255,0.08)' }} />
|
|
<div style={{ position: 'absolute', bottom: -30, right: 40, width: 80, height: 80, borderRadius: 40, background: 'rgba(255,255,255,0.05)' }} />
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', position: 'relative', zIndex: 1 }}>
|
|
<div>
|
|
<div style={{ fontSize: 13, color: 'rgba(255,255,255,0.7)', marginBottom: 8, letterSpacing: 1 }}>我的积分</div>
|
|
<div style={{ fontFamily: T.serif, fontSize: 42, fontWeight: 700, color: '#fff', lineHeight: 1 }}>1,280</div>
|
|
</div>
|
|
<div style={{
|
|
display: 'flex', alignItems: 'center', gap: 4,
|
|
background: 'rgba(255,255,255,0.2)', borderRadius: 20,
|
|
padding: '6px 14px', fontSize: 13, color: '#fff', fontWeight: 500,
|
|
}}>
|
|
积分明细
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M9 18l6-6-6-6"/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── 快捷操作 ── */}
|
|
<div style={{ display: 'flex', justifyContent: 'space-around', marginBottom: 24 }}>
|
|
{quickActions.map((action, i) => (
|
|
<div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 8 }}>
|
|
<div style={{
|
|
width: 52, height: 52, borderRadius: 26,
|
|
background: action.bg,
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
boxShadow: `0 4px 12px ${action.shadow}`,
|
|
}}>
|
|
{action.icon}
|
|
</div>
|
|
<span style={{ fontSize: 12, color: T.tx2, fontWeight: 500 }}>{action.label}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* ── 分类标签 ── */}
|
|
<div style={{ display: 'flex', gap: 10, marginBottom: 20, overflowX: 'auto' }}>
|
|
{categories.map((cat, i) => (
|
|
<div
|
|
key={i}
|
|
onClick={() => setActiveCategory(i)}
|
|
style={{
|
|
padding: '7px 18px',
|
|
borderRadius: 20,
|
|
fontSize: 14,
|
|
fontWeight: activeCategory === i ? 600 : 400,
|
|
background: activeCategory === i ? T.pri : T.surface,
|
|
color: activeCategory === i ? '#fff' : T.tx2,
|
|
whiteSpace: 'nowrap',
|
|
transition: 'all 0.2s',
|
|
cursor: 'pointer',
|
|
}}
|
|
>
|
|
{cat}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* ── 商品网格 ── */}
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
|
|
{products.map((product, i) => (
|
|
<div key={i} style={{
|
|
background: T.card,
|
|
borderRadius: T.rSm,
|
|
overflow: 'hidden',
|
|
boxShadow: '0 2px 8px rgba(0,0,0,0.04)',
|
|
}}>
|
|
{/* 商品图 */}
|
|
<div style={{ position: 'relative' }}>
|
|
<ProductPlaceholder label={product.name} />
|
|
{product.tag && (
|
|
<div style={{
|
|
position: 'absolute', top: 8, left: 8,
|
|
background: product.tag === '热门' ? T.dan : T.acc,
|
|
color: '#fff', fontSize: 10, fontWeight: 600,
|
|
padding: '2px 8px', borderRadius: 6,
|
|
}}>
|
|
{product.tag}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* 商品信息 */}
|
|
<div style={{ padding: '10px 12px 14px' }}>
|
|
<div style={{
|
|
fontSize: 14, fontWeight: 600, color: T.tx,
|
|
lineHeight: 1.4, marginBottom: 8,
|
|
display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden',
|
|
height: 40,
|
|
}}>
|
|
{product.name}
|
|
</div>
|
|
<div style={{ display: 'flex', alignItems: 'baseline', gap: 6 }}>
|
|
<span style={{ fontFamily: T.serif, fontSize: 18, fontWeight: 700, color: T.pri }}>
|
|
{product.points}
|
|
</span>
|
|
<span style={{ fontSize: 11, color: T.pri, fontWeight: 500 }}>积分</span>
|
|
</div>
|
|
<div style={{ fontSize: 12, color: T.tx3, textDecoration: 'line-through', marginTop: 2 }}>
|
|
¥{product.price}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* ── 底部留白 ── */}
|
|
<div style={{ height: 16 }} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// ─── 渲染 ───
|
|
function App() {
|
|
return (
|
|
<div className="screens">
|
|
<div className="screen-wrap">
|
|
<span className="screen-label">积分商城 — 主页</span>
|
|
<IosFrame time="9:41" battery={85}>
|
|
<MallPage />
|
|
</IosFrame>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
|
|
</script>
|
|
</body>
|
|
</html>
|