feat: Add dynamic route
This commit is contained in:
parent
d5b6e2a777
commit
9d926b2760
|
@ -0,0 +1,475 @@
|
|||
import { config } from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { result_code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
const adminList = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: '#',
|
||||
redirect: '/dashboard/analysis',
|
||||
name: 'Dashboard',
|
||||
meta: {
|
||||
title: 'router.dashboard',
|
||||
icon: 'ant-design:dashboard-filled',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'analysis',
|
||||
component: 'views/Dashboard/Analysis',
|
||||
name: 'Analysis',
|
||||
meta: {
|
||||
title: 'router.analysis',
|
||||
noCache: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'workplace',
|
||||
component: 'views/Dashboard/Workplace',
|
||||
name: 'Workplace',
|
||||
meta: {
|
||||
title: 'router.workplace',
|
||||
noCache: true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/guide',
|
||||
component: '#',
|
||||
name: 'Guide',
|
||||
meta: {},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
component: 'views/Guide/Guide',
|
||||
name: 'GuideDemo',
|
||||
meta: {
|
||||
title: 'router.guide',
|
||||
icon: 'cib:telegram-plane'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/components',
|
||||
component: '#',
|
||||
redirect: '/components/icon',
|
||||
name: 'ComponentsDemo',
|
||||
meta: {
|
||||
title: 'router.component',
|
||||
icon: 'bx:bxs-component',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'form',
|
||||
component: '##',
|
||||
name: 'Form',
|
||||
meta: {
|
||||
title: 'router.form',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-form',
|
||||
component: 'views/Components/Form/DefaultForm',
|
||||
name: 'DefaultForm',
|
||||
meta: {
|
||||
title: 'router.defaultForm'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-form',
|
||||
component: 'views/Components/Form/UseFormDemo',
|
||||
name: 'UseForm',
|
||||
meta: {
|
||||
title: 'UseForm'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-form',
|
||||
component: 'views/Components/Form/RefForm',
|
||||
name: 'RefForm',
|
||||
meta: {
|
||||
title: 'RefForm'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'table',
|
||||
component: '##',
|
||||
name: 'TableDemo',
|
||||
meta: {
|
||||
title: 'router.table',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'default-table',
|
||||
component: 'views/Components/Table/DefaultTable',
|
||||
name: 'DefaultTable',
|
||||
meta: {
|
||||
title: 'router.defaultTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'use-table',
|
||||
component: 'views/Components/Table/UseTableDemo',
|
||||
name: 'UseTable',
|
||||
meta: {
|
||||
title: 'UseTable'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'ref-table',
|
||||
component: 'views/Components/Table/RefTable',
|
||||
name: 'RefTable',
|
||||
meta: {
|
||||
title: 'RefTable'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'editor-demo',
|
||||
component: '##',
|
||||
name: 'EditorDemo',
|
||||
meta: {
|
||||
title: 'router.editor',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'editor',
|
||||
component: 'views/Components/Editor/Editor',
|
||||
name: 'Editor',
|
||||
meta: {
|
||||
title: 'router.richText'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
component: 'views/Components/Search',
|
||||
name: 'Search',
|
||||
meta: {
|
||||
title: 'router.search'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'descriptions',
|
||||
component: 'views/Components/Descriptions',
|
||||
name: 'Descriptions',
|
||||
meta: {
|
||||
title: 'router.descriptions'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'image-viewer',
|
||||
component: 'views/Components/ImageViewer',
|
||||
name: 'ImageViewer',
|
||||
meta: {
|
||||
title: 'router.imageViewer'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'dialog',
|
||||
component: 'views/Components/Dialog',
|
||||
name: 'Dialog',
|
||||
meta: {
|
||||
title: 'router.dialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'icon',
|
||||
component: 'views/Components/Icon',
|
||||
name: 'Icon',
|
||||
meta: {
|
||||
title: 'router.icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'echart',
|
||||
component: 'views/Components/Echart',
|
||||
name: 'Echart',
|
||||
meta: {
|
||||
title: 'router.echart'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'count-to',
|
||||
component: 'views/Components/CountTo',
|
||||
name: 'CountTo',
|
||||
meta: {
|
||||
title: 'router.countTo'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'watermark',
|
||||
component: 'views/Components/Watermark',
|
||||
name: 'Watermark',
|
||||
meta: {
|
||||
title: 'router.watermark'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'qrcode',
|
||||
component: 'views/Components/Qrcode',
|
||||
name: 'Qrcode',
|
||||
meta: {
|
||||
title: 'router.qrcode'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'highlight',
|
||||
component: 'views/Components/Highlight',
|
||||
name: 'Highlight',
|
||||
meta: {
|
||||
title: 'router.highlight'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'infotip',
|
||||
component: 'views/Components/Infotip',
|
||||
name: 'Infotip',
|
||||
meta: {
|
||||
title: 'router.infotip'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/level',
|
||||
component: '#',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
name: 'Level',
|
||||
meta: {
|
||||
title: 'router.level',
|
||||
icon: 'carbon:skill-level-advanced'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1',
|
||||
name: 'Menu1',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu1'
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1',
|
||||
name: 'Menu11',
|
||||
component: '##',
|
||||
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||
meta: {
|
||||
title: 'router.menu11',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'menu1-1-1',
|
||||
name: 'Menu111',
|
||||
component: 'views/Level/Menu111',
|
||||
meta: {
|
||||
title: 'router.menu111'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu1-2',
|
||||
name: 'Menu12',
|
||||
component: 'views/Level/Menu12',
|
||||
meta: {
|
||||
title: 'router.menu12'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'menu2',
|
||||
name: 'Menu2Demo',
|
||||
component: 'views/Level/Menu2',
|
||||
meta: {
|
||||
title: 'router.menu2'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/example',
|
||||
component: '#',
|
||||
redirect: '/example/example-dialog',
|
||||
name: 'Example',
|
||||
meta: {
|
||||
title: 'router.example',
|
||||
icon: 'ep:management',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'example-dialog',
|
||||
component: 'views/Example/Dialog/ExampleDialog',
|
||||
name: 'ExampleDialog',
|
||||
meta: {
|
||||
title: 'router.exampleDialog'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-page',
|
||||
component: 'views/Example/Page/ExamplePage',
|
||||
name: 'ExamplePage',
|
||||
meta: {
|
||||
title: 'router.examplePage'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-add',
|
||||
component: 'views/Example/Page/ExampleAdd',
|
||||
name: 'ExampleAdd',
|
||||
meta: {
|
||||
title: 'router.exampleAdd',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-edit',
|
||||
component: 'views/Example/Page/ExampleEdit',
|
||||
name: 'ExampleEdit',
|
||||
meta: {
|
||||
title: 'router.exampleEdit',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'example-detail',
|
||||
component: 'views/Example/Page/ExampleDetail',
|
||||
name: 'ExampleDetail',
|
||||
meta: {
|
||||
title: 'router.exampleDetail',
|
||||
noTagsView: true,
|
||||
noCache: true,
|
||||
hidden: true,
|
||||
showMainRoute: true,
|
||||
activeMenu: '/example/example-page'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/error',
|
||||
component: '#',
|
||||
redirect: '/error/404',
|
||||
name: 'Error',
|
||||
meta: {
|
||||
title: 'router.errorPage',
|
||||
icon: 'ci:error',
|
||||
alwaysShow: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '404-demo',
|
||||
component: 'views/Error/404',
|
||||
name: '404Demo',
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '403-demo',
|
||||
component: 'views/Error/403',
|
||||
name: '403Demo',
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '500-demo',
|
||||
component: 'views/Error/500',
|
||||
name: '500Demo',
|
||||
meta: {
|
||||
title: '500'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const testList: string[] = [
|
||||
'/dashboard',
|
||||
'/dashboard/analysis',
|
||||
'/dashboard/workplace',
|
||||
'/guide',
|
||||
'/guide/index',
|
||||
'/components',
|
||||
'/components/form',
|
||||
'/components/form/default-form',
|
||||
'/components/form/use-form',
|
||||
'/components/form/ref-form',
|
||||
'/components/table',
|
||||
'/components/table/default-table',
|
||||
'/components/table/use-table',
|
||||
'/components/table/ref-table',
|
||||
'/components/editor-demo',
|
||||
'/components/editor-demo/editor',
|
||||
'/components/search',
|
||||
'/components/descriptions',
|
||||
'/components/image-viewer',
|
||||
'/components/dialog',
|
||||
'/components/icon',
|
||||
'/components/echart',
|
||||
'/components/count-to',
|
||||
'/components/watermark',
|
||||
'/components/qrcode',
|
||||
'/components/highlight',
|
||||
'/components/infotip',
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
'/level/menu1/menu1-1/menu1-1-1',
|
||||
'/level/menu1/menu1-2',
|
||||
'/level/menu2',
|
||||
'/example',
|
||||
'/example/example-dialog',
|
||||
'/example/example-page',
|
||||
'/example/example-add',
|
||||
'/example/example-edit',
|
||||
'/example/example-detail',
|
||||
'/error',
|
||||
'/error/404-demo',
|
||||
'/error/403-demo',
|
||||
'/error/500-demo'
|
||||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
return {
|
||||
code: result_code,
|
||||
data: {
|
||||
list: roleName === 'admin' ? adminList : testList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
|
@ -26,6 +26,30 @@ const List: {
|
|||
]
|
||||
|
||||
export default [
|
||||
// 列表接口
|
||||
{
|
||||
url: '/user/list',
|
||||
method: 'get',
|
||||
response: ({ query }) => {
|
||||
const { username, pageIndex, pageSize } = query
|
||||
|
||||
const mockList = List.filter((item) => {
|
||||
if (username && item.username.indexOf(username) < 0) return false
|
||||
return true
|
||||
})
|
||||
const pageList = mockList.filter(
|
||||
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
|
||||
)
|
||||
|
||||
return {
|
||||
code: result_code,
|
||||
data: {
|
||||
total: mockList.length,
|
||||
list: pageList
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 登录接口
|
||||
{
|
||||
url: '/user/login',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useAxios } from '@/hooks/web/useAxios'
|
||||
import type { UserLoginType } from './types'
|
||||
import type { UserLoginType, UserType } from './types'
|
||||
|
||||
const { request } = useAxios()
|
||||
|
||||
|
@ -13,3 +13,22 @@ export const loginApi = (data: UserLoginType) => {
|
|||
export const loginOutApi = () => {
|
||||
return request({ url: '/user/loginOut', method: 'get' })
|
||||
}
|
||||
|
||||
export const getUserListApi = ({ params }: AxiosConfig) => {
|
||||
return request<{
|
||||
total: number
|
||||
list: UserType[]
|
||||
}>({ url: '/user/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export const getAdminRoleApi = ({ params }: AxiosConfig) => {
|
||||
return request<{
|
||||
list: AppCustomRouteRecordRaw[]
|
||||
}>({ url: '/role/list', method: 'get', params })
|
||||
}
|
||||
|
||||
export const getTestRoleApi = ({ params }: AxiosConfig) => {
|
||||
return request<{
|
||||
list: string[]
|
||||
}>({ url: '/role/list', method: 'get', params })
|
||||
}
|
||||
|
|
|
@ -2,3 +2,10 @@ export type UserLoginType = {
|
|||
username: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export type UserType = {
|
||||
username: string
|
||||
password: string
|
||||
role: string
|
||||
roleId: string
|
||||
}
|
||||
|
|
|
@ -126,7 +126,10 @@ export default {
|
|||
exampleAdd: 'Example page - add',
|
||||
exampleEdit: 'Example page - edit',
|
||||
exampleDetail: 'Example page - detail',
|
||||
errorPage: 'Error page'
|
||||
errorPage: 'Error page',
|
||||
authorization: 'Authorization',
|
||||
user: 'User management',
|
||||
role: 'Role management'
|
||||
},
|
||||
analysis: {
|
||||
newUser: 'New user',
|
||||
|
@ -393,5 +396,18 @@ export default {
|
|||
content: 'Content',
|
||||
save: 'Save',
|
||||
detail: 'Detail'
|
||||
},
|
||||
userDemo: {
|
||||
title: 'User management',
|
||||
message:
|
||||
'Because it is simulated data, only two accounts with different permissions are provided, which can be modified and combined by developers according to the actual situation.',
|
||||
index: 'Index',
|
||||
action: 'Action',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
role: 'Role',
|
||||
remark: 'Remark',
|
||||
remarkMessage1: 'Back end control routing permission',
|
||||
remarkMessage2: 'Front end control routing permission'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,10 @@ export default {
|
|||
exampleAdd: '综合示例 - 新增',
|
||||
exampleEdit: '综合示例 - 编辑',
|
||||
exampleDetail: '综合示例 - 详情',
|
||||
errorPage: '错误页面'
|
||||
errorPage: '错误页面',
|
||||
authorization: '权限管理',
|
||||
user: '用户管理',
|
||||
role: '角色管理'
|
||||
},
|
||||
analysis: {
|
||||
newUser: '新增用户',
|
||||
|
@ -390,5 +393,17 @@ export default {
|
|||
content: '内容',
|
||||
save: '保存',
|
||||
detail: '详情'
|
||||
},
|
||||
userDemo: {
|
||||
title: '用户管理',
|
||||
message: '由于是模拟数据,所以只提供了两种不同权限的帐号,开发者可根据实际情况自行改造结合。',
|
||||
index: '序号',
|
||||
action: '操作',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
role: '角色',
|
||||
remark: '备注',
|
||||
remarkMessage1: '后端控制路由权限',
|
||||
remarkMessage2: '前端控制路由权限'
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,15 @@ router.beforeEach(async (to, from, next) => {
|
|||
to.path === '/' ? next({ path: permissionStore.addRouters[0]?.path as string }) : next()
|
||||
return
|
||||
}
|
||||
await permissionStore.generateRoutes()
|
||||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = wsCache.get('roleRouters') || []
|
||||
const userInfo = wsCache.get(appStore.getUserInfo)
|
||||
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
|
|
|
@ -424,31 +424,60 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
},
|
||||
children: [
|
||||
{
|
||||
path: '404',
|
||||
path: '404-demo',
|
||||
component: () => import('@/views/Error/404.vue'),
|
||||
name: '404',
|
||||
name: '404Demo',
|
||||
meta: {
|
||||
title: '404'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '403',
|
||||
path: '403-demo',
|
||||
component: () => import('@/views/Error/403.vue'),
|
||||
name: '403',
|
||||
name: '403Demo',
|
||||
meta: {
|
||||
title: '403'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
path: '500-demo',
|
||||
component: () => import('@/views/Error/500.vue'),
|
||||
name: '500',
|
||||
name: '500Demo',
|
||||
meta: {
|
||||
title: '500'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
// {
|
||||
// path: '/authorization',
|
||||
// component: Layout,
|
||||
// redirect: '/authorization/user',
|
||||
// name: 'Authorization',
|
||||
// meta: {
|
||||
// title: t('router.authorization'),
|
||||
// icon: 'eos-icons:role-binding',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'user',
|
||||
// component: () => import('@/views/Authorization/User.vue'),
|
||||
// name: 'User',
|
||||
// meta: {
|
||||
// title: t('router.user')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'role',
|
||||
// component: () => import('@/views/Authorization/Role.vue'),
|
||||
// name: 'Role',
|
||||
// meta: {
|
||||
// title: t('router.role')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
|
@ -459,7 +488,7 @@ const router = createRouter({
|
|||
})
|
||||
|
||||
export const resetRouter = (): void => {
|
||||
const resetWhiteNameList = ['RedirectRoot', 'Redirect', 'Login', 'Root', 'Dashboard', 'Page404']
|
||||
const resetWhiteNameList = ['Redirect', 'Login', 'NoFind']
|
||||
router.getRoutes().forEach((route) => {
|
||||
const { name } = route
|
||||
if (name && !resetWhiteNameList.includes(name as string)) {
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { asyncRouterMap, constantRouterMap } from '@/router'
|
||||
// import { useCache } from '@/hooks/web/useCache'
|
||||
import { flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
// import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
import { store } from '../index'
|
||||
// import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
// const { wsCache } = useCache()
|
||||
|
||||
// const appStore = useAppStoreWithOut()
|
||||
|
||||
export interface PermissionState {
|
||||
routers: AppRouteRecordRaw[]
|
||||
addRouters: AppRouteRecordRaw[]
|
||||
|
@ -44,21 +37,23 @@ export const usePermissionStore = defineStore({
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
generateRoutes(): Promise<unknown> {
|
||||
generateRoutes(
|
||||
type: 'admin' | 'test' | 'none',
|
||||
routers?: AppCustomRouteRecordRaw[] | string[]
|
||||
): Promise<unknown> {
|
||||
return new Promise<void>((resolve) => {
|
||||
// 路由权限控制,如果不需要权限控制,请注释
|
||||
// let routerMap: AppRouteRecordRaw[] = []
|
||||
// if (wsCache.get(appStore.getUserInfo).username === 'admin') {
|
||||
// // 模拟前端控制权限
|
||||
// routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap))
|
||||
// } else {
|
||||
// // 模拟后端控制权限
|
||||
// routerMap = generateRoutesFn2(wsCache.get(appStore.getUserInfo).checkedNodes)
|
||||
// }
|
||||
|
||||
// 不需要权限控制
|
||||
const routerMap: AppRouteRecordRaw[] = cloneDeep(asyncRouterMap)
|
||||
|
||||
let routerMap: AppRouteRecordRaw[] = []
|
||||
if (type === 'admin') {
|
||||
// 模拟后端过滤菜单
|
||||
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
|
||||
} else if (type === 'test') {
|
||||
// 模拟前端过滤菜单
|
||||
routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
|
||||
} else {
|
||||
// 直接读取静态路由表
|
||||
routerMap = cloneDeep(asyncRouterMap)
|
||||
}
|
||||
console.log(routerMap)
|
||||
// 动态路由,404一定要放到最后面
|
||||
this.addRouters = routerMap.concat([
|
||||
{
|
||||
|
|
|
@ -16,9 +16,6 @@ export const useTagsViewStore = defineStore({
|
|||
visitedViews: [],
|
||||
cachedViews: new Set()
|
||||
}),
|
||||
persist: {
|
||||
enabled: true
|
||||
},
|
||||
getters: {
|
||||
getVisitedViews(): RouteLocationNormalizedLoaded[] {
|
||||
return this.visitedViews
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
import type { Router, RouteLocationNormalized, RouteRecordNormalized, RouteMeta } from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
import { omit, cloneDeep } from 'lodash-es'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const modules = import.meta.glob('../../views/**/*.{vue,tsx}')
|
||||
const modules = import.meta.glob('../views/**/*.{vue,tsx}')
|
||||
|
||||
/* Layout */
|
||||
export const Layout = () => import('@/layout/Layout.vue')
|
||||
|
@ -41,6 +35,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
|
|||
// 前端控制路由生成
|
||||
export const generateRoutesFn1 = (
|
||||
routes: AppRouteRecordRaw[],
|
||||
keys: string[],
|
||||
basePath = '/'
|
||||
): AppRouteRecordRaw[] => {
|
||||
const res: AppRouteRecordRaw[] = []
|
||||
|
@ -55,7 +50,6 @@ export const generateRoutesFn1 = (
|
|||
let data: Nullable<AppRouteRecordRaw> = null
|
||||
|
||||
let onlyOneChild: Nullable<string> = null
|
||||
|
||||
if (route.children && route.children.length === 1 && !meta.alwaysShow) {
|
||||
onlyOneChild = (
|
||||
isUrl(route.children[0].path)
|
||||
|
@ -64,16 +58,14 @@ export const generateRoutesFn1 = (
|
|||
) as string
|
||||
}
|
||||
|
||||
// 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。
|
||||
const list = wsCache.get(appStore.getUserInfo).checkedNodes
|
||||
// 开发者可以根据实际情况进行扩展
|
||||
for (const item of list) {
|
||||
for (const item of keys) {
|
||||
// 通过路径去匹配
|
||||
if (isUrl(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
|
||||
if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
|
||||
data = Object.assign({}, route)
|
||||
} else {
|
||||
const routePath = pathResolve(basePath, onlyOneChild || route.path)
|
||||
if (routePath === item.path || meta.followRoute === item.path) {
|
||||
if (routePath === item || meta.followRoute === item) {
|
||||
data = Object.assign({}, route)
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +73,7 @@ export const generateRoutesFn1 = (
|
|||
|
||||
// recursive child routes
|
||||
if (route.children && data) {
|
||||
data.children = generateRoutesFn1(route.children, pathResolve(basePath, data.path))
|
||||
data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
|
||||
}
|
||||
if (data) {
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
|
@ -91,7 +83,7 @@ export const generateRoutesFn1 = (
|
|||
}
|
||||
|
||||
// 后端控制路由生成
|
||||
export const generateRoutesFn2 = (routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
|
@ -102,15 +94,14 @@ export const generateRoutesFn2 = (routes: AppRouteRecordRaw[]): AppRouteRecordRa
|
|||
meta: route.meta
|
||||
}
|
||||
if (route.component) {
|
||||
const comModule =
|
||||
modules[`../../${route.component}.vue`] || modules[`../../${route.component}.tsx`]
|
||||
if (comModule) {
|
||||
const comModule = modules[`../${route.component}.vue`] || modules[`../${route.component}.tsx`]
|
||||
const component = route.component as string
|
||||
if (!comModule && !component.includes('#')) {
|
||||
console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)
|
||||
} else {
|
||||
// 动态加载路由文件,可根据实际情况进行自定义逻辑
|
||||
const component = route.component as string
|
||||
data.component =
|
||||
component === '#' ? Layout : component.includes('##') ? getParentLayout() : comModule
|
||||
} else {
|
||||
console.error(`未找到${route.component}.vue文件或${route.component}.tsx文件,请创建`)
|
||||
}
|
||||
}
|
||||
// recursive child routes
|
||||
|
@ -124,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppRouteRecordRaw[]): AppRouteRecordRa
|
|||
|
||||
export const pathResolve = (parentPath: string, path: string) => {
|
||||
const childPath = path.startsWith('/') || !path ? path : `/${path}`
|
||||
return `${parentPath}${childPath}`
|
||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/')
|
||||
}
|
||||
|
||||
// 路由降级
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table } from '@/components/Table'
|
||||
import { getUserListApi } from '@/api/login'
|
||||
import { UserType } from '@/api/login/types'
|
||||
import { ref, h } from 'vue'
|
||||
import { ElButton } from 'element-plus'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
field: 'index',
|
||||
label: t('userDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('userDemo.username')
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('userDemo.password')
|
||||
},
|
||||
{
|
||||
field: 'role',
|
||||
label: t('userDemo.role')
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: t('userDemo.remark'),
|
||||
formatter: (row: UserType) => {
|
||||
return h(
|
||||
'span',
|
||||
row.username === 'admin' ? t('userDemo.remarkMessage1') : t('userDemo.remarkMessage2')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('userDemo.action')
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
let tableDataList = ref<UserType[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getUserListApi({
|
||||
params: params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
|
||||
const acitonFn = (data: TableSlotDefault) => {
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('userDemo.title')" :message="t('userDemo.message')">
|
||||
<Table :columns="columns" :data="tableDataList" :loading="loading" :selection="false">
|
||||
<template #action="data">
|
||||
<ElButton type="primary" @click="acitonFn(data as TableSlotDefault)">
|
||||
{{ t('tableDemo.action') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</template>
|
|
@ -0,0 +1,88 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Table } from '@/components/Table'
|
||||
import { getUserListApi } from '@/api/login'
|
||||
import { UserType } from '@/api/login/types'
|
||||
import { ref, h } from 'vue'
|
||||
import { ElButton } from 'element-plus'
|
||||
|
||||
interface Params {
|
||||
pageIndex?: number
|
||||
pageSize?: number
|
||||
}
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
field: 'index',
|
||||
label: t('userDemo.index'),
|
||||
type: 'index'
|
||||
},
|
||||
{
|
||||
field: 'username',
|
||||
label: t('userDemo.username')
|
||||
},
|
||||
{
|
||||
field: 'password',
|
||||
label: t('userDemo.password')
|
||||
},
|
||||
{
|
||||
field: 'role',
|
||||
label: t('userDemo.role')
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
label: t('userDemo.remark'),
|
||||
formatter: (row: UserType) => {
|
||||
return h(
|
||||
'span',
|
||||
row.username === 'admin' ? t('userDemo.remarkMessage1') : t('userDemo.remarkMessage2')
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
label: t('userDemo.action')
|
||||
}
|
||||
]
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
let tableDataList = ref<UserType[]>([])
|
||||
|
||||
const getTableList = async (params?: Params) => {
|
||||
const res = await getUserListApi({
|
||||
params: params || {
|
||||
pageIndex: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
if (res) {
|
||||
tableDataList.value = res.data.list
|
||||
}
|
||||
}
|
||||
|
||||
getTableList()
|
||||
|
||||
const acitonFn = (data: TableSlotDefault) => {
|
||||
console.log(data)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('userDemo.title')" :message="t('userDemo.message')">
|
||||
<Table :columns="columns" :data="tableDataList" :loading="loading" :selection="false">
|
||||
<template #action="data">
|
||||
<ElButton type="primary" @click="acitonFn(data as TableSlotDefault)">
|
||||
{{ t('tableDemo.action') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</Table>
|
||||
</ContentWrap>
|
||||
</template>
|
|
@ -5,7 +5,7 @@ import { useI18n } from '@/hooks/web/useI18n'
|
|||
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
||||
import { required } from '@/utils/formRules'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { loginApi } from '@/api/login'
|
||||
import { loginApi, getTestRoleApi, getAdminRoleApi } from '@/api/login'
|
||||
import type { UserLoginType } from '@/api/login/types'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
@ -126,17 +126,41 @@ const signIn = async () => {
|
|||
if (res) {
|
||||
const { wsCache } = useCache()
|
||||
wsCache.set(appStore.getUserInfo, res.data)
|
||||
await permissionStore.generateRoutes().catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
// push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
push({ path: permissionStore.addRouters[0].path })
|
||||
getRole()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取角色信息
|
||||
const getRole = async () => {
|
||||
const { getFormData } = methods
|
||||
const formData = await getFormData<UserLoginType>()
|
||||
const params = {
|
||||
roleName: formData.username
|
||||
}
|
||||
// admin - 模拟后端过滤菜单
|
||||
// test - 模拟前端过滤菜单
|
||||
const res =
|
||||
formData.username === 'admin'
|
||||
? await getAdminRoleApi({ params })
|
||||
: await getTestRoleApi({ params })
|
||||
if (res) {
|
||||
const { wsCache } = useCache()
|
||||
const routers = res.data.list || []
|
||||
wsCache.set('roleRouters', routers)
|
||||
|
||||
formData.username === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('test', routers).catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -62,4 +62,13 @@ declare global {
|
|||
props?: Recordable
|
||||
fullPath?: string
|
||||
}
|
||||
|
||||
declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
name: string
|
||||
meta: RouteMeta
|
||||
component: string
|
||||
path: string
|
||||
redirect: string
|
||||
children?: AppCustomRouteRecordRaw[]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue