feat: add menu type

This commit is contained in:
chansee97 2024-04-05 14:46:54 +08:00
parent ec28d4e53d
commit 446d67e6d8
8 changed files with 33 additions and 36 deletions

2
.env
View File

@ -5,7 +5,7 @@ VITE_APP_NAME=Nova - Admin
# 路由模式 # 路由模式
VITE_ROUTE_MODE = web VITE_ROUTE_MODE = web
# 权限路由模式: static dynamic # 权限路由模式: static dynamic
VITE_AUTH_ROUTE_MODE=static VITE_AUTH_ROUTE_MODE=dynamic
# 设置登陆后跳转地址 # 设置登陆后跳转地址
VITE_HOME_PATH = /dashboard/workbench VITE_HOME_PATH = /dashboard/workbench

View File

@ -5,6 +5,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '仪表盘', 'meta.title': '仪表盘',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:analysis', 'meta.icon': 'icon-park-outline:analysis',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 1, 'id': 1,
'pid': null, 'pid': null,
@ -16,6 +17,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:alarm', 'meta.icon': 'icon-park-outline:alarm',
'meta.pinTab': true, 'meta.pinTab': true,
'meta.menuType': 'page',
'componentPath': '/dashboard/workbench/index.vue', 'componentPath': '/dashboard/workbench/index.vue',
'id': 2, 'id': 2,
'pid': 1, 'pid': 1,
@ -26,6 +28,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '监控页', 'meta.title': '监控页',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:anchor', 'meta.icon': 'icon-park-outline:anchor',
'meta.menuType': 'page',
'componentPath': '/dashboard/monitor/index.vue', 'componentPath': '/dashboard/monitor/index.vue',
'id': 3, 'id': 3,
'pid': 1, 'pid': 1,
@ -36,6 +39,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '多级菜单演示', 'meta.title': '多级菜单演示',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list', 'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 4, 'id': 4,
'pid': null, 'pid': null,
@ -46,6 +50,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '多级菜单子页', 'meta.title': '多级菜单子页',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list', 'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'page',
'componentPath': '/test/test2/index.vue', 'componentPath': '/test/test2/index.vue',
'id': 6, 'id': 6,
'pid': 4, 'pid': 4,
@ -58,6 +63,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.icon': 'icon-park-outline:list', 'meta.icon': 'icon-park-outline:list',
'meta.hide': true, 'meta.hide': true,
'meta.activeMenu': '/test/test2', 'meta.activeMenu': '/test/test2',
'meta.menuType': 'page',
'componentPath': '/test/test2/detail/index.vue', 'componentPath': '/test/test2/detail/index.vue',
'id': 7, 'id': 7,
'pid': 4, 'pid': 4,
@ -68,6 +74,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '多级菜单', 'meta.title': '多级菜单',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list', 'meta.icon': 'icon-park-outline:list',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 8, 'id': 8,
'pid': 4, 'pid': 4,
@ -88,6 +95,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '列表页', 'meta.title': '列表页',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:list-two', 'meta.icon': 'icon-park-outline:list-two',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 10, 'id': 10,
'pid': null, 'pid': null,
@ -118,6 +126,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '功能示例', 'meta.title': '功能示例',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:application-one', 'meta.icon': 'icon-park-outline:application-one',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 13, 'id': 13,
'pid': null, 'pid': null,
@ -159,6 +168,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '编辑器', 'meta.title': '编辑器',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:editor', 'meta.icon': 'icon-park-outline:editor',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 18, 'id': 18,
'pid': 13, 'pid': 13,
@ -219,6 +229,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '外链文档', 'meta.title': '外链文档',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:file-doc', 'meta.icon': 'icon-park-outline:file-doc',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 24, 'id': 24,
'pid': null, 'pid': null,
@ -260,6 +271,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '权限', 'meta.title': '权限',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:people-safe', 'meta.icon': 'icon-park-outline:people-safe',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 28, 'id': 28,
'pid': null, 'pid': null,
@ -293,6 +305,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '异常页', 'meta.title': '异常页',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:error-computer', 'meta.icon': 'icon-park-outline:error-computer',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 31, 'id': 31,
'pid': null, 'pid': null,
@ -336,6 +349,7 @@ export const staticRoutes: AppRoute.RowRoute[] = [
'meta.title': '系统设置', 'meta.title': '系统设置',
'meta.requiresAuth': true, 'meta.requiresAuth': true,
'meta.icon': 'icon-park-outline:setting', 'meta.icon': 'icon-park-outline:setting',
'meta.menuType': 'dir',
'componentPath': null, 'componentPath': null,
'id': 35, 'id': 35,
'pid': null, 'pid': null,

View File

@ -1,7 +1,7 @@
import { request } from '../http' import { request } from '../http'
interface Ilogin { interface Ilogin {
username: string userName: string
password: string password: string
} }

View File

@ -52,8 +52,8 @@ export const useAuthStore = defineStore('auth-store', {
}, },
/* 用户登录 */ /* 用户登录 */
async login(username: string, password: string) { async login(userName: string, password: string) {
const { isSuccess, data } = await fetchLogin({ username, password }) const { isSuccess, data } = await fetchLogin({ userName, password })
if (!isSuccess) { if (!isSuccess) {
window.$message.error('登录失败,请检查用户名和密码') window.$message.error('登录失败,请检查用户名和密码')
return return

View File

@ -72,7 +72,7 @@ export const useRouteStore = defineStore('route-store', {
id: item.id, id: item.id,
pid: item.pid, pid: item.pid,
label: label:
(!item.children || item.children.length === 0) (!item.meta.menuType || item.meta.menuType === 'page')
? () => ? () =>
h( h(
RouterLink, RouterLink,
@ -83,36 +83,13 @@ export const useRouteStore = defineStore('route-store', {
}, },
{ default: () => $t(`route.${String(item.name)}`, item.meta.title) }, { default: () => $t(`route.${String(item.name)}`, item.meta.title) },
) )
: $t(`route.${String(item.name)}`, item.meta.title), : () => $t(`route.${String(item.name)}`, item.meta.title),
key: item.path, key: item.path,
icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined, icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
} }
return target return target
}) })
}, },
setRedirect(routes: AppRoute.Route[]) {
routes.forEach((route) => {
if (route.children) {
if (!route.redirect) {
// 过滤出没有隐藏的子元素集
const visibleChilds = route.children.filter(child => !child.meta.hide)
// 过滤出含有order属性的页面
const orderChilds = visibleChilds.filter(child => child.meta.order)
// 重定向页默认第一个子元素的路径
let target = route.children[0]
if (orderChilds.length > 0)
// 有order则取最小者重定向
target = min(orderChilds, i => i.meta.order as number) as AppRoute.Route
route.redirect = target.path
}
this.setRedirect(route.children)
}
})
},
createRoutes(routes: AppRoute.RowRoute[]) { createRoutes(routes: AppRoute.RowRoute[]) {
const { hasPermission } = usePermission() const { hasPermission } = usePermission()
// 结构化meta字段 // 结构化meta字段
@ -128,14 +105,19 @@ export const useRouteStore = defineStore('route-store', {
// 生成路由有redirect的不需要引入文件 // 生成路由有redirect的不需要引入文件
const modules = import.meta.glob('@/views/**/*.vue') const modules = import.meta.glob('@/views/**/*.vue')
resultRouter = resultRouter.map((item: any) => { resultRouter = resultRouter.map((item: AppRoute.Route) => {
if (item.componentPath && !item.redirect) if (item.componentPath && !item.redirect)
item.component = modules[`/src/views${item.componentPath}`] item.component = modules[`/src/views${item.componentPath}`]
// 判断是否是目录,代表目录的路由没有实际页面
if (item.meta.menuType === 'dir')
item.redirect = '/404'
return item return item
}) })
// 生成路由表
resultRouter = arrayToTree(resultRouter) as AppRoute.Route[] resultRouter = arrayToTree(resultRouter) as AppRoute.Route[]
this.setRedirect(resultRouter)
const appRootRoute: RouteRecordRaw = { const appRootRoute: RouteRecordRaw = {
path: '/appRoot', path: '/appRoot',
name: 'appRoot', name: 'appRoot',

View File

@ -7,7 +7,7 @@ declare namespace ApiAuth {
/** 用户id */ /** 用户id */
id: number id: number
/** 用户名 */ /** 用户名 */
username: string userName: string
/* 用户头像 */ /* 用户头像 */
avatar?: string avatar?: string
/* 用户邮箱 */ /* 用户邮箱 */

View File

@ -23,10 +23,10 @@ declare namespace AppRoute {
withoutTab?: boolean withoutTab?: boolean
/** 当前路由是否会被固定在Tab中,用于一些常驻页面 */ /** 当前路由是否会被固定在Tab中,用于一些常驻页面 */
pinTab?: boolean pinTab?: boolean
/** 当前路由i18n标识 */ /** 当前路由在左侧菜单是目录还是页面,不设置默认为page */
i18nKey?: string menuType?: 'dir' | 'page'
} }
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
interface baseRoute { interface baseRoute {
/** 路由名称(路由唯一标识) */ /** 路由名称(路由唯一标识) */
name: string name: string
@ -42,6 +42,7 @@ declare namespace AppRoute {
pid: number | null pid: number | null
} }
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
type RowRoute = { type RowRoute = {
[K in keyof RouteMeta as `meta.${K}`]?: RouteMeta[K] [K in keyof RouteMeta as `meta.${K}`]?: RouteMeta[K]
} & baseRoute } & baseRoute

View File

@ -52,7 +52,7 @@ function handleValidateClick() {
{{ userInfo?.id }} {{ userInfo?.id }}
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="用户名"> <n-descriptions-item label="用户名">
{{ userInfo?.username }} {{ userInfo?.userName }}
</n-descriptions-item> </n-descriptions-item>
<n-descriptions-item label="真实名称"> <n-descriptions-item label="真实名称">
{{ userInfo?.nickname }} {{ userInfo?.nickname }}