mirror of
https://github.com/chansee97/nova-admin.git
synced 2025-11-19 15:28:01 +08:00
fix: 完整支持新路由结构
This commit is contained in:
parent
ab72ff1bdd
commit
423e79f64d
@ -45,6 +45,10 @@ export function deleteMenu(id: number) {
|
||||
* 查询菜单树
|
||||
* GET /menu/selectTree
|
||||
*/
|
||||
export function getMenuOptions() {
|
||||
return request.Get<Api.Response<Entity.TreeNode[]>>('/menu/options')
|
||||
export function getMenuOptions(excludePermissions?: boolean) {
|
||||
return request.Get<Api.Response<Entity.TreeNode[]>>('/menu/options', {
|
||||
params: {
|
||||
excludePermissions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -39,13 +39,13 @@ const options = computed(() => {
|
||||
|
||||
return routeStore.rowRoutes.filter((item) => {
|
||||
const conditions = [
|
||||
t(`route.${String(item.name)}`, item.title || item.name)?.includes(searchValue.value),
|
||||
t(`${String(item.i18nKey)}`, item.title)?.includes(searchValue.value),
|
||||
item.path?.includes(searchValue.value),
|
||||
]
|
||||
return conditions.some(condition => !item.hide && condition)
|
||||
return conditions.some(condition => !item.menuVisible && condition)
|
||||
}).map((item) => {
|
||||
return {
|
||||
label: t(`route.${String(item.name)}`, item.title || item.name),
|
||||
label: t(`${String(item.i18nKey)}`, item.title),
|
||||
value: item.path,
|
||||
icon: item.icon,
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ const emit = defineEmits<{
|
||||
>
|
||||
<div class="flex-center gap-2 text-nowrap">
|
||||
<nova-icon :icon="route.meta.icon" />
|
||||
<span>{{ $t(`route.${String(route.name)}`, route.meta.title) }}</span>
|
||||
<span>{{ $t(`${String(route.meta.i18nKey)}`, route.meta.title) }}</span>
|
||||
<button
|
||||
v-if="closable"
|
||||
type="button"
|
||||
|
||||
@ -11,8 +11,8 @@ export function setupRouterGuard(router: Router) {
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// 判断是否是外链,如果是直接打开网页并拦截跳转
|
||||
if (to.meta.href) {
|
||||
window.open(to.meta.href)
|
||||
if (to.meta.isLink) {
|
||||
window.open(to.meta.linkPath)
|
||||
next(false) // 取消当前导航
|
||||
return
|
||||
}
|
||||
@ -23,7 +23,7 @@ export function setupRouterGuard(router: Router) {
|
||||
const isLogin = Boolean(local.get('accessToken'))
|
||||
|
||||
// 处理根路由重定向
|
||||
if (to.name === 'root') {
|
||||
if (to.path === '/') {
|
||||
if (isLogin) {
|
||||
// 已登录,重定向到首页
|
||||
next({ path: import.meta.env.VITE_HOME_PATH, replace: true })
|
||||
@ -35,22 +35,19 @@ export function setupRouterGuard(router: Router) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果是login路由,直接放行
|
||||
if (to.name === 'login') {
|
||||
// login页面不需要任何认证检查,直接放行
|
||||
}
|
||||
// 如果路由明确设置了requiresAuth为false,直接放行
|
||||
else if (to.meta.requiresAuth === false) {
|
||||
// 明确设置为false的路由直接放行
|
||||
// 继续执行后面的逻辑
|
||||
}
|
||||
// 如果路由设置了requiresAuth为true,且用户未登录,重定向到登录页
|
||||
else if (to.meta.requiresAuth === true && !isLogin) {
|
||||
// 如果用户未登录,重定向到登录页
|
||||
if (!isLogin) {
|
||||
const redirect = to.name === 'not-found' ? undefined : to.fullPath
|
||||
next({ path: '/login', query: { redirect } })
|
||||
return
|
||||
}
|
||||
|
||||
// 如果用户已登录且访问login页面,重定向到首页
|
||||
if (to.name === 'login' && isLogin) {
|
||||
next({ path: '/' })
|
||||
return
|
||||
}
|
||||
|
||||
// 判断路由有无进行初始化
|
||||
if (!routeStore.isInitAuthRoute && to.name !== 'login') {
|
||||
try {
|
||||
@ -76,17 +73,11 @@ export function setupRouterGuard(router: Router) {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果用户已登录且访问login页面,重定向到首页
|
||||
if (to.name === 'login' && isLogin) {
|
||||
next({ path: '/' })
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
})
|
||||
router.beforeResolve((to) => {
|
||||
// 设置菜单高亮
|
||||
routeStore.setActiveMenu(to.meta.activeMenu ?? to.fullPath)
|
||||
routeStore.setActiveMenu(to.meta.activePath ?? to.fullPath)
|
||||
// 添加tabs
|
||||
tabStore.addTab(to)
|
||||
// 设置高亮标签;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import type { App } from 'vue'
|
||||
import type { RouteRecord } from 'vue-router'
|
||||
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
|
||||
import { setupRouterGuard } from './guard'
|
||||
import { routes } from './routes.inner'
|
||||
@ -6,7 +7,7 @@ import { routes } from './routes.inner'
|
||||
const { VITE_ROUTE_MODE = 'hash', VITE_BASE_URL } = import.meta.env
|
||||
export const router = createRouter({
|
||||
history: VITE_ROUTE_MODE === 'hash' ? createWebHashHistory(VITE_BASE_URL) : createWebHistory(VITE_BASE_URL),
|
||||
routes,
|
||||
routes: routes as unknown as RouteRecord[],
|
||||
})
|
||||
// 安装vue路由
|
||||
export async function installRouter(app: App) {
|
||||
|
||||
@ -1,17 +1,42 @@
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import Layout from '@/layouts/index.vue'
|
||||
|
||||
/* 页面中的一些固定路由,错误页等 */
|
||||
export const routes: RouteRecordRaw[] = [
|
||||
export const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'root',
|
||||
path: '/appRoot',
|
||||
name: 'appRoot',
|
||||
redirect: import.meta.env.VITE_HOME_PATH,
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '',
|
||||
icon: 'icon-park-outline:home',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/user-center',
|
||||
name: 'userCenter',
|
||||
component: () => import('@/views/build-in/user-center/index.vue'),
|
||||
meta: {
|
||||
title: '个人中心',
|
||||
icon: 'carbon:user-avatar-filled-alt',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/home',
|
||||
name: 'home',
|
||||
component: () => import('@/views/build-in/home/index.vue'),
|
||||
meta: {
|
||||
title: '首页',
|
||||
icon: 'icon-park-outline:analysis',
|
||||
pinTab: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/build-in/login/index.vue'), // 注意这里要带上 文件后缀.vue
|
||||
component: () => import('@/views/build-in/login/index.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
withoutTab: true,
|
||||
|
||||
@ -1,14 +1,4 @@
|
||||
export const staticRoutes: Entity.Menu[] = [
|
||||
{
|
||||
path: '/home',
|
||||
title: '概览',
|
||||
icon: 'icon-park-outline:analysis',
|
||||
pinTab: true,
|
||||
menuType: 'page',
|
||||
component: '/build-in/home/index.vue',
|
||||
id: 1,
|
||||
parentId: 0,
|
||||
},
|
||||
{
|
||||
path: '/multi',
|
||||
title: '多级菜单演示',
|
||||
@ -30,7 +20,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
path: '/multi/multi-2/detail',
|
||||
title: '菜单详情页',
|
||||
icon: 'icon-park-outline:list',
|
||||
menuVisible: true,
|
||||
menuVisible: false,
|
||||
activePath: '/multi/multi-2',
|
||||
menuType: 'page',
|
||||
component: '/demo/multi/multi-2/detail/index.vue',
|
||||
@ -50,6 +40,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '多级菜单3-1',
|
||||
icon: 'icon-park-outline:list',
|
||||
component: '/demo/multi/multi-3/multi-4/index.vue',
|
||||
menuType: 'page',
|
||||
id: 20201,
|
||||
parentId: 202,
|
||||
},
|
||||
@ -66,6 +57,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '卡片列表',
|
||||
icon: 'icon-park-outline:view-grid-list',
|
||||
component: '/demo/list/card-list/index.vue',
|
||||
menuType: 'page',
|
||||
id: 302,
|
||||
parentId: 3,
|
||||
},
|
||||
@ -74,6 +66,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '拖拽列表',
|
||||
icon: 'icon-park-outline:menu-fold',
|
||||
component: '/demo/list/draggable-list/index.vue',
|
||||
menuType: 'page',
|
||||
id: 303,
|
||||
parentId: 3,
|
||||
},
|
||||
@ -90,6 +83,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '请求示例',
|
||||
icon: 'icon-park-outline:international',
|
||||
component: '/demo/fetch/index.vue',
|
||||
menuType: 'page',
|
||||
id: 401,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -98,6 +92,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: 'ECharts',
|
||||
icon: 'icon-park-outline:chart-proportion',
|
||||
component: '/demo/echarts/index.vue',
|
||||
menuType: 'page',
|
||||
id: 402,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -107,6 +102,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
icon: 'carbon:map',
|
||||
keepAlive: true,
|
||||
component: '/demo/map/index.vue',
|
||||
menuType: 'page',
|
||||
id: 403,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -123,6 +119,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: 'MarkDown',
|
||||
icon: 'ri:markdown-line',
|
||||
component: '/demo/editor/md/index.vue',
|
||||
menuType: 'page',
|
||||
id: 40401,
|
||||
parentId: 404,
|
||||
},
|
||||
@ -131,6 +128,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '富文本',
|
||||
icon: 'icon-park-outline:edit-one',
|
||||
component: '/demo/editor/rich/index.vue',
|
||||
menuType: 'page',
|
||||
id: 40402,
|
||||
parentId: 404,
|
||||
},
|
||||
@ -139,6 +137,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '剪贴板',
|
||||
icon: 'icon-park-outline:clipboard',
|
||||
component: '/demo/clipboard/index.vue',
|
||||
menuType: 'page',
|
||||
id: 405,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -147,6 +146,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '图标',
|
||||
icon: 'local:cool',
|
||||
component: '/demo/icons/index.vue',
|
||||
menuType: 'page',
|
||||
id: 406,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -155,6 +155,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '二维码',
|
||||
icon: 'icon-park-outline:two-dimensional-code',
|
||||
component: '/demo/qr-code/index.vue',
|
||||
menuType: 'page',
|
||||
id: 407,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -163,6 +164,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '省市区联动',
|
||||
icon: 'icon-park-outline:add-subset',
|
||||
component: '/demo/cascader/index.vue',
|
||||
menuType: 'page',
|
||||
id: 408,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -171,6 +173,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '字典示例',
|
||||
icon: 'icon-park-outline:book-one',
|
||||
component: '/demo/dict/index.vue',
|
||||
menuType: 'page',
|
||||
id: 409,
|
||||
parentId: 4,
|
||||
},
|
||||
@ -187,6 +190,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
component: '/demo/documents/vue/index.vue',
|
||||
menuType: 'page',
|
||||
id: 501,
|
||||
parentId: 5,
|
||||
},
|
||||
@ -195,25 +199,30 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: 'Vite',
|
||||
icon: 'logos:vitejs',
|
||||
component: '/demo/documents/vite/index.vue',
|
||||
menuType: 'page',
|
||||
id: 502,
|
||||
parentId: 5,
|
||||
},
|
||||
{
|
||||
path: 'https://vueuse.org/guide/',
|
||||
path: '/VueUse',
|
||||
title: 'VueUse(外链)',
|
||||
icon: 'logos:vueuse',
|
||||
isLink: true,
|
||||
id: 503,
|
||||
parentId: 5,
|
||||
isLink: true,
|
||||
linkPath: 'https://vueuse.org/guide/',
|
||||
menuType: 'page',
|
||||
},
|
||||
|
||||
{
|
||||
path: 'https://nova-admin-docs.netlify.app/',
|
||||
title: 'Nova docs',
|
||||
path: '/Nova',
|
||||
title: 'Nova docs(外链)',
|
||||
icon: 'local:logo',
|
||||
isLink: true,
|
||||
id: 504,
|
||||
parentId: 5,
|
||||
isLink: true,
|
||||
linkPath: 'https://nova-admin-docs.netlify.app/',
|
||||
menuType: 'page',
|
||||
},
|
||||
{
|
||||
path: '/public',
|
||||
@ -222,6 +231,8 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
isLink: true,
|
||||
id: 505,
|
||||
parentId: 5,
|
||||
linkPath: '/public',
|
||||
menuType: 'page',
|
||||
},
|
||||
{
|
||||
path: '/permission',
|
||||
@ -236,6 +247,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '权限示例',
|
||||
icon: 'icon-park-outline:right-user',
|
||||
component: '/demo/permission/permission/index.vue',
|
||||
menuType: 'page',
|
||||
id: 601,
|
||||
parentId: 6,
|
||||
},
|
||||
@ -244,6 +256,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: 'super可见',
|
||||
icon: 'icon-park-outline:wrong-user',
|
||||
component: '/demo/permission/just-super/index.vue',
|
||||
menuType: 'page',
|
||||
id: 602,
|
||||
parentId: 6,
|
||||
},
|
||||
@ -260,6 +273,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '用户设置',
|
||||
icon: 'icon-park-outline:every-user',
|
||||
component: '/system/user/index.vue',
|
||||
menuType: 'page',
|
||||
id: 701,
|
||||
parentId: 7,
|
||||
},
|
||||
@ -268,6 +282,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '角色设置',
|
||||
icon: 'icon-park-outline:every-user',
|
||||
component: '/system/role/index.vue',
|
||||
menuType: 'page',
|
||||
id: 702,
|
||||
parentId: 7,
|
||||
},
|
||||
@ -276,6 +291,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '字典设置',
|
||||
icon: 'icon-park-outline:book-one',
|
||||
component: '/system/dict/index.vue',
|
||||
menuType: 'page',
|
||||
id: 703,
|
||||
parentId: 7,
|
||||
},
|
||||
@ -284,6 +300,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '菜单设置',
|
||||
icon: 'icon-park-outline:application-menu',
|
||||
component: '/system/menu/index.vue',
|
||||
menuType: 'page',
|
||||
id: 704,
|
||||
parentId: 7,
|
||||
},
|
||||
@ -292,6 +309,7 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '部门管理',
|
||||
icon: 'icon-park-outline:application-menu',
|
||||
component: '/system/dept/index.vue',
|
||||
menuType: 'page',
|
||||
id: 705,
|
||||
parentId: 7,
|
||||
},
|
||||
@ -300,17 +318,8 @@ export const staticRoutes: Entity.Menu[] = [
|
||||
title: '关于',
|
||||
icon: 'icon-park-outline:info',
|
||||
component: '/demo/about/index.vue',
|
||||
menuType: 'page',
|
||||
id: 8,
|
||||
parentId: 0,
|
||||
},
|
||||
{
|
||||
path: '/user-center',
|
||||
title: '个人中心',
|
||||
menuVisible: true,
|
||||
icon: 'carbon:user-avatar-filled-alt',
|
||||
component: '/build-in/user-center/index.vue',
|
||||
id: 999,
|
||||
parentId: 0,
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
@ -5,7 +5,7 @@ import { useRouteStore } from './router'
|
||||
import { useTabStore } from './tab'
|
||||
|
||||
interface AuthStatus {
|
||||
userInfo: Entity.User | Record<string, any>
|
||||
userInfo: Entity.User
|
||||
roles: string[]
|
||||
permissions: string[]
|
||||
}
|
||||
@ -65,7 +65,13 @@ export const useAuthStore = defineStore('auth-store', {
|
||||
const { data } = await fetchLogin(loginData)
|
||||
|
||||
// 处理登录信息
|
||||
await this.handleLoginInfo(data)
|
||||
try {
|
||||
await this.handleLoginInfo(data)
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to handle login info:', error)
|
||||
throw error
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
await this.updataUserInfo()
|
||||
|
||||
@ -1,88 +1,66 @@
|
||||
/// <reference path="../../typings/global.d.ts" />
|
||||
import type { MenuOption } from 'naive-ui'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import { usePermission } from '@/hooks'
|
||||
import Layout from '@/layouts/index.vue'
|
||||
import { $t, renderIcon } from '@/utils'
|
||||
import { clone, min, omit, pick } from 'radash'
|
||||
import { clone, isEmpty, min, pick } from 'radash'
|
||||
import { RouterLink } from 'vue-router'
|
||||
import arrayToTree from 'array-to-tree'
|
||||
|
||||
const metaFields: AppRoute.MetaKeys[]
|
||||
= ['title', 'icon', 'requiresAuth', 'roles', 'keepAlive', 'hide', 'order', 'href', 'activeMenu', 'withoutTab', 'pinTab', 'menuType']
|
||||
|
||||
function standardizedRoutes(route: AppRoute.RowRoute[]) {
|
||||
return clone(route).map((i) => {
|
||||
const route = omit(i, metaFields)
|
||||
|
||||
Reflect.set(route, 'meta', pick(i, metaFields))
|
||||
return route
|
||||
}) as AppRoute.Route[]
|
||||
}
|
||||
|
||||
export function createRoutes(routes: AppRoute.RowRoute[]) {
|
||||
const { hasPermission } = usePermission()
|
||||
const metaFields: (keyof Entity.Menu)[]
|
||||
= ['title', 'icon', 'keepAlive', 'activePath', 'tabVisible', 'pinTab', 'menuType', 'linkPath', 'isLink', 'i18nKey']
|
||||
|
||||
export function createRoutes(routes: Entity.Menu[]) {
|
||||
// Structure the meta field
|
||||
let resultRouter = standardizedRoutes(routes)
|
||||
|
||||
// Route permission filtering
|
||||
resultRouter = resultRouter.filter(i => hasPermission(i.meta.roles))
|
||||
const routerWithMeta: App.RouteRecord[] = clone(routes).map((i) => {
|
||||
const meta = pick(i, metaFields)
|
||||
return {
|
||||
name: `${i.title}_${i.id}`,
|
||||
path: i.path,
|
||||
component: i.component,
|
||||
meta,
|
||||
id: i.id,
|
||||
parentId: i.parentId || 0,
|
||||
}
|
||||
})
|
||||
|
||||
// Generate routes, no need to import files for those with redirect
|
||||
const modules = import.meta.glob('@/views/**/*.vue')
|
||||
resultRouter = resultRouter.map((item: AppRoute.Route) => {
|
||||
if (item.component && !item.redirect)
|
||||
let resultRouter = routerWithMeta.map((item) => {
|
||||
if (item.component)
|
||||
item.component = modules[`/src/views${item.component}`]
|
||||
return item
|
||||
})
|
||||
|
||||
// Generate route tree
|
||||
resultRouter = arrayToTree(resultRouter, {
|
||||
parentProperty: 'parentId',
|
||||
}) as AppRoute.Route[]
|
||||
|
||||
const appRootRoute: RouteRecordRaw = {
|
||||
path: '/appRoot',
|
||||
name: 'appRoot',
|
||||
redirect: import.meta.env.VITE_HOME_PATH,
|
||||
component: Layout,
|
||||
meta: {
|
||||
title: '',
|
||||
icon: 'icon-park-outline:home',
|
||||
},
|
||||
children: [],
|
||||
}
|
||||
|
||||
})
|
||||
// Set the correct redirect path for the route
|
||||
setRedirect(resultRouter)
|
||||
|
||||
// Insert the processed route into the root route
|
||||
appRootRoute.children = resultRouter as unknown as RouteRecordRaw[]
|
||||
return appRootRoute
|
||||
return resultRouter
|
||||
}
|
||||
|
||||
// Generate an array of route names that need to be kept alive
|
||||
export function generateCacheRoutes(routes: AppRoute.RowRoute[]) {
|
||||
export function generateCacheRoutes(routes: Entity.Menu[]) {
|
||||
return routes
|
||||
.filter(i => i.keepAlive)
|
||||
.map(i => i.name)
|
||||
.map(i => i.title)
|
||||
}
|
||||
|
||||
function setRedirect(routes: AppRoute.Route[]) {
|
||||
function setRedirect(routes: App.RouteRecord[]) {
|
||||
routes.forEach((route) => {
|
||||
if (route.children) {
|
||||
if (!route.redirect) {
|
||||
// Filter out a collection of child elements that are not hidden
|
||||
const visibleChilds = route.children.filter(child => !child.meta.hide)
|
||||
const visibleChilds = route.children.filter(child => !child.meta?.menuVisible)
|
||||
|
||||
// Redirect page to the path of the first child element by default
|
||||
let target = visibleChilds[0]
|
||||
|
||||
// Filter out pages with the order attribute
|
||||
const orderChilds = visibleChilds.filter(child => child.meta.order)
|
||||
const orderChilds = visibleChilds.filter(child => !isEmpty(child.meta.sort))
|
||||
|
||||
if (orderChilds.length > 0)
|
||||
target = min(orderChilds, i => i.meta.order!) as AppRoute.Route
|
||||
target = min(orderChilds, i => i.meta.sort!)!
|
||||
|
||||
if (target)
|
||||
route.redirect = target.path
|
||||
@ -94,31 +72,36 @@ function setRedirect(routes: AppRoute.Route[]) {
|
||||
}
|
||||
|
||||
/* 生成侧边菜单的数据 */
|
||||
export function createMenus(userRoutes: AppRoute.RowRoute[]) {
|
||||
const resultMenus = standardizedRoutes(userRoutes)
|
||||
|
||||
// filter menus that do not need to be displayed
|
||||
const visibleMenus = resultMenus.filter(route => !route.meta.hide)
|
||||
|
||||
export function createMenus(userRoutes: Entity.Menu[]): MenuOption[] {
|
||||
const menus = transformAuthRoutesToMenus(userRoutes)
|
||||
// generate side menu
|
||||
return arrayToTree(transformAuthRoutesToMenus(visibleMenus), {
|
||||
return arrayToTree(menus, {
|
||||
parentProperty: 'parentId',
|
||||
})
|
||||
}
|
||||
|
||||
// render the returned routing table as a sidebar
|
||||
function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
|
||||
const { hasPermission } = usePermission()
|
||||
function transformAuthRoutesToMenus(userRoutes: Entity.Menu[]) {
|
||||
const homeRoute: Entity.Menu = {
|
||||
id: 9999999999999,
|
||||
parentId: 0,
|
||||
path: '/home',
|
||||
title: '首页',
|
||||
icon: 'icon-park-outline:analysis',
|
||||
menuVisible: true,
|
||||
menuType: 'page',
|
||||
}
|
||||
userRoutes.unshift(homeRoute)
|
||||
return userRoutes
|
||||
// Filter out side menus without permission
|
||||
.filter(i => hasPermission(i.meta.roles))
|
||||
// filter menus that do not need to be displayed
|
||||
.filter(route => route.menuVisible !== false)
|
||||
// Sort the menu according to the order size
|
||||
.sort((a, b) => {
|
||||
if (a.meta && a.meta.order && b.meta && b.meta.order)
|
||||
return a.meta.order - b.meta.order
|
||||
else if (a.meta && a.meta.order)
|
||||
if (a.sort && b.sort)
|
||||
return a.sort - b.sort
|
||||
else if (a.sort)
|
||||
return -1
|
||||
else if (b.meta && b.meta.order)
|
||||
else if (b.sort)
|
||||
return 1
|
||||
else return 0
|
||||
})
|
||||
@ -128,7 +111,7 @@ function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
|
||||
id: item.id,
|
||||
parentId: item.parentId,
|
||||
label:
|
||||
(!item.meta.menuType || item.meta.menuType === 'page')
|
||||
(item.menuType !== 'directory')
|
||||
? () =>
|
||||
h(
|
||||
RouterLink,
|
||||
@ -137,11 +120,11 @@ function transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]) {
|
||||
path: item.path,
|
||||
},
|
||||
},
|
||||
{ default: () => $t(`route.${String(item.name)}`, item.meta.title) },
|
||||
{ default: () => $t(String(item.i18nKey), item.title) },
|
||||
)
|
||||
: () => $t(`route.${String(item.name)}`, item.meta.title),
|
||||
: () => $t(String(item.i18nKey), item.title),
|
||||
key: item.path,
|
||||
icon: item.meta.icon ? renderIcon(item.meta.icon) : undefined,
|
||||
icon: item.icon ? renderIcon(item.icon) : undefined,
|
||||
}
|
||||
return target
|
||||
})
|
||||
|
||||
@ -41,9 +41,6 @@ export const useRouteStore = defineStore('route-store', {
|
||||
try {
|
||||
// Get user's route
|
||||
const { data } = await fetchUserMenus()
|
||||
if (!data) {
|
||||
throw new Error('Failed to fetch user routes')
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
@ -59,7 +56,6 @@ export const useRouteStore = defineStore('route-store', {
|
||||
},
|
||||
async initAuthRoute() {
|
||||
this.isInitAuthRoute = false
|
||||
|
||||
try {
|
||||
// Initialize route information
|
||||
const rowRoutes = await this.initRouteInfo()
|
||||
@ -72,7 +68,10 @@ export const useRouteStore = defineStore('route-store', {
|
||||
|
||||
// Generate actual route and insert
|
||||
const routes = createRoutes(rowRoutes)
|
||||
router.addRoute(routes)
|
||||
// Add each route as a child of appRoot
|
||||
routes.forEach((route) => {
|
||||
router.addRoute('appRoot', route as any)
|
||||
})
|
||||
|
||||
// Generate side menu
|
||||
this.menus = createMenus(rowRoutes)
|
||||
|
||||
8
src/typings/entities/menu.d.ts
vendored
8
src/typings/entities/menu.d.ts
vendored
@ -9,7 +9,7 @@ namespace Entity {
|
||||
/**
|
||||
* 组件路径
|
||||
*/
|
||||
component?: string
|
||||
component?: string | (() => Promise<unknown>)
|
||||
/**
|
||||
* 菜单图标
|
||||
*/
|
||||
@ -33,7 +33,7 @@ namespace Entity {
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
menuType?: MenuType
|
||||
menuType: MenuType
|
||||
/**
|
||||
* 父菜单ID
|
||||
*/
|
||||
@ -50,6 +50,10 @@ namespace Entity {
|
||||
* 权限标识
|
||||
*/
|
||||
perms?: string
|
||||
/**
|
||||
* 外链地址
|
||||
*/
|
||||
linkPath?: string
|
||||
/**
|
||||
* 备注信息
|
||||
*/
|
||||
|
||||
23
src/typings/global.d.ts
vendored
23
src/typings/global.d.ts
vendored
@ -48,9 +48,13 @@ declare namespace NaiveUI {
|
||||
type ThemeColor = 'default' | 'error' | 'primary' | 'info' | 'success' | 'warning'
|
||||
}
|
||||
|
||||
declare namespace Storage {
|
||||
declare namespace App {
|
||||
type lang = 'zhCN' | 'enUS'
|
||||
|
||||
interface Session {
|
||||
dict: DictMap
|
||||
dict: {
|
||||
[key: string]: Entity.Dict[]
|
||||
}
|
||||
}
|
||||
|
||||
interface Local {
|
||||
@ -65,12 +69,13 @@ declare namespace Storage {
|
||||
/* 存储当前语言 */
|
||||
lang: App.lang
|
||||
}
|
||||
}
|
||||
|
||||
declare namespace App {
|
||||
type lang = 'zhCN' | 'enUS'
|
||||
}
|
||||
|
||||
interface DictMap {
|
||||
[key: string]: Entity.Dict[]
|
||||
interface RouteRecord {
|
||||
name: string
|
||||
path: string
|
||||
redirect?: string
|
||||
component?: string | (() => Promise<unknown>)
|
||||
meta: Entity.Menu
|
||||
children?: RouteRecord[]
|
||||
}
|
||||
}
|
||||
|
||||
76
src/typings/route.d.ts
vendored
76
src/typings/route.d.ts
vendored
@ -1,76 +0,0 @@
|
||||
declare namespace AppRoute {
|
||||
|
||||
type MenuType = 'directory' | 'page' | 'permission'
|
||||
/** 单个路由所携带的meta标识 */
|
||||
interface RouteMeta {
|
||||
/* 页面标题,通常必选。 */
|
||||
title: string
|
||||
/* 图标,一般配合菜单使用 */
|
||||
icon?: string
|
||||
/* 是否需要登录权限。 */
|
||||
requiresAuth?: boolean
|
||||
/* 可以访问的角色 */
|
||||
roles?: Entity.RoleType[]
|
||||
/* 是否开启页面缓存 */
|
||||
keepAlive?: boolean
|
||||
/* 菜单显示状态 - 适配新菜单实体 */
|
||||
menuVisible?: boolean
|
||||
/* 菜单排序 - 适配新菜单实体 */
|
||||
sort?: number
|
||||
/* 是否为外链 - 适配新菜单实体 */
|
||||
isLink?: boolean
|
||||
/* 高亮菜单路径 - 适配新菜单实体 */
|
||||
activePath?: string
|
||||
/* 标签栏显示状态 - 适配新菜单实体 */
|
||||
tabVisible?: boolean
|
||||
/** 当前路由是否会被固定在Tab中,用于一些常驻页面 */
|
||||
pinTab?: boolean
|
||||
/** 当前路由在左侧菜单是目录还是页面,不设置默认为page */
|
||||
menuType?: MenuType
|
||||
|
||||
/* 以下字段保持向后兼容 */
|
||||
/* 有些路由我们并不想在菜单中显示,比如某些编辑页面。 */
|
||||
hide?: boolean
|
||||
/* 菜单排序。 */
|
||||
order?: number
|
||||
/* 嵌套外链 */
|
||||
href?: string
|
||||
/** 当前路由不在左侧菜单显示,但需要高亮某个菜单的情况 */
|
||||
activeMenu?: string
|
||||
/** 当前路由是否会被添加到Tab中 */
|
||||
withoutTab?: boolean
|
||||
}
|
||||
|
||||
type MetaKeys = keyof RouteMeta
|
||||
|
||||
interface baseRoute {
|
||||
/** 路由名称(路由唯一标识) */
|
||||
name: string
|
||||
/** 路由路径 */
|
||||
path: string
|
||||
/** 路由重定向 */
|
||||
redirect?: string
|
||||
/* 页面组件地址 */
|
||||
component?: string | null
|
||||
/* 路由id */
|
||||
id: number
|
||||
/* 父级路由id,顶级页面为null */
|
||||
parentId: number | null
|
||||
}
|
||||
|
||||
/** 单个路由的类型结构(动态路由模式:后端返回此类型结构的路由) */
|
||||
type RowRoute = RouteMeta & baseRoute
|
||||
|
||||
/**
|
||||
* 挂载到项目上的真实路由结构
|
||||
*/
|
||||
interface Route extends baseRoute {
|
||||
/** 子路由 */
|
||||
children?: Route[]
|
||||
/* 页面组件 */
|
||||
component: any
|
||||
/** 路由描述 */
|
||||
meta: RouteMeta
|
||||
}
|
||||
|
||||
}
|
||||
3
src/typings/router.d.ts
vendored
3
src/typings/router.d.ts
vendored
@ -1,5 +1,6 @@
|
||||
import 'vue-router'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta extends AppRoute.RouteMeta {}
|
||||
interface RouteMeta extends Entity.Menu {
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ interface StorageData<T> {
|
||||
/**
|
||||
* LocalStorage部分操作
|
||||
*/
|
||||
function createLocalStorage<T extends Storage.Local>() {
|
||||
function createLocalStorage<T extends App.Local>() {
|
||||
// 默认缓存期限为7天
|
||||
|
||||
function set<K extends keyof T>(key: K, value: T[K], expire: number = 60 * 60 * 24 * 7) {
|
||||
@ -52,7 +52,7 @@ function createLocalStorage<T extends Storage.Local>() {
|
||||
* sessionStorage部分操作
|
||||
*/
|
||||
|
||||
function createSessionStorage<T extends Storage.Session>() {
|
||||
function createSessionStorage<T extends App.Session>() {
|
||||
function set<K extends keyof T>(key: K, value: T[K]) {
|
||||
const json = JSON.stringify(value)
|
||||
window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json)
|
||||
|
||||
@ -7,7 +7,7 @@ const dictStore = useDictStore()
|
||||
|
||||
const selectedDictType = ref('')
|
||||
const selectedDictValue = ref('')
|
||||
const dictTypeOptions = ref<Array<{ label: string; value: string }>>([])
|
||||
const dictTypeOptions = ref<Array<{ label: string, value: string }>>([])
|
||||
const displayData = ref<Entity.DictData[] | Record<string, any>>([])
|
||||
|
||||
// 使用 useDict hook 获取字典数据
|
||||
@ -18,7 +18,7 @@ const dictUtils = computed(() => {
|
||||
enumMap: ref({}),
|
||||
valueMap: ref({}),
|
||||
labelMap: ref({}),
|
||||
options: ref([])
|
||||
options: ref([]),
|
||||
}
|
||||
}
|
||||
return useDict(selectedDictType.value)
|
||||
@ -81,7 +81,8 @@ function showOptions() {
|
||||
|
||||
// 根据值获取标签(使用 enumMap)
|
||||
const dictLabel = computed(() => {
|
||||
if (!selectedDictValue.value || !dictUtils.value.enumMap.value) return '--'
|
||||
if (!selectedDictValue.value || !dictUtils.value.enumMap.value)
|
||||
return '--'
|
||||
return dictUtils.value.enumMap.value[selectedDictValue.value] || '--'
|
||||
})
|
||||
|
||||
@ -104,19 +105,19 @@ function removeCache() {
|
||||
<n-card title="字典演示">
|
||||
<n-flex vertical>
|
||||
<n-flex align="center">
|
||||
<n-select
|
||||
v-model:value="selectedDictType"
|
||||
:options="dictTypeOptions"
|
||||
placeholder="请选择字典类型"
|
||||
@update:value="changeSelect"
|
||||
<n-select
|
||||
v-model:value="selectedDictType"
|
||||
:options="dictTypeOptions"
|
||||
placeholder="请选择字典类型"
|
||||
@update:value="changeSelect"
|
||||
/>
|
||||
<n-select
|
||||
v-model:value="selectedDictValue"
|
||||
:options="dictUtils.options.value"
|
||||
placeholder="请选择字典项"
|
||||
<n-select
|
||||
v-model:value="selectedDictValue"
|
||||
:options="dictUtils.options.value"
|
||||
placeholder="请选择字典项"
|
||||
/>
|
||||
</n-flex>
|
||||
|
||||
|
||||
<n-flex>
|
||||
<n-button @click="showRawData">
|
||||
显示原始数据 (rawData)
|
||||
@ -134,36 +135,62 @@ function removeCache() {
|
||||
显示选项格式 (options)
|
||||
</n-button>
|
||||
</n-flex>
|
||||
|
||||
|
||||
<n-flex>
|
||||
<n-button @click="clearCache" type="warning">
|
||||
<n-button type="warning" @click="clearCache">
|
||||
清理所有缓存
|
||||
</n-button>
|
||||
<n-button @click="removeCache" type="error" :disabled="!selectedDictType">
|
||||
<n-button type="error" :disabled="!selectedDictType" @click="removeCache">
|
||||
移除当前字典缓存
|
||||
</n-button>
|
||||
</n-flex>
|
||||
|
||||
<pre class="bg-gray-100 p-4 rounded text-sm overflow-auto max-h-96">
|
||||
{{ JSON.stringify(displayData, null, 2) }}</pre>
|
||||
{{ JSON.stringify(displayData, null, 2) }}</pre>
|
||||
|
||||
<n-flex align="center" v-if="selectedDictValue">
|
||||
<n-flex v-if="selectedDictValue" align="center">
|
||||
<n-text>选中值: {{ selectedDictValue }}</n-text>
|
||||
<n-text type="info">
|
||||
对应标签: {{ dictLabel }}
|
||||
</n-text>
|
||||
</n-flex>
|
||||
|
||||
|
||||
<n-divider />
|
||||
|
||||
<n-text depth="3">useDict Hook 使用说明:</n-text>
|
||||
|
||||
<n-text depth="3">
|
||||
useDict Hook 使用说明:
|
||||
</n-text>
|
||||
<n-ul>
|
||||
<n-li><n-text code>useDict(dictType)</n-text> - 使用字典类型获取字典工具对象</n-li>
|
||||
<n-li><n-text code>rawData</n-text> - 原始字典数据数组 Entity.DictData[]</n-li>
|
||||
<n-li><n-text code>enumMap</n-text> - 枚举映射 { value: name }</n-li>
|
||||
<n-li><n-text code>valueMap</n-text> - 值映射 { value: dictData }</n-li>
|
||||
<n-li><n-text code>labelMap</n-text> - 标签映射 { name: dictData }</n-li>
|
||||
<n-li><n-text code>options</n-text> - 选项数组 [{ label: name, value }]</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
useDict(dictType)
|
||||
</n-text> - 使用字典类型获取字典工具对象
|
||||
</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
rawData
|
||||
</n-text> - 原始字典数据数组 Entity.DictData[]
|
||||
</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
enumMap
|
||||
</n-text> - 枚举映射 { value: name }
|
||||
</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
valueMap
|
||||
</n-text> - 值映射 { value: dictData }
|
||||
</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
labelMap
|
||||
</n-text> - 标签映射 { name: dictData }
|
||||
</n-li>
|
||||
<n-li>
|
||||
<n-text code>
|
||||
options
|
||||
</n-text> - 选项数组 [{ label: name, value }]
|
||||
</n-li>
|
||||
<n-li>字典数据会自动缓存60分钟,支持手动清理缓存</n-li>
|
||||
<n-li>推荐使用 useDict hook 而不是直接调用 API 或 store</n-li>
|
||||
</n-ul>
|
||||
|
||||
@ -6,7 +6,7 @@ const { modifyTab } = useTabStore()
|
||||
const { fullPath, query } = useRoute()
|
||||
|
||||
modifyTab(fullPath, (target) => {
|
||||
target.meta.title = `详情页${query.id}`
|
||||
target.meta.title = `详情页${query.id || ''}`
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@ -5,13 +5,13 @@ const router = useRouter()
|
||||
<template>
|
||||
<n-card class="h-130vh">
|
||||
这个页面包含了一个不在侧边菜单的详情页面
|
||||
<n-button @click="router.push({ path: '/multi/multi2/detail', query: { id: 1 } })">
|
||||
<n-button @click="router.push({ path: '/multi/multi-2/detail', query: { id: 1 } })">
|
||||
跳转详情子页1
|
||||
</n-button>
|
||||
<n-button @click="router.push({ path: '/multi/multi2/detail', query: { id: 2 } })">
|
||||
<n-button @click="router.push({ path: '/multi/multi-2/detail', query: { id: 2 } })">
|
||||
跳转详情子页2
|
||||
</n-button>
|
||||
<n-button @click="router.push({ path: '/multi/multi2/detail', query: { id: 3 } })">
|
||||
<n-button @click="router.push({ path: '/multi/multi-2/detail', query: { id: 3 } })">
|
||||
跳转详情子页3
|
||||
</n-button>
|
||||
</n-card>
|
||||
|
||||
@ -5,8 +5,6 @@ import { useAuthStore } from '@/store'
|
||||
const authStore = useAuthStore()
|
||||
const { hasPermission } = usePermission()
|
||||
|
||||
const roleList: Entity.RoleType[] = ['super', 'admin', 'user']
|
||||
|
||||
function toggleUserRole(role: Entity.RoleType) {
|
||||
authStore.login(role, '123456')
|
||||
}
|
||||
@ -14,7 +12,7 @@ function toggleUserRole(role: Entity.RoleType) {
|
||||
|
||||
<template>
|
||||
<n-card title="权限示例">
|
||||
<n-h1> 当前权限:{{ authStore.userInfo!.roles.map(r => r.roleName) }}</n-h1>
|
||||
<n-h1> 当前权限:{{ authStore.userInfo.roles.map((r: Entity.Role) => r.roleName) }}</n-h1>
|
||||
<n-button-group>
|
||||
<n-button v-for="item in roleList" :key="item" type="default" @click="toggleUserRole(item)">
|
||||
{{ item }}
|
||||
@ -22,10 +20,10 @@ function toggleUserRole(role: Entity.RoleType) {
|
||||
</n-button-group>
|
||||
<n-h2>v-permission 指令用法</n-h2>
|
||||
<n-space>
|
||||
<n-button v-permission="['super']">
|
||||
<n-button v-role="['super']">
|
||||
仅super可见
|
||||
</n-button>
|
||||
<n-button v-permission="['admin']">
|
||||
<n-button v-role="['admin']">
|
||||
admin可见
|
||||
</n-button>
|
||||
</n-space>
|
||||
|
||||
@ -36,7 +36,6 @@ export function createMenuColumns(actions: MenuColumnActions): DataTableColumns<
|
||||
{
|
||||
title: '菜单名称',
|
||||
key: 'title',
|
||||
width: 400,
|
||||
},
|
||||
{
|
||||
title: '图标',
|
||||
|
||||
@ -1,43 +0,0 @@
|
||||
<template>
|
||||
<pro-input
|
||||
title="国际化标识Key"
|
||||
path="i18nKey"
|
||||
placeholder="Eg: system.user"
|
||||
/>
|
||||
<pro-field
|
||||
title="菜单图标"
|
||||
path="icon"
|
||||
>
|
||||
<template #input="{ inputProps }">
|
||||
<icon-select
|
||||
:value="inputProps.value"
|
||||
@update:value="inputProps.onUpdateValue"
|
||||
/>
|
||||
</template>
|
||||
</pro-field>
|
||||
<pro-input
|
||||
required
|
||||
title="路由路径"
|
||||
tooltip="目录类型的路由路径,如:/system"
|
||||
path="path"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system"
|
||||
/>
|
||||
<pro-digit
|
||||
title="排序"
|
||||
tooltip="数字越小,同级中越靠前"
|
||||
path="sort"
|
||||
/>
|
||||
<pro-switch
|
||||
title="启用"
|
||||
path="status"
|
||||
:field-props="{
|
||||
checkedValue: 0,
|
||||
uncheckedValue: 1,
|
||||
}"
|
||||
/>
|
||||
<pro-switch
|
||||
title="菜单可见"
|
||||
path="menuVisible"
|
||||
/>
|
||||
</template>
|
||||
@ -2,9 +2,6 @@
|
||||
import { useBoolean } from '@/hooks'
|
||||
import { createMenu, getMenuById, getMenuOptions, updateMenu } from '@/api'
|
||||
import { createProModalForm } from 'pro-naive-ui'
|
||||
import DirectoryForm from './DirectoryForm.vue'
|
||||
import PageForm from './PageForm.vue'
|
||||
import PermissionForm from './PermissionForm.vue'
|
||||
|
||||
interface Props {
|
||||
modalName?: string
|
||||
@ -49,21 +46,8 @@ const modalTitle = computed(() => {
|
||||
|
||||
const treeData = ref<Entity.TreeNode[]>([])
|
||||
|
||||
// 动态组件映射
|
||||
const formComponents = {
|
||||
directory: DirectoryForm,
|
||||
page: PageForm,
|
||||
permission: PermissionForm,
|
||||
}
|
||||
|
||||
// 当前使用的表单组件
|
||||
const currentFormComponent = computed(() => {
|
||||
const menuType = modalForm.values.value?.menuType || 'directory'
|
||||
return formComponents[menuType as keyof typeof formComponents]
|
||||
})
|
||||
|
||||
async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
||||
getMenuOptions().then((res) => {
|
||||
getMenuOptions(true).then((res) => {
|
||||
treeData.value = res.data
|
||||
})
|
||||
|
||||
@ -76,6 +60,14 @@ async function openModal(type: ModalType = 'add', data?: Partial<Entity.Menu>) {
|
||||
modalForm.values.value.parentId = data.id
|
||||
modalForm.values.value.path = `${data.path}/`
|
||||
}
|
||||
|
||||
if (data?.menuType === 'directory') {
|
||||
modalForm.values.value.menuType = 'page'
|
||||
}
|
||||
|
||||
if (data?.menuType === 'page') {
|
||||
modalForm.values.value.menuType = 'permission'
|
||||
}
|
||||
},
|
||||
async edit() {
|
||||
if (!data?.id)
|
||||
@ -123,6 +115,9 @@ async function submitModal(filedValues: Partial<Entity.Menu>) {
|
||||
}
|
||||
}
|
||||
|
||||
function handlePathChange(path: string) {
|
||||
modalForm.values.value.component = `${path}/index.vue`
|
||||
}
|
||||
defineExpose({
|
||||
openModal,
|
||||
})
|
||||
@ -163,15 +158,119 @@ defineExpose({
|
||||
/>
|
||||
<pro-input
|
||||
required
|
||||
title="标题"
|
||||
title="名称"
|
||||
path="title"
|
||||
/>
|
||||
<component :is="currentFormComponent" />
|
||||
<pro-input
|
||||
v-if="modalForm.values.value.menuType !== 'permission'"
|
||||
title="国际化标识Key"
|
||||
path="i18nKey"
|
||||
placeholder="Eg: system.user"
|
||||
/>
|
||||
<pro-field
|
||||
v-if="modalForm.values.value.menuType !== 'permission'"
|
||||
title="菜单图标"
|
||||
path="icon"
|
||||
>
|
||||
<template #input="{ inputProps }">
|
||||
<icon-select
|
||||
:value="inputProps.value"
|
||||
@update:value="inputProps.onUpdateValue"
|
||||
/>
|
||||
</template>
|
||||
</pro-field>
|
||||
<pro-input
|
||||
v-if="modalForm.values.value.menuType !== 'permission'"
|
||||
required
|
||||
title="路由路径"
|
||||
tooltip="页面路由路径,与组件路径对应"
|
||||
path="path"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user"
|
||||
@change="handlePathChange"
|
||||
/>
|
||||
<pro-input
|
||||
v-if="modalForm.values.value.menuType === 'page'"
|
||||
required
|
||||
title="组件路径"
|
||||
tooltip="页面组件的文件路径"
|
||||
path="component"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user/index.vue"
|
||||
/>
|
||||
|
||||
<pro-digit
|
||||
title="排序"
|
||||
tooltip="数字越小,同级中越靠前, 默认为0"
|
||||
path="sort"
|
||||
/>
|
||||
<pro-input
|
||||
title="权限标识"
|
||||
tooltip="需与后端装饰器一致,如@RequirePermissions('system:user:list')"
|
||||
path="perms"
|
||||
placeholder="Eg: system:user:list"
|
||||
/>
|
||||
<pro-switch
|
||||
title="启用"
|
||||
path="status"
|
||||
:field-props="{
|
||||
checkedValue: 0,
|
||||
uncheckedValue: 1,
|
||||
}"
|
||||
/>
|
||||
<pro-textarea
|
||||
title="备注"
|
||||
path="remark"
|
||||
class="col-span-2"
|
||||
/>
|
||||
<n-collapse v-if="modalForm.values.value.menuType === 'page'" class="col-span-2">
|
||||
<n-collapse-item>
|
||||
<template #header>
|
||||
<icon-park-outline-setting class="mr-2" />
|
||||
高级设置
|
||||
</template>
|
||||
<div class="grid grid-cols-2">
|
||||
<pro-input
|
||||
title="高亮菜单路径"
|
||||
tooltip="当前路由不在侧边菜单显示,但需要高亮为某个菜单"
|
||||
path="activePath"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user"
|
||||
/>
|
||||
<pro-switch
|
||||
title="菜单可见"
|
||||
path="menuVisible"
|
||||
/>
|
||||
<pro-switch
|
||||
title="标签栏可见"
|
||||
path="tabVisible"
|
||||
/>
|
||||
<pro-switch
|
||||
title="页面缓存"
|
||||
tooltip="开启配置后,切换页面数据不会清空"
|
||||
path="keepAlive"
|
||||
/>
|
||||
<pro-switch
|
||||
title="常驻标签栏"
|
||||
path="pinTab"
|
||||
/>
|
||||
<pro-switch
|
||||
title="跳转外链"
|
||||
tooltip="开启配置后,点击菜单会跳转到外链地址"
|
||||
path="isLink"
|
||||
/>
|
||||
<pro-input
|
||||
v-if="modalForm.values.value.isLink"
|
||||
required
|
||||
title="外链地址"
|
||||
tooltip="开启跳转外链配置后,点击菜单会跳转到外链地址"
|
||||
path="linkPath"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: https://www.baidu.com"
|
||||
/>
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
</pro-modal-form>
|
||||
</template>
|
||||
|
||||
@ -1,83 +0,0 @@
|
||||
<template>
|
||||
<pro-input
|
||||
title="国际化标识Key"
|
||||
path="i18nKey"
|
||||
placeholder="Eg: system.user"
|
||||
/>
|
||||
<pro-field
|
||||
title="菜单图标"
|
||||
path="icon"
|
||||
>
|
||||
<template #input="{ inputProps }">
|
||||
<icon-select
|
||||
:value="inputProps.value"
|
||||
@update:value="inputProps.onUpdateValue"
|
||||
/>
|
||||
</template>
|
||||
</pro-field>
|
||||
<pro-input
|
||||
required
|
||||
title="路由路径"
|
||||
tooltip="页面路由路径,与组件路径对应"
|
||||
path="path"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user"
|
||||
@update:value="$emit('path', $event)"
|
||||
/>
|
||||
<pro-input
|
||||
title="高亮菜单路径"
|
||||
tooltip="当前路由不在侧边菜单显示,但需要高亮为某个菜单"
|
||||
path="activePath"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user"
|
||||
/>
|
||||
<pro-input
|
||||
required
|
||||
title="组件路径"
|
||||
tooltip="页面组件的文件路径"
|
||||
path="component"
|
||||
class="col-span-2"
|
||||
placeholder="Eg: /system/user/index.vue"
|
||||
/>
|
||||
<pro-digit
|
||||
title="排序"
|
||||
tooltip="数字越小,同级中越靠前"
|
||||
path="sort"
|
||||
/>
|
||||
<pro-input
|
||||
title="权限标识"
|
||||
tooltip="后端装饰器一致,如@RequirePermissions('system:user:list')"
|
||||
path="perms"
|
||||
placeholder="Eg: system:user:list"
|
||||
/>
|
||||
<pro-switch
|
||||
title="启用"
|
||||
path="status"
|
||||
:field-props="{
|
||||
checkedValue: 0,
|
||||
uncheckedValue: 1,
|
||||
}"
|
||||
/>
|
||||
<pro-switch
|
||||
title="菜单可见"
|
||||
path="menuVisible"
|
||||
/>
|
||||
<pro-switch
|
||||
title="标签栏可见"
|
||||
path="tabVisible"
|
||||
/>
|
||||
<pro-switch
|
||||
title="页面缓存"
|
||||
tooltip="开启配置后,切换页面数据不会清空"
|
||||
path="keepAlive"
|
||||
/>
|
||||
<pro-switch
|
||||
title="常驻标签栏"
|
||||
path="pinTab"
|
||||
/>
|
||||
<pro-switch
|
||||
title="外链菜单"
|
||||
tooltip="开启配置后,点击菜单会跳转到外链地址"
|
||||
path="isLink"
|
||||
/>
|
||||
</template>
|
||||
@ -1,22 +0,0 @@
|
||||
<template>
|
||||
<pro-input
|
||||
required
|
||||
title="权限标识"
|
||||
tooltip="后端装饰器一致,如@RequirePermissions('system:user:add')"
|
||||
path="perms"
|
||||
placeholder="Eg: system:user:add"
|
||||
/>
|
||||
<pro-digit
|
||||
title="排序"
|
||||
tooltip="数字越小,同级中越靠前"
|
||||
path="sort"
|
||||
/>
|
||||
<pro-switch
|
||||
title="启用"
|
||||
path="status"
|
||||
:field-props="{
|
||||
checkedValue: 0,
|
||||
uncheckedValue: 1,
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
@ -26,5 +26,10 @@
|
||||
"isolatedModules": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts",
|
||||
"./**/*.tsx",
|
||||
"./**/*.vue"
|
||||
],
|
||||
"exclude": ["node_modules", "eslint.config.js"]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user