25 KiB
25 KiB
侧边栏导航系统设计方案
项目名称: 制度文件管理学习AI智能体 - 前端优化
设计日期: 2026-05-14
版本: v1.0
状态: 已批准
1. 设计背景与目标
1.1 当前问题
现有系统采用顶部水平导航栏布局,存在以下问题:
- 空间利用率低: 7 个功能模块占用顶部空间,在宽屏显示器上浪费水平空间
- 可扩展性差: 新增功能模块会导致顶部导航拥挤
- 视觉层级不清晰: 系统标题、导航、用户信息混在同一行,缺乏层次感
- 移动端体验差: 水平导航在小屏幕上需要滚动或换行
1.2 设计目标
将现有的顶部 header 导航重构为左侧固定侧边栏系统,实现:
- 提升空间利用率: 垂直导航释放顶部和水平空间
- 增强可扩展性: 支持更多功能模块而不影响布局
- 改善视觉层级: 清晰分离品牌区、导航区、用户区
- 优化响应式体验: 桌面/平板/移动端均有最佳表现
- 保持一致性: 遵循已有的白色体系设计规范
2. 设计决策记录
2.1 关键选择
| 决策项 | 选择 | 理由 |
|---|---|---|
| 侧边栏位置 | 左侧 | 符合主流商业系统习惯(VS Code、Notion、Slack) |
| 标题与用户信息位置 | 侧边栏内 | 整体感强,减少页面元素碎片化 |
| 默认宽度规格 | 展开时 200px / 收起时 56px | 紧凑型设计,最大化主内容区空间 |
| 实现方案 | 方案 A:经典固定侧边栏 | 实现简单、性能优、符合企业级应用标准 |
2.2 未采用的替代方案
- 方案 B(可拖拽调整宽度): 实现复杂度高,当前需求不需要此功能
- 方案 C(全响应式混合导航): 过度工程化,增加维护成本
- 右侧边栏: 不符合用户阅读习惯(从左到右)
3. 整体布局架构
3.1 布局结构图
桌面端(≥768px):
┌─────────────────────────────────────────────────────┐
│ │
│ ┌──────────┬──────────────────────────────────┐ │
│ │ │ │ │
│ │ 侧边栏 │ 主内容区域 │ │
│ │ (200px) │ (剩余所有空间) │ │
│ │ │ │ │
│ │ [品牌区] │ - ReadModule │ │
│ │ [导航项] │ - ManageModule │ │
│ │ [用户区] │ - QAModule │ │
│ │ │ - ExamModule │ │
│ └──────────┴──────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘
收起状态(56px):
┌────────┬───────────────────────────────────────────┐
│ │ │
│ 56px │ 主内容区域 │
│ │ (宽度 = 视口宽度 - 56px) │
│ 图标 │ │
│ 仅显示 │ │
└────────┴───────────────────────────────────────────┘
移动端(<768px):
┌──────────────────────────┐
│ ☰ 制度文件管理学习AI智能体 │ ← 顶部栏(固定,48px高)
├──────────────────────────┤
│ │
│ │
│ 主内容区域 │ ← 全屏显示
│ │
│ │
└──────────────────────────┘
点击 ☰ 后(Overlay模式):
┌────┬─────────────────────┐
│ │ ✕ │
│ 侧 │ │
│ 边 │ 主内容区(暗化) │
│ 栏 │ │
│ │ │
└────┴─────────────────────┘
3.2 技术实现要点
- 布局方式: Flexbox(外层容器
display: flex) - 侧边栏定位: 固定定位或正常流(
flex-shrink: 0) - 主内容区:
flex: 1; overflow: auto - 高度控制: 侧边栏
height: 100vh
4. 侧边栏内部组件设计
4.1 组件架构
AppSidebar.vue (主容器)
├── SidebarHeader.vue (品牌区)
│ ├── Logo/图标
│ ├── 系统名称
│ └── 折叠按钮
├── SidebarNav.vue (导航菜单)
│ └── SidebarNavItem.vue × N (单个导航项)
│ ├── 图标
│ ├── 文字标签
│ └── Tooltip (收起状态)
├── <div class="sidebar-spacer"> (弹性空间)
└── SidebarUser.vue (用户信息区)
├── 用户头像
├── 用户名 + 角色
└── 操作按钮 (设置/退出)
4.2 品牌区设计 (SidebarHeader)
展开状态 (200px):
┌──────────────────┐
│ 🤖 制度文件管理 │
│ 学习AI智能体 │
│ [←] │
└──────────────────┘
收起状态 (56px):
┌────┐
│ 🤖 │
│ [→] │
└────┘
组件接口:
<SidebarHeader
:collapsed="Boolean"
@toggle="Function"
/>
样式规范:
.sidebar-header {
height: var(--sidebar-header-height, 64px);
padding: 12px 16px;
display: flex;
flex-direction: column;
justify-content: center;
border-bottom: 1px solid var(--border-sidebar);
}
.brand {
display: flex;
align-items: center;
gap: 10px;
}
.brand-icon {
font-size: 28px;
line-height: 1;
flex-shrink: 0;
}
.brand-text {
font-size: 14px;
font-weight: 700;
color: var(--text-primary);
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
}
.collapse-btn {
align-self: flex-end;
margin-top: 8px;
width: 24px;
height: 24px;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
transition: all var(--transition-fast);
}
.collapse-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
4.3 导航菜单设计 (SidebarNav)
导航项配置数据结构:
const navItems = [
{ key: 'read', label: '文件查看', icon: '📄', permission: 'read' },
{ key: 'manage', label: '文件管理', icon: '📁', permission: 'manage' },
{ key: 'qa', label: '知识问答', icon: '💬', permission: 'qa' },
{ key: 'exam', label: '考察训练', icon: '🔍', permission: 'exam' },
{ key: 'mind', label: '纲要学习', icon: '📋', permission: 'mind' },
{ key: 'dashboard', label: '综合看板', icon: '📊', permission: 'dashboard' },
{ key: 'permission', label: '权限管理', icon: '👥', permission: 'permission' }
]
视觉状态对比:
| 状态 | 展开时 (200px) | 收起时 (56px) |
|---|---|---|
| 默认 | 📄 文件查看 (灰色文字) |
📄 (灰色图标) |
| 悬停 | 浅灰背景 + 深色文字 | 浅灰背景 + Tooltip 显示 "文件查看" |
| 激活 | 蓝色浅背景 + 蓝色粗体文字 + 左侧蓝色指示条 | 蓝色图标 + 左侧蓝色指示条 |
组件接口:
<SidebarNav
:items="Array"
:active-key="String"
:collapsed="Boolean"
@select="Function(key)"
/>
样式规范:
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
height: var(--sidebar-nav-item-height, 40px);
padding: 0 16px;
margin: 2px 8px;
border-radius: 6px;
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
color: var(--text-secondary);
background: transparent;
}
.nav-item:hover {
background: var(--bg-sidebar-hover, #f5f7fa);
color: var(--text-primary);
}
.nav-item.active {
background: var(--bg-sidebar-active, #e6f4ff);
color: var(--color-primary, #3b82f6);
font-weight: 600;
}
.nav-item.active::before {
content: '';
position: absolute;
left: -8px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 20px;
background: var(--color-primary);
border-radius: 2px;
}
.nav-icon {
font-size: 20px;
line-height: 1;
flex-shrink: 0;
}
.nav-label {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
Tooltip 实现(收起状态):
.nav-tooltip {
position: absolute;
left: calc(100% + 12px);
top: 50%;
transform: translateY(-50%) translateX(-4px);
padding: 6px 12px;
background: rgba(26, 26, 46, 0.92);
color: #ffffff;
font-size: 13px;
border-radius: 6px;
white-space: nowrap;
z-index: 1000;
pointer-events: none;
opacity: 0;
transition: all 200ms ease;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.nav-tooltip::before {
content: '';
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
border: 6px solid transparent;
border-right-color: rgba(26, 26, 46, 0.92);
}
.nav-item:hover .nav-tooltip {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
4.4 用户信息区设计 (SidebarUser)
展开状态:
┌──────────────────┐
│ ┌─┐ │
│ │张│ 系统管理员 │
│ └─┘ 角色:管理员 │
│ ⚙️ 设置 🚪退出 │
└──────────────────┘
收起状态:
┌────┐
│ 👤 │ ← hover: tooltip "系统管理员"
│ ⚙️ │
│ 🚪 │
└────┘
组件接口:
<SidebarUser
:collapsed="Boolean"
:user-info="Object"
:role-label="String"
@logout="Function"
@settings="Function"
/>
样式规范:
.sidebar-user {
padding: 16px;
border-top: 1px solid var(--border-sidebar);
background: var(--bg-elevated);
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: var(--color-primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
flex-shrink: 0;
}
.user-details {
overflow: hidden;
}
.user-name {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user-role {
font-size: 12px;
color: var(--text-secondary);
}
.user-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.action-btn {
padding: 6px 12px;
border: none;
background: transparent;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
color: var(--text-secondary);
transition: all var(--transition-fast);
}
.action-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
5. 交互细节与动画效果
5.1 展开/收起切换机制
触发方式:
- 主要方式: 点击侧边栏顶部的折叠按钮(箭头图标)
- 辅助方式(可选): 键盘快捷键
Ctrl + B
状态管理逻辑:
// composables/useSidebar.js
import { ref, watch, onMounted, onUnmounted } from 'vue'
export function useSidebar() {
const collapsed = ref(false)
const isMobile = ref(false)
const mobileOpen = ref(false)
// 从 localStorage 恢复状态
onMounted(() => {
const savedState = localStorage.getItem('sidebar-collapsed')
if (savedState !== null) {
collapsed.value = savedState === 'true'
}
// 初始化移动端检测
checkMobile()
window.addEventListener('resize', debounce(checkMobile, 150))
})
// 监听变化并持久化
watch(collapsed, (val) => {
localStorage.setItem('sidebar-collapsed', String(val))
}, { immediate: false })
const checkMobile = () => {
isMobile.value = window.innerWidth < 768
if (isMobile.value && mobileOpen.value) {
mobileOpen.value = false
}
}
const toggleCollapse = () => {
collapsed.value = !collapsed.value
}
const openMobile = () => {
mobileOpen.value = true
}
const closeMobile = () => {
mobileOpen.value = false
}
return {
collapsed,
isMobile,
mobileOpen,
toggleCollapse,
openMobile,
closeMobile
}
}
动画时间线:
时间轴(展开 → 收起):
0ms ──── 点击折叠按钮 ────→
↓
0-280ms CSS transition: width 200px → 56px
↓
100ms Vue transition: 文字 opacity 1 → 0 (提前消失)
↓
280ms 动画完成,进入完全收起状态
时间轴(收起 → 展开):
0ms ──── 点击展开按钮 ────→
↓
0-280ms CSS transition: width 56px → 200px
↓
180ms Vue transition: 文字 opacity 0 → 1 (延迟出现)
↓
280ms 动画完成,进入完全展开状态
CSS 过渡定义:
.sidebar {
width: var(--sidebar-width, 200px);
transition: width var(--sidebar-transition-duration) var(--sidebar-transition-easing);
will-change: width;
}
.sidebar.collapsed {
width: var(--sidebar-collapsed-width, 56px);
}
/* 文字淡入淡出 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 200ms ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 主内容区适配 */
.main-content {
margin-left: var(--sidebar-width, 200px);
transition: margin-left var(--sidebar-transition-duration) var(--sidebar-transition-easing);
will-change: margin-left;
}
.main-content.sidebar-collapsed {
margin-left: var(--sidebar-collapsed-width, 56px);
}
5.2 移动端适配策略
断点定义:
| 设备类型 | 屏幕宽度 | 行为 |
|---|---|---|
| 桌面端 | ≥ 1024px | 完整侧边栏(200px),支持手动折叠 |
| 平板端 | 768px - 1023px | 默认收起(56px),悬停自动展开 |
| 移动端 | < 768px | 隐藏侧边栏,汉堡菜单按钮呼出(overlay) |
移动端交互流程:
- 初始状态: 侧边栏隐藏,左上角显示 ☰ 按钮
- 打开侧边栏: 点击 ☰ → 侧边栏从左侧滑入 + 半透明遮罩层
- 使用导航: 点击导航项 → 切换模块 + 自动关闭侧边栏
- 关闭侧边栏:
- 点击遮罩层
- 点击 ✕ 关闭按钮
- 在侧边栏内向右滑动(可选手势)
移动端样式实现:
@media (max-width: 767px) {
.mobile-menu-btn {
position: fixed;
top: 12px;
left: 12px;
z-index: 1002;
width: 40px;
height: 40px;
border: none;
background: var(--bg-card);
border-radius: 8px;
box-shadow: var(--shadow-md);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 1001;
transform: translateX(-100%);
transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: var(--shadow-lg);
}
.sidebar.mobile-open {
transform: translateX(0);
}
.mobile-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
opacity: 0;
pointer-events: none;
transition: opacity 300ms ease;
}
.mobile-overlay.active {
opacity: 1;
pointer-events: auto;
}
.main-content {
margin-left: 0 !important;
}
}
6. 性能优化策略
6.1 渲染性能优化
| 优化技术 | 应用场景 | 预期收益 |
|---|---|---|
| GPU 加速动画 | 侧边栏宽度过渡、主内容区 margin 过渡 | 避免 layout thrashing |
| will-change 提示 | .sidebar, .main-content 元素 |
提前创建合成层 |
| 事件防抖 | resize 事件监听器 | 减少不必要的重计算 |
| 被动监听 | touch 事件、scroll 事件 | 提升滚动流畅度 |
| 按需加载 | MobileMenuButton 组件 | 减少首屏加载体积 |
6.2 内存优化
- 避免内存泄漏: 在
onUnmounted中移除事件监听器 - 合理使用 ref/computed: 避免不必要的响应式依赖
- 虚拟列表(未来扩展): 如果导航项超过 20 个,使用虚拟滚动
6.3 可访问性 (Accessibility)
- 键盘导航: 支持 Tab 键切换导航项,Enter 键激活
- ARIA 标签: 为侧边栏添加
role="navigation"和aria-label - 焦点管理: 打开/关闭侧边栏时正确管理焦点陷阱
- 屏幕阅读器: 为图标添加
aria-label或隐藏的文字标签 - 颜色对比度: 确保所有文本满足 WCAG 2.1 AA 标准(4.5:1)
7. 文件结构与组件清单
7.1 新增文件
src/
├── components/
│ └── layout/
│ ├── AppSidebar.vue # 侧边栏主组件(~200行)
│ ├── SidebarHeader.vue # 品牌区组件(~80行)
│ ├── SidebarNav.vue # 导航菜单容器(~60行)
│ ├── SidebarNavItem.vue # 单个导航项(~100行)
│ ├── SidebarUser.vue # 用户信息区(~120行)
│ └── MobileMenuButton.vue # 移动端汉堡按钮(~40行)
│
├── composables/
│ └── useSidebar.js # 侧边栏状态管理(~80行)
│
└── utils/
└── debounce.js # 防抖工具函数(~15行)
7.2 修改文件
src/
├── views/
│ └── Home.vue # 重构:移除 header,引入侧边栏布局
│
├── design-tokens.css # 扩展:添加侧边栏相关 CSS 变量
│
└── style.css # 更新:可能需要微调全局样式
7.3 组件职责划分
| 组件 | 职责 | Props | Events |
|---|---|---|---|
| AppSidebar | 侧边栏主容器,协调子组件 | collapsed, navItems, activeKey, userInfo, isMobile, mobileOpen |
update:collapsed, navigate, logout, settings |
| SidebarHeader | 显示品牌标识和折叠按钮 | collapsed |
toggle |
| SidebarNav | 渲染导航项列表 | items[], activeKey, collapsed |
select(key) |
| SidebarNavItem | 单个导航项的展示和交互 | item{key,label,icon}, active, collapsed |
click |
| SidebarUser | 显示用户信息和操作按钮 | collapsed, userInfo{}, roleLabel |
logout, settings |
| MobileMenuButton | 移动端的菜单触发按钮 | 无 | click |
8. 样式变量扩展
在现有的 src/design-tokens.css 中添加以下变量:
/* ========== 侧边栏系统 ========== */
:root {
/* 尺寸规范 */
--sidebar-width: 200px;
--sidebar-collapsed-width: 56px;
--sidebar-nav-item-height: 40px;
--sidebar-header-height: 64px;
--sidebar-user-height: auto;
/* 颜色规范 */
--bg-sidebar: #ffffff;
--bg-sidebar-hover: #f5f7fa;
--bg-sidebar-active: #e6f4ff;
--border-sidebar: #e5e7eb;
/* 动画规范 */
--sidebar-transition-duration: 280ms;
--sidebar-transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
/* 响应式断点 */
--breakpoint-mobile: 768px;
--breakpoint-tablet: 1024px;
/* 圆角规范 */
--radius-nav-item: 6px;
--radius-tooltip: 6px;
/* 阴影规范 */
--shadow-sidebar: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-mobile-overlay: 0 4px 16px rgba(0, 0, 0, 0.12);
}
9. 测试策略
9.1 单元测试
-
useSidebar composable:
- 测试初始状态从 localStorage 正确恢复
- 测试
toggleCollapse函数切换状态 - 测试移动端检测逻辑
- 测试状态变更后正确保存到 localStorage
-
各组件渲染测试:
- 测试展开/收起状态下正确的 DOM 结构
- 测试 props 正确传递给子组件
- 测试 events 正确触发
9.2 集成测试
- Home.vue 集成:
- 测试侧边栏与主内容区的协同工作
- 测试导航切换功能是否正常
- 测试用户登出流程
9.3 视觉回归测试
- 使用 Playwright 或 Cypress 进行截图对比
- 测试不同断点下的布局表现
- 验证动画流畅性
9.4 手动测试清单
- 桌面端:侧边栏正常展开/收起
- 桌面端:点击导航项正确切换模块
- 桌面端:刷新页面后保持上次的状态
- 平板端:默认收起,悬停显示 tooltip
- 移动端:汉堡菜单按钮可见且可点击
- 移动端:侧边栏以 overlay 模式滑出
- 移动端:点击遮罩层可关闭侧边栏
- 键盘导航:Tab 键可在导航项间切换
- 无障碍:屏幕阅读器可正确朗读导航项
10. 迁移计划与风险控制
10.1 分阶段实施
Phase 1: 基础设施搭建(预计 2 小时)
- 创建组件目录结构
- 实现
useSidebarcomposable - 扩展
design-tokens.css
Phase 2: 组件开发(预计 3 小时)
- 实现 AppSidebar 及其子组件
- 实现展开/收起动画
- 实现 Tooltip 功能
Phase 3: 集成与适配(预计 2 小时)
- 修改 Home.vue,移除旧 header
- 集成新的侧边栏布局
- 实现移动端响应式
Phase 4: 测试与优化(预计 1 小时)
- 手动测试所有场景
- 性能优化
- 修复边界情况 bug
总预计工时: ~8 小时
10.2 风险识别与缓解
| 风险 | 可能性 | 影响 | 缓解措施 |
|---|---|---|---|
| 与现有样式冲突 | 中 | 高 | 使用 scoped styles + BEM 命名 |
| 移动端兼容性问题 | 低 | 中 | 充分测试主流设备 |
| 性能问题(大量 DOM 操作) | 低 | 中 | 使用 Vue 的 transition 组件 |
| 用户习惯改变导致困惑 | 中 | 低 | 提供引导提示(首次使用) |
| localStorage 不可用 | 极低 | 低 | try-catch 包裹,降级处理 |
10.3 回滚方案
如果新版本出现严重问题,可以通过 Git 快速回滚到上一个稳定版本:
git revert <commit-hash>
# 或
git reset --hard <previous-stable-commit>
建议在合并前打 tag 以便快速回滚:
git tag -a v1.0-before-sidebar-refactor -m "Pre sidebar refactor"
11. 成功标准
11.1 功能完整性
- 所有 7 个功能模块均可通过侧边栏访问
- 展开/收起功能正常工作
- 状态记忆功能正常(localStorage)
- Tooltip 在收起状态下正确显示
- 移动端 overlay 模式正常工作
11.2 性能指标
- 展开/收起动画帧率 ≥ 60fps
- 首屏加载时间增加 < 200ms
- 内存占用增加 < 5MB
- 无明显的布局抖动(CLS < 0.1)
11.3 用户体验指标
- 用户可在 3 秒内理解如何使用侧边栏
- 导航切换操作步骤 ≤ 2 步
- 视觉风格与整体白色体系一致
- 不同屏幕尺寸下均表现良好
12. 未来迭代方向(超出本次范围)
12.1 短期增强(可选)
- 拖拽调整宽度(方案 B 特性)
- 多级嵌套菜单(如果功能模块有子分类)
- 搜索框集成(在侧边栏顶部添加全局搜索)
- 通知徽章(在导航项右上角显示未读数量)
12.2 中期优化
- 主题定制(深色模式、自定义配色)
- 键盘快捷键完整支持
- 手势导航增强(边缘滑动、长按预览)
- 国际化(i18n)支持
12.3 长期规划
- 插件系统(允许第三方扩展侧边栏功能)
- AI 辅助导航(根据使用频率智能排序)
- 跨应用同步(多标签页间同步侧边栏状态)
附录 A: 参考资料
附录 B: 术语表
| 术语 | 定义 |
|---|---|
| Sidebar | 侧边栏,垂直排列的导航面板 |
| Collapsed | 收起状态,仅显示图标 |
| Expanded | 展开状态,显示图标+文字 |
| Overlay | 遮罩层,半透明背景覆盖主内容区 |
| Tooltip | 工具提示,鼠标悬停时显示的文字说明 |
| Responsive | 响应式,适应不同屏幕尺寸 |
| localStorage | 浏览器本地存储,用于持久化用户偏好 |
文档维护者: AI Assistant
最后更新: 2026-05-14
下次评审日期: 实施完成后