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