feat(router): Add dynamic routing
This commit is contained in:
parent
95a2bd884d
commit
b218ccc9cc
|
@ -4,5 +4,8 @@ import type { UserLoginType } from './types'
|
||||||
const { request } = useAxios()
|
const { request } = useAxios()
|
||||||
|
|
||||||
export const loginApi = (data: UserLoginType) => {
|
export const loginApi = (data: UserLoginType) => {
|
||||||
return request({ url: '/user/login', method: 'post', data })
|
return request({ url: '/user/login', method: 'post', data } as AxiosConfig<
|
||||||
|
Recordable,
|
||||||
|
UserLoginType
|
||||||
|
>)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,8 @@ import { config } from '@/config/axios/config'
|
||||||
const { default_headers } = config
|
const { default_headers } = config
|
||||||
|
|
||||||
export function useAxios() {
|
export function useAxios() {
|
||||||
function request({
|
function request(option: AxiosConfig): AxiosPromise {
|
||||||
url,
|
const { url, method, params, data, headersType, responseType } = option
|
||||||
method,
|
|
||||||
params,
|
|
||||||
data,
|
|
||||||
headersType,
|
|
||||||
responseType
|
|
||||||
}: AxiosConfig): AxiosPromise {
|
|
||||||
return service({
|
return service({
|
||||||
url: url,
|
url: url,
|
||||||
method,
|
method,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { watch, ref, nextTick, unref } from 'vue'
|
import { nextTick, unref } from 'vue'
|
||||||
import type { NProgressOptions } from 'nprogress'
|
import type { NProgressOptions } from 'nprogress'
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
|
@ -7,26 +7,27 @@ import { useCssVar } from '@vueuse/core'
|
||||||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
||||||
|
|
||||||
export function useNProgress() {
|
export function useNProgress() {
|
||||||
const isLoading = ref(false)
|
|
||||||
NProgress.configure({ showSpinner: false } as NProgressOptions)
|
NProgress.configure({ showSpinner: false } as NProgressOptions)
|
||||||
|
initColor()
|
||||||
|
|
||||||
watch(
|
async function initColor() {
|
||||||
() => isLoading.value,
|
await nextTick()
|
||||||
async (loading: boolean) => {
|
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
|
||||||
loading ? NProgress.start() : NProgress.done()
|
if (bar) {
|
||||||
await nextTick()
|
bar.style.background = unref(primaryColor.value)
|
||||||
const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef
|
|
||||||
if (bar) {
|
|
||||||
bar.style.background = unref(primaryColor.value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
|
|
||||||
function toggle() {
|
function start() {
|
||||||
isLoading.value = !isLoading.value
|
NProgress.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
NProgress.done()
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
toggle
|
start,
|
||||||
|
done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts">
|
||||||
|
// import { computed } from 'vue'
|
||||||
|
// const getCaches = computed((): string[] => {
|
||||||
|
// return []
|
||||||
|
// })
|
||||||
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<RouterView>
|
||||||
<router-view v-slot="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<KeepAlive>
|
||||||
</router-view>
|
<Component :is="Component" :key="route.fullPath" />
|
||||||
</section>
|
</KeepAlive>
|
||||||
|
</template>
|
||||||
|
</RouterView>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -20,12 +20,19 @@ export default {
|
||||||
login: 'Sign in',
|
login: 'Sign in',
|
||||||
otherLogin: 'Sign in with',
|
otherLogin: 'Sign in with',
|
||||||
remember: 'Remember me',
|
remember: 'Remember me',
|
||||||
forgetPassword: 'Forget password'
|
forgetPassword: 'Forget password',
|
||||||
|
usernamePlaceholder: 'username is admin or test',
|
||||||
|
passwordPlaceholder: 'password is admin or test'
|
||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
level: 'Multi level menu',
|
level: 'Multi level menu',
|
||||||
menu: 'Menu'
|
menu: 'Menu',
|
||||||
|
menu1: 'Menu1',
|
||||||
|
menu11: 'Menu1-1',
|
||||||
|
menu111: 'Menu1-1-1',
|
||||||
|
menu12: 'Menu1-2',
|
||||||
|
menu2: 'Menu2'
|
||||||
},
|
},
|
||||||
mock: {
|
mock: {
|
||||||
loginErr: 'Wrong account or password'
|
loginErr: 'Wrong account or password'
|
||||||
|
|
|
@ -20,12 +20,19 @@ export default {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
otherLogin: '其他登录方式',
|
otherLogin: '其他登录方式',
|
||||||
remember: '记住我',
|
remember: '记住我',
|
||||||
forgetPassword: '忘记密码'
|
forgetPassword: '忘记密码',
|
||||||
|
usernamePlaceholder: '用户名为 admin 或者 test ',
|
||||||
|
passwordPlaceholder: '密码为 admin 或者 test '
|
||||||
},
|
},
|
||||||
router: {
|
router: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
level: '多级菜单',
|
level: '多级菜单',
|
||||||
menu: '菜单'
|
menu: '菜单',
|
||||||
|
menu1: '菜单1',
|
||||||
|
menu11: '菜单1-1',
|
||||||
|
menu111: '菜单1-1-1',
|
||||||
|
menu12: '菜单1-2',
|
||||||
|
menu2: '菜单2'
|
||||||
},
|
},
|
||||||
mock: {
|
mock: {
|
||||||
loginErr: '账号或密码错误'
|
loginErr: '账号或密码错误'
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
import { useCache } from '@/hooks/web/useCache'
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
// import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { useTitle } from '@/hooks/web/useTitle'
|
import { useTitle } from '@/hooks/web/useTitle'
|
||||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||||
|
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStoreWithOut()
|
||||||
|
|
||||||
const appStore = useAppStoreWithOut()
|
const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
|
|
||||||
const { toggle } = useNProgress()
|
const { start, done } = useNProgress()
|
||||||
|
|
||||||
const whiteList = ['/login'] // 不重定向白名单
|
const whiteList = ['/login'] // 不重定向白名单
|
||||||
|
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
console.log(from)
|
start()
|
||||||
toggle()
|
|
||||||
if (wsCache.get(appStore.getUserInfo)) {
|
if (wsCache.get(appStore.getUserInfo)) {
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
} else {
|
} else {
|
||||||
// if (permissionStore.getIsAddRouters) {
|
if (permissionStore.getIsAddRouters) {
|
||||||
// next()
|
next()
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// permissionStore.generateRoutes().then(() => {
|
await permissionStore.generateRoutes()
|
||||||
// permissionStore.addRouters.forEach(async (route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
// await router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
// })
|
})
|
||||||
// const redirectPath = from.query.redirect || to.path
|
const redirectPath = from.query.redirect || to.path
|
||||||
// const redirect = decodeURIComponent(redirectPath as string)
|
const redirect = decodeURIComponent(redirectPath as string)
|
||||||
// const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
|
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }
|
||||||
// permissionStore.setIsAddRouters(true)
|
permissionStore.setIsAddRouters(true)
|
||||||
// next(nextData)
|
next(nextData)
|
||||||
// })
|
|
||||||
next()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (whiteList.indexOf(to.path) !== -1) {
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
|
@ -45,7 +45,7 @@ router.beforeEach((to, from, next) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
router.afterEach(async (to) => {
|
router.afterEach((to) => {
|
||||||
useTitle(to?.meta?.title as string)
|
useTitle(to?.meta?.title as string)
|
||||||
toggle() // 结束Progress
|
done() // 结束Progress
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
import ParentLayout from '@/components/ParentView/index.vue'
|
|
||||||
import type { RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'
|
|
||||||
|
|
||||||
export const getParentLayout = (name: string) => {
|
|
||||||
return () =>
|
|
||||||
new Promise((resolve) => {
|
|
||||||
resolve({
|
|
||||||
...ParentLayout,
|
|
||||||
name
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
|
|
||||||
if (!route) return route
|
|
||||||
const { matched, ...opt } = route
|
|
||||||
return {
|
|
||||||
...opt,
|
|
||||||
matched: (matched
|
|
||||||
? matched.map((item) => ({
|
|
||||||
meta: item.meta,
|
|
||||||
name: item.name,
|
|
||||||
path: item.path
|
|
||||||
}))
|
|
||||||
: undefined) as RouteRecordNormalized[]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { createRouter, createWebHashHistory } from 'vue-router'
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
// import { getParentLayout } from './helper'
|
import { getParentLayout } from '@/utils/routerHelper'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
|
@ -15,7 +16,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||||
name: 'Redirect',
|
name: 'Redirect',
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/redirect/:path*',
|
path: '/redirect/:path(.*)',
|
||||||
name: 'Redirect',
|
name: 'Redirect',
|
||||||
component: () => import('@/views/Redirect/Redirect.vue'),
|
component: () => import('@/views/Redirect/Redirect.vue'),
|
||||||
meta: {}
|
meta: {}
|
||||||
|
@ -37,66 +38,66 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// export const asyncRouterMap: AppRouteRecordRaw[] = [
|
export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||||
// {
|
{
|
||||||
// path: '/level',
|
path: '/level',
|
||||||
// component: Layout,
|
component: Layout,
|
||||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||||
// name: 'Level',
|
name: 'Level',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: t('router.level')
|
title: t('router.level')
|
||||||
// },
|
},
|
||||||
// children: [
|
children: [
|
||||||
// {
|
{
|
||||||
// path: 'menu1',
|
path: 'menu1',
|
||||||
// name: 'Menu1',
|
name: 'Menu1',
|
||||||
// component: getParentLayout('Menu1'),
|
component: getParentLayout(),
|
||||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: `${t('router.menu')}1`
|
title: t('router.menu1')
|
||||||
// },
|
},
|
||||||
// children: [
|
children: [
|
||||||
// {
|
{
|
||||||
// path: 'menu1-1',
|
path: 'menu1-1',
|
||||||
// name: 'Menu11',
|
name: 'Menu11',
|
||||||
// component: getParentLayout('Menu11Demo'),
|
component: getParentLayout(),
|
||||||
// redirect: '/level/menu1/menu1-1/menu1-1-1',
|
redirect: '/level/menu1/menu1-1/menu1-1-1',
|
||||||
// meta: {
|
meta: {
|
||||||
// title: `${t('router.menu')}1-1`,
|
title: t('router.menu11'),
|
||||||
// alwaysShow: true
|
alwaysShow: true
|
||||||
// },
|
},
|
||||||
// children: [
|
children: [
|
||||||
// {
|
{
|
||||||
// path: 'menu1-1-1',
|
path: 'menu1-1-1',
|
||||||
// name: 'Menu111',
|
name: 'Menu111',
|
||||||
// component: () => import('@/views/Level/Menu111.vue'),
|
component: () => import('@/views/Level/Menu111.vue'),
|
||||||
// meta: {
|
meta: {
|
||||||
// title: `${t('router.menu')}1-1-1`
|
title: t('router.menu111')
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'menu1-2',
|
path: 'menu1-2',
|
||||||
// name: 'Menu12',
|
name: 'Menu12',
|
||||||
// component: () => import('@/views/Level/Menu12.vue'),
|
component: () => import('@/views/Level/Menu12.vue'),
|
||||||
// meta: {
|
meta: {
|
||||||
// title: `${t('router.menu')}1-2`
|
title: t('router.menu12')
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
// },
|
},
|
||||||
// {
|
{
|
||||||
// path: 'menu2',
|
path: 'menu2',
|
||||||
// name: 'Menu2Demo',
|
name: 'Menu2Demo',
|
||||||
// component: () => import('@/views/Level/Menu2.vue'),
|
component: () => import('@/views/Level/Menu2.vue'),
|
||||||
// meta: {
|
meta: {
|
||||||
// title: `${t('router.menu')}2`
|
title: t('router.menu2')
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
// }
|
}
|
||||||
// ]
|
]
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
|
|
|
@ -1,180 +1,96 @@
|
||||||
// import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
// import { asyncRouterMap, constantRouterMap } from '@/router'
|
import { asyncRouterMap, constantRouterMap } from '@/router'
|
||||||
// import { useCache } from '@/hooks/web/useCache'
|
// import { useCache } from '@/hooks/web/useCache'
|
||||||
// import { getParentLayout } from '@/router/helper'
|
import { flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||||
// import { store } from '../index'
|
// import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||||
|
import { store } from '../index'
|
||||||
// import { useAppStoreWithOut } from '@/store/modules/app'
|
// import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
// import { isUrl } from '@/utils/is'
|
import { cloneDeep } from 'lodash-es'
|
||||||
// import { deepClone } from '@/utils'
|
|
||||||
|
|
||||||
// const { wsCache } = useCache()
|
// const { wsCache } = useCache()
|
||||||
|
|
||||||
// const appStore = useAppStoreWithOut()
|
// const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
// const modules = import.meta.glob('../../views/**/*.{vue,tsx}')
|
export interface PermissionState {
|
||||||
|
routers: AppRouteRecordRaw[]
|
||||||
|
addRouters: AppRouteRecordRaw[]
|
||||||
|
isAddRouters: boolean
|
||||||
|
activeTab: string
|
||||||
|
menuTabRouters: AppRouteRecordRaw[]
|
||||||
|
}
|
||||||
|
|
||||||
// /* Layout */
|
export const usePermissionStore = defineStore({
|
||||||
// const Layout = () => import('@/layout/index.vue')
|
id: 'permission',
|
||||||
|
state: (): PermissionState => ({
|
||||||
|
routers: [],
|
||||||
|
addRouters: [],
|
||||||
|
isAddRouters: false,
|
||||||
|
menuTabRouters: [],
|
||||||
|
activeTab: ''
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
getRouters(): AppRouteRecordRaw[] {
|
||||||
|
return this.routers
|
||||||
|
},
|
||||||
|
getAddRouters(): AppRouteRecordRaw[] {
|
||||||
|
return flatMultiLevelRoutes(cloneDeep(this.addRouters))
|
||||||
|
},
|
||||||
|
getIsAddRouters(): boolean {
|
||||||
|
return this.isAddRouters
|
||||||
|
},
|
||||||
|
getActiveTab(): string {
|
||||||
|
return this.activeTab
|
||||||
|
},
|
||||||
|
getMenuTabRouters(): AppRouteRecordRaw[] {
|
||||||
|
return this.menuTabRouters
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
generateRoutes(): 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)
|
||||||
|
// }
|
||||||
|
|
||||||
// export interface PermissionState {
|
// 不需要权限控制
|
||||||
// routers: AppRouteRecordRaw[]
|
const routerMap: AppRouteRecordRaw[] = cloneDeep(asyncRouterMap)
|
||||||
// addRouters: AppRouteRecordRaw[]
|
|
||||||
// isAddRouters: boolean
|
|
||||||
// activeTab: string
|
|
||||||
// menuTabRouters: AppRouteRecordRaw[]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export const usePermissionStore = defineStore({
|
// 动态路由,404一定要放到最后面
|
||||||
// id: 'permission',
|
this.addRouters = routerMap
|
||||||
// state: (): PermissionState => ({
|
// .concat([
|
||||||
// routers: [],
|
// {
|
||||||
// addRouters: [],
|
// path: '/:path(.*)*',
|
||||||
// isAddRouters: false,
|
// redirect: '/404',
|
||||||
// menuTabRouters: [],
|
// name: '404',
|
||||||
// activeTab: ''
|
// meta: {
|
||||||
// }),
|
// hidden: true,
|
||||||
// getters: {
|
// breadcrumb: false
|
||||||
// getRouters(): AppRouteRecordRaw[] {
|
// }
|
||||||
// return this.routers
|
// }
|
||||||
// },
|
// ])
|
||||||
// getAddRouters(): AppRouteRecordRaw[] {
|
// 渲染菜单的所有路由
|
||||||
// return this.addRouters
|
this.routers = cloneDeep(constantRouterMap).concat(routerMap)
|
||||||
// },
|
resolve()
|
||||||
// getIsAddRouters(): boolean {
|
})
|
||||||
// return this.isAddRouters
|
},
|
||||||
// },
|
setIsAddRouters(state: boolean): void {
|
||||||
// getActiveTab(): string {
|
this.isAddRouters = state
|
||||||
// return this.activeTab
|
},
|
||||||
// },
|
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
||||||
// getMenuTabRouters(): AppRouteRecordRaw[] {
|
this.menuTabRouters = routers
|
||||||
// return this.menuTabRouters
|
},
|
||||||
// }
|
setAcitveTab(activeTab: string): void {
|
||||||
// },
|
this.activeTab = activeTab
|
||||||
// actions: {
|
}
|
||||||
// generateRoutes(): Promise<unknown> {
|
}
|
||||||
// return new Promise<void>((resolve) => {
|
})
|
||||||
// // 路由权限控制
|
|
||||||
// let routerMap: AppRouteRecordRaw[] = []
|
|
||||||
// if (wsCache.get(appStore.getUserInfo).roleName === 'admin') {
|
|
||||||
// // 模拟前端控制权限
|
|
||||||
// routerMap = generateRoutesFn(deepClone(asyncRouterMap, ['component']))
|
|
||||||
// } else {
|
|
||||||
// // 模拟后端控制权限
|
|
||||||
// routerMap = getFilterRoutes(wsCache.get(appStore.getUserInfo).checkedNodes)
|
|
||||||
// }
|
|
||||||
// // const routerMap: AppRouteRecordRaw[] = generateRoutesFn(deepClone(asyncRouterMap, ['component']))
|
|
||||||
// // 动态路由,404一定要放到最后面
|
|
||||||
// this.addRouters = routerMap.concat([
|
|
||||||
// {
|
|
||||||
// path: '/:path(.*)*',
|
|
||||||
// redirect: '/404',
|
|
||||||
// name: '404',
|
|
||||||
// meta: {
|
|
||||||
// hidden: true,
|
|
||||||
// breadcrumb: false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ])
|
|
||||||
// // 渲染菜单的所有路由
|
|
||||||
// this.routers = deepClone(constantRouterMap, ['component']).concat(routerMap)
|
|
||||||
// resolve()
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// setIsAddRouters(state: boolean): void {
|
|
||||||
// this.isAddRouters = state
|
|
||||||
// },
|
|
||||||
// setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
|
||||||
// this.menuTabRouters = routers
|
|
||||||
// },
|
|
||||||
// setAcitveTab(activeTab: string): void {
|
|
||||||
// this.activeTab = activeTab
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
|
|
||||||
// // 路由过滤,主要用于权限控制
|
export function usePermissionStoreWithOut() {
|
||||||
// function generateRoutesFn(routes: AppRouteRecordRaw[], basePath = '/'): AppRouteRecordRaw[] {
|
return usePermissionStore(store)
|
||||||
// const res: AppRouteRecordRaw[] = []
|
}
|
||||||
|
|
||||||
// for (const route of routes) {
|
|
||||||
// // skip some route
|
|
||||||
// if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let onlyOneChild: Nullable<string> = null
|
|
||||||
|
|
||||||
// if (route.children && route.children.length === 1 && !route.meta.alwaysShow) {
|
|
||||||
// onlyOneChild = (
|
|
||||||
// isUrl(route.children[0].path)
|
|
||||||
// ? route.children[0].path
|
|
||||||
// : path.resolve(path.resolve(basePath, route.path), route.children[0].path)
|
|
||||||
// ) as string
|
|
||||||
// }
|
|
||||||
|
|
||||||
// let data: Nullable<AppRouteRecordRaw> = null
|
|
||||||
|
|
||||||
// // 如不需要路由权限,可注释以下逻辑
|
|
||||||
// // 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。
|
|
||||||
// const list = wsCache.get(appStore.getUserInfo).checkedNodes
|
|
||||||
// // 开发者可以根据实际情况进行扩展
|
|
||||||
// for (const item of list) {
|
|
||||||
// // 通过路径去匹配
|
|
||||||
// if (isUrl(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
|
|
||||||
// data = Object.assign({}, route)
|
|
||||||
// } else {
|
|
||||||
// const routePath = path.resolve(basePath, onlyOneChild || route.path)
|
|
||||||
// if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) {
|
|
||||||
// data = Object.assign({}, route)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// // 如不需要路由权限,解注释下面一行
|
|
||||||
// // data = Object.assign({}, route)
|
|
||||||
|
|
||||||
// // recursive child routes
|
|
||||||
// if (route.children && data) {
|
|
||||||
// data.children = generateRoutesFn(route.children, path.resolve(basePath, data.path))
|
|
||||||
// }
|
|
||||||
// if (data) {
|
|
||||||
// res.push(data as AppRouteRecordRaw)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return res
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // 模拟后端过滤路由
|
|
||||||
// function getFilterRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
|
|
||||||
// const res: AppRouteRecordRaw[] = []
|
|
||||||
|
|
||||||
// for (const route of routes) {
|
|
||||||
// const data: AppRouteRecordRaw = {
|
|
||||||
// path: route.path,
|
|
||||||
// name: route.name,
|
|
||||||
// redirect: route.redirect,
|
|
||||||
// meta: {}
|
|
||||||
// }
|
|
||||||
// data.meta = Object.assign({}, route.meta || {}, { title: route.meta.title })
|
|
||||||
// if (route.component) {
|
|
||||||
// // 动态加载路由文件,可根据实际情况进行自定义逻辑
|
|
||||||
// const component = route.component as string
|
|
||||||
// data.component = (
|
|
||||||
// component === '#'
|
|
||||||
// ? Layout
|
|
||||||
// : component.includes('##')
|
|
||||||
// ? getParentLayout(component.split('##')[1])
|
|
||||||
// : modules[`../../${route.component}.vue`] || modules[`../../${route.component}.tsx`]
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// // recursive child routes
|
|
||||||
// if (route.children) {
|
|
||||||
// data.children = getFilterRoutes(route.children)
|
|
||||||
// }
|
|
||||||
// res.push(data as AppRouteRecordRaw)
|
|
||||||
// }
|
|
||||||
// return res
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export function usePermissionStoreWithOut() {
|
|
||||||
// return usePermissionStore(store)
|
|
||||||
// }
|
|
||||||
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
import { createRouter, createWebHashHistory } from 'vue-router'
|
||||||
|
import type { Router, RouteLocationNormalized, RouteRecordNormalized } 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}')
|
||||||
|
|
||||||
|
/* Layout */
|
||||||
|
const Layout = () => import('@/layout/index.vue')
|
||||||
|
|
||||||
|
export const getParentLayout = () => {
|
||||||
|
return () =>
|
||||||
|
new Promise((resolve) => {
|
||||||
|
resolve({
|
||||||
|
name: 'ParentLayout'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRoute(route: RouteLocationNormalized): RouteLocationNormalized {
|
||||||
|
if (!route) return route
|
||||||
|
const { matched, ...opt } = route
|
||||||
|
return {
|
||||||
|
...opt,
|
||||||
|
matched: (matched
|
||||||
|
? matched.map((item) => ({
|
||||||
|
meta: item.meta,
|
||||||
|
name: item.name,
|
||||||
|
path: item.path
|
||||||
|
}))
|
||||||
|
: undefined) as RouteRecordNormalized[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 前端控制路由生成
|
||||||
|
export function generateRoutesFn1(
|
||||||
|
routes: AppRouteRecordRaw[],
|
||||||
|
basePath = '/'
|
||||||
|
): AppRouteRecordRaw[] {
|
||||||
|
const res: AppRouteRecordRaw[] = []
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
// skip some route
|
||||||
|
if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let data: Nullable<AppRouteRecordRaw> = null
|
||||||
|
|
||||||
|
let onlyOneChild: Nullable<string> = null
|
||||||
|
|
||||||
|
if (route.children && route.children.length === 1 && !route.meta.alwaysShow) {
|
||||||
|
onlyOneChild = (
|
||||||
|
isUrl(route.children[0].path)
|
||||||
|
? route.children[0].path
|
||||||
|
: pathResolve(pathResolve(basePath, route.path), route.children[0].path)
|
||||||
|
) as string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。
|
||||||
|
const list = wsCache.get(appStore.getUserInfo).checkedNodes
|
||||||
|
// 开发者可以根据实际情况进行扩展
|
||||||
|
for (const item of list) {
|
||||||
|
// 通过路径去匹配
|
||||||
|
if (isUrl(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
|
||||||
|
data = Object.assign({}, route)
|
||||||
|
} else {
|
||||||
|
const routePath = pathResolve(basePath, onlyOneChild || route.path)
|
||||||
|
if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) {
|
||||||
|
data = Object.assign({}, route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// recursive child routes
|
||||||
|
if (route.children && data) {
|
||||||
|
data.children = generateRoutesFn1(route.children, pathResolve(basePath, data.path))
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
res.push(data as AppRouteRecordRaw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后端控制路由生成
|
||||||
|
export function generateRoutesFn2(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
|
||||||
|
const res: AppRouteRecordRaw[] = []
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
const data: AppRouteRecordRaw = {
|
||||||
|
path: route.path,
|
||||||
|
name: route.name,
|
||||||
|
redirect: route.redirect,
|
||||||
|
meta: route.meta
|
||||||
|
}
|
||||||
|
if (route.component) {
|
||||||
|
// 动态加载路由文件,可根据实际情况进行自定义逻辑
|
||||||
|
const component = route.component as string
|
||||||
|
data.component =
|
||||||
|
component === '#'
|
||||||
|
? Layout
|
||||||
|
: component.includes('##')
|
||||||
|
? getParentLayout()
|
||||||
|
: modules[`../../${route.component}.vue`] || modules[`../../${route.component}.tsx`]
|
||||||
|
}
|
||||||
|
// recursive child routes
|
||||||
|
if (route.children) {
|
||||||
|
data.children = generateRoutesFn2(route.children)
|
||||||
|
}
|
||||||
|
res.push(data as AppRouteRecordRaw)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pathResolve(parentPath: string, path: string) {
|
||||||
|
return `${parentPath}/${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由降级
|
||||||
|
export function flatMultiLevelRoutes(routes: AppRouteRecordRaw[]) {
|
||||||
|
const modules: AppRouteRecordRaw[] = cloneDeep(routes)
|
||||||
|
for (let index = 0; index < modules.length; index++) {
|
||||||
|
const route = modules[index]
|
||||||
|
if (!isMultipleRoute(route)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
promoteRouteLevel(route)
|
||||||
|
}
|
||||||
|
return modules
|
||||||
|
}
|
||||||
|
|
||||||
|
// 层级是否大于2
|
||||||
|
function isMultipleRoute(route: AppRouteRecordRaw) {
|
||||||
|
if (!route || !Reflect.has(route, 'children') || !route.children?.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = route.children
|
||||||
|
|
||||||
|
let flag = false
|
||||||
|
for (let index = 0; index < children.length; index++) {
|
||||||
|
const child = children[index]
|
||||||
|
if (child.children?.length) {
|
||||||
|
flag = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flag
|
||||||
|
}
|
||||||
|
|
||||||
|
// 路由降级
|
||||||
|
function promoteRouteLevel(route: AppRouteRecordRaw) {
|
||||||
|
let router: Router | null = createRouter({
|
||||||
|
routes: [route as unknown as RouteRecordNormalized],
|
||||||
|
history: createWebHashHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
const routes = router.getRoutes()
|
||||||
|
addToChildren(routes, route.children || [], route)
|
||||||
|
router = null
|
||||||
|
|
||||||
|
route.children = route.children?.map((item) => omit(item, 'children'))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加所有子菜单
|
||||||
|
function addToChildren(
|
||||||
|
routes: RouteRecordNormalized[],
|
||||||
|
children: AppRouteRecordRaw[],
|
||||||
|
routeModule: AppRouteRecordRaw
|
||||||
|
) {
|
||||||
|
for (let index = 0; index < children.length; index++) {
|
||||||
|
const child = children[index]
|
||||||
|
const route = routes.find((item) => item.name === child.name)
|
||||||
|
if (!route) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
routeModule.children = routeModule.children || []
|
||||||
|
if (!routeModule.children.find((item) => item.name === route.name)) {
|
||||||
|
routeModule.children?.push(route as unknown as AppRouteRecordRaw)
|
||||||
|
}
|
||||||
|
if (child.children?.length) {
|
||||||
|
addToChildren(routes, child.children, routeModule)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts"></script>
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>Menu11</div>
|
<div>Menu111</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, unref } from 'vue'
|
import { reactive, ref, unref, watch } from 'vue'
|
||||||
import { Form } from '@/components/Form'
|
import { Form } from '@/components/Form'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
||||||
|
@ -7,6 +7,17 @@ import { required } from '@/utils/formRules'
|
||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { loginApi } from '@/api/login'
|
import { loginApi } from '@/api/login'
|
||||||
import type { UserLoginType } from '@/api/login/types'
|
import type { UserLoginType } from '@/api/login/types'
|
||||||
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||||
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
|
const { currentRoute, addRoute, push } = useRouter()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
@ -29,6 +40,9 @@ const schema = reactive<FormSchema[]>([
|
||||||
component: 'Input',
|
component: 'Input',
|
||||||
colProps: {
|
colProps: {
|
||||||
span: 24
|
span: 24
|
||||||
|
},
|
||||||
|
componentProps: {
|
||||||
|
placeholder: t('login.usernamePlaceholder')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -42,7 +56,8 @@ const schema = reactive<FormSchema[]>([
|
||||||
componentProps: {
|
componentProps: {
|
||||||
style: {
|
style: {
|
||||||
width: '100%'
|
width: '100%'
|
||||||
}
|
},
|
||||||
|
placeholder: t('login.passwordPlaceholder')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -81,6 +96,20 @@ const { register, elFormRef, methods } = useForm()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
|
||||||
|
const iconColor = '#999'
|
||||||
|
|
||||||
|
const redirect = ref<string>('')
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => currentRoute.value,
|
||||||
|
(route: RouteLocationNormalizedLoaded) => {
|
||||||
|
redirect.value = route?.query?.redirect as string
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
async function signIn() {
|
async function signIn() {
|
||||||
const formRef = unref(elFormRef)
|
const formRef = unref(elFormRef)
|
||||||
|
@ -89,10 +118,23 @@ async function signIn() {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
const { getFormData } = methods
|
const { getFormData } = methods
|
||||||
const formData = (await getFormData()) as UserLoginType
|
const formData = (await getFormData()) as UserLoginType
|
||||||
|
|
||||||
const res = await loginApi(formData)
|
const res = await loginApi(formData)
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
.finally(() => (loading.value = false))
|
.finally(() => (loading.value = false))
|
||||||
console.log(res)
|
|
||||||
|
if (res) {
|
||||||
|
const { wsCache } = useCache()
|
||||||
|
wsCache.set(appStore.getUserInfo, res.data)
|
||||||
|
await permissionStore.generateRoutes().catch(() => {})
|
||||||
|
|
||||||
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
|
console.log(route)
|
||||||
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
|
})
|
||||||
|
permissionStore.setIsAddRouters(true)
|
||||||
|
push({ path: redirect.value || '/level' })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -125,16 +167,28 @@ async function signIn() {
|
||||||
|
|
||||||
<template #otherIcon>
|
<template #otherIcon>
|
||||||
<div class="flex justify-between w-[100%]">
|
<div class="flex justify-between w-[100%]">
|
||||||
<Icon icon="ant-design:github-filled" :size="iconSize" class="cursor-pointer anticon" />
|
<Icon
|
||||||
<Icon icon="ant-design:wechat-filled" :size="iconSize" class="cursor-pointer anticon" />
|
icon="ant-design:github-filled"
|
||||||
|
:size="iconSize"
|
||||||
|
class="cursor-pointer anticon"
|
||||||
|
:color="iconColor"
|
||||||
|
/>
|
||||||
|
<Icon
|
||||||
|
icon="ant-design:wechat-filled"
|
||||||
|
:size="iconSize"
|
||||||
|
class="cursor-pointer anticon"
|
||||||
|
:color="iconColor"
|
||||||
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
icon="ant-design:alipay-circle-filled"
|
icon="ant-design:alipay-circle-filled"
|
||||||
:size="iconSize"
|
:size="iconSize"
|
||||||
|
:color="iconColor"
|
||||||
class="cursor-pointer anticon"
|
class="cursor-pointer anticon"
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
icon="ant-design:weibo-circle-filled"
|
icon="ant-design:weibo-circle-filled"
|
||||||
:size="iconSize"
|
:size="iconSize"
|
||||||
|
:color="iconColor"
|
||||||
class="cursor-pointer anticon"
|
class="cursor-pointer anticon"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -25,9 +25,9 @@ declare type AxiosMethod = 'get' | 'post' | 'delete' | 'put'
|
||||||
|
|
||||||
declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
declare type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
|
||||||
|
|
||||||
declare type AxiosConfig = {
|
declare type AxiosConfig<T = Recordable, K = Recordable> = {
|
||||||
params?: Recordable
|
params?: T
|
||||||
data?: Recordable
|
data?: K
|
||||||
url?: string
|
url?: string
|
||||||
method?: AxiosMethod
|
method?: AxiosMethod
|
||||||
headersType?: string
|
headersType?: string
|
||||||
|
|
Loading…
Reference in New Issue