# 确保 Admin 页面侧边栏一直显示的完整步骤

## 概述

确保 uni-app-x 的 Admin 页面在切换过程中保持侧边栏显示需要从多个维度进行配置。以下是完整的步骤检查清单。

---

## 第一部分：文件和路由配置

### 1.1 创建新的 Admin 页面文件

**文件路径**: `pages/mall/admin/maintain/<category>/<page-name>.uvue`

**重点**:

- ✅ 使用 UTF-8 编码（**不要 BOM**）
- ✅ 严格的 SFC 结构: `<template>` → `<script>` → `<style>`
- ✅ 没有额外内容在closing tags之后

**模板**:

```uvue
<template>
  <AdminLayout currentPage="<page-id>">
    <view class="page">
      <!-- 页面内容 -->
    </view>
  </AdminLayout>
</template>

<script setup lang="uts">
import AdminLayout from '@/layouts/admin/AdminLayout.uvue'
</script>

<style scoped>
.page {
  padding: 20px;
}
</style>
```

### 1.2 在 pages.json 中注册路由

**位置**: `pages.json` 中的合适分类下

**格式**:

```json
{
  "path": "maintain/<category>/<page-name>",
  "style": {
    "navigationBarTitleText": "页面标题",
    "navigationStyle": "custom"
  }
}
```

**重点**:

- ✅ `navigationStyle: "custom"` 必须设置，允许自定义 AdminLayout
- ✅ `path` 必须与文件结构匹配

---

## 第二部分：菜单配置

### 2.1 在 menu.uts 中定义菜单项

**文件**: `layouts/admin/utils/menu.uts`

**两种菜单结构**:

#### 选项 A：有子菜单的菜单组（推荐）

```typescript
{
  id: 'maintain',
  title: '维护',
  icon: '/static/maintain.svg',
  path: '/pages/mall/admin/maintain/...',
  groups: [
    {
      id: 'dev-config',
      title: '开发配置',
      children: [
        {
          id: 'module-config',
          title: '模块配置',
          path: '/pages/mall/admin/maintain/dev-config/module-config'
        }
      ]
    }
  ]
}
```

#### 选项 B：没有子菜单的菜单组（叶子节点）

```typescript
{
  id: 'maintain',
  title: '维护',
  groups: [
    {
      id: 'system-info',
      title: '系统信息',
      path: '/pages/mall/admin/maintain/system-info',
      children: []  // ⚠️ 必须显式设置为空数组
    }
  ]
}
```

**重点**:

- ✅ 每个 menu item 必须有唯一的 `id`
- ✅ 如果是叶子节点，必须显式设置 `children: []`
- ✅ `path` 必须与 pages.json 路由匹配

### 2.2 菜单 ID 命名规则

**建议**:

```
一级菜单: maintain / user / order / product
二级组: dev-config / security / data / external
子项: module-config / permission / cron-job

currentPage 值应该与 menu.id 对应:
- 对应一级: currentPage="maintain"
- 对应二级: currentPage="system-info"
- 对应三级: currentPage="module-config"
```

---

## 第三部分：导航逻辑

### 3.1 nav.uts 匹配规则

**文件**: `layouts/admin/utils/nav.uts`

**关键函数**: `findActiveByCurrentPage(menuList, currentPage)`

**匹配顺序** (必须按此顺序):

1. 一级菜单 ID 匹配: `m.id === currentPage`
2. 二级菜单组 ID 匹配: `g.id === currentPage` ⚠️ **包括叶子节点**
3. 二级菜单组 path 匹配: `normalize(g.path) === normalize(currentPage)`
4. 三级菜单子项 ID 匹配: `c.id === currentPage`
5. 三级菜单子项 path 匹配: `normalize(c.path) === normalize(currentPage)`
6. 四级及以上: 递归查找
7. 默认兜底: 返回 `{ activeMenuId: 'home', activeSubId: '' }`

**核心代码**:

```typescript
// 关键：在检查 children 前，先检查 group 本身是否是叶子节点
for (const g of groups) {
  if (g.id === page) {
    return { activeMenuId: m.id, activeSubId: g.id }; // ✅ 叶子节点匹配
  }
  if (g.path && normalize(g.path) === pageNorm) {
    return { activeMenuId: m.id, activeSubId: g.id }; // ✅ 叶子节点路径匹配
  }
  // 然后才检查 children
  const cs = g.children ?? [];
  // ...
}
```

### 3.2 页面中使用 currentPage

**在页面文件中**:

```uvue
<AdminLayout currentPage="system-info">
  <!-- 页面内容 -->
</AdminLayout>
```

**currentPage 值确定规则**:

- 如果页面是二级菜单组的叶子: 使用 group id (`system-info`)
- 如果页面是三级菜单子项: 使用 child id (`module-config`)
- 也可以使用路径形式 (`/pages/mall/admin/maintain/system-info`)

---

## 第四部分：AdminLayout 组件

### 4.1 AdminLayout.uvue 的关键逻辑

**文件**: `layouts/admin/AdminLayout.uvue`

**核心职责**:

```typescript
// 1. 导入必要的生命周期和工具
import { onShow } from "@dcloudio/uni-app";
import { findActiveByCurrentPage } from "./utils/nav.uts";

// 2. 接收 currentPage prop
const props = defineProps<{ currentPage: string }>();

// 3. 同步导航状态的关键函数
const syncActiveByCurrentPage = () => {
  let current = props.currentPage;
  if (!current) {
    // 如果没有 currentPage，从路由获取
    const pages = getCurrentPages();
    const last = pages[pages.length - 1];
    current = last?.route ? `/${last.route}` : "";
  }
  const r = findActiveByCurrentPage(menuList.value, current);
  activeMenuId.value = r.activeMenuId; // ✅ 更新一级菜单
  activeSubId.value = r.activeSubId; // ✅ 更新二级菜单
};

// 4. 在多个生命周期调用同步函数
watch(
  () => props.currentPage,
  () => syncActiveByCurrentPage(),
  { immediate: true },
);
onMounted(() => syncActiveByCurrentPage());
onShow(() => syncActiveByCurrentPage());

// 5. 计算二级侧边栏的内容
const activeGroups = computed(() => {
  const m = menuList.value.find((it) => it.id === activeMenuId.value);
  return m?.groups ?? [];
});

// 6. 根据 activeSubId 计算面包屑标题
const breadcrumb = computed(() => {
  let subTitle = "";
  const groups = activeGroups.value;
  for (const g of groups) {
    // ✅ 检查 group 本身（支持叶子节点）
    if (g.id === activeSubId.value) {
      subTitle = g.title;
      break;
    }
    // ✅ 检查 group 的 children
    const cs = g.children ?? [];
    const hit = cs.find((c) => c.id === activeSubId.value);
    if (hit) {
      subTitle = hit.title;
      break;
    }
  }
  return subTitle
    ? `${activeMenuTitle.value} / ${subTitle}`
    : activeMenuTitle.value;
});
```

**渲染 AdminSubSider 时**:

```uvue
<AdminSubSider
  v-if="activeGroups.length > 0"
  :activeMenuTitle="activeMenuTitle"
  :groups="activeGroups"
  :activeSubId="activeSubId"
  :activeMenuId="activeMenuId || 'home'"
  @sub-click="onSubClick"
/>
```

---

## 第五部分：AdminSubSider 二级侧边栏

### 5.1 二级侧边栏的 groupAsChild 逻辑

**文件**: `layouts/admin/components/AdminSubSider.uvue`

**关键逻辑**:

```typescript
// 支持 group 作为菜单项（叶子节点）的点击处理
const handleGroupTitleClick = (group: MenuGroup) => {
  // 如果 group 有 path，直接导航
  if (group.path) {
    go(group.path)
  }
  // 否则选中这个 group
  else {
    activeSubId.value = group.id
  }
}

// 模板中
<template v-for="group in groups">
  <view
    v-if="!group.children || group.children.length === 0"
    class="group-as-child"
    @click="handleGroupTitleClick(group)"
    :class="{ active: activeSubId === group.id }"
  >
    {{ group.title }}
  </view>
  <view v-else class="group-normal">
    <!-- 正常的组处理 -->
  </view>
</template>
```

---

## 第六部分：状态管理（可选但推荐）

### 6.1 使用 state.uts 管理全局状态

**文件**: `layouts/admin/state.uts`

```typescript
import { ref } from "vue";

// 跨页面持久化的状态
export const tabs = ref<TabItem[]>([]);
export const activeTabId = ref("");
export const isCollapsed = ref(false);
export const hasNotification = ref(false);
```

**优点**:

- ✅ 页面切换时保持侧边栏收起/展开状态
- ✅ 标签页状态持久化
- ✅ 通知状态保持

---

## 完整检查清单

### 📋 新增页面时必须检查:

- [ ] **文件**
  - [ ] 文件位置正确: `pages/mall/admin/maintain/<category>/<page-name>.uvue`
  - [ ] 编码是 UTF-8（无 BOM）
  - [ ] 正确的 SFC 结构
  - [ ] `<script setup>` 导入了 `AdminLayout`

- [ ] **路由配置**
  - [ ] 在 `pages.json` 中注册了路由
  - [ ] `navigationStyle: "custom"` 设置正确
  - [ ] `path` 与文件结构匹配

- [ ] **菜单配置**
  - [ ] 在 `menu.uts` 中定义了菜单项
  - [ ] menu id 唯一且命名规范
  - [ ] 如果是叶子节点，设置 `children: []`
  - [ ] 路径与 pages.json 匹配

- [ ] **页面文件**
  - [ ] `currentPage` prop 值正确
  - [ ] `currentPage` 与菜单配置中的 id 对应

- [ ] **缓存清理**
  - [ ] 删除 `unpackage/dist`
  - [ ] 删除 `.hbuilderx/cache`
  - [ ] 清理浏览器缓存 (Ctrl+Shift+Delete)
  - [ ] 强制刷新页面 (Ctrl+Shift+R)

---

## 调试技巧

### 验证导航匹配

```powershell
node test-system-info-nav.js
```

### 浏览器控制台诊断

```javascript
// 在浏览器 DevTools 中运行
const pages = getCurrentPages();
const route = pages[pages.length - 1]?.route;
console.log("当前路由:", route);

// 查看 AdminLayout 组件状态
// 打开 Vue DevTools 查看 activeMenuId, activeSubId
```

### 常见问题排查

| 问题         | 原因                       | 解决                             |
| ------------ | -------------------------- | -------------------------------- |
| 侧边栏不显示 | `currentPage` 未匹配到菜单 | 检查 nav.uts 匹配逻辑            |
| 高亮错误     | menu.id 不一致             | 确保 currentPage 与 menu id 相同 |
| 页面切换闪烁 | 缓存问题                   | 清理缓存，强制刷新               |
| 编译错误     | 文件编码问题               | 重新创建文件，确保无 BOM         |
| 组件无法解析 | @ 别名未配置               | 检查 tsconfig.json 的 paths      |

---

## 总结：一句话版本

**确保 Admin 页面侧边栏一直显示的关键是**:

> 正确配置菜单结构 → 准确设置 currentPage → 实现导航匹配逻辑 → 同步状态到 AdminLayout → 支持叶子节点处理

---

## 参考文件

| 文件                                                    | 职责         |
| ------------------------------------------------------- | ------------ |
| `pages/mall/admin/maintain/<category>/<page-name>.uvue` | 页面文件     |
| `pages.json`                                            | 路由配置     |
| `layouts/admin/utils/menu.uts`                          | 菜单定义     |
| `layouts/admin/utils/nav.uts`                           | 导航匹配逻辑 |
| `layouts/admin/AdminLayout.uvue`                        | 布局容器     |
| `layouts/admin/components/AdminSubSider.uvue`           | 二级侧边栏   |
| `layouts/admin/state.uts`                               | 全局状态     |
| `tsconfig.json`                                         | 编译配置     |
