feat: init
This commit is contained in:
16
src/router/app-menus/index.ts
Normal file
16
src/router/app-menus/index.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { appExternalRoutes, appRoutes } from '../routes'
|
||||
|
||||
const mixinRoutes = [...appRoutes, ...appExternalRoutes]
|
||||
|
||||
const appClientMenus = mixinRoutes.map((el) => {
|
||||
const { name, path, meta, redirect, children } = el
|
||||
return {
|
||||
name,
|
||||
path,
|
||||
meta,
|
||||
redirect,
|
||||
children,
|
||||
}
|
||||
})
|
||||
|
||||
export default appClientMenus
|
||||
18
src/router/constants.ts
Normal file
18
src/router/constants.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const WHITE_LIST = [
|
||||
{ name: 'notFound', children: [] },
|
||||
{ name: 'login', children: [] },
|
||||
]
|
||||
|
||||
export const NOT_FOUND = {
|
||||
name: 'notFound',
|
||||
}
|
||||
|
||||
export const REDIRECT_ROUTE_NAME = 'Redirect'
|
||||
|
||||
export const DEFAULT_ROUTE_NAME = 'Workplace'
|
||||
|
||||
export const DEFAULT_ROUTE = {
|
||||
title: 'menu.dashboard.workplace',
|
||||
name: DEFAULT_ROUTE_NAME,
|
||||
fullPath: '/dashboard/workplace',
|
||||
}
|
||||
17
src/router/guard/index.ts
Normal file
17
src/router/guard/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { setRouteEmitter } from '@/utils/route-listener'
|
||||
import type { Router } from 'vue-router'
|
||||
import setupPermissionGuard from './permission'
|
||||
import setupUserLoginInfoGuard from './userLoginInfo'
|
||||
|
||||
function setupPageGuard(router: Router) {
|
||||
router.beforeEach(async (to) => {
|
||||
// emit route change
|
||||
setRouteEmitter(to)
|
||||
})
|
||||
}
|
||||
|
||||
export default function createRouteGuard(router: Router) {
|
||||
setupPageGuard(router)
|
||||
setupUserLoginInfoGuard(router)
|
||||
setupPermissionGuard(router)
|
||||
}
|
||||
48
src/router/guard/permission.ts
Normal file
48
src/router/guard/permission.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import type { Router, RouteRecordNormalized } from 'vue-router'
|
||||
|
||||
import usePermission from '@/hooks/permission'
|
||||
import { useAppStore, useUserStore } from '@/store'
|
||||
import { NOT_FOUND, WHITE_LIST } from '../constants'
|
||||
import { appRoutes } from '../routes'
|
||||
|
||||
export default function setupPermissionGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
const appStore = useAppStore()
|
||||
const userStore = useUserStore()
|
||||
const Permission = usePermission()
|
||||
const permissionsAllow = Permission.accessRouter(to)
|
||||
if (appStore.menuFromServer) {
|
||||
// 针对来自服务端的菜单配置进行处理
|
||||
// Handle routing configuration from the server
|
||||
|
||||
// 根据需要自行完善来源于服务端的菜单配置的permission逻辑
|
||||
// Refine the permission logic from the server's menu configuration as needed
|
||||
if (!appStore.appAsyncMenus.length && !WHITE_LIST.find((el) => el.name === to.name)) {
|
||||
await appStore.fetchServerMenuConfig()
|
||||
}
|
||||
const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST]
|
||||
|
||||
let exist = false
|
||||
while (serverMenuConfig.length && !exist) {
|
||||
const element = serverMenuConfig.shift()
|
||||
if (element?.name === to.name) exist = true
|
||||
|
||||
if (element?.children) {
|
||||
serverMenuConfig.push(...(element.children as unknown as RouteRecordNormalized[]))
|
||||
}
|
||||
}
|
||||
if (exist && permissionsAllow) {
|
||||
next()
|
||||
} else next(NOT_FOUND)
|
||||
} else {
|
||||
// eslint-disable-next-line no-lonely-if
|
||||
if (permissionsAllow) next()
|
||||
else {
|
||||
const destination = Permission.findFirstPermissionRoute(appRoutes, userStore.role) || NOT_FOUND
|
||||
next(destination)
|
||||
}
|
||||
}
|
||||
NProgress.done()
|
||||
})
|
||||
}
|
||||
43
src/router/guard/userLoginInfo.ts
Normal file
43
src/router/guard/userLoginInfo.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import type { LocationQueryRaw, Router } from 'vue-router'
|
||||
|
||||
import { useUserStore } from '@/store'
|
||||
import { isLogin } from '@/utils/auth'
|
||||
|
||||
export default function setupUserLoginInfoGuard(router: Router) {
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
NProgress.start()
|
||||
const userStore = useUserStore()
|
||||
if (isLogin()) {
|
||||
if (userStore.role) {
|
||||
next()
|
||||
} else {
|
||||
try {
|
||||
await userStore.info()
|
||||
next()
|
||||
} catch (error) {
|
||||
await userStore.logout()
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (to.name === 'login') {
|
||||
next()
|
||||
return
|
||||
}
|
||||
next({
|
||||
name: 'login',
|
||||
query: {
|
||||
redirect: to.name,
|
||||
...to.query,
|
||||
} as LocationQueryRaw,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
37
src/router/index.ts
Normal file
37
src/router/index.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css'
|
||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||
|
||||
import createRouteGuard from './guard'
|
||||
import { appRoutes } from './routes'
|
||||
import { NOT_FOUND_ROUTE, REDIRECT_MAIN } from './routes/base'
|
||||
|
||||
NProgress.configure({ showSpinner: false }) // NProgress Configuration
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory('./'),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
redirect: 'login',
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
component: () => import('@/views/login/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
},
|
||||
},
|
||||
...appRoutes,
|
||||
REDIRECT_MAIN,
|
||||
NOT_FOUND_ROUTE,
|
||||
],
|
||||
scrollBehavior() {
|
||||
return { top: 0 }
|
||||
},
|
||||
})
|
||||
|
||||
createRouteGuard(router)
|
||||
|
||||
export default router
|
||||
31
src/router/routes/base.ts
Normal file
31
src/router/routes/base.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { REDIRECT_ROUTE_NAME } from '@/router/constants'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
|
||||
export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue')
|
||||
|
||||
export const REDIRECT_MAIN: RouteRecordRaw = {
|
||||
path: '/redirect',
|
||||
name: 'redirectWrapper',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/redirect/:path',
|
||||
name: REDIRECT_ROUTE_NAME,
|
||||
component: () => import('@/views/redirect/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
hideInMenu: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const NOT_FOUND_ROUTE: RouteRecordRaw = {
|
||||
path: '/:pathMatch(.*)*',
|
||||
name: 'notFound',
|
||||
component: () => import('@/views/not-found/index.vue'),
|
||||
}
|
||||
10
src/router/routes/externalModules/arco.ts
Normal file
10
src/router/routes/externalModules/arco.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
path: 'https://arco.design',
|
||||
name: 'arcoWebsite',
|
||||
meta: {
|
||||
locale: 'menu.arcoWebsite',
|
||||
icon: 'icon-link',
|
||||
requiresAuth: true,
|
||||
order: 8,
|
||||
},
|
||||
}
|
||||
10
src/router/routes/externalModules/faq.ts
Normal file
10
src/router/routes/externalModules/faq.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export default {
|
||||
path: 'www.baidu.com',
|
||||
name: 'faq',
|
||||
meta: {
|
||||
locale: 'menu.faq',
|
||||
icon: 'icon-question-circle',
|
||||
requiresAuth: true,
|
||||
order: 9,
|
||||
},
|
||||
}
|
||||
20
src/router/routes/index.ts
Normal file
20
src/router/routes/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { RouteRecordNormalized } from 'vue-router'
|
||||
|
||||
const modules = import.meta.glob('./modules/*.ts', { eager: true })
|
||||
const externalModules = import.meta.glob('./externalModules/*.ts', {
|
||||
eager: true,
|
||||
})
|
||||
|
||||
function formatModules(_modules: any, result: RouteRecordNormalized[]) {
|
||||
Object.keys(_modules).forEach((key) => {
|
||||
const defaultModule = _modules[key].default
|
||||
if (!defaultModule) return
|
||||
const moduleList = Array.isArray(defaultModule) ? [...defaultModule] : [defaultModule]
|
||||
result.push(...moduleList)
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
export const appRoutes: RouteRecordNormalized[] = formatModules(modules, [])
|
||||
|
||||
export const appExternalRoutes: RouteRecordNormalized[] = formatModules(externalModules, [])
|
||||
40
src/router/routes/modules/dashboard.ts
Normal file
40
src/router/routes/modules/dashboard.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const DASHBOARD: AppRouteRecordRaw = {
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.dashboard',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-dashboard',
|
||||
order: 0,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'workplace',
|
||||
name: 'Workplace',
|
||||
component: () => import('@/views/dashboard/workplace/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.dashboard.workplace',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
/** simple */
|
||||
{
|
||||
path: 'monitor',
|
||||
name: 'Monitor',
|
||||
component: () => import('@/views/dashboard/monitor/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.dashboard.monitor',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
/** simple end */
|
||||
],
|
||||
}
|
||||
|
||||
export default DASHBOARD
|
||||
48
src/router/routes/modules/exception.ts
Normal file
48
src/router/routes/modules/exception.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const EXCEPTION: AppRouteRecordRaw = {
|
||||
path: '/exception',
|
||||
name: 'exception',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.exception',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-exclamation-circle',
|
||||
order: 6,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '403',
|
||||
name: '403',
|
||||
component: () => import('@/views/exception/403/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.exception.403',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '404',
|
||||
name: '404',
|
||||
component: () => import('@/views/exception/404/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.exception.404',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '500',
|
||||
name: '500',
|
||||
component: () => import('@/views/exception/500/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.exception.500',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default EXCEPTION
|
||||
38
src/router/routes/modules/list.ts
Normal file
38
src/router/routes/modules/list.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const LIST: AppRouteRecordRaw = {
|
||||
path: '/list',
|
||||
name: 'list',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.list',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-list',
|
||||
order: 2,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'search-table', // The midline path complies with SEO specifications
|
||||
name: 'SearchTable',
|
||||
component: () => import('@/views/list/search-table/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.list.searchTable',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'card',
|
||||
name: 'Card',
|
||||
component: () => import('@/views/list/card/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.list.cardList',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default LIST
|
||||
28
src/router/routes/modules/profile.ts
Normal file
28
src/router/routes/modules/profile.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const PROFILE: AppRouteRecordRaw = {
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.profile',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-file',
|
||||
order: 4,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'basic',
|
||||
name: 'Basic',
|
||||
component: () => import('@/views/profile/basic/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.profile.basic',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default PROFILE
|
||||
38
src/router/routes/modules/user.ts
Normal file
38
src/router/routes/modules/user.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const USER: AppRouteRecordRaw = {
|
||||
path: '/user',
|
||||
name: 'user',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.user',
|
||||
icon: 'icon-user',
|
||||
requiresAuth: true,
|
||||
order: 7,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'info',
|
||||
name: 'Info',
|
||||
component: () => import('@/views/user/info/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.user.info',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
name: 'Setting',
|
||||
component: () => import('@/views/user/setting/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.user.setting',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default USER
|
||||
38
src/router/routes/modules/visualization.ts
Normal file
38
src/router/routes/modules/visualization.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DEFAULT_LAYOUT } from '../base'
|
||||
import { AppRouteRecordRaw } from '../types'
|
||||
|
||||
const VISUALIZATION: AppRouteRecordRaw = {
|
||||
path: '/visualization',
|
||||
name: 'visualization',
|
||||
component: DEFAULT_LAYOUT,
|
||||
meta: {
|
||||
locale: 'menu.visualization',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-apps',
|
||||
order: 1,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'data-analysis',
|
||||
name: 'DataAnalysis',
|
||||
component: () => import('@/views/visualization/data-analysis/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.visualization.dataAnalysis',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'multi-dimension-data-analysis',
|
||||
name: 'MultiDimensionDataAnalysis',
|
||||
component: () => import('@/views/visualization/multi-dimension-data-analysis/index.vue'),
|
||||
meta: {
|
||||
locale: 'menu.visualization.multiDimensionDataAnalysis',
|
||||
requiresAuth: true,
|
||||
roles: ['admin'],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export default VISUALIZATION
|
||||
17
src/router/routes/types.ts
Normal file
17
src/router/routes/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import type { NavigationGuard, RouteMeta } from 'vue-router'
|
||||
|
||||
export type Component<T = any> = ReturnType<typeof defineComponent> | (() => Promise<typeof import('*.vue')>) | (() => Promise<T>)
|
||||
|
||||
export interface AppRouteRecordRaw {
|
||||
path: string
|
||||
name?: string | symbol
|
||||
meta?: RouteMeta
|
||||
redirect?: string
|
||||
component: Component | string
|
||||
children?: AppRouteRecordRaw[]
|
||||
alias?: string | string[]
|
||||
props?: Record<string, any>
|
||||
beforeEnter?: NavigationGuard | NavigationGuard[]
|
||||
fullPath?: string
|
||||
}
|
||||
16
src/router/typings.d.ts
vendored
Normal file
16
src/router/typings.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'vue-router'
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta {
|
||||
roles?: string[] // Controls roles that have access to the page
|
||||
requiresAuth: boolean // Whether login is required to access the current page (every route must declare)
|
||||
icon?: string // The icon show in the side menu
|
||||
locale?: string // The locale name show in side menu and breadcrumb
|
||||
hideInMenu?: boolean // If true, it is not displayed in the side menu
|
||||
hideChildrenInMenu?: boolean // if set true, the children are not displayed in the side menu
|
||||
activeMenu?: string // if set name, the menu will be highlighted according to the name you set
|
||||
order?: number // Sort routing menu items. If set key, the higher the value, the more forward it is
|
||||
noAffix?: boolean // if set true, the tag will not affix in the tab-bar
|
||||
ignoreCache?: boolean // if set true, the page will not be cached
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user