mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-05-03 16:26:45 +08:00
add: 补充部分文档,修复鉴权bug
This commit is contained in:
parent
d1cb4ddb41
commit
54d370c7a0
@ -44,7 +44,7 @@ export const SETUP_ROUTER_GUARD = true
|
|||||||
* 可以根据权限与白名单进行过滤, 但是 meta hidden 属性拥有最高的控制权限
|
* 可以根据权限与白名单进行过滤, 但是 meta hidden 属性拥有最高的控制权限
|
||||||
* 如果 mete hidden 设置为 false 则永远不会显示菜单选项
|
* 如果 mete hidden 设置为 false 则永远不会显示菜单选项
|
||||||
*/
|
*/
|
||||||
export const WHITE_ROUTES = ['login', 'error-page', 'doc']
|
export const WHITE_ROUTES = ['RLogin', 'ErrorPage', 'RayTemplateDoc']
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -18,19 +18,43 @@
|
|||||||
|
|
||||||
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
|
import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor'
|
||||||
import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
|
import { appendRequestHeaders } from '@/axios/helper/axiosCopilot'
|
||||||
|
import { APP_CATCH_KEY } from '@/appConfig/appConfig'
|
||||||
|
import { getCache } from '@/utils/cache'
|
||||||
|
|
||||||
import type { RequestInterceptorConfig, ImplementFunction } from '@/axios/type'
|
import type { RequestInterceptorConfig, ImplementFunction } from '@/axios/type'
|
||||||
const { setImplementQueue } = useAxiosInterceptor()
|
const { setImplementQueue } = useAxiosInterceptor()
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 这里只是示例如何获取到系统缓存的 token 并且返回请求头 token 的 key 和 value
|
||||||
|
* 尽可能的拆分每个拦截器的功能函数
|
||||||
|
* 这是这个包存在的意义
|
||||||
|
*
|
||||||
|
* 当然你也可以根据 request instance 来特殊处理, 这里暂时不做演示
|
||||||
|
*/
|
||||||
|
const requestHeaderToken = (ins: RequestInterceptorConfig, mode: string) => {
|
||||||
|
const token = getCache(APP_CATCH_KEY.token)
|
||||||
|
|
||||||
|
if (ins.url) {
|
||||||
|
// TODO: 根据 url 不同是否设置 token
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: 'X-TOKEN',
|
||||||
|
value: token,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 注入请求头信息 */
|
/** 注入请求头信息 */
|
||||||
const injectRequestHeaders: ImplementFunction<RequestInterceptorConfig> = (
|
const injectRequestHeaders: ImplementFunction<RequestInterceptorConfig> = (
|
||||||
ins,
|
ins,
|
||||||
mode,
|
mode,
|
||||||
) => {
|
) => {
|
||||||
appendRequestHeaders(ins, [
|
appendRequestHeaders(ins, [
|
||||||
|
requestHeaderToken(ins, mode),
|
||||||
{
|
{
|
||||||
key: 'X-TOKEN',
|
key: 'Demo-Header-Key',
|
||||||
value: 'token',
|
value: 'Demo Header Value',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
9
src/components/AppComponents/README.md
Normal file
9
src/components/AppComponents/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
## 描述
|
||||||
|
|
||||||
|
> 该组件包存放依赖系统数据的公共组件。
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
|
||||||
|
- 该组件包仅存放与系统数据有绑定、关联的组件,纯组件或纯 UI 组件应放置于外层包中
|
||||||
|
- 以 `App` 开头标记组件是系统组件
|
||||||
|
- 组件应该尽量避免与其他系统组件有关联性
|
@ -24,42 +24,23 @@ import { getCache, setCache } from '@/utils/cache'
|
|||||||
import { useSignin } from '@/store'
|
import { useSignin } from '@/store'
|
||||||
import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig'
|
import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig'
|
||||||
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
|
import { redirectRouterToDashboard } from '@/router/helper/routerCopilot'
|
||||||
|
import { validRole, validMenuItemShow } from '@/router/helper/routerCopilot'
|
||||||
|
|
||||||
import type { Router, NavigationGuardNext } from 'vue-router'
|
import type {
|
||||||
|
Router,
|
||||||
|
NavigationGuardNext,
|
||||||
|
RouteLocationNormalized,
|
||||||
|
} from 'vue-router'
|
||||||
|
|
||||||
export const permissionRouter = (router: Router) => {
|
export const permissionRouter = (router: Router) => {
|
||||||
const { beforeEach } = router
|
const { beforeEach } = router
|
||||||
|
|
||||||
const { path } = ROOT_ROUTE
|
|
||||||
|
|
||||||
beforeEach((to, from, next) => {
|
beforeEach((to, from, next) => {
|
||||||
const token = getCache(APP_CATCH_KEY.token)
|
const token = getCache(APP_CATCH_KEY.token)
|
||||||
const route = getCache('menuKey')
|
const route = getCache('menuKey')
|
||||||
const { signinCallback } = storeToRefs(useSignin())
|
|
||||||
const role = computed(() => signinCallback.value.role)
|
|
||||||
const { meta } = to
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* 检查是否有权限, 如果权限不匹配则重定向至首页(默认为 dashboard)
|
|
||||||
* 权限匹配使用严格比对, 对大小写、空格等敏感
|
|
||||||
*/
|
|
||||||
const hasRole = () => {
|
|
||||||
/** 如果未设置权限则默认无需鉴权 */
|
|
||||||
if (meta.role) {
|
|
||||||
/** 空权限列表默认无需鉴权 */
|
|
||||||
if (meta.role.length === 0) {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return meta.role.includes(role.value)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token !== 'no') {
|
if (token !== 'no') {
|
||||||
if (hasRole()) {
|
if (validMenuItemShow(to as unknown as IMenuOptions)) {
|
||||||
if (to.path === '/' || from.path === '/login') {
|
if (to.path === '/' || from.path === '/login') {
|
||||||
if (route !== 'no') {
|
if (route !== 'no') {
|
||||||
next(route)
|
next(route)
|
||||||
|
@ -23,6 +23,7 @@ import { ROOT_ROUTE } from '@/appConfig/appConfig'
|
|||||||
import { setCache } from '@/utils/cache'
|
import { setCache } from '@/utils/cache'
|
||||||
|
|
||||||
import type { Router } from 'vue-router'
|
import type { Router } from 'vue-router'
|
||||||
|
import type { AppRouteMeta } from '@/router/type'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -31,17 +32,23 @@ import type { Router } from 'vue-router'
|
|||||||
*
|
*
|
||||||
* 如果为超级管理员, 则会默认获取所有权限
|
* 如果为超级管理员, 则会默认获取所有权限
|
||||||
*/
|
*/
|
||||||
export const validRole = (option: IMenuOptions) => {
|
export const validRole = (meta: AppRouteMeta) => {
|
||||||
const { signinCallback } = storeToRefs(useSignin())
|
const { signinCallback } = storeToRefs(useSignin())
|
||||||
const role = computed(() => signinCallback.value.role)
|
const role = computed(() => signinCallback.value.role)
|
||||||
|
|
||||||
const { meta } = option
|
const { role: metaRole } = meta
|
||||||
|
|
||||||
if (SUPER_ADMIN?.length && SUPER_ADMIN.includes(role.value)) {
|
if (SUPER_ADMIN?.length && SUPER_ADMIN.includes(role.value)) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
if (meta?.role) {
|
// 如果 role 为 undefind 或者空数组, 则认为该路由不做权限过滤
|
||||||
return meta.role.includes(role.value)
|
if (!metaRole || !metaRole?.length) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否含有该权限
|
||||||
|
if (metaRole) {
|
||||||
|
return metaRole.includes(role.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@ -63,7 +70,7 @@ export const validMenuItemShow = (option: IMenuOptions) => {
|
|||||||
meta?.hidden === undefined || meta?.hidden === false ? false : meta?.hidden
|
meta?.hidden === undefined || meta?.hidden === false ? false : meta?.hidden
|
||||||
|
|
||||||
// 如果是超级管理员(预设为 admin), 则根据其菜单栏(hidden)字段判断是否显示
|
// 如果是超级管理员(预设为 admin), 则根据其菜单栏(hidden)字段判断是否显示
|
||||||
if (validRole(option)) {
|
if (validRole(meta)) {
|
||||||
return true && !hidden
|
return true && !hidden
|
||||||
} else {
|
} else {
|
||||||
// 如果为基础路由, 不进行鉴权则根据其菜单栏(hidden)字段判断是否显示
|
// 如果为基础路由, 不进行鉴权则根据其菜单栏(hidden)字段判断是否显示
|
||||||
@ -71,9 +78,14 @@ export const validMenuItemShow = (option: IMenuOptions) => {
|
|||||||
return true && !hidden
|
return true && !hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果 role 为 undefind 或者空数组, 则认为该路由不做权限过滤
|
||||||
|
if (!meta?.role || !meta.role?.length) {
|
||||||
|
return true && !hidden
|
||||||
|
}
|
||||||
|
|
||||||
// 判断权限是否匹配和菜单栏(hidden)字段判断是否显示
|
// 判断权限是否匹配和菜单栏(hidden)字段判断是否显示
|
||||||
if (meta?.role) {
|
if (meta?.role && meta.role.length) {
|
||||||
return validRole(option) && !hidden
|
return validRole(meta) && !hidden
|
||||||
}
|
}
|
||||||
|
|
||||||
return true && !hidden
|
return true && !hidden
|
||||||
@ -127,7 +139,8 @@ export const redirectRouterToDashboard = (isReplace = true) => {
|
|||||||
const { push, replace } = router
|
const { push, replace } = router
|
||||||
const { path } = ROOT_ROUTE
|
const { path } = ROOT_ROUTE
|
||||||
|
|
||||||
isReplace ? push(path) : replace(path)
|
|
||||||
|
|
||||||
setCache('menuKey', path)
|
setCache('menuKey', path)
|
||||||
|
console.log('path', path)
|
||||||
|
|
||||||
|
isReplace ? push(path) : replace(path)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ const axios: AppRouteRecordRaw = {
|
|||||||
icon: 'axios',
|
icon: 'axios',
|
||||||
order: 3,
|
order: 3,
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
|
hidden: false,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
29
src/store/README.md
Normal file
29
src/store/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
## 描述
|
||||||
|
|
||||||
|
> pinia store 仓库包。存放全局公共状态。
|
||||||
|
|
||||||
|
## 约束
|
||||||
|
|
||||||
|
- 状态管理器应该按照其用途进行分包(见名知意)
|
||||||
|
- 包名以用途名命名
|
||||||
|
- 默认以 index.ts 作为入口,其余的辅助函数、类型,分别在该文件夹下进行补充(type.ts、helper.ts。。。)
|
||||||
|
- 仓库使用 `piniaPluginPersistedstate` 作为中间件,用于缓存仓库数据避免刷新丢失(但是该方法有缺陷,不能缓存函数)
|
||||||
|
- 默认不全部缓存参数,如果需要缓存参数,需要在 `defineStore` 第三个参数配置 `persist` 属性
|
||||||
|
- `defineStore` 第一个参数必须全局唯一
|
||||||
|
- 缓存插件 key 应该按照 `piniaXXXStore` 格式命名(XXX 表示该包名称)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const useDemoStore = defineStore('demo', () => {}, {
|
||||||
|
persist: {
|
||||||
|
key: 'piniaDemoStore',
|
||||||
|
paths: ['demoState'],
|
||||||
|
storage: sessionStorage | localStorage,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- 最后在 index.ts 中暴露使用
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export { useDemo } from './modules/demo/index'
|
||||||
|
```
|
@ -14,6 +14,7 @@
|
|||||||
import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
|
import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
|
||||||
import RayIcon from '@/components/RayIcon/index'
|
import RayIcon from '@/components/RayIcon/index'
|
||||||
import { validteValueType } from '@/utils/hook'
|
import { validteValueType } from '@/utils/hook'
|
||||||
|
import { getCache, setCache } from '@/utils/cache'
|
||||||
|
|
||||||
import type { VNode } from 'vue'
|
import type { VNode } from 'vue'
|
||||||
|
|
||||||
@ -156,3 +157,12 @@ export const hasMenuIcon = (option: IMenuOptions) => {
|
|||||||
|
|
||||||
return () => icon
|
return () => icon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 获取缓存的 menu key, 如果未获取到则使用 ROOTROUTE path 当作默认激活路由菜单 */
|
||||||
|
export const getCatchMenuKey = () => {
|
||||||
|
const { path: rootPath } = ROOT_ROUTE
|
||||||
|
const cacheMenuKey: MenuKey =
|
||||||
|
getCache('menuKey') === 'no' ? rootPath : getCache('menuKey')
|
||||||
|
|
||||||
|
return cacheMenuKey
|
||||||
|
}
|
||||||
|
@ -23,7 +23,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NEllipsis } from 'naive-ui'
|
import { NEllipsis } from 'naive-ui'
|
||||||
import RayIcon from '@/components/RayIcon/index'
|
|
||||||
|
|
||||||
import { getCache, setCache } from '@/utils/cache'
|
import { getCache, setCache } from '@/utils/cache'
|
||||||
import { validMenuItemShow } from '@/router/helper/routerCopilot'
|
import { validMenuItemShow } from '@/router/helper/routerCopilot'
|
||||||
@ -32,9 +31,9 @@ import {
|
|||||||
matchMenuOption,
|
matchMenuOption,
|
||||||
updateDocumentTitle,
|
updateDocumentTitle,
|
||||||
hasMenuIcon,
|
hasMenuIcon,
|
||||||
|
getCatchMenuKey,
|
||||||
} from './helper'
|
} from './helper'
|
||||||
import { useI18n } from '@/locales/useI18n'
|
import { useI18n } from '@/locales/useI18n'
|
||||||
import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig'
|
|
||||||
import routeModules from '@/router/routeModules'
|
import routeModules from '@/router/routeModules'
|
||||||
import { useKeepAlive } from '@/store'
|
import { useKeepAlive } from '@/store'
|
||||||
import { useVueRouter } from '@/router/helper/useVueRouter'
|
import { useVueRouter } from '@/router/helper/useVueRouter'
|
||||||
@ -50,13 +49,8 @@ export const useMenu = defineStore(
|
|||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { setKeepAliveInclude } = useKeepAlive()
|
const { setKeepAliveInclude } = useKeepAlive()
|
||||||
|
|
||||||
const { path: rootPath } = ROOT_ROUTE
|
|
||||||
|
|
||||||
const cacheMenuKey =
|
|
||||||
getCache('menuKey') === 'no' ? rootPath : getCache('menuKey')
|
|
||||||
|
|
||||||
const menuState = reactive({
|
const menuState = reactive({
|
||||||
menuKey: cacheMenuKey as MenuKey, // 当前菜单 `key`
|
menuKey: getCatchMenuKey(), // 当前菜单 `key`
|
||||||
options: [] as IMenuOptions[], // 菜单列表
|
options: [] as IMenuOptions[], // 菜单列表
|
||||||
collapsed: false, // 是否折叠菜单
|
collapsed: false, // 是否折叠菜单
|
||||||
menuTagOptions: [] as MenuTagOptions[], // tag 标签菜单
|
menuTagOptions: [] as MenuTagOptions[], // tag 标签菜单
|
||||||
@ -196,7 +190,7 @@ export const useMenu = defineStore(
|
|||||||
icon: hasMenuIcon(option),
|
icon: hasMenuIcon(option),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (option.path === cacheMenuKey) {
|
if (option.path === getCatchMenuKey()) {
|
||||||
/** 设置菜单标签 */
|
/** 设置菜单标签 */
|
||||||
setMenuTagOptions(attr)
|
setMenuTagOptions(attr)
|
||||||
/** 设置浏览器标题 */
|
/** 设置浏览器标题 */
|
||||||
|
@ -21,7 +21,6 @@
|
|||||||
|
|
||||||
import { isEmpty } from 'lodash-es'
|
import { isEmpty } from 'lodash-es'
|
||||||
import { removeCache } from '@/utils/cache'
|
import { removeCache } from '@/utils/cache'
|
||||||
import { useMenu } from '@/store'
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
SigninForm,
|
SigninForm,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user