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

11 KiB
Raw Blame History

确保 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之后

模板:

<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 中的合适分类下

格式:

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

重点:

  • navigationStyle: "custom" 必须设置,允许自定义 AdminLayout
  • path 必须与文件结构匹配

第二部分:菜单配置

2.1 在 menu.uts 中定义菜单项

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

两种菜单结构:

选项 A有子菜单的菜单组推荐

{
  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没有子菜单的菜单组叶子节点

{
  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: '' }

核心代码:

// 关键:在检查 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

在页面文件中:

<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

核心职责:

// 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 时:

<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

关键逻辑:

// 支持 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

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)

调试技巧

验证导航匹配

node test-system-info-nav.js

浏览器控制台诊断

// 在浏览器 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 编译配置