vue3+Ant Design搭建管理后台 02 —— 路由和菜单配置

1,540 阅读5分钟

项目源码

Github

配置路由

首先创建3个页面:

view/dashboard.vue

<template>
  <h1>dashboard view</h1>
</template>

view/user_list.vue

<template>
  <h1>user list view</h1>
</template>

login.vue

<template>
  <h1>login view</h1>
</template>

修改router/index.js文件:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(''),
  routes: [
    {
      path: '',
      name: 'basic',
      component: () => import('../layout/Layout.vue'),
      children: [
        {
          path: '/dashboard',
          name: 'dashboard',
          component: () => import('../view/dashboard.vue')
        },
        {
          path: '/user/list',
          name: '/user/list',
          component: () => import('../view/user_list.vue')
        }
      ]
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('../view/login.vue')
    }
  ]
})

export default router

然后再修改layout/Layout.vue文件中右侧页面主体:

<!-- 右侧页面主体开始 -->
<a-layout-content
:style="{ margin: '12px 10px', padding: '18px', background: '#fff', minHeight: '280px', borderRadius: '4px' }"
>
<router-view></router-view>
</a-layout-content>
<!-- 右侧页面主体结束 -->

修改完成之后路由就配置好了,分别访问router/index.ts文件中注册的三个地址:/dashboard/user/list/login

可以看到路由已经注册成功了

*因为login页面没有注册在Layout之下,所以在新的页面中打开,这也正是我们想要的效果


commit-hash: 2051283


菜单配置

修改layout/Layout.vue文件:

<template>
  <a-layout>
    <!-- 左侧部分开始 -->
    <a-layout-sider :style="{ overflow: 'auto', height: '100vh' }" v-model:collapsed="collapsed">
      <!-- 左侧logo开始 -->
      <div class="logo">
        <img src="/vite.svg" class="logo_img" alt="Vite logo" />
        <span class="logo_text" v-show="!collapsed">Ant Design</span>
      </div>
      <!-- 左侧logo结束 -->

      <!-- 左侧菜单开始 -->
      <a-menu v-model:selectedKeys="selectedKeys" :theme="state.theme" :items="menu" mode="inline" @click="menuClicked"/>
      <!-- 左侧菜单结束 -->
    </a-layout-sider>
    <!-- 左侧部分结束 -->

    <!-- 右侧部分开始 -->
    <a-layout>
      <!-- 右侧header开始 -->
      <a-layout-header style="background: #fff; padding: 0; height: 50px;">
        <MenuUnfoldOutlined
          v-if="collapsed"
          class="trigger"
          @click="() => (collapsed = !collapsed)"
        />
        <MenuFoldOutlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
      </a-layout-header>
      <!-- 右侧header结束 -->

      <!-- 右侧页面主体开始 -->
      <a-layout-content
        :style="{ margin: '12px 10px', padding: '18px', background: '#fff', minHeight: '280px', borderRadius: '4px' }"
      >
        <router-view></router-view>
      </a-layout-content>
      <!-- 右侧页面主体结束 -->

    </a-layout>
    <!-- 右侧部分结束 -->
  </a-layout>
</template>
<script setup lang="js">
import { ref, h } from 'vue';
import { useRouter } from 'vue-router'

const selectedKeys = ref(['1']);
const collapsed = ref(false);

const router = useRouter()
const state = ref({
  theme: 'dark'
})

// 菜单
const menu = ref([
  {
    key: 'dashboard',
    icon: () => h(DashboardOutlined),
    label: '仪表盘',
    title: '仪表盘',
    path: '/dashboard'
  },
  {
    key: 'sub1',
    icon: () => h(SettingOutlined),
    label: '系统设置',
    title: '系统设置',
    children: [
      {
        key: 'user_list',
        label: '用户管理',
        icon: () => h(AppstoreOutlined),
        path: '/user/list'
      },
    ],
  },
]);

// 菜单点击事件
const menuClicked = ({item, key}) => {
  console.log(item, key)
  // 跳转到菜单配置的path地址取
  router.push({ path: item.path })
}

import {
  MenuUnfoldOutlined,
  MenuFoldOutlined,
  AppstoreOutlined,
  SettingOutlined,
  DashboardOutlined
} from '@ant-design/icons-vue'

</script>
<style>
 .trigger {
  display: block;
  font-size: 18px;
  width: 40px;
  line-height: 50px;
  padding: 2px 16px 0 16px;
  cursor: pointer;
  transition: color 0.3s;
}

 .trigger:hover {
  color: #1890ff;
}

 .logo {
  height: 32px;
  margin: 9px;
  overflow: hidden;
}
.logo_img {
  margin-left: 12px;
  display: block;
  width: 32px;
  height: 32px;
  float: left;
}

.logo_text {
  display: block;
  float: left;
  line-height: 32px;
  text-align: center;
  color: #fff;
  font-size: 20px;
  margin-left: 8px;
  font-weight: 900;
}

.site-layout .site-layout-background {
  background: #fff;
}
</style>

修改之后启动项目可以看到菜单已经配置好了:


commit-hash: 63bfad2


统一配置菜单和路由

上边我们实现了 路由菜单 的配置,但是正常情况下菜单和路由是一起配置的,并且结构也相似,而且后期还会动态从后端获取菜单和路由数据,这里我们先实现 统一配置

我们这里说的动态配置路由和菜单是对Layout下的页面的配置,login页面并不是嵌套在Layout之下的

我们先在一个地方存储菜单和路由的数据结构,首先创建/store/menu.js文件:

const menu = [
  {
    name: '仪表盘', // 菜单名称(router用)
    label: '仪表盘', // 菜单名称(menu用)
    key: '/dashboard', // 菜单路径,点击所跳转的地址,全局唯一
    icon: 'DashboardOutlined', // 菜单图标
    component: 'Dashboard', // 菜单对应的页面组件,这个在router里边回用到
  },
  {
    name: '系统设置',
    label: '系统设置',
    key: '/system',
    icon: 'SettingOutlined',
    children: [ // 子菜单
      {
        name: '用户管理',
        label: '用户管理',
        key: '/user/list',
        icon: 'AppstoreOutlined',
        component: 'UserList',
      }
    ]
  }
]

export default menu

定义完菜单和路由的基本数据结构之后我们再创建router/view_component.js文件:

/**
 * 注册页面组件
 */
interface RouteMap {
  [key: string]: () => Promise<typeof import("*.vue")>;
}

const routerComponents: RouteMap = {
  Dashboard: () => import('../view/dashboard.vue'),
  UserList: () => import('../view/user_list.vue')
}

export default routerComponents

创建router/generator.js文件:

// 生成router可用的数据结构
import routerComponents from './view_component'
import router from './index'

export interface Menu {
  name: string;
  label: string;
  key: string;
  icon: string;
  component?: string;
  children: Menu[];
}

const genRouter = (menu: Menu[]) => {
  const childrenNav: any[] = []

  const makeRouter = (list: Menu[]) => {
    for (let item of list) {
      if (item.component) {
        childrenNav.push({
          path: item.key,
          name: item.component,
          component: routerComponents[item.component],
          meta: {
            keepAlive: true
          }
        })
      }

      if (item.hasOwnProperty('children') && item.children.length > 0) {
        makeRouter(item.children)
      }
    }
  }

  makeRouter(menu)

  return childrenNav;
}

const initRouter = (menu: Menu[]) => {
  const routers = genRouter(menu)
  routers.forEach(r => {
    router.addRoute('basic', r)
  })
}

export default initRouter

再修改router/index.js文件:

import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(''),
  routes: [
    {
      path: '',
      name: 'basic',
      component: () => import('../layout/Layout.vue'),
      children: []
    },
    {
      path: '/login',
      name: 'login',
      component: () => import('../view/login.vue')
    }
  ]
})

export default router

然后再修改main.ts文件(这是临时修改,后边还会做调整),在main.js文件中注册路由:

import { createApp } from 'vue'
import router from './router'
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import App from './App.vue'

// 暂时在这里注册路由,后期会做调整
import menu from  './store/menu'
import initRouter from './router/route_generator';

initRouter(menu)

createApp(App).use(router).use(Antd).mount('#app')

这样路由就调整完成了

接下来再修改菜单

修改菜单比较简单,直接在Layout中引入menu并使用即可

修改layout/layout.vue文件:

<template>
  <a-layout>
    <!-- 左侧部分开始 -->
    <a-layout-sider :style="{ overflow: 'auto', height: '100vh' }" v-model:collapsed="collapsed">
      <!-- 左侧logo开始 -->
      <div class="logo">
        <img src="/vite.svg" class="logo_img" alt="Vite logo" />
        <span class="logo_text" v-show="!collapsed">Ant Design</span>
      </div>
      <!-- 左侧logo结束 -->

      <!-- 左侧菜单开始 -->
      <a-menu v-model:selectedKeys="selectedKeys" :theme="state.theme" :items="state.menu" mode="inline" @click="menuClicked"/>
      <!-- 左侧菜单结束 -->
    </a-layout-sider>
    <!-- 左侧部分结束 -->

    <!-- 右侧部分开始 -->
    <a-layout>
      <!-- 右侧header开始 -->
      <a-layout-header style="background: #fff; padding: 0; height: 50px;">
        <MenuUnfoldOutlined
          v-if="collapsed"
          class="trigger"
          @click="() => (collapsed = !collapsed)"
        />
        <MenuFoldOutlined v-else class="trigger" @click="() => (collapsed = !collapsed)" />
      </a-layout-header>
      <!-- 右侧header结束 -->

      <!-- 右侧页面主体开始 -->
      <a-layout-content
        :style="{ margin: '12px 10px', padding: '18px', background: '#fff', minHeight: '280px', borderRadius: '4px' }"
      >
        <router-view></router-view>
      </a-layout-content>
      <!-- 右侧页面主体结束 -->

    </a-layout>
    <!-- 右侧部分结束 -->
  </a-layout>
</template>
<script setup lang="js">

import { ref, h, onMounted, reactive } from 'vue';
import { useRouter } from 'vue-router'

// 加载菜单和图标
import menu from '../store/menu'
import * as icons from '@ant-design/icons-vue'

const selectedKeys = ref([]);
const collapsed = ref(false);

const router = useRouter()

const state = reactive({
  theme: 'dark',
  menu: null, // menu设置为动态值,上边a-menu标签的items值也改为state.menu
})

onMounted(() => {
  // 给state.menu赋值
  state.menu = menu

  // 我们在menu.js里边配置的icon为一个字符串,但是a-menu组件需要的icon为一个图标组件
  // 所以这里需要把icon名称转换为icon组件
  const genMenuIcon = (list) => {
    for(let item of list) {
      if (item.icon && typeof item.icon === 'string') {
        item.icon = h(eval('icons.' + item.icon))
      }

      if (item.hasOwnProperty('children') && item.children.length > 0) {
        genMenuIcon(item.children)
      } else {
        delete(item.children)
      }
    }
  }

  genMenuIcon(state.menu)
})

// 菜单点击事件
const menuClicked = ({item, key}) => {
  console.log(item, key)
  // 跳转到菜单配置的path地址取
  router.push({ path: key })
}

import {
  MenuUnfoldOutlined,
  MenuFoldOutlined,
  AppstoreOutlined,
  SettingOutlined,
  DashboardOutlined
} from '@ant-design/icons-vue'

</script>
<style>
 .trigger {
  display: block;
  font-size: 18px;
  width: 40px;
  line-height: 50px;
  padding: 2px 16px 0 16px;
  cursor: pointer;
  transition: color 0.3s;
}

 .trigger:hover {
  color: #1890ff;
}

 .logo {
  height: 32px;
  margin: 9px;
  overflow: hidden;
}
.logo_img {
  margin-left: 12px;
  display: block;
  width: 32px;
  height: 32px;
  float: left;
}

.logo_text {
  display: block;
  float: left;
  line-height: 32px;
  text-align: center;
  color: #fff;
  font-size: 20px;
  margin-left: 8px;
  font-weight: 900;
}

.site-layout .site-layout-background {
  background: #fff;
}
</style>

修改完成之后启动项目,可以看到菜单路由都配置正常了,后边再新增页面可以直接在store/menu.ts中新增菜单结构,并在router/view_component.ts中注册对应的页面即可


总结

  • 完成了菜单和路由的统一配置

commit-hash: dc33091