Files
hms/docs/discussions/menu-prototype.html
iven b8c84ed9af feat(health): 菜单方案B重组 — 患者中心+随访关怀+配置归入系统管理+文章标签合并
方案B业务流程导向菜单优化:
- "患者管理" → "患者中心",吸收日常监测/诊断/知情同意/咨询
- "诊疗服务" → "随访关怀",只保留随访相关
- 告警规则/危急值阈值 → 系统管理
- 文章分类/标签菜单软删除,合并为文章管理页内 Tab

变更文件:
- 迁移 164: 重命名目录+移动叶子菜单+重建 menu_roles
- ArticleManageList.tsx: 分类/标签管理合并为页内 Tab
- 讨论记录 + 可视化原型 HTML

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 08:13:23 +08:00

1184 lines
43 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 菜单优化原型对比</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--sidebar-w: 280px;
--header-h: 56px;
--blue: #1677ff;
--blue-bg: #e6f4ff;
--gray-1: #1d2129;
--gray-2: #4e5969;
--gray-3: #86909c;
--gray-4: #c9cdd4;
--gray-5: #e5e6eb;
--gray-6: #f2f3f5;
--gray-7: #f7f8fa;
--red: #f53f3f;
--orange: #ff7d00;
--green: #00b42a;
--purple: #722ed1;
--radius: 6px;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
background: var(--gray-7);
color: var(--gray-1);
height: 100vh;
overflow: hidden;
}
/* ===== Top Control Bar ===== */
.control-bar {
height: var(--header-h);
background: #fff;
border-bottom: 1px solid var(--gray-5);
display: flex;
align-items: center;
padding: 0 24px;
gap: 16px;
position: relative;
z-index: 100;
}
.control-bar .logo {
font-size: 16px;
font-weight: 600;
color: var(--blue);
white-space: nowrap;
margin-right: 8px;
}
.scheme-tabs {
display: flex;
gap: 4px;
background: var(--gray-6);
border-radius: 8px;
padding: 3px;
}
.scheme-tab {
padding: 6px 16px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
transition: all 0.2s;
border: none;
background: transparent;
color: var(--gray-2);
white-space: nowrap;
}
.scheme-tab:hover { color: var(--gray-1); }
.scheme-tab.active {
background: #fff;
color: var(--blue);
font-weight: 500;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
.scheme-tab .badge {
display: inline-block;
font-size: 10px;
padding: 1px 6px;
border-radius: 10px;
margin-left: 6px;
font-weight: 500;
}
.badge-current { background: var(--gray-5); color: var(--gray-3); }
.badge-recommend { background: #e8ffea; color: var(--green); }
.badge-minimal { background: #fff7e6; color: var(--orange); }
.badge-advanced { background: #f5e8ff; color: var(--purple); }
.role-tabs {
display: flex;
gap: 4px;
background: var(--gray-6);
border-radius: 8px;
padding: 3px;
}
.role-tab {
padding: 6px 12px;
border-radius: 6px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
border: none;
background: transparent;
color: var(--gray-2);
white-space: nowrap;
}
.role-tab:hover { color: var(--gray-1); }
.role-tab.active {
background: var(--blue);
color: #fff;
font-weight: 500;
}
.toggle-group {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
font-size: 12px;
color: var(--gray-2);
}
.toggle-switch {
width: 36px;
height: 20px;
background: var(--gray-4);
border-radius: 10px;
position: relative;
cursor: pointer;
transition: background 0.2s;
}
.toggle-switch.active { background: var(--blue); }
.toggle-switch::after {
content: '';
width: 16px;
height: 16px;
background: #fff;
border-radius: 50%;
position: absolute;
top: 2px;
left: 2px;
transition: transform 0.2s;
box-shadow: 0 1px 2px rgba(0,0,0,0.15);
}
.toggle-switch.active::after { transform: translateX(16px); }
/* ===== Main Layout ===== */
.main-layout {
display: flex;
height: calc(100vh - var(--header-h));
}
/* ===== Sidebar ===== */
.sidebar {
width: var(--sidebar-w);
background: #fff;
border-right: 1px solid var(--gray-5);
display: flex;
flex-direction: column;
flex-shrink: 0;
transition: width 0.2s;
}
.sidebar-header {
padding: 16px 16px 8px;
display: flex;
align-items: center;
gap: 8px;
}
.sidebar-header .app-icon {
width: 32px;
height: 32px;
background: linear-gradient(135deg, var(--blue), #4096ff);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
font-size: 16px;
}
.sidebar-header .app-name {
font-size: 14px;
font-weight: 600;
color: var(--gray-1);
}
.sidebar-search {
padding: 0 12px 8px;
}
.sidebar-search input {
width: 100%;
padding: 6px 10px;
border: 1px solid var(--gray-5);
border-radius: var(--radius);
font-size: 12px;
outline: none;
background: var(--gray-7);
transition: border 0.2s;
}
.sidebar-search input:focus {
border-color: var(--blue);
background: #fff;
}
.sidebar-search input::placeholder { color: var(--gray-4); }
.sidebar-menu {
flex: 1;
overflow-y: auto;
padding: 4px 8px 16px;
}
.sidebar-menu::-webkit-scrollbar { width: 4px; }
.sidebar-menu::-webkit-scrollbar-track { background: transparent; }
.sidebar-menu::-webkit-scrollbar-thumb { background: var(--gray-4); border-radius: 2px; }
/* Menu Group */
.menu-group {
margin-bottom: 4px;
}
.menu-group-header {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 8px 4px;
cursor: pointer;
user-select: none;
}
.menu-group-header .group-icon {
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
color: var(--gray-3);
}
.menu-group-header .group-title {
font-size: 11px;
font-weight: 600;
color: var(--gray-3);
text-transform: uppercase;
letter-spacing: 0.5px;
flex: 1;
}
.menu-group-header .group-arrow {
font-size: 10px;
color: var(--gray-4);
transition: transform 0.2s;
}
.menu-group-header .group-arrow.collapsed { transform: rotate(-90deg); }
.menu-group-header .group-count {
font-size: 10px;
color: var(--gray-4);
background: var(--gray-6);
padding: 1px 6px;
border-radius: 10px;
}
.menu-group-items {
overflow: hidden;
transition: max-height 0.3s ease;
}
.menu-group-items.collapsed { max-height: 0 !important; }
/* Menu Item */
.menu-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
border-radius: var(--radius);
cursor: pointer;
transition: all 0.15s;
font-size: 13px;
color: var(--gray-2);
position: relative;
}
.menu-item:hover {
background: var(--gray-7);
color: var(--gray-1);
}
.menu-item.active {
background: var(--blue-bg);
color: var(--blue);
font-weight: 500;
}
.menu-item .item-icon {
width: 18px;
height: 18px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
flex-shrink: 0;
}
.menu-item .item-label { flex: 1; }
.menu-item .item-tag {
font-size: 10px;
padding: 1px 6px;
border-radius: 4px;
font-weight: 500;
}
.tag-new { background: #e8ffea; color: var(--green); }
.tag-moved { background: #e6f4ff; color: var(--blue); }
.tag-frozen { background: var(--gray-6); color: var(--gray-3); }
.tag-config { background: #fff7e6; color: var(--orange); }
.menu-item.frozen {
opacity: 0.4;
cursor: default;
}
.menu-item.frozen:hover { background: transparent; }
/* Sub-group (3rd level) */
.sub-group {
margin: 2px 0;
}
.sub-group-header {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 12px 4px 24px;
cursor: pointer;
user-select: none;
}
.sub-group-header .sub-title {
font-size: 12px;
color: var(--gray-3);
font-weight: 500;
flex: 1;
}
.sub-group-header .sub-arrow {
font-size: 9px;
color: var(--gray-4);
transition: transform 0.2s;
}
.sub-group-header .sub-arrow.collapsed { transform: rotate(-90deg); }
.sub-group-items {
overflow: hidden;
transition: max-height 0.25s ease;
padding-left: 12px;
}
.sub-group-items.collapsed { max-height: 0 !important; }
.sub-group-items .menu-item {
padding-left: 28px;
font-size: 12px;
}
/* ===== Content Area ===== */
.content-area {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 32px;
}
.comparison-card {
background: #fff;
border-radius: 12px;
padding: 32px;
max-width: 680px;
width: 100%;
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
}
.comparison-card h2 {
font-size: 18px;
font-weight: 600;
margin-bottom: 16px;
color: var(--gray-1);
}
.stat-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 24px;
}
.stat-item {
background: var(--gray-7);
border-radius: 8px;
padding: 12px 16px;
text-align: center;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: var(--blue);
}
.stat-value.green { color: var(--green); }
.stat-value.orange { color: var(--orange); }
.stat-value.red { color: var(--red); }
.stat-label {
font-size: 11px;
color: var(--gray-3);
margin-top: 2px;
}
.change-metrics {
margin-top: 16px;
}
.change-metric {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
border-bottom: 1px solid var(--gray-6);
font-size: 13px;
}
.change-metric:last-child { border-bottom: none; }
.change-metric .metric-label {
color: var(--gray-2);
flex: 1;
}
.change-metric .metric-value {
font-weight: 600;
font-size: 14px;
}
.metric-improve { color: var(--green); }
.metric-worsen { color: var(--red); }
.metric-same { color: var(--gray-3); }
.diff-legend {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--gray-5);
}
.diff-legend h4 {
font-size: 13px;
color: var(--gray-2);
margin-bottom: 8px;
}
.legend-items {
display: flex;
gap: 16px;
flex-wrap: wrap;
}
.legend-item {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--gray-2);
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 2px;
}
.dot-new { background: var(--green); }
.dot-moved { background: var(--blue); }
.dot-frozen { background: var(--gray-4); }
.dot-config { background: var(--orange); }
/* Highlight animation */
@keyframes highlight-in {
from { background: #fffbe6; }
to { background: transparent; }
}
.menu-item.highlight { animation: highlight-in 2s ease; }
/* Sidebar collapsed state */
.sidebar.collapsed {
width: 64px;
}
.sidebar.collapsed .sidebar-header .app-name,
.sidebar.collapsed .sidebar-search,
.sidebar.collapsed .menu-item .item-label,
.sidebar.collapsed .menu-item .item-tag,
.sidebar.collapsed .menu-group-header .group-title,
.sidebar.collapsed .menu-group-header .group-arrow,
.sidebar.collapsed .menu-group-header .group-count,
.sidebar.collapsed .sub-group-header,
.sidebar.collapsed .sub-group-items {
display: none;
}
.sidebar.collapsed .menu-group-header {
justify-content: center;
padding: 8px 0;
}
.sidebar.collapsed .menu-item {
justify-content: center;
padding: 10px 0;
}
.sidebar.collapsed .menu-group-items {
display: flex;
flex-direction: column;
align-items: center;
}
</style>
</head>
<body>
<!-- Control Bar -->
<div class="control-bar">
<div class="logo">HMS 菜单优化原型</div>
<div class="scheme-tabs" id="schemeTabs">
<button class="scheme-tab active" data-scheme="current">
当前方案
<span class="badge badge-current">现状</span>
</button>
<button class="scheme-tab" data-scheme="a">
方案 A
<span class="badge badge-minimal">最小改动</span>
</button>
<button class="scheme-tab" data-scheme="b">
方案 B
<span class="badge badge-recommend">推荐</span>
</button>
<button class="scheme-tab" data-scheme="c">
方案 C
<span class="badge badge-advanced">激进</span>
</button>
</div>
<div style="width:1px;height:24px;background:var(--gray-5);margin:0 4px;"></div>
<div class="role-tabs" id="roleTabs">
<button class="role-tab active" data-role="admin">管理员</button>
<button class="role-tab" data-role="doctor">医生</button>
<button class="role-tab" data-role="nurse">护士</button>
<button class="role-tab" data-role="health_manager">健康管理师</button>
<button class="role-tab" data-role="operator">运营</button>
</div>
<div class="toggle-group">
<span>三级折叠</span>
<div class="toggle-switch active" id="toggleCollapse" onclick="toggleCollapse()"></div>
<span style="margin-left:12px">侧边栏</span>
<div class="toggle-switch" id="toggleSidebar" onclick="toggleSidebar()"></div>
</div>
</div>
<!-- Main -->
<div class="main-layout">
<div class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="app-icon">H</div>
<div class="app-name">HMS 健康管理</div>
</div>
<div class="sidebar-search">
<input type="text" placeholder="搜索菜单..." id="searchInput" oninput="filterMenu(this.value)">
</div>
<div class="sidebar-menu" id="menuContainer"></div>
</div>
<div class="content-area" id="contentArea"></div>
</div>
<script>
// ============================================================
// Menu Data Definitions
// ============================================================
const ICONS = {
dashboard: '📊', patient: '👤', tag: '🏷️', doctor: '👨‍⚕️',
schedule: '📅', appointment: '📋', followup: '🔄', template: '📝',
consultation: '💬', inbox: '📥', diagnosis: '🩺', consent: '📄',
monitor: '📈', alert: '🚨', alertRule: '⚙️', device: '📟',
gateway: '📡', threshold: '📉', article: '📰', category: '📂',
articleTag: '🔖', points: '⭐', product: '🎁', order: '🛒',
event: '🎪', media: '🖼️', banner: '🎨', chat: '🤖', prompt: '💡',
analysis: '🔍', knowledge: '📚', usage: '📉', config: '⚙️',
user: '👥', role: '🔐', org: '🏢', workflow: '🔄', message: '✉️',
settings: '🛠️', plugin: '🧩', oauth: '🔗', statistics: '📊',
realtime: '📡', alertDash: '🔔', daily: '📋', care: '❤️',
family: '👨‍👩‍👧', medication: '💊', dialysis: '🩸', shift: '🕐',
home: '🏠', vitals: '💓', healthData: '📈',
};
function icon(name) { return ICONS[name] || '📁'; }
// Role definitions: which paths each role can see
const ROLES = {
current: {
admin: '*',
doctor: ['/', '/health/statistics', '/health/patients', '/health/doctors',
'/health/follow-up-tasks', '/health/follow-up-templates', '/health/consultations',
'/health/action-inbox', '/health/diagnoses', '/health/consents',
'/health/daily-monitoring', '/health/alert-dashboard', '/health/alerts',
'/health/ai-analysis', '/health/ai-usage', '/ai/chat', '/messages'],
nurse: ['/', '/health/statistics', '/health/patients',
'/health/follow-up-tasks', '/health/consultations', '/health/action-inbox',
'/health/diagnoses', '/health/consents', '/health/daily-monitoring',
'/health/alert-dashboard', '/health/alerts', '/messages'],
health_manager: ['/', '/health/statistics', '/health/patients', '/health/doctors',
'/health/tags', '/health/follow-up-tasks', '/health/follow-up-templates',
'/health/consultations', '/health/action-inbox', '/health/diagnoses',
'/health/consents', '/health/daily-monitoring', '/health/realtime-monitor',
'/health/alert-dashboard', '/health/alerts', '/health/alert-rules',
'/health/devices', '/health/critical-value-thresholds',
'/health/ai-prompts', '/health/ai-analysis', '/health/ai-knowledge',
'/health/ai-usage', '/health/ai-config', '/ai/chat', '/messages'],
operator: ['/', '/health/statistics', '/health/patients', '/health/tags',
'/health/devices', '/health/alert-dashboard', '/health/alerts',
'/health/articles', '/health/article-categories', '/health/article-tags',
'/health/points-rules', '/health/points-products', '/health/points-orders',
'/health/offline-events', '/health/media-library', '/health/banners',
'/health/ai-usage', '/messages'],
},
};
// ============================================================
// Scheme Definitions
// ============================================================
function buildCurrentScheme() {
return [
{ title: '工作台', icon: icon('home'), items: [
{ label: '工作台', path: '/', icon: icon('dashboard') },
{ label: '统计报表', path: '/health/statistics', icon: icon('statistics') },
]},
{ title: '患者管理', icon: icon('patient'), items: [
{ label: '患者管理', path: '/health/patients', icon: icon('patient') },
{ label: '标签管理', path: '/health/tags', icon: icon('tag') },
{ label: '医护管理', path: '/health/doctors', icon: icon('doctor') },
]},
{ title: '诊疗服务', icon: '🏥', items: [
{ label: '排班管理', path: '/health/schedules', icon: icon('schedule'), frozen: true },
{ label: '预约排班', path: '/health/appointments', icon: icon('appointment'), frozen: true },
{ label: '随访管理', path: '/health/follow-up-tasks', icon: icon('followup') },
{ label: '随访模板管理', path: '/health/follow-up-templates', icon: icon('template') },
{ label: '咨询管理', path: '/health/consultations', icon: icon('consultation') },
{ label: '行动收件箱', path: '/health/action-inbox', icon: icon('inbox') },
{ label: '诊断记录', path: '/health/diagnoses', icon: icon('diagnosis') },
{ label: '知情同意', path: '/health/consents', icon: icon('consent') },
{ label: '日常监测', path: '/health/daily-monitoring', icon: icon('daily') },
]},
{ title: '健康监测', icon: icon('monitor'), items: [
{ label: '实时监控', path: '/health/realtime-monitor', icon: icon('realtime') },
{ label: '告警仪表盘', path: '/health/alert-dashboard', icon: icon('alertDash') },
{ label: '告警列表', path: '/health/alerts', icon: icon('alert') },
{ label: '告警规则', path: '/health/alert-rules', icon: icon('alertRule') },
{ label: '设备管理', path: '/health/devices', icon: icon('device') },
{ label: 'BLE 网关', path: '/health/ble-gateways', icon: icon('gateway') },
{ label: '危急值阈值', path: '/health/critical-value-thresholds', icon: icon('threshold') },
]},
{ title: '运营管理', icon: '📢', items: [
{ label: '资讯管理', path: '/health/articles', icon: icon('article') },
{ label: '文章分类', path: '/health/article-categories', icon: icon('category') },
{ label: '文章标签', path: '/health/article-tags', icon: icon('articleTag') },
{ label: '积分规则', path: '/health/points-rules', icon: icon('points') },
{ label: '商品管理', path: '/health/points-products', icon: icon('product') },
{ label: '订单管理', path: '/health/points-orders', icon: icon('order') },
{ label: '线下活动', path: '/health/offline-events', icon: icon('event') },
{ label: '媒体库', path: '/health/media-library', icon: icon('media') },
{ label: '轮播图管理', path: '/health/banners', icon: icon('banner') },
]},
{ title: 'AI 助手', icon: '🤖', items: [
{ label: 'AI 客服', path: '/ai/chat', icon: icon('chat') },
{ label: 'AI Prompt 管理', path: '/health/ai-prompts', icon: icon('prompt') },
{ label: 'AI 分析历史', path: '/health/ai-analysis', icon: icon('analysis') },
{ label: 'AI 知识库', path: '/health/ai-knowledge', icon: icon('knowledge') },
{ label: 'AI 用量统计', path: '/health/ai-usage', icon: icon('usage') },
{ label: 'AI 配置', path: '/health/ai-config', icon: icon('config') },
]},
{ title: '系统管理', icon: '⚙️', items: [
{ label: '用户管理', path: '/users', icon: icon('user') },
{ label: '权限管理', path: '/roles', icon: icon('role') },
{ label: '组织架构', path: '/organizations', icon: icon('org') },
{ label: '工作流', path: '/workflow', icon: icon('workflow') },
{ label: '消息中心', path: '/messages', icon: icon('message') },
{ label: '系统设置', path: '/settings', icon: icon('settings') },
{ label: '插件管理', path: '/plugins/admin', icon: icon('plugin') },
{ label: 'OAuth 合作方', path: '/health/oauth-clients', icon: icon('oauth') },
]},
];
}
function buildSchemeA() {
// Minimal changes: move daily-monitoring to 健康监测, merge article cats/tags into tab
return [
{ title: '工作台', icon: icon('home'), items: [
{ label: '工作台', path: '/', icon: icon('dashboard') },
{ label: '统计报表', path: '/health/statistics', icon: icon('statistics') },
]},
{ title: '患者管理', icon: icon('patient'), items: [
{ label: '患者管理', path: '/health/patients', icon: icon('patient') },
{ label: '标签管理', path: '/health/tags', icon: icon('tag') },
{ label: '医护管理', path: '/health/doctors', icon: icon('doctor') },
]},
{ title: '诊疗服务', icon: '🏥', items: [
{ label: '排班管理', path: '/health/schedules', icon: icon('schedule'), frozen: true },
{ label: '预约排班', path: '/health/appointments', icon: icon('appointment'), frozen: true },
{ label: '随访管理', path: '/health/follow-up-tasks', icon: icon('followup') },
{ label: '随访模板管理', path: '/health/follow-up-templates', icon: icon('template') },
{ label: '咨询管理', path: '/health/consultations', icon: icon('consultation') },
{ label: '行动收件箱', path: '/health/action-inbox', icon: icon('inbox') },
{ label: '诊断记录', path: '/health/diagnoses', icon: icon('diagnosis') },
]},
{ title: '健康监测', icon: icon('monitor'), items: [
{ label: '实时监控', path: '/health/realtime-monitor', icon: icon('realtime') },
{ label: '告警仪表盘', path: '/health/alert-dashboard', icon: icon('alertDash') },
{ label: '告警列表', path: '/health/alerts', icon: icon('alert') },
{ label: '告警规则', path: '/health/alert-rules', icon: icon('alertRule') },
{ label: '日常监测', path: '/health/daily-monitoring', icon: icon('daily'), tag: 'moved', tagText: '从诊疗服务移入' },
{ label: '设备管理', path: '/health/devices', icon: icon('device') },
{ label: 'BLE 网关', path: '/health/ble-gateways', icon: icon('gateway') },
{ label: '危急值阈值', path: '/health/critical-value-thresholds', icon: icon('threshold') },
]},
{ title: '运营管理', icon: '📢', items: [
{ label: '资讯管理', path: '/health/articles', icon: icon('article'), tag: 'merged', tagText: '含分类/标签Tab' },
{ label: '积分规则', path: '/health/points-rules', icon: icon('points') },
{ label: '商品管理', path: '/health/points-products', icon: icon('product') },
{ label: '订单管理', path: '/health/points-orders', icon: icon('order') },
{ label: '线下活动', path: '/health/offline-events', icon: icon('event') },
{ label: '媒体库', path: '/health/media-library', icon: icon('media') },
{ label: '轮播图管理', path: '/health/banners', icon: icon('banner') },
]},
{ title: 'AI 助手', icon: '🤖', items: [
{ label: 'AI 客服', path: '/ai/chat', icon: icon('chat') },
{ label: 'AI Prompt 管理', path: '/health/ai-prompts', icon: icon('prompt') },
{ label: 'AI 分析历史', path: '/health/ai-analysis', icon: icon('analysis') },
{ label: 'AI 知识库', path: '/health/ai-knowledge', icon: icon('knowledge') },
{ label: 'AI 用量统计', path: '/health/ai-usage', icon: icon('usage') },
{ label: 'AI 配置', path: '/health/ai-config', icon: icon('config') },
]},
{ title: '系统管理', icon: '⚙️', items: [
{ label: '用户管理', path: '/users', icon: icon('user') },
{ label: '权限管理', path: '/roles', icon: icon('role') },
{ label: '组织架构', path: '/organizations', icon: icon('org') },
{ label: '工作流', path: '/workflow', icon: icon('workflow') },
{ label: '消息中心', path: '/messages', icon: icon('message') },
{ label: '系统设置', path: '/settings', icon: icon('settings') },
{ label: '插件管理', path: '/plugins/admin', icon: icon('plugin') },
{ label: 'OAuth 合作方', path: '/health/oauth-clients', icon: icon('oauth') },
]},
];
}
function buildSchemeB() {
// Recommended: Patient center + follow-up care + config items to system
return [
{ title: '工作台', icon: icon('home'), items: [
{ label: '工作台', path: '/', icon: icon('dashboard') },
{ label: '统计报表', path: '/health/statistics', icon: icon('statistics') },
]},
{ title: '患者中心', icon: '🫀', tag: 'new', tagText: '原"患者管理"扩充', items: [
{ label: '患者管理', path: '/health/patients', icon: icon('patient') },
{ label: '日常监测', path: '/health/daily-monitoring', icon: icon('daily'), tag: 'moved', tagText: '从诊疗服务移入' },
{ label: '诊断记录', path: '/health/diagnoses', icon: icon('diagnosis'), tag: 'moved', tagText: '从诊疗服务移入' },
{ label: '知情同意', path: '/health/consents', icon: icon('consent'), tag: 'moved', tagText: '从诊疗服务移入' },
{ label: '咨询管理', path: '/health/consultations', icon: icon('consultation'), tag: 'moved', tagText: '从诊疗服务移入' },
{ label: '标签管理', path: '/health/tags', icon: icon('tag') },
{ label: '医护管理', path: '/health/doctors', icon: icon('doctor') },
]},
{ title: '随访关怀', icon: '🤝', tag: 'new', tagText: '原"诊疗服务"瘦身', items: [
{ label: '行动收件箱', path: '/health/action-inbox', icon: icon('inbox'), tag: 'reorder', tagText: '提到首位' },
{ label: '随访任务', path: '/health/follow-up-tasks', icon: icon('followup') },
{ label: '随访模板', path: '/health/follow-up-templates', icon: icon('template') },
{ label: '排班管理', path: '/health/schedules', icon: icon('schedule'), frozen: true },
{ label: '预约排班', path: '/health/appointments', icon: icon('appointment'), frozen: true },
]},
{ title: '监测告警', icon: icon('monitor'), items: [
{ label: '实时监控', path: '/health/realtime-monitor', icon: icon('realtime') },
{ label: '告警仪表盘', path: '/health/alert-dashboard', icon: icon('alertDash') },
{ label: '告警列表', path: '/health/alerts', icon: icon('alert') },
{ label: '设备管理', path: '/health/devices', icon: icon('device') },
{ label: 'BLE 网关', path: '/health/ble-gateways', icon: icon('gateway') },
]},
{ title: '运营管理', icon: '📢', items: [
{ label: '资讯管理', path: '/health/articles', icon: icon('article'), tag: 'merged', tagText: '含分类/标签Tab' },
{ label: '积分规则', path: '/health/points-rules', icon: icon('points') },
{ label: '商品管理', path: '/health/points-products', icon: icon('product') },
{ label: '订单管理', path: '/health/points-orders', icon: icon('order') },
{ label: '线下活动', path: '/health/offline-events', icon: icon('event') },
{ label: '媒体库', path: '/health/media-library', icon: icon('media') },
{ label: '轮播图管理', path: '/health/banners', icon: icon('banner') },
]},
{ title: 'AI 助手', icon: '🤖', items: [
{ label: 'AI 客服', path: '/ai/chat', icon: icon('chat') },
{ label: 'AI Prompt 管理', path: '/health/ai-prompts', icon: icon('prompt') },
{ label: 'AI 分析历史', path: '/health/ai-analysis', icon: icon('analysis') },
{ label: 'AI 知识库', path: '/health/ai-knowledge', icon: icon('knowledge') },
{ label: 'AI 用量统计', path: '/health/ai-usage', icon: icon('usage') },
{ label: 'AI 配置', path: '/health/ai-config', icon: icon('config') },
]},
{ title: '系统管理', icon: '⚙️', items: [
{ label: '用户管理', path: '/users', icon: icon('user') },
{ label: '权限管理', path: '/roles', icon: icon('role') },
{ label: '组织架构', path: '/organizations', icon: icon('org') },
{ label: '告警规则', path: '/health/alert-rules', icon: icon('alertRule'), tag: 'config', tagText: '配置项归入系统' },
{ label: '危急值阈值', path: '/health/critical-value-thresholds', icon: icon('threshold'), tag: 'config', tagText: '配置项归入系统' },
{ label: '工作流', path: '/workflow', icon: icon('workflow') },
{ label: '消息中心', path: '/messages', icon: icon('message') },
{ label: '系统设置', path: '/settings', icon: icon('settings') },
{ label: '插件管理', path: '/plugins/admin', icon: icon('plugin') },
{ label: 'OAuth 合作方', path: '/health/oauth-clients', icon: icon('oauth') },
]},
];
}
function buildSchemeC() {
// Advanced: Patient-centric with patient detail tabs
return [
{ title: '首页', icon: icon('home'), items: [
{ label: '工作台', path: '/', icon: icon('dashboard') },
{ label: '统计报表', path: '/health/statistics', icon: icon('statistics') },
]},
{ title: '患者中心', icon: '🫀', tag: 'new', tagText: '核心入口详情页含Tab', items: [
{ label: '患者管理', path: '/health/patients', icon: icon('patient') },
{ label: '标签管理', path: '/health/tags', icon: icon('tag') },
{ label: '医护管理', path: '/health/doctors', icon: icon('doctor') },
{ label: '—— 患者详情页内 Tab ——', icon: '↳', disabled: true },
{ label: '体征数据', path: '/health/patients/:id/vitals', icon: icon('vitals'), tag: 'tab', tagText: '详情页Tab' },
{ label: '随访记录', path: '/health/patients/:id/followup', icon: icon('followup'), tag: 'tab', tagText: '详情页Tab' },
{ label: '咨询记录', path: '/health/patients/:id/consultation', icon: icon('consultation'), tag: 'tab', tagText: '详情页Tab' },
{ label: '诊断记录', path: '/health/patients/:id/diagnosis', icon: icon('diagnosis'), tag: 'tab', tagText: '详情页Tab' },
{ label: '知情同意', path: '/health/patients/:id/consent', icon: icon('consent'), tag: 'tab', tagText: '详情页Tab' },
]},
{ title: '排班与随访', icon: '📋', tag: 'new', tagText: '护士/健康管理师高频', items: [
{ label: '行动收件箱', path: '/health/action-inbox', icon: icon('inbox') },
{ label: '随访任务', path: '/health/follow-up-tasks', icon: icon('followup') },
{ label: '随访模板', path: '/health/follow-up-templates', icon: icon('template') },
{ label: '排班管理', path: '/health/schedules', icon: icon('schedule'), frozen: true },
{ label: '预约排班', path: '/health/appointments', icon: icon('appointment'), frozen: true },
]},
{ title: '监测中心', icon: icon('monitor'), items: [
{ label: '实时监控', path: '/health/realtime-monitor', icon: icon('realtime') },
{ label: '日常监测', path: '/health/daily-monitoring', icon: icon('daily'), tag: 'moved', tagText: '移入监测中心' },
{ label: '告警仪表盘', path: '/health/alert-dashboard', icon: icon('alertDash') },
{ label: '告警列表', path: '/health/alerts', icon: icon('alert') },
{ label: '告警规则', path: '/health/alert-rules', icon: icon('alertRule') },
{ label: '危急值阈值', path: '/health/critical-value-thresholds', icon: icon('threshold') },
{ label: '设备管理', path: '/health/devices', icon: icon('device') },
{ label: 'BLE 网关', path: '/health/ble-gateways', icon: icon('gateway') },
]},
{ title: '运营中心', icon: '📢', items: [
{ label: '资讯管理', path: '/health/articles', icon: icon('article'), tag: 'merged', tagText: '含分类/标签Tab' },
{ label: '积分规则', path: '/health/points-rules', icon: icon('points') },
{ label: '商品管理', path: '/health/points-products', icon: icon('product') },
{ label: '订单管理', path: '/health/points-orders', icon: icon('order') },
{ label: '线下活动', path: '/health/offline-events', icon: icon('event') },
{ label: '媒体库', path: '/health/media-library', icon: icon('media') },
{ label: '轮播图管理', path: '/health/banners', icon: icon('banner') },
]},
{ title: 'AI 助手', icon: '🤖', items: [
{ label: 'AI 客服', path: '/ai/chat', icon: icon('chat') },
{ label: 'AI Prompt 管理', path: '/health/ai-prompts', icon: icon('prompt') },
{ label: 'AI 分析历史', path: '/health/ai-analysis', icon: icon('analysis') },
{ label: 'AI 知识库', path: '/health/ai-knowledge', icon: icon('knowledge') },
{ label: 'AI 用量统计', path: '/health/ai-usage', icon: icon('usage') },
{ label: 'AI 配置', path: '/health/ai-config', icon: icon('config') },
]},
{ title: '系统管理', icon: '⚙️', items: [
{ label: '用户管理', path: '/users', icon: icon('user') },
{ label: '权限管理', path: '/roles', icon: icon('role') },
{ label: '组织架构', path: '/organizations', icon: icon('org') },
{ label: '工作流', path: '/workflow', icon: icon('workflow') },
{ label: '消息中心', path: '/messages', icon: icon('message') },
{ label: '系统设置', path: '/settings', icon: icon('settings') },
{ label: '插件管理', path: '/plugins/admin', icon: icon('plugin') },
{ label: 'OAuth 合作方', path: '/health/oauth-clients', icon: icon('oauth') },
]},
];
}
// Role visibility for each scheme (inherit from current, add overrides)
function getRolePaths(scheme, role) {
// For all schemes, use the same role paths as current (paths don't change, just grouping)
return ROLES.current[role] || ROLES.current.admin;
}
function isPathVisible(scheme, role, path) {
if (role === 'admin') return true;
const paths = getRolePaths(scheme, role);
if (paths === '*') return true;
// For moved items in scheme B/C, check by path
return paths.includes(path);
}
// ============================================================
// Render Engine
// ============================================================
let currentScheme = 'current';
let currentRole = 'admin';
let collapseEnabled = true;
const schemes = {
current: buildCurrentScheme,
a: buildSchemeA,
b: buildSchemeB,
c: buildSchemeC,
};
const schemeNames = {
current: '当前方案',
a: '方案 A最小改动',
b: '方案 B业务流程导向 · 推荐)',
c: '方案 C患者中心化',
};
function render() {
const menuData = schemes[currentScheme]();
const container = document.getElementById('menuContainer');
const contentArea = document.getElementById('contentArea');
// Filter by role
const filtered = menuData.map(group => {
const filteredItems = group.items.filter(item => {
if (item.disabled) return true; // show disabled dividers
return isPathVisible(currentScheme, currentRole, item.path);
});
// Only show groups that have visible items
const hasVisible = filteredItems.some(i => !i.disabled);
if (!hasVisible && currentRole !== 'admin') return null;
return { ...group, items: filteredItems };
}).filter(Boolean);
// Build sidebar HTML
let html = '';
let totalItems = 0;
filtered.forEach((group, gi) => {
const visibleCount = group.items.filter(i => !i.disabled).length;
totalItems += visibleCount;
html += `<div class="menu-group">`;
html += `<div class="menu-group-header" onclick="toggleGroup(this, ${gi})">`;
html += ` <span class="group-icon">${group.icon}</span>`;
html += ` <span class="group-title">${group.title}</span>`;
if (group.tag === 'new') {
html += `<span class="item-tag tag-new">新</span>`;
}
html += ` <span class="group-count">${visibleCount}</span>`;
html += ` <span class="group-arrow">▼</span>`;
html += `</div>`;
html += `<div class="menu-group-items" id="group-${gi}" style="max-height:1000px">`;
group.items.forEach(item => {
if (item.disabled) {
html += `<div class="menu-item" style="opacity:0.3;cursor:default;font-size:11px;color:var(--gray-3)">${item.icon} ${item.label}</div>`;
return;
}
const classes = ['menu-item'];
if (item.frozen) classes.push('frozen');
let tagHtml = '';
if (item.tag === 'moved') tagHtml = `<span class="item-tag tag-moved">移入</span>`;
else if (item.tag === 'new') tagHtml = `<span class="item-tag tag-new">新</span>`;
else if (item.tag === 'config') tagHtml = `<span class="item-tag tag-config">配置</span>`;
else if (item.tag === 'merged') tagHtml = `<span class="item-tag tag-moved">合并</span>`;
else if (item.tag === 'tab') tagHtml = `<span class="item-tag tag-new">Tab</span>`;
else if (item.tag === 'reorder') tagHtml = `<span class="item-tag tag-moved">置顶</span>`;
else if (item.frozen) tagHtml = `<span class="item-tag tag-frozen">冻结</span>`;
const title = item.tagText ? ` title="${item.tagText}"` : '';
html += `<div class="${classes.join(' ')}"${title} onclick="selectItem(this, '${item.path}')">`;
html += ` <span class="item-icon">${item.icon}</span>`;
html += ` <span class="item-label">${item.label}</span>`;
html += tagHtml;
html += `</div>`;
});
html += `</div></div>`;
});
container.innerHTML = html;
// Build content area stats
const allItems = menuData.flatMap(g => g.items.filter(i => !i.disabled && !i.frozen));
const frozenItems = menuData.flatMap(g => g.items.filter(i => i.frozen));
const activeCount = allItems.length;
const dirCount = menuData.length;
const maxDirItems = Math.max(...menuData.map(g => g.items.length));
// Count changes from current
let changes = [];
if (currentScheme === 'a') {
changes = [
{ label: '日常监测移入健康监测', type: 'improve' },
{ label: '文章分类/标签合并为Tab', type: 'improve' },
{ label: '总菜单项 -2', type: 'improve' },
];
} else if (currentScheme === 'b') {
changes = [
{ label: '患者管理→患者中心(+4项', type: 'improve' },
{ label: '诊疗服务→随访关怀(-4项', type: 'improve' },
{ label: '告警规则/阈值→系统管理', type: 'improve' },
{ label: '文章分类/标签合并为Tab', type: 'improve' },
{ label: '行动收件箱提到首位', type: 'improve' },
];
} else if (currentScheme === 'c') {
changes = [
{ label: '患者详情页升级多Tab枢纽', type: 'improve' },
{ label: '日常监测移入监测中心', type: 'improve' },
{ label: '文章分类/标签合并为Tab', type: 'improve' },
{ label: '医生核心操作仅需2个目录', type: 'improve' },
{ label: '需重构患者详情页架构', type: 'worsen' },
];
} else {
changes = [
{ label: '7个顶级目录', type: 'same' },
{ label: '诊疗服务9项最臃肿', type: 'worsen' },
{ label: '运营管理9项', type: 'worsen' },
{ label: '医生需穿越4个目录', type: 'worsen' },
];
}
const balanceScore = currentScheme === 'current' ? 5.2 :
currentScheme === 'a' ? 6.5 :
currentScheme === 'b' ? 8.0 : 7.5;
const efficiencyScore = currentScheme === 'current' ? 4.0 :
currentScheme === 'a' ? 5.5 :
currentScheme === 'b' ? 7.5 : 8.5;
let contentHtml = `<div class="comparison-card">`;
contentHtml += `<h2>${schemeNames[currentScheme]}</h2>`;
contentHtml += `<div class="stat-grid">`;
contentHtml += `<div class="stat-item"><div class="stat-value">${dirCount}</div><div class="stat-label">顶级目录</div></div>`;
contentHtml += `<div class="stat-item"><div class="stat-value">${activeCount}</div><div class="stat-label">活跃菜单项</div></div>`;
contentHtml += `<div class="stat-item"><div class="stat-value">${frozenItems.length}</div><div class="stat-label">冻结项</div></div>`;
contentHtml += `</div>`;
contentHtml += `<div class="stat-grid">`;
contentHtml += `<div class="stat-item"><div class="stat-value">${maxDirItems}</div><div class="stat-label">最大目录项数</div></div>`;
contentHtml += `<div class="stat-item"><div class="stat-value green">${balanceScore}</div><div class="stat-label">均衡性 /10</div></div>`;
contentHtml += `<div class="stat-item"><div class="stat-value ${efficiencyScore >= 7 ? 'green' : efficiencyScore >= 5 ? 'orange' : 'red'}">${efficiencyScore}</div><div class="stat-label">导航效率 /10</div></div>`;
contentHtml += `</div>`;
contentHtml += `<div class="change-metrics">`;
changes.forEach(c => {
const cls = c.type === 'improve' ? 'metric-improve' : c.type === 'worsen' ? 'metric-worsen' : 'metric-same';
const arrow = c.type === 'improve' ? '↑' : c.type === 'worsen' ? '↓' : '→';
contentHtml += `<div class="change-metric"><span class="metric-label">${c.label}</span><span class="metric-value ${cls}">${arrow}</span></div>`;
});
contentHtml += `</div>`;
// Legend for tags
const hasTags = currentScheme !== 'current';
if (hasTags) {
contentHtml += `<div class="diff-legend"><h4>标记说明</h4><div class="legend-items">`;
contentHtml += `<div class="legend-item"><span class="legend-dot dot-moved"></span>移入/重组</div>`;
contentHtml += `<div class="legend-item"><span class="legend-dot dot-new"></span>新增/Tab</div>`;
contentHtml += `<div class="legend-item"><span class="legend-dot dot-config"></span>配置项归入</div>`;
contentHtml += `<div class="legend-item"><span class="legend-dot dot-frozen"></span>冻结</div>`;
contentHtml += `</div></div>`;
}
contentHtml += `</div>`;
contentArea.innerHTML = contentHtml;
}
function toggleGroup(header, gi) {
const items = document.getElementById(`group-${gi}`);
const arrow = header.querySelector('.group-arrow');
if (items.classList.contains('collapsed')) {
items.classList.remove('collapsed');
items.style.maxHeight = items.scrollHeight + 'px';
arrow.classList.remove('collapsed');
} else {
items.classList.add('collapsed');
arrow.classList.add('collapsed');
}
}
function selectItem(el, path) {
document.querySelectorAll('.menu-item.active').forEach(e => e.classList.remove('active'));
el.classList.add('active');
}
function filterMenu(query) {
const q = query.toLowerCase().trim();
document.querySelectorAll('.menu-item').forEach(item => {
const label = item.querySelector('.item-label');
if (!label) return;
const text = label.textContent.toLowerCase();
item.style.display = (!q || text.includes(q)) ? '' : 'none';
});
}
function toggleCollapse() {
collapseEnabled = !collapseEnabled;
document.getElementById('toggleCollapse').classList.toggle('active', collapseEnabled);
if (!collapseEnabled) {
// Expand all groups
document.querySelectorAll('.menu-group-items').forEach(el => {
el.classList.remove('collapsed');
el.style.maxHeight = el.scrollHeight + 'px';
});
document.querySelectorAll('.group-arrow').forEach(el => el.classList.remove('collapsed'));
}
}
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('toggleSidebar');
sidebar.classList.toggle('collapsed');
toggle.classList.toggle('active');
}
// Scheme tabs
document.getElementById('schemeTabs').addEventListener('click', e => {
const tab = e.target.closest('.scheme-tab');
if (!tab) return;
document.querySelectorAll('.scheme-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentScheme = tab.dataset.scheme;
render();
});
// Role tabs
document.getElementById('roleTabs').addEventListener('click', e => {
const tab = e.target.closest('.role-tab');
if (!tab) return;
document.querySelectorAll('.role-tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentRole = tab.dataset.role;
render();
});
// Initial render
render();
</script>
</body>
</html>