Files
medical-mall/ADMIN_SIDEBAR_COMPLETE_GUIDE.md
2026-02-02 20:07:37 +08:00

423 lines
11 KiB
Markdown
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.
# 确保 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` | 编译配置 |