# 侧边栏导航系统实施计划

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 将现有的顶部水平 header 导航重构为左侧固定侧边栏导航系统，支持展开/收起、状态记忆、响应式适配，并保持与现有白色体系设计规范的一致性。

**Architecture:** 采用 Vue 3 Composition API + Flexbox 布局实现经典的左侧固定侧边栏模式。通过 `useSidebar` composable 集中管理状态（展开/收起、移动端检测、localStorage 持久化），将侧边栏拆分为 5 个职责单一的子组件（品牌区、导航菜单、导航项、用户信息区、移动端按钮），确保代码可维护性和可测试性。

**Tech Stack:** 
- Vue 3 (Composition API, `<script setup>`)
- CSS Variables (白色体系 design-tokens)
- localStorage (状态持久化)
- Ant Design Vue (图标、部分组件)

---

## 文件结构总览

### 新增文件 (8个)
```
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行)
```

### 修改文件 (3个)
```
src/
├── views/
│   └── Home.vue                        # 主要改造：移除旧header，集成侧边栏布局
│
├── design-tokens.css                   # 扩展：添加侧边栏相关CSS变量
│
└── style.css                           # 微调：可能需要调整全局样式
```

---

## Task 1: 创建基础设施 - 工具函数和设计令牌

**Files:**
- Create: `src/utils/debounce.js`
- Modify: `src/design-tokens.css`

**目标**: 建立项目所需的基础工具函数和扩展设计变量定义

- [ ] **Step 1: 创建防抖工具函数**

```javascript
// src/utils/debounce.js
export function debounce(fn, delay = 150) {
  let timer = null
  
  return function (...args) {
    if (timer) {
      clearTimeout(timer)
    }
    
    timer = setTimeout(() => {
      fn.apply(this, args)
      timer = null
    }, delay)
  }
}
```

- [ ] **Step 2: 扩展设计令牌文件**

在 `src/design-tokens.css` 的 `:root` 选择器末尾添加以下变量：

```css
/* ========== 侧边栏系统 ========== */
:root {
  /* ... 已有变量 ... */
  
  /* 尺寸规范 */
  --sidebar-width: 200px;
  --sidebar-collapsed-width: 56px;
  --sidebar-nav-item-height: 40px;
  --sidebar-header-height: 64px;
  
  /* 颜色规范 */
  --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;
}
```

- [ ] **Step 3: 验证**

运行开发服务器，确认无语法错误：
```bash
npm run dev
```
预期：正常启动，控制台无错误

- [ ] **Step 4: Commit**

```bash
git add src/utils/debounce.js src/design-tokens.css
git commit -m "chore: add debounce utility and sidebar design tokens"
```

---

## Task 2: 实现 useSidebar Composable

**Files:**
- Create: `src/composables/useSidebar.js`

**目标**: 创建侧边栏状态管理的核心逻辑，包括展开/收起切换、移动端检测、localStorage 持久化

- [ ] **Step 1: 编写 useSidebar 基础结构**

```javascript
// src/composables/useSidebar.js
import { ref, watch, onMounted, onUnmounted } from 'vue'
import { debounce } from '../utils/debounce'

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', debouncedCheckMobile)
  })

  onUnmounted(() => {
    window.removeEventListener('resize', debouncedCheckMobile)
  })

  // 监听变化并持久化
  watch(collapsed, (val) => {
    localStorage.setItem('sidebar-collapsed', String(val))
  }, { immediate: false })

  const debouncedCheckMobile = debounce(checkMobile, 150)

  function checkMobile() {
    isMobile.value = window.innerWidth < 768
    if (isMobile.value && mobileOpen.value) {
      mobileOpen.value = false
    }
  }

  function toggleCollapse() {
    collapsed.value = !collapsed.value
  }

  function openMobile() {
    mobileOpen.value = true
  }

  function closeMobile() {
    mobileOpen.value = false
  }

  return {
    collapsed,
    isMobile,
    mobileOpen,
    toggleCollapse,
    openMobile,
    closeMobile
  }
}
```

- [ ] **Step 2: 在临时测试文件中验证功能**

在任意 `.vue` 文件的 `<script setup>` 中临时添加：
```javascript
import { useSidebar } from './composables/useSidebar'

const { collapsed, isMobile, toggleCollapse } = useSidebar()

// 打开浏览器控制台，手动调用 toggleCollapse() 测试
window.testSidebar = { collapsed, isMobile, toggleCollapse }
```
预期：`window.testSidebar.toggleCollapsed()` 可正确切换状态

- [ ] **Step 3: 清理测试代码**

删除临时代码

- [ ] **Step 4: Commit**

```bash
git add src/composables/useSidebar.js
git commit -m "feat: add useSidebar composable for state management"
```

---

## Task 3: 实现 SidebarHeader 组件

**Files:**
- Create: `src/components/layout/SidebarHeader.vue`

**目标**: 实现侧边栏顶部的品牌区域，包含 Logo、系统名称、折叠按钮

- [ ] **Step 1: 编写组件模板和脚本**

```vue
<template>
  <div class="sidebar-header">
    <div class="brand">
      <span class="brand-icon">🤖</span>
      <transition name="fade">
        <span v-show="!collapsed" class="brand-text">
          制度文件管理学习<br/>AI智能体
        </span>
      </transition>
    </div>
    <button 
      class="collapse-btn" 
      @click="$emit('toggle')"
      :title="collapsed ? '展开侧边栏' : '收起侧边栏'"
    >
      {{ collapsed ? '→' : '←' }}
    </button>
  </div>
</template>

<script setup>
defineProps({
  collapsed: {
    type: Boolean,
    default: false
  }
})

defineEmits(['toggle'])
</script>

<style scoped>
.sidebar-header {
  height: var(--sidebar-header-height);
  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;
}

.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);
  font-size: 14px;
  transition: all var(--transition-fast);
}

.collapse-btn:hover {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 200ms ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
```

- [ ] **Step 2: 手动验证**

在 `Home.vue` 中临时导入并渲染该组件，检查：
- 展开时显示完整文字
- 收起时文字隐藏
- 点击折叠按钮触发事件

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/SidebarHeader.vue
git commit -m "feat: add SidebarHeader component with brand area and collapse button"
```

---

## Task 4: 实现 SidebarNavItem 组件

**Files:**
- Create: `src/components/layout/SidebarNavItem.vue`

**目标**: 实现单个导航项，支持激活状态、悬停效果、Tooltip（收起状态）

- [ ] **Step 1: 编写组件完整代码**

```vue
<template>
  <button
    :class="['nav-item', { active: isActive }]"
    @click="$emit('select', item.key)"
    :aria-label="item.label"
    :aria-current="isActive ? 'page' : undefined"
  >
    <span class="nav-indicator"></span>
    <span class="nav-icon" v-html="item.icon"></span>
    <transition name="fade">
      <span v-show="!collapsed" class="nav-label">{{ item.label }}</span>
    </transition>
    
    <!-- Tooltip (仅收起状态显示) -->
    <transition name="tooltip">
      <span v-if="collapsed && isHovering" class="nav-tooltip">
        {{ item.label }}
      </span>
    </transition>
  </button>
</template>

<script setup>
import { ref } from 'vue'

const props = defineProps({
  item: {
    type: Object,
    required: true
  },
  active: {
    type: Boolean,
    default: false
  },
  collapsed: {
    type: Boolean,
    default: false
  }
})

defineEmits(['select'])

const isHovering = ref(false)
</script>

<style scoped>
.nav-item {
  position: relative;
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--sidebar-nav-item-height);
  padding: 0 16px;
  margin: 2px 8px;
  border: none;
  border-radius: var(--radius-nav-item);
  cursor: pointer;
  background: transparent;
  color: var(--text-secondary);
  transition: all var(--transition-fast);
  white-space: nowrap;
  overflow: hidden;
}

.nav-item:hover {
  background: var(--bg-sidebar-hover);
  color: var(--text-primary);
}

.nav-item.active {
  background: var(--bg-sidebar-active);
  color: var(--color-primary);
  font-weight: 600;
}

.nav-item.active .nav-indicator {
  opacity: 1;
  transform: scaleY(1);
}

.nav-indicator {
  position: absolute;
  left: -8px;
  top: 50%;
  transform: translateY(-50%) scaleY(0);
  width: 3px;
  height: 20px;
  background: var(--color-primary);
  border-radius: 2px;
  opacity: 0;
  transition: all var(--transition-fast);
}

.nav-icon {
  font-size: 20px;
  line-height: 1;
  flex-shrink: 0;
}

.nav-label {
  font-size: 14px;
  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: var(--radius-tooltip);
  white-space: nowrap;
  z-index: 1000;
  pointer-events: none;
  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 {
  transform: translateY(-50%) translateX(0);
}

.tooltip-enter-active,
.tooltip-leave-active {
  transition: all 200ms ease;
}

.tooltip-enter-from,
.tooltip-leave-to {
  opacity: 0;
  transform: translateY(-50%) translateX(-8px);
}

/* 文字淡入淡出 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 200ms ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
```

**注意**: 当前实现中 Tooltip 通过 CSS `:hover` 显示。如果需要更精确的控制（延迟显示等），可以在 `@mouseenter` 和 `@mouseleave` 事件中设置 `isHovering`。

- [ ] **Step 2: 验证交互状态**

在临时测试页面中渲染多个实例：
- 默认状态：灰色图标+文字
- 悬停状态：背景色变浅
- 激活状态：蓝色背景 + 左侧指示条
- 收起状态：仅显示图标，悬停显示 tooltip

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/SidebarNavItem.vue
git commit -m "feat: add SidebarNavItem with active state and tooltip support"
```

---

## Task 5: 实现 SidebarNav 组件

**Files:**
- Create: `src/components/layout/SidebarNav.vue`

**目标**: 导航菜单容器，遍历渲染导航项列表

- [ ] **Step 1: 编写组件代码**

```vue
<template>
  <nav class="sidebar-nav" role="navigation" aria-label="主导航菜单">
    <SidebarNavItem
      v-for="item in items"
      :key="item.key"
      :item="item"
      :active="activeKey === item.key"
      :collapsed="collapsed"
      @select="handleSelect"
    />
  </nav>
</template>

<script setup>
import SidebarNavItem from './SidebarNavItem.vue'

const props = defineProps({
  items: {
    type: Array,
    required: true
  },
  activeKey: {
    type: String,
    required: true
  },
  collapsed: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits(['select'])

function handleSelect(key) {
  emit('select', key)
}
</script>

<style scoped>
.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 8px 0;
}

/* 自定义滚动条 */
.sidebar-nav::-webkit-scrollbar {
  width: 4px;
}

.sidebar-nav::-webkit-scrollbar-track {
  background: transparent;
}

.sidebar-nav::-webkit-scrollbar-thumb {
  background: var(--border-default);
  border-radius: 2px;
}

.sidebar-nav::-webkit-scrollbar-thumb:hover {
  background: var(--text-tertiary);
}
</style>
```

- [ ] **Step 2: 集成测试**

在临时页面中使用真实的导航项配置数据测试：
```javascript
const testItems = [
  { key: 'read', label: '文件查看', icon: '📄' },
  { key: 'manage', label: '文件管理', icon: '📁' },
  { key: 'qa', label: '知识问答', icon: '💬' }
]
```
预期：正确渲染 3 个导航项，点击可触发 select 事件

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/SidebarNav.vue
git commit -m "feat: add SidebarNav container component"
```

---

## Task 6: 实现 SidebarUser 组件

**Files:**
- Create: `src/components/layout/SidebarUser.vue`

**目标**: 用户信息展示区，包含头像、用户名、角色、操作按钮

- [ ] **Step 1: 编写组件代码**

```vue
<template>
  <div class="sidebar-user">
    <div class="user-info">
      <div class="user-avatar">
        {{ userInitial }}
      </div>
      <transition name="fade">
        <div v-show="!collapsed" class="user-details">
          <div class="user-name">{{ displayName }}</div>
          <div class="user-role">角色：{{ roleLabel }}</div>
        </div>
      </transition>
    </div>
    
    <transition name="fade">
      <div v-show="!collapsed" class="user-actions">
        <button 
          class="action-btn" 
          @click="$emit('settings')"
          title="设置"
        >
          ⚙️ 设置
        </button>
        <button 
          class="action-btn" 
          @click="handleLogout"
          title="退出登录"
        >
          🚪 退出
        </button>
      </div>
    </transition>
  </div>
</template>

<script setup>
import { computed } from 'vue'
import { Modal } from 'ant-design-vue'

const props = defineProps({
  collapsed: {
    type: Boolean,
    default: false
  },
  userInfo: {
    type: Object,
    default: () => ({})
  },
  roleLabel: {
    type: String,
    default: '未知角色'
  }
})

const emit = defineEmits(['logout', 'settings'])

const displayName = computed(() => {
  return props.userInfo?.realName || props.userInfo?.username || '用户'
})

const userInitial = computed(() => {
  const name = displayName.value
  return name.charAt(0).toUpperCase()
})

function handleLogout() {
  Modal.confirm({
    title: '确认退出',
    content: '确定要退出登录吗？',
    okText: '确定',
    cancelText: '取消',
    onOk() {
      emit('logout')
    }
  })
}
</script>

<style scoped>
.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: #ffffff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  font-size: 16px;
  flex-shrink: 0;
}

.user-details {
  overflow: hidden;
  flex: 1;
}

.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);
  margin-top: 2px;
}

.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);
}

.fade-enter-active,
.fade-leave-active {
  transition: opacity 200ms ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
```

- [ ] **Step 2: 验证用户信息显示**

传入模拟数据：
```javascript
{
  userInfo: { realName: '张三' },
  roleLabel: '系统管理员'
}
```
预期：显示头像（字母"张"）、用户名、角色标签

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/SidebarUser.vue
git commit -m "feat: add SidebarUser component with avatar and actions"
```

---

## Task 7: 实现 MobileMenuButton 组件

**Files:**
- Create: `src/components/layout/MobileMenuButton.vue`

**目标**: 移动端的汉堡菜单按钮，仅在屏幕宽度 < 768px 时显示

- [ ] **Step 1: 编写组件代码**

```vue
<template>
  <button
    class="mobile-menu-btn"
    @click="$emit('click')"
    aria-label="打开导航菜单"
    type="button"
  >
    <span class="hamburger-icon">☰</span>
  </button>
</template>

<script setup>
defineEmits(['click'])
</script>

<style scoped>
.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: none; /* 默认隐藏，通过媒体查询显示 */
  align-items: center;
  justify-content: center;
  font-size: 20px;
  color: var(--text-primary);
  transition: all var(--transition-fast);
}

.mobile-menu-btn:hover {
  background: var(--bg-hover);
  transform: scale(1.05);
}

.mobile-menu-btn:active {
  transform: scale(0.95);
}

@media (max-width: 767px) {
  .mobile-menu-btn {
    display: flex;
  }
}
</style>
```

- [ ] **Step 2: 验证响应式行为**

调整浏览器窗口宽度：
- ≥ 768px：按钮隐藏
- < 768px：按钮可见且可点击

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/MobileMenuButton.vue
git commit -m "feat: add MobileMenuButton for responsive navigation"
```

---

## Task 8: 实现 AppSidebar 主组件

**Files:**
- Create: `src/components/layout/AppSidebar.vue`

**目标**: 整合所有子组件，实现完整的侧边栏功能，包括桌面端固定模式和移动端 overlay 模式

- [ ] **Step 1: 编写主组件代码**

```vue
<template>
  <!-- 移动端遮罩层 -->
  <transition name="overlay">
    <div 
      v-if="isMobile && mobileOpen" 
      class="mobile-overlay"
      @click="$emit('update:mobileOpen', false)"
      aria-hidden="true"
    ></div>
  </transition>

  <!-- 侧边栏主体 -->
  <aside
    class="sidebar"
    :class="{ 
      collapsed, 
      'mobile-open': isMobile && mobileOpen 
    }"
    role="navigation"
    :aria-label="collapsed ? '收起的导航菜单' : '导航菜单'"
  >
    <SidebarHeader 
      :collapsed="collapsed" 
      @toggle="$emit('update:collapsed', !collapsed)" 
    />
    
    <SidebarNav 
      :items="navItems" 
      :active-key="activeKey"
      :collapsed="collapsed"
      @select="handleNavigate"
    />
    
    <div class="sidebar-spacer"></div>
    
    <SidebarUser 
      :collapsed="collapsed"
      :user-info="userInfo"
      :role-label="roleLabel"
      @logout="$emit('logout')"
      @settings="$emit('settings')"
    />
  </aside>
</template>

<script setup>
import SidebarHeader from './SidebarHeader.vue'
import SidebarNav from './SidebarNav.vue'
import SidebarUser from './SidebarUser.vue'

const props = defineProps({
  collapsed: {
    type: Boolean,
    default: false
  },
  navItems: {
    type: Array,
    required: true
  },
  activeKey: {
    type: String,
    required: true
  },
  userInfo: {
    type: Object,
    default: () => ({})
  },
  roleLabel: {
    type: String,
    default: ''
  },
  isMobile: {
    type: Boolean,
    default: false
  },
  mobileOpen: {
    type: Boolean,
    default: false
  }
})

const emit = defineEmits([
  'update:collapsed',
  'update:mobileOpen',
  'navigate',
  'logout',
  'settings'
])

function handleNavigate(key) {
  emit('navigate', key)
  
  // 移动端选择后自动关闭侧边栏
  if (props.isMobile) {
    emit('update:mobileOpen', false)
  }
}
</script>

<style scoped>
.sidebar {
  width: var(--sidebar-width);
  background: var(--bg-sidebar);
  border-right: 1px solid var(--border-sidebar);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transition: width var(--sidebar-transition-duration) var(--sidebar-transition-easing);
  will-change: width;
  flex-shrink: 0;
}

.sidebar.collapsed {
  width: var(--sidebar-collapsed-width);
}

.sidebar-spacer {
  flex: 1;
  min-height: 0;
}

/* 移动端 Overlay 模式 */
.mobile-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  z-index: 1000;
}

@media (max-width: 767px) {
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    height: 100vh;
    z-index: 1001;
    transform: translateX(-100%);
    box-shadow: var(--shadow-lg);
  }
  
  .sidebar.mobile-open {
    transform: translateX(0);
  }
}

.overlay-enter-active,
.overlay-leave-active {
  transition: opacity 300ms ease;
}

.overlay-enter-from,
.overlay-leave-to {
  opacity: 0;
}
</style>
```

- [ ] **Step 2: 集成测试**

在临时页面中组装所有子组件，测试：
- 展开/收起动画流畅
- 导航点击事件正确传递
- 用户信息正确显示
- 移动端模式下 overlay 行为正常

- [ ] **Step 3: Commit**

```bash
git add src/components/layout/AppSidebar.vue
git commit -m "feat: add AppSidebar main container with responsive support"
```

---

## Task 9: 改造 Home.vue - 集成侧边栏布局

**Files:**
- Modify: `src/views/Home.vue`

**目标**: 移除现有的 `<a-layout-header>`，替换为新的侧边栏布局系统

- [ ] **Step 1: 备份当前 Home.vue**

```bash
cp src/views/Home.vue src/views/Home.vue.backup
```

- [ ] **Step 2: 重写模板部分**

将现有的 `<template>` 替换为：

```vue
<template>
  <div class="app-layout">
    <!-- 移动端菜单按钮 -->
    <MobileMenuButton 
      v-if="isMobile"
      @click="openMobile"
    />
    
    <!-- 侧边栏 -->
    <AppSidebar
      v-model:collapsed="collapsed"
      v-model:mobile-open="mobileOpen"
      :nav-items="visibleModules"
      :active-key="currentModule"
      :user-info="userInfo"
      :role-label="currentRoleLabel"
      :is-mobile="isMobile"
      @navigate="handleModuleChange"
      @logout="handleLogout"
      @settings="handleSettings"
    />
    
    <!-- 主内容区域 -->
    <main 
      class="main-content" 
      :class="{ 'sidebar-collapsed': collapsed && !isMobile }"
    >
      <ReadModule 
        v-if="currentModule === 'read'" 
        ref="readModuleRef" 
      />
      <ManageModule 
        v-else-if="currentModule === 'manage'" 
      />
      <QAModule 
        v-else-if="currentModule === 'qa'" 
        ref="qaModuleRef"
      />
      <ExamModule 
        v-else-if="currentModule === 'exam'" 
      />
      <MindModule 
        v-else-if="currentModule === 'mind'" 
      />
      <DashboardModule 
        v-else-if="currentModule === 'dashboard'" 
      />
      <PermissionModule 
        v-else-if="currentModule === 'permission'" 
        @permissions-updated="handlePermissionsUpdated"
        @refresh-modules="handleRefreshModules"
      />
      <div 
        v-else 
        class="empty-state"
      >
        <a-empty description="暂无权限访问任何功能，请联系管理员分配权限" />
      </div>
    </main>
  </div>
</template>
```

**关键变更说明**:
- ❌ 删除 `<a-layout>` 和 `<a-layout-header>`
- ✅ 新增 `<div class="app-layout">` 作为根容器
- ✅ 引入 `<MobileMenuButton>` 和 `<AppSidebar>`
- ✅ 主内容区从 `<section>` 改为 `<main>` 并添加动态类名

- [ ] **Step 3: 更新脚本部分**

在 `<script setup>` 中添加导入和使用：

```javascript
// 新增导入
import { useSidebar } from '../composables/useSidebar'
import AppSidebar from '../components/layout/AppSidebar.vue'
import MobileMenuButton from '../components/layout/MobileMenuButton.vue'

// 使用 composable
const {
  collapsed,
  isMobile,
  mobileOpen,
  toggleCollapse,
  openMobile,
  closeMobile
} = useSidebar()

// 新增方法
function handleSettings() {
  message.info('设置功能开发中...')
}

function handleLogout() {
  // 调用原有的登出逻辑
  console.log('用户请求登出')
  // 可以在这里调用原有的 logout 方法
}
```

**保留不变的部分**:
- 所有模块的导入语句
- `visibleModules` 计算属性
- `handleModuleChange` 函数
- 权限检查相关逻辑
- 用户信息获取逻辑

- [ ] **Step 4: 更新样式部分**

将 `<style scoped>` 替换为：

```css
<style scoped>
.app-layout {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

.main-content {
  flex: 1;
  margin-left: var(--sidebar-width);
  overflow: auto;
  transition: margin-left var(--sidebar-transition-duration) var(--sidebar-transition-easing);
  background: var(--bg-page);
  will-change: margin-left;
}

.main-content.sidebar-collapsed {
  margin-left: var(--sidebar-collapsed-width);
}

.empty-state {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
}

/* 移动端适配 */
@media (max-width: 767px) {
  .main-content {
    margin-left: 0 !important;
  }
}
</style>
```

**关键变更**:
- ❌ 删除 `.top-nav-bar`, `.nav-tab`, `.nav-icon`, `.nav-label` 等旧样式
- ✅ 新增 `.app-layout`, `.main-content` 布局样式
- ✅ 添加响应式断点处理

- [ ] **Step 5: 功能验证**

逐一测试以下场景：
1. ✅ 页面加载后侧边栏默认展开（或从 localStorage 恢复）
2. ✅ 点击折叠按钮可切换展开/收起
3. ✅ 点击导航项可切换模块内容
4. ✅ 刷新页面后保持上次的状态
5. ✅ 用户信息正确显示
6. ✅ 退出按钮弹出确认对话框
7. ✅ 调整浏览器宽度至 < 768px，出现汉堡按钮
8. ✅ 移动端点击汉堡按钮，侧边栏以 overlay 方式滑出
9. ✅ 移动端点击遮罩层或导航项，侧边栏关闭

- [ ] **Step 6: 清理备份文件**

确认一切正常后删除备份：
```bash
rm src/views/Home.vue.backup
```

- [ ] **Step 7: Commit**

```bash
git add src/views/Home.vue
git commit -m "refactor: replace header navigation with sidebar layout system"
```

---

## Task 10: 全局样式微调与优化

**Files:**
- Modify: `src/style.css`

**目标**: 确保全局样式与新侧边栏布局兼容，移除可能的冲突样式

- [ ] **Step 1: 检查并移除冲突的全局样式**

搜索并评估以下可能影响侧边栏的全局规则：
- `body`, `html` 的 margin/padding 设置
- 全局的 `overflow` 规则
- 可能影响 fixed 定位元素的样式

如果发现冲突，在 `style.css` 中添加必要的覆盖或修正。

- [ ] **Step 2: 添加平滑滚动增强（可选）**

```css
/* 全局平滑滚动 */
html {
  scroll-behavior: smooth;
}

/* 优化滚动条样式（Webkit） */
::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: var(--border-default);
  border-radius: 4px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--text-tertiary);
}
```

- [ ] **Step 3: 验证视觉效果**

在不同页面间切换，确保：
- 无意外的布局偏移
- 滚动条样式统一
- 过渡动画流畅自然

- [ ] **Step 4: Commit**

```bash
git add src/style.css
git commit -m "style: global styles adjustment for sidebar compatibility"
```

---

## Task 11: 最终测试与验收

**Files:** 无新增/修改（仅测试）

**目标**: 全面验证所有功能符合设计规范，修复边界情况 bug

- [ ] **Step 1: 功能完整性测试清单**

使用以下清单逐项验证：

#### 桌面端（≥ 768px）:
- [ ] 侧边栏默认展开，显示完整导航项（图标+文字）
- [ ] 点击折叠按钮，侧边栏平滑收起至 56px
- [ ] 收起状态下，鼠标悬停导航项显示 tooltip
- [ ] 点击导航项，主内容区切换到对应模块
- [ ] 激活的导航项有蓝色背景和左侧指示条
- [ ] 用户信息区显示头像、姓名、角色
- [ ] 点击"退出"按钮，弹出确认对话框
- [ ] 刷新页面后，侧边栏状态保持（localStorage）
- [ ] 按 Ctrl+B 可快捷切换（如果实现了）

#### 移动端（< 768px）:
- [ ] 侧边栏默认隐藏
- [ ] 左上角显示汉堡菜单按钮（☰）
- [ ] 点击汉堡按钮，侧边栏从左侧滑入
- [ ] 显示半透明黑色遮罩层
- [ ] 点击遮罩层，侧边栏滑出并关闭
- [ ] 点击导航项，切换模块并自动关闭侧边栏
- [ ] 侧边栏内可正常滚动（如果导航项多）

#### 响应式断点:
- [ ] 768px 附近切换无明显闪烁
- [ ] 平板尺寸下表现合理（可根据设计决定是否默认收起）

- [ ] **Step 2: 性能测试**

使用浏览器 DevTools 检查：
- 展开/收起动画帧率 ≥ 60fps（Performance 面板）
- 无明显的内存泄漏（Memory 面板时间线）
- 首屏加载时间增加 < 200ms（Network 面板）

- [ ] **Step 3: 兼容性测试**

在以下浏览器中快速验证：
- Chrome/Edge (最新版)
- Firefox (最新版)
- Safari (如果有 Mac/iOS 设备)

- [ ] **Step 4: 可访问性基础检查**

- [ ] 键盘 Tab 键可在导航项间切换
- [ ] Enter/Space 键可激活导航项
- [ ] 屏幕阅读器可识别当前页面（aria-current）
- [ ] 颜色对比度满足 WCAG AA 标准

- [ ] **Step 5: 边界情况处理**

测试以下边缘场景：
- [ ] 快速连续点击折叠按钮（不会卡顿或错乱）
- [ ] 在移动端旋转设备方向（横竖屏切换）
- [ ] localStorage 被禁用或满载时的降级处理
- [ ] 极小屏幕（320px）下的显示效果
- [ ] 用户没有任何权限时的空状态提示

- [ ] **Step 6: 修复发现的问题**

如果在上述测试中发现问题，立即修复并记录在案。

- [ ] **Step 7: 最终 Commit**

```bash
git add -A
git commit -m "test: final verification and bug fixes for sidebar navigation"
```

---

## 自我审查清单

### ✅ Spec 覆盖度检查

| 设计规范章节 | 对应任务 | 状态 |
|------------|---------|------|
| §3 整体布局架构 | Task 9 | ✅ |
| §4.2 品牌区设计 | Task 3 | ✅ |
| §4.3 导航菜单设计 | Task 4, 5 | ✅ |
| §4.4 用户信息区设计 | Task 6 | ✅ |
| §5.1 展开/收起机制 | Task 2, 8 | ✅ |
| §5.2 移动端适配 | Task 7, 8, 9 | ✅ |
| §6 性能优化 | Task 10, 11 | ✅ |
| §7 文件结构 | 所有任务 | ✅ |
| §8 样式变量 | Task 1 | ✅ |

### ✅ 占位符扫描

- ❌ 无 TBD 或 TODO
- ❌ 无"实现细节待定"
- ❌ 无"类似其他组件"

### ✅ 类型一致性检查

- Props 名称在各组件间一致 (`collapsed`, `navItems`, `activeKey`)
- Event 名称遵循 Vue 3 规范 (`update:collapsed`, `navigate`, `logout`)
- CSS 变量引用正确（全部来自 design-tokens.css）

### ✅ 代码完整性检查

每个 Step 都包含：
- ✅ 实际可执行的代码（非伪代码）
- ✅ 明确的文件路径
- ✅ 验证方法（手动测试或命令输出预期）
- ✅ Git commit 命令

---

## 执行建议

**预计总工时**: ~8 小时（按 2-5 分钟/步计算）

**推荐执行顺序**: Task 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11

**并行可能性**: 
- Task 3-7 可由不同开发者并行实现（组件间依赖弱）
- Task 1-2 必须最先完成（基础设施）

**风险缓解**:
- 每个 Task 结束都有 commit 点，可随时回滚
- Task 9 前有备份机制（Home.vue.backup）
- Task 11 提供全面的回归测试清单

---

**计划版本**: v1.0  
**创建日期**: 2026-05-14  
**基于设计规范**: `docs/superpowers/specs/2026-05-14-sidebar-navigation-design.md`
