mirror of
https://gitee.com/fantastic-admin/basic.git
synced 2024-12-05 13:37:45 +08:00
用户权限变更后,自动更新导航菜单和页面权限,无需刷新页面或者重新登录
This commit is contained in:
parent
c34a2a623e
commit
a2173b3137
@ -6,7 +6,10 @@ import hotkeys from 'hotkeys-js'
|
||||
import eventBus from './utils/eventBus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const { auth } = useAuth()
|
||||
|
||||
const buttonConfig = ref({
|
||||
autoInsertSpace: true,
|
||||
@ -62,11 +65,13 @@ import.meta.env.VITE_APP_DEBUG_TOOL === 'vconsole' && new VConsole()
|
||||
<template>
|
||||
<el-config-provider :locale="zhCn" :size="settingsStore.settings.app.elementSize" :button="buttonConfig">
|
||||
<RouterView
|
||||
v-if="auth(route.meta.auth ?? '')"
|
||||
:style="{
|
||||
'--g-main-sidebar-actual-width': mainSidebarActualWidth,
|
||||
'--g-sub-sidebar-actual-width': subSidebarActualWidth,
|
||||
}"
|
||||
/>
|
||||
<not-allowed v-else />
|
||||
<system-info />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
@ -1,8 +1,3 @@
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
enabled: false
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const router = useRouter()
|
||||
|
||||
@ -11,15 +6,15 @@ const data = ref({
|
||||
countdown: 5,
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
data.value.inter && clearInterval(data.value.inter)
|
||||
onUnmounted(() => {
|
||||
data.value.inter && window.clearInterval(data.value.inter)
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
data.value.inter = setInterval(() => {
|
||||
data.value.inter = window.setInterval(() => {
|
||||
data.value.countdown--
|
||||
if (data.value.countdown === 0) {
|
||||
data.value.inter && clearInterval(data.value.inter)
|
||||
data.value.inter && window.clearInterval(data.value.inter)
|
||||
goBack()
|
||||
}
|
||||
}, 1000)
|
@ -77,6 +77,43 @@ export default [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/permission_example',
|
||||
component: 'Layout',
|
||||
redirect: '/permission_example/index',
|
||||
name: 'permissionExample',
|
||||
meta: {
|
||||
title: '权限验证',
|
||||
i18n: 'route.permission',
|
||||
icon: 'ri:shield-keyhole-line',
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'index',
|
||||
name: 'permissionExampleIndex',
|
||||
component: 'permission_example/index.vue',
|
||||
meta: {
|
||||
title: '权限验证',
|
||||
i18n: 'route.permission',
|
||||
sidebar: false,
|
||||
breadcrumb: false,
|
||||
activeMenu: '/permission_example',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
name: 'permissionExampleTest',
|
||||
component: 'permission_example/test.vue',
|
||||
meta: {
|
||||
title: '测试页面',
|
||||
auth: ['permission.browse'],
|
||||
sidebar: false,
|
||||
breadcrumb: false,
|
||||
activeMenu: '/permission_example',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -90,11 +90,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
|
||||
})
|
||||
}
|
||||
routeStore.flatRoutesUnallowed.forEach((route) => {
|
||||
if (!/^(https?:|mailto:|tel:)/.test(route.path)) {
|
||||
removeRoutes.push(router.addRoute(route as RouteRecordRaw))
|
||||
}
|
||||
})
|
||||
routeStore.setCurrentRemoveRoutes(removeRoutes)
|
||||
// 动态路由生成并注册后,重新进入当前路由
|
||||
next({
|
||||
|
@ -18,6 +18,17 @@ const routes: RouteRecordRaw = {
|
||||
component: () => import('@/views/permission_example/index.vue'),
|
||||
meta: {
|
||||
title: '权限验证',
|
||||
sidebar: false,
|
||||
breadcrumb: false,
|
||||
activeMenu: '/permission_example',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'test',
|
||||
name: 'permissionExampleTest',
|
||||
component: () => import('@/views/permission_example/test.vue'),
|
||||
meta: {
|
||||
title: '测试页面',
|
||||
auth: ['permission.browse'],
|
||||
sidebar: false,
|
||||
breadcrumb: false,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { RouteMeta, RouteRecordRaw } from 'vue-router'
|
||||
import useSettingsStore from './settings'
|
||||
import useUserStore from './user'
|
||||
import api from '@/api'
|
||||
@ -15,27 +15,27 @@ const useRouteStore = defineStore(
|
||||
const userStore = useUserStore()
|
||||
|
||||
const isGenerate = ref(false)
|
||||
const routes = ref<Route.recordMainRaw[]>([])
|
||||
const routesUnallowed = ref<Route.recordMainRaw[]>([])
|
||||
const fileSystemRoutes = ref<RouteRecordRaw[]>([])
|
||||
const routesRaw = ref<Route.recordMainRaw[]>([])
|
||||
const filesystemRoutesRaw = ref<RouteRecordRaw[]>([])
|
||||
const currentRemoveRoutes = ref<Function[]>([])
|
||||
|
||||
// 将多层嵌套路由处理成两层,保留顶层和最子层路由,中间层级将被拍平
|
||||
function flatAsyncRoutes<T extends RouteRecordRaw>(routes: T): T {
|
||||
if (routes.children) {
|
||||
routes.children = flatAsyncRoutesRecursive(routes.children, [{
|
||||
path: routes.path,
|
||||
title: routes.meta?.title,
|
||||
hide: !routes.meta?.breadcrumb && routes.meta?.breadcrumb === false,
|
||||
}], routes.path)
|
||||
function flatAsyncRoutes<T extends RouteRecordRaw>(route: T): T {
|
||||
if (route.children) {
|
||||
route.children = flatAsyncRoutesRecursive(route.children, [{
|
||||
path: route.path,
|
||||
title: route.meta?.title,
|
||||
hide: !route.meta?.breadcrumb && route.meta?.breadcrumb === false,
|
||||
}], route.path, route.meta?.auth)
|
||||
}
|
||||
return routes
|
||||
return route
|
||||
}
|
||||
function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = ''): RouteRecordRaw[] {
|
||||
function flatAsyncRoutesRecursive(routes: RouteRecordRaw[], breadcrumb: Route.breadcrumb[] = [], baseUrl = '', baseAuth: RouteMeta['auth']): RouteRecordRaw[] {
|
||||
const res: RouteRecordRaw[] = []
|
||||
routes.forEach((route) => {
|
||||
if (route.children) {
|
||||
const childrenBaseUrl = resolveRoutePath(baseUrl, route.path)
|
||||
const childrenBaseAuth = baseAuth ?? route.meta?.auth
|
||||
const tmpBreadcrumb = cloneDeep(breadcrumb)
|
||||
tmpBreadcrumb.push({
|
||||
path: childrenBaseUrl,
|
||||
@ -47,10 +47,11 @@ const useRouteStore = defineStore(
|
||||
if (!tmpRoute.meta) {
|
||||
tmpRoute.meta = {}
|
||||
}
|
||||
tmpRoute.meta.auth = childrenBaseAuth
|
||||
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
|
||||
delete tmpRoute.children
|
||||
res.push(tmpRoute)
|
||||
const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl)
|
||||
const childrenRoutes = flatAsyncRoutesRecursive(route.children, tmpBreadcrumb, childrenBaseUrl, childrenBaseAuth)
|
||||
childrenRoutes.forEach((item) => {
|
||||
// 如果 path 一样则覆盖,因为子路由的 path 可能设置为空,导致和父路由一样,直接注册会提示路由重复
|
||||
if (res.some(v => v.path === item.path)) {
|
||||
@ -78,6 +79,7 @@ const useRouteStore = defineStore(
|
||||
if (!tmpRoute.meta) {
|
||||
tmpRoute.meta = {}
|
||||
}
|
||||
tmpRoute.meta.auth = baseAuth ?? tmpRoute.meta?.auth
|
||||
tmpRoute.meta.breadcrumbNeste = tmpBreadcrumb
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
@ -88,45 +90,27 @@ const useRouteStore = defineStore(
|
||||
const flatRoutes = computed(() => {
|
||||
const settingsStore = useSettingsStore()
|
||||
const returnRoutes: RouteRecordRaw[] = []
|
||||
if (routes.value) {
|
||||
if (routesRaw.value) {
|
||||
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
|
||||
routes.value.forEach((item) => {
|
||||
returnRoutes.push(...cloneDeep(item.children))
|
||||
routesRaw.value.forEach((item) => {
|
||||
const tmpRoutes = cloneDeep(item.children) as RouteRecordRaw[]
|
||||
tmpRoutes.map((v) => {
|
||||
if (!v.meta) {
|
||||
v.meta = {}
|
||||
}
|
||||
v.meta.auth = item.meta?.auth ?? v.meta?.auth
|
||||
return v
|
||||
})
|
||||
returnRoutes.push(...tmpRoutes)
|
||||
})
|
||||
returnRoutes.forEach(item => flatAsyncRoutes(item))
|
||||
}
|
||||
else {
|
||||
returnRoutes.push(...cloneDeep(fileSystemRoutes.value))
|
||||
returnRoutes.push(...cloneDeep(filesystemRoutesRaw.value))
|
||||
}
|
||||
}
|
||||
return returnRoutes
|
||||
})
|
||||
const flatRoutesUnallowed = computed(() => {
|
||||
const twoLevelRoutes: RouteRecordRaw[] = []
|
||||
if (settingsStore.settings.app.routeBaseOn !== 'filesystem') {
|
||||
if (routesUnallowed.value) {
|
||||
routesUnallowed.value.forEach((item) => {
|
||||
twoLevelRoutes.push(...cloneDeep(item.children) as RouteRecordRaw[])
|
||||
})
|
||||
twoLevelRoutes.forEach(item => flatAsyncRoutes(item))
|
||||
}
|
||||
}
|
||||
else {
|
||||
twoLevelRoutes.push(...cloneDeep(routesUnallowed.value) as RouteRecordRaw[])
|
||||
}
|
||||
const returnRoutes: RouteRecordRaw[] = []
|
||||
twoLevelRoutes.forEach((item) => {
|
||||
if (item.children) {
|
||||
item.children.forEach((child) => {
|
||||
const tmpChild = cloneDeep(child)
|
||||
tmpChild.path = resolveRoutePath(item.path, tmpChild.path)
|
||||
tmpChild.component = () => import('@/views/403.vue')
|
||||
returnRoutes.push(tmpChild)
|
||||
})
|
||||
}
|
||||
})
|
||||
return returnRoutes
|
||||
})
|
||||
const flatSystemRoutes = computed(() => {
|
||||
const routes = [...systemRoutes]
|
||||
routes.forEach(item => flatAsyncRoutes(item))
|
||||
@ -171,40 +155,26 @@ const useRouteStore = defineStore(
|
||||
})
|
||||
return res
|
||||
}
|
||||
// 根据权限过滤路由(过滤出没有权限的路由)
|
||||
function filterAsyncRoutesNoAuth<T extends Route.recordMainRaw[] | RouteRecordRaw[]>(routes: T, permissions: string[]): T {
|
||||
const res: any = []
|
||||
routes.forEach((route) => {
|
||||
const tmpRoute = cloneDeep(route)
|
||||
if (hasPermission(permissions, route)) {
|
||||
if (tmpRoute.children) {
|
||||
tmpRoute.children = filterAsyncRoutesNoAuth(tmpRoute.children, permissions)
|
||||
tmpRoute.children.length && res.push(tmpRoute)
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.push(tmpRoute)
|
||||
}
|
||||
})
|
||||
return res
|
||||
}
|
||||
// 根据权限动态生成路由(前端生成)
|
||||
async function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
|
||||
let allowedRoutes
|
||||
let unallowedRoutes: Route.recordMainRaw[] = []
|
||||
const routes = computed(() => {
|
||||
let returnRoutes: Route.recordMainRaw[]
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
allowedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
unallowedRoutes = filterAsyncRoutesNoAuth(asyncRoutes, permissions)
|
||||
returnRoutes = filterAsyncRoutes(routesRaw.value as any, userStore.permissions)
|
||||
}
|
||||
else {
|
||||
allowedRoutes = cloneDeep(asyncRoutes)
|
||||
returnRoutes = cloneDeep(routesRaw.value) as any
|
||||
}
|
||||
return returnRoutes
|
||||
})
|
||||
|
||||
// 根据权限动态生成路由(前端生成)
|
||||
async function generateRoutesAtFront(asyncRoutes: Route.recordMainRaw[]) {
|
||||
// 设置 routes 数据
|
||||
routesRaw.value = cloneDeep(asyncRoutes) as any
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
await userStore.getPermissions()
|
||||
}
|
||||
isGenerate.value = true
|
||||
routes.value = allowedRoutes.filter(item => item.children?.length !== 0) as any
|
||||
routesUnallowed.value = unallowedRoutes as any
|
||||
}
|
||||
// 格式化后端路由数据
|
||||
function formatBackRoutes(routes: any, views = import.meta.glob('../../views/**/*.vue')): Route.recordMainRaw[] {
|
||||
@ -229,41 +199,23 @@ const useRouteStore = defineStore(
|
||||
await api.get('route/list', {
|
||||
baseURL: '/mock/',
|
||||
}).then(async (res) => {
|
||||
const asyncRoutes = formatBackRoutes(res.data)
|
||||
let allowedRoutes
|
||||
let unallowedRoutes: Route.recordMainRaw[] = []
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
allowedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
unallowedRoutes = filterAsyncRoutesNoAuth(asyncRoutes, permissions)
|
||||
}
|
||||
else {
|
||||
allowedRoutes = cloneDeep(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
routesRaw.value = formatBackRoutes(res.data)
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
userStore.getPermissions()
|
||||
}
|
||||
isGenerate.value = true
|
||||
routes.value = allowedRoutes.filter(item => item.children.length !== 0) as any
|
||||
routesUnallowed.value = unallowedRoutes as any
|
||||
}).catch(() => {})
|
||||
}
|
||||
// 根据权限动态生成路由(文件系统生成)
|
||||
async function generateRoutesAtFilesystem(asyncRoutes: RouteRecordRaw[]) {
|
||||
let allowedRoutes
|
||||
let unallowedRoutes: RouteRecordRaw[] = []
|
||||
// 设置 routes 数据
|
||||
filesystemRoutesRaw.value = cloneDeep(asyncRoutes) as any
|
||||
// 如果权限功能开启,则需要对路由数据进行筛选过滤
|
||||
if (settingsStore.settings.app.enablePermission) {
|
||||
const permissions = await userStore.getPermissions()
|
||||
allowedRoutes = filterAsyncRoutes(asyncRoutes, permissions)
|
||||
unallowedRoutes = filterAsyncRoutesNoAuth(asyncRoutes, permissions)
|
||||
await userStore.getPermissions()
|
||||
}
|
||||
else {
|
||||
allowedRoutes = cloneDeep(asyncRoutes)
|
||||
}
|
||||
// 设置 routes 数据
|
||||
isGenerate.value = true
|
||||
fileSystemRoutes.value = allowedRoutes.filter(item => item.children?.length !== 0) as any
|
||||
routesUnallowed.value = unallowedRoutes as any
|
||||
}
|
||||
// 记录 accessRoutes 路由,用于登出时删除路由
|
||||
function setCurrentRemoveRoutes(routes: Function[]) {
|
||||
@ -272,7 +224,8 @@ const useRouteStore = defineStore(
|
||||
// 清空动态路由
|
||||
function removeRoutes() {
|
||||
isGenerate.value = false
|
||||
routes.value = []
|
||||
routesRaw.value = []
|
||||
filesystemRoutesRaw.value = []
|
||||
currentRemoveRoutes.value.forEach((removeRoute) => {
|
||||
removeRoute()
|
||||
})
|
||||
@ -282,9 +235,8 @@ const useRouteStore = defineStore(
|
||||
return {
|
||||
isGenerate,
|
||||
routes,
|
||||
fileSystemRoutes,
|
||||
currentRemoveRoutes,
|
||||
flatRoutes,
|
||||
flatRoutesUnallowed,
|
||||
flatSystemRoutes,
|
||||
generateRoutesAtFront,
|
||||
generateRoutesAtBack,
|
||||
|
1
src/types/components.d.ts
vendored
1
src/types/components.d.ts
vendored
@ -19,6 +19,7 @@ declare module '@vue/runtime-core' {
|
||||
ImagePreview: typeof import('./../components/ImagePreview/index.vue')['default']
|
||||
ImagesUpload: typeof import('./../components/ImagesUpload/index.vue')['default']
|
||||
ImageUpload: typeof import('./../components/ImageUpload/index.vue')['default']
|
||||
NotAllowed: typeof import('./../components/NotAllowed/index.vue')['default']
|
||||
PageHeader: typeof import('./../components/PageHeader/index.vue')['default']
|
||||
PageMain: typeof import('./../components/PageMain/index.vue')['default']
|
||||
PcasCascader: typeof import('./../components/PcasCascader/index.vue')['default']
|
||||
|
@ -16,21 +16,16 @@ export default function useAuth() {
|
||||
function auth(value: string | string[]) {
|
||||
let auth
|
||||
if (typeof value === 'string') {
|
||||
auth = hasPermission(value)
|
||||
auth = value !== '' ? hasPermission(value) : true
|
||||
}
|
||||
else {
|
||||
auth = value.some((item) => {
|
||||
return hasPermission(item)
|
||||
})
|
||||
auth = value.length > 0 ? value.some(item => hasPermission(item)) : true
|
||||
}
|
||||
return auth
|
||||
}
|
||||
|
||||
function authAll(value: string[]) {
|
||||
const auth = value.every((item) => {
|
||||
return hasPermission(item)
|
||||
})
|
||||
return auth
|
||||
return value.length > 0 ? value.every(item => hasPermission(item)) : true
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -4,27 +4,27 @@ meta:
|
||||
</route>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ElLoading, ElMessage } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import useSettingsStore from '@/store/modules/settings'
|
||||
import useUserStore from '@/store/modules/user'
|
||||
|
||||
const router = useRouter()
|
||||
const { auth, authAll } = useAuth()
|
||||
const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
|
||||
function accountChange(val: any) {
|
||||
ElLoading.service({
|
||||
lock: true,
|
||||
text: '帐号切换中',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
})
|
||||
userStore.login({
|
||||
// 模拟账号切换
|
||||
async function accountChange(val: any) {
|
||||
await userStore.login({
|
||||
account: val,
|
||||
password: '',
|
||||
}).then(() => {
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
})
|
||||
await userStore.getPermissions()
|
||||
useMainPage().reload()
|
||||
}
|
||||
function goTest() {
|
||||
router.push({
|
||||
name: 'permissionExampleTest',
|
||||
})
|
||||
}
|
||||
function permissionCheck(permissions: string | string[]) {
|
||||
@ -63,6 +63,12 @@ function permissionCheck2(permissions: string[]) {
|
||||
</el-radio-group>
|
||||
<h3>帐号权限</h3>
|
||||
<div>{{ userStore.permissions }}</div>
|
||||
<h3>访问鉴权页面</h3>
|
||||
<div>
|
||||
<el-button @click="goTest">
|
||||
点击访问
|
||||
</el-button>
|
||||
</div>
|
||||
<h3>鉴权组件(请对照代码查看)</h3>
|
||||
<div>
|
||||
<Auth value="permission.browse" style="margin-bottom: 10px;">
|
||||
|
12
src/views/permission_example/test.vue
Normal file
12
src/views/permission_example/test.vue
Normal file
@ -0,0 +1,12 @@
|
||||
<route lang="yaml">
|
||||
meta:
|
||||
enabled: false
|
||||
</route>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<page-main>
|
||||
你能看到这个页面,说明你有访问权限。
|
||||
</page-main>
|
||||
</div>
|
||||
</template>
|
Loading…
Reference in New Issue
Block a user