diff --git a/src/appConfig/routerConfig.ts b/src/appConfig/routerConfig.ts index 8583e79e..47128ab0 100644 --- a/src/appConfig/routerConfig.ts +++ b/src/appConfig/routerConfig.ts @@ -44,7 +44,7 @@ export const SETUP_ROUTER_GUARD = true * 可以根据权限与白名单进行过滤, 但是 meta hidden 属性拥有最高的控制权限 * 如果 mete hidden 设置为 false 则永远不会显示菜单选项 */ -export const WHITE_ROUTES = ['login', 'error-page', 'doc'] +export const WHITE_ROUTES = ['RLogin', 'ErrorPage', 'RayTemplateDoc'] /** * diff --git a/src/axios/inject/requestInject.ts b/src/axios/inject/requestInject.ts index 09012734..d41d2481 100644 --- a/src/axios/inject/requestInject.ts +++ b/src/axios/inject/requestInject.ts @@ -18,19 +18,43 @@ import { useAxiosInterceptor, axiosCanceler } from '@/axios/helper/interceptor' 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' 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 = ( ins, mode, ) => { appendRequestHeaders(ins, [ + requestHeaderToken(ins, mode), { - key: 'X-TOKEN', - value: 'token', + key: 'Demo-Header-Key', + value: 'Demo Header Value', }, ]) } diff --git a/src/components/AppComponents/README.md b/src/components/AppComponents/README.md new file mode 100644 index 00000000..d8e8cafd --- /dev/null +++ b/src/components/AppComponents/README.md @@ -0,0 +1,9 @@ +## 描述 + +> 该组件包存放依赖系统数据的公共组件。 + +## 约束 + +- 该组件包仅存放与系统数据有绑定、关联的组件,纯组件或纯 UI 组件应放置于外层包中 +- 以 `App` 开头标记组件是系统组件 +- 组件应该尽量避免与其他系统组件有关联性 diff --git a/src/router/helper/permission.ts b/src/router/helper/permission.ts index d47e23f3..0bee3bb6 100644 --- a/src/router/helper/permission.ts +++ b/src/router/helper/permission.ts @@ -24,42 +24,23 @@ import { getCache, setCache } from '@/utils/cache' import { useSignin } from '@/store' import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig' 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) => { const { beforeEach } = router - const { path } = ROOT_ROUTE - beforeEach((to, from, next) => { const token = getCache(APP_CATCH_KEY.token) 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 (hasRole()) { + if (validMenuItemShow(to as unknown as IMenuOptions)) { if (to.path === '/' || from.path === '/login') { if (route !== 'no') { next(route) diff --git a/src/router/helper/routerCopilot.ts b/src/router/helper/routerCopilot.ts index 6199aebf..d514e15d 100644 --- a/src/router/helper/routerCopilot.ts +++ b/src/router/helper/routerCopilot.ts @@ -23,6 +23,7 @@ import { ROOT_ROUTE } from '@/appConfig/appConfig' import { setCache } from '@/utils/cache' 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 role = computed(() => signinCallback.value.role) - const { meta } = option + const { role: metaRole } = meta if (SUPER_ADMIN?.length && SUPER_ADMIN.includes(role.value)) { return true } else { - if (meta?.role) { - return meta.role.includes(role.value) + // 如果 role 为 undefind 或者空数组, 则认为该路由不做权限过滤 + if (!metaRole || !metaRole?.length) { + return true + } + + // 判断是否含有该权限 + if (metaRole) { + return metaRole.includes(role.value) } return true @@ -63,7 +70,7 @@ export const validMenuItemShow = (option: IMenuOptions) => { meta?.hidden === undefined || meta?.hidden === false ? false : meta?.hidden // 如果是超级管理员(预设为 admin), 则根据其菜单栏(hidden)字段判断是否显示 - if (validRole(option)) { + if (validRole(meta)) { return true && !hidden } else { // 如果为基础路由, 不进行鉴权则根据其菜单栏(hidden)字段判断是否显示 @@ -71,9 +78,14 @@ export const validMenuItemShow = (option: IMenuOptions) => { return true && !hidden } + // 如果 role 为 undefind 或者空数组, 则认为该路由不做权限过滤 + if (!meta?.role || !meta.role?.length) { + return true && !hidden + } + // 判断权限是否匹配和菜单栏(hidden)字段判断是否显示 - if (meta?.role) { - return validRole(option) && !hidden + if (meta?.role && meta.role.length) { + return validRole(meta) && !hidden } return true && !hidden @@ -127,7 +139,8 @@ export const redirectRouterToDashboard = (isReplace = true) => { const { push, replace } = router const { path } = ROOT_ROUTE - isReplace ? push(path) : replace(path) - setCache('menuKey', path) + console.log('path', path) + + isReplace ? push(path) : replace(path) } diff --git a/src/router/modules/axios.ts b/src/router/modules/axios.ts index 201cae23..6665f906 100644 --- a/src/router/modules/axios.ts +++ b/src/router/modules/axios.ts @@ -11,6 +11,7 @@ const axios: AppRouteRecordRaw = { icon: 'axios', order: 3, keepAlive: true, + hidden: false, }, } diff --git a/src/store/README.md b/src/store/README.md new file mode 100644 index 00000000..c33b32a9 --- /dev/null +++ b/src/store/README.md @@ -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' +``` diff --git a/src/store/modules/menu/helper.ts b/src/store/modules/menu/helper.ts index 2bd7f018..25dc478b 100644 --- a/src/store/modules/menu/helper.ts +++ b/src/store/modules/menu/helper.ts @@ -14,6 +14,7 @@ import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig' import RayIcon from '@/components/RayIcon/index' import { validteValueType } from '@/utils/hook' +import { getCache, setCache } from '@/utils/cache' import type { VNode } from 'vue' @@ -156,3 +157,12 @@ export const hasMenuIcon = (option: IMenuOptions) => { 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 +} diff --git a/src/store/modules/menu/index.ts b/src/store/modules/menu/index.ts index 2151c0ed..2aff1676 100644 --- a/src/store/modules/menu/index.ts +++ b/src/store/modules/menu/index.ts @@ -23,7 +23,6 @@ */ import { NEllipsis } from 'naive-ui' -import RayIcon from '@/components/RayIcon/index' import { getCache, setCache } from '@/utils/cache' import { validMenuItemShow } from '@/router/helper/routerCopilot' @@ -32,9 +31,9 @@ import { matchMenuOption, updateDocumentTitle, hasMenuIcon, + getCatchMenuKey, } from './helper' import { useI18n } from '@/locales/useI18n' -import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig' import routeModules from '@/router/routeModules' import { useKeepAlive } from '@/store' import { useVueRouter } from '@/router/helper/useVueRouter' @@ -50,13 +49,8 @@ export const useMenu = defineStore( const { t } = useI18n() const { setKeepAliveInclude } = useKeepAlive() - const { path: rootPath } = ROOT_ROUTE - - const cacheMenuKey = - getCache('menuKey') === 'no' ? rootPath : getCache('menuKey') - const menuState = reactive({ - menuKey: cacheMenuKey as MenuKey, // 当前菜单 `key` + menuKey: getCatchMenuKey(), // 当前菜单 `key` options: [] as IMenuOptions[], // 菜单列表 collapsed: false, // 是否折叠菜单 menuTagOptions: [] as MenuTagOptions[], // tag 标签菜单 @@ -196,7 +190,7 @@ export const useMenu = defineStore( icon: hasMenuIcon(option), }) - if (option.path === cacheMenuKey) { + if (option.path === getCatchMenuKey()) { /** 设置菜单标签 */ setMenuTagOptions(attr) /** 设置浏览器标题 */ diff --git a/src/store/modules/signin/index.ts b/src/store/modules/signin/index.ts index bbab7c58..202c3373 100644 --- a/src/store/modules/signin/index.ts +++ b/src/store/modules/signin/index.ts @@ -21,7 +21,6 @@ import { isEmpty } from 'lodash-es' import { removeCache } from '@/utils/cache' -import { useMenu } from '@/store' import type { SigninForm,