From 49725bbed5b1cb6c8e0343418c35f663beab4d54 Mon Sep 17 00:00:00 2001 From: ray_wuhao <443547225@qq.com> Date: Thu, 1 Jun 2023 17:35:48 +0800 Subject: [PATCH] =?UTF-8?q?v3.3.0=20=E5=8F=91=E5=B8=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 16 ++++ COMMONPROBLEM.md | 21 +++++ README.md | 4 + cfg.ts | 5 +- src/appConfig/appConfig.ts | 23 ++++- .../RayTransitionComponent/index.vue | 21 +++-- src/layout/components/MenuTag/index.tsx | 5 +- .../SiderBar/Components/GlobalSeach/index.tsx | 4 +- src/locales/lang/en-US/menu.json | 2 +- src/locales/lang/zh-CN/menu.json | 2 +- src/router/README.md | 88 ++++++++++++++++++- src/router/helper/expandRoutes.ts | 78 ++++++++++++++++ src/router/helper/merge.ts | 34 +++++++ src/router/helper/orderRoutes.ts | 36 ++++++++ src/router/index.ts | 2 +- src/router/modules/axios.ts | 2 + src/router/modules/dashboard.ts | 1 + src/router/modules/doc-local.ts | 1 + src/router/modules/doc.ts | 1 + src/router/modules/echart.ts | 1 + src/router/modules/multi-menu.ts | 17 ++++ src/router/modules/office.ts | 6 +- src/router/modules/rely.ts | 3 +- src/router/modules/table.ts | 1 + src/router/permission.ts | 6 +- src/router/route-module.ts | 38 -------- src/router/routeModules.ts | 30 +++++++ src/router/routes.ts | 18 ++-- src/router/type.ts | 6 ++ src/store/index.ts | 1 + src/store/modules/keep-alive/index.ts | 72 +++++++++++++++ src/store/modules/keep-alive/type.ts | 3 + src/store/modules/menu/index.ts | 19 ++-- src/types/appConfig.ts | 6 ++ src/types/cfg.ts | 2 - src/types/store.d.ts | 4 +- src/utils/hook.ts | 7 ++ src/views/error/index.tsx | 7 +- src/views/login/components/Signin/index.tsx | 6 +- .../multi/views/multi-menu-one/index.tsx | 15 +++- .../multi-menu-two/views/sub-menu/index.tsx | 15 +++- .../views/multi-menu-two-one/index.tsx | 33 +++++++ vite.config.ts | 2 - 43 files changed, 556 insertions(+), 108 deletions(-) create mode 100644 COMMONPROBLEM.md create mode 100644 src/router/helper/expandRoutes.ts create mode 100644 src/router/helper/merge.ts create mode 100644 src/router/helper/orderRoutes.ts delete mode 100644 src/router/route-module.ts create mode 100644 src/router/routeModules.ts create mode 100644 src/store/modules/keep-alive/index.ts create mode 100644 src/store/modules/keep-alive/type.ts create mode 100644 src/views/multi/views/multi-menu-two/views/sub-menu/views/multi-menu-two-one/index.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a4215d2..9d4bd3a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # CHANGE LOG +## 3.3.0 + +### 特征 + +- 取消 RootRoute 属性暴露全局 +- 新增 Route Meta keepAlive 配置开启页面缓存(可以在 AppConfig APP_KEEP_ALIVE 中进行缓存的配置管理) +- 回退使用自动导入路由模块方式,具体使用方法查看 [路由配置](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/src/router/README.md) +- 新增 Route Meta order 配置,配置菜单顺序 +- 支持更多 appConfig 配置 + +### 补充 + +- 后续该模板还会持续维护,会尽可能多的支持更多业务场景 +- 最近破坏性更新很多,发布比较频繁,后续应该不会有这么大的破坏性更新。核心重点会放在模板整体的健壮性、可维护性上 +- 未来希望模板拆分为一个高拓展性的工程,积木式管理项目,让项目模块之间尽可能的解耦。让模板有更好的拓展性,让你在使用时,可以根据自身业务需求进行拓展(当然,我希望你能以项目的基本维护原则延续) + ## 3.2.3 ### 特征 diff --git a/COMMONPROBLEM.md b/COMMONPROBLEM.md new file mode 100644 index 00000000..fa14ec84 --- /dev/null +++ b/COMMONPROBLEM.md @@ -0,0 +1,21 @@ +## 常见问题 + +### 路由 + +#### 缓存失效 + +> 如果出现缓存配置不生效的情况可以按照如下方法进行排查 + +- 查看 APP_KEEP_ALIVE setupKeepAlive 属性是否配置为 true +- 查看每个组件的 `name` 是否唯一,[`KeepAlive`](https://cn.vuejs.org/guide/built-ins/keep-alive.html) 组件重度依赖组件 `name` 作为唯一标识。详情可以查看官方文档 +- 查看该页面的路由配置是否正确,比如:`path` 是否按照模板约定方式进行配置 + +#### 自动导入失败 + +> 模板采用自动导入路由模块方式。如果发现路由导入有误、或者导入报错,请查看文件命名是否有误。 + +### 国际化 + +#### 国际化切换错误、警告 + +> 模板二次封装 [`useI18n`](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/src/locales/useI18n.ts) 方法,首选该方法作为国际化语言切换方法。 diff --git a/README.md b/README.md index e4a90294..47c1df18 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,10 @@ - [日志](https://github.com/XiaoDaiGua-Ray/xiaodaigua-ray.github.io/blob/main/CHANGELOG.md) +## 常见问题 + +- [常见问题](https://github.com/XiaoDaiGua-Ray/ray-template/blob/main/COMMONPROBLEM.md) + ## 功能 - 主题切换 diff --git a/cfg.ts b/cfg.ts index e18666c3..704d1928 100644 --- a/cfg.ts +++ b/cfg.ts @@ -29,9 +29,9 @@ * ``` * 该属性是用于全局注入的配置方法 * - * const { rootRoute } = __APP_CFG__ + * const { appPrimaryColor } = __APP_CFG__ * - * 以上例子展示, 从 __APP_CFG__ 中解构取出 rootRoute 根路由配置信息 + * 以上例子展示, 从 __APP_CFG__ 中解构取出 appPrimaryColor 根路由配置信息 * __APP_CFG__ 会被挂载于全局变量 `window` 下(vite define 默认是挂载于 window 下) * ``` */ @@ -59,7 +59,6 @@ const config: AppConfigExport = { preloadingConfig: PRE_LOADING_CONFIG, /** 默认主题色(不可省略, 必填), 也用于 ejs 注入 */ appPrimaryColor: APP_PRIMARY_COLOR, - rootRoute: ROOT_ROUTE, sideBarLogo: SIDE_BAR_LOGO, /** * diff --git a/src/appConfig/appConfig.ts b/src/appConfig/appConfig.ts index 39851c3c..078ba861 100644 --- a/src/appConfig/appConfig.ts +++ b/src/appConfig/appConfig.ts @@ -16,7 +16,22 @@ import type { PreloadingConfig, RootRoute, } from '@/types/cfg' -import type { MenuCollapsedConfig } from '@/types/appConfig' +import type { MenuCollapsedConfig, AppKeepAlive } from '@/types/appConfig' + +/** + * + * 系统缓存 + * + * 说明: + * - setupKeepAlive: 是否启用系统页面缓存, 设置为 false 则关闭系统页面缓存 + * - keepAliveExclude: 排除哪些页面不缓存 + * - maxKeepAliveLength: 最大缓存页面数量 + */ +export const APP_KEEP_ALIVE: Readonly = { + setupKeepAlive: true, + keepAliveExclude: [], + maxKeepAliveLength: 5, +} /** 首屏加载信息配置 */ export const PRE_LOADING_CONFIG: PreloadingConfig = { @@ -32,7 +47,7 @@ export const PRE_LOADING_CONFIG: PreloadingConfig = { * * 如果修改了该项目的首页路由配置, 需要更改该配置项, 以免重定向首页操作出现错误 */ -export const ROOT_ROUTE: RootRoute = { +export const ROOT_ROUTE: Readonly = { name: 'Dashboard', path: '/dashboard', } @@ -67,7 +82,7 @@ export const SIDE_BAR_LOGO: LayoutSideBarLogo = { * * MENU_COLLAPSED_INDENT 配置菜单每级的缩进 */ -export const MENU_COLLAPSED_CONFIG: MenuCollapsedConfig = { +export const MENU_COLLAPSED_CONFIG: Readonly = { MENU_COLLAPSED_WIDTH: 64, MENU_COLLAPSED_MODE: 'width', MENU_COLLAPSED_ICON_SIZE: 22, @@ -91,4 +106,4 @@ export const APP_CATCH_KEY = { signin: 'signin', localeLanguage: 'localeLanguage', token: 'token', -} +} as const diff --git a/src/components/RayTransitionComponent/index.vue b/src/components/RayTransitionComponent/index.vue index b14a3f63..ff7e12c5 100644 --- a/src/components/RayTransitionComponent/index.vue +++ b/src/components/RayTransitionComponent/index.vue @@ -6,12 +6,23 @@ :mode="transitionMode" :appear="transitionAppear" > - + + + + diff --git a/src/layout/components/MenuTag/index.tsx b/src/layout/components/MenuTag/index.tsx index 3c1e1ec3..1416e5c4 100644 --- a/src/layout/components/MenuTag/index.tsx +++ b/src/layout/components/MenuTag/index.tsx @@ -26,6 +26,7 @@ import RayIcon from '@/components/RayIcon/index' import { useMenu, useSetting } from '@/store' import { uuid } from '@/utils/hook' import { hasClass } from '@/utils/element' +import { ROOT_ROUTE } from '@/appConfig/appConfig' import type { MenuOption, ScrollbarInst } from 'naive-ui' @@ -46,9 +47,7 @@ const MenuTag = defineComponent({ setMenuTagOptions, } = menuStore const { changeSwitcher } = settingStore - const { - rootRoute: { path }, - } = __APP_CFG__ + const { path } = ROOT_ROUTE const exclude = ['closeAll', 'closeRight', 'closeLeft', 'closeOther'] let currentContentmenuIndex = -1 // 当前右键标签页索引位置 diff --git a/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx b/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx index 41bbc0d6..5fbfc9bf 100644 --- a/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx +++ b/src/layout/components/SiderBar/Components/GlobalSeach/index.tsx @@ -20,7 +20,7 @@ import { useMenu } from '@/store' import { validRole } from '@/router/basic' import type { MenuOption } from 'naive-ui' -import type { RouteMeta } from 'vue-router' +import type { AppRouteMeta } from '@/router/type' const GlobalSeach = defineComponent({ name: 'GlobalSeach', @@ -108,7 +108,7 @@ const GlobalSeach = defineComponent({ } const handleSearchItemClick = (option: MenuOption) => { - const meta = option.meta as RouteMeta + const meta = option.meta as AppRouteMeta /** 如果配置站外跳转则不会关闭搜索框 */ if (meta.windowOpen) { diff --git a/src/locales/lang/en-US/menu.json b/src/locales/lang/en-US/menu.json index 5e735abe..26d8ac5c 100644 --- a/src/locales/lang/en-US/menu.json +++ b/src/locales/lang/en-US/menu.json @@ -7,7 +7,7 @@ "scrollReveal": "Scroll Reveal", "Axios": "Axios Request", "Table": "Table", - "MultiMenu": "MultiMenu", + "MultiMenu": "MultiMenu(catch)", "Doc": "Doc", "DocLocal": "Doc (China)", "Office": "Office", diff --git a/src/locales/lang/zh-CN/menu.json b/src/locales/lang/zh-CN/menu.json index 7aad141f..7c4f7f0c 100644 --- a/src/locales/lang/zh-CN/menu.json +++ b/src/locales/lang/zh-CN/menu.json @@ -7,7 +7,7 @@ "scrollReveal": "滚动动画", "Axios": "请求", "Table": "表格", - "MultiMenu": "多级菜单", + "MultiMenu": "多级菜单(缓存)", "Doc": "文档", "DocLocal": "文档 (国内地址)", "Office": "办公", diff --git a/src/router/README.md b/src/router/README.md index a007e853..39f52945 100644 --- a/src/router/README.md +++ b/src/router/README.md @@ -1,7 +1,86 @@ -- 类型 +## 路由添加规则 + +- modules 中每一个 ts 文件视为一个路由模块 +- path 以 `/` 开头则视为根路由 +- 如果 path 为根路由,且不没有子级,则直接返回该路由 +- 如果 path 为根路由,且不含有子级,则拼接完整 path 路径,然后返回最后一层路由 +- 子级中不会存在 `/` 开头的情况(模板约定约束),如果存在则不用管,按照前三条逻辑执行代码,如果有误,由开发人员手动更改配置 + +```ts +const demo = { + path: '/multi', + name: 'MultiMenu', + meta: { + i18nKey: 'MultiMenu', + icon: 'table', + }, + children: [ + { + path: 'multi-menu-one', + name: 'MultiMenuOne', + meta: { + noLocalTitle: '多级菜单-1', + }, + key: 'multi-menu-one', + breadcrumbLabel: '多级菜单-1', + show: true, + }, + { + path: 'multi-menu-two', + name: 'MultiMenuTwo', + meta: { + noLocalTitle: '多级菜单-2', + }, + children: [ + { + path: 'sub-menu', + name: 'SubMenu', + meta: { + noLocalTitle: '多级菜单-2-1', + }, + key: 'sub-menu', + breadcrumbLabel: '多级菜单-2-1', + show: true, + }, + ], + key: 'multi-menu-two', + breadcrumbLabel: '多级菜单-2', + show: true, + }, + ], +} + +// 转换后 + +const transform = [ + { + path: '/multi/multi-menu-one', + name: 'MultiMenuOne', + meta: { + noLocalTitle: '多级菜单-1', + }, + key: 'multi-menu-one', + breadcrumbLabel: '多级菜单-1', + show: true, + }, + { + path: '/multi/multi-menu-two/sub-menu', + name: 'SubMenu', + meta: { + noLocalTitle: '多级菜单-2-1', + }, + key: 'sub-menu', + breadcrumbLabel: '多级菜单-2-1', + show: true, + }, +] +``` + +## 类型 ```ts interface RouteMeta { + order?: number i18nKey: string icon?: string windowOpen?: string @@ -9,17 +88,20 @@ interface RouteMeta { hidden?: boolean noLocalTitle?: string | number ignoreAutoResetScroll?: boolean + keepAlive?: boolean } ``` -- 说明 +## 说明 ``` +order: 菜单顺序,值越大越靠后。仅对顶层有效,子菜单该值无效 i18nKey: i18n 国际化 key, 会优先使用该字段 -icon: icon 图标, 用于 Menu 菜单(依赖 RayIcon 组件实现) +icon: icon 图标, 用于 Menu 菜单(依赖 RayIcon 组件实现) windowOpen: 超链接打开(新开窗口打开) role: 权限表 hidden: 是否显示 noLocalTitle: 不使用国际化渲染 Menu Titile ignoreAutoResetScroll: 该页面内容区域自动初始化滚动条位置 +keepAlive: 是否缓存该页面(需要配置 APP_KEEP_ALIVE setupKeepAlive 属性为 true 启用才有效) ``` diff --git a/src/router/helper/expandRoutes.ts b/src/router/helper/expandRoutes.ts new file mode 100644 index 00000000..ed54cc95 --- /dev/null +++ b/src/router/helper/expandRoutes.ts @@ -0,0 +1,78 @@ +/** + * + * @author Ray + * + * @date 2023-06-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** + * + * 该功能基于 代码改进实现 + * 自动展开所有路由 + */ + +import { cloneDeep } from 'lodash-es' + +import type { AppRouteRecordRaw } from '@/router/type' + +const isRootPath = (path: string) => path.startsWith('/') + +/** + * + * @param arr route modules + * @param result callback expand routes modules result + * @param path route path + * @returns callback expand routes modules result + * + * @remark 该方法会视 / 开头 path 为根路由 + */ +const routePromotion = ( + arr: AppRouteRecordRaw[], + result: AppRouteRecordRaw[] = [], + path = '', +) => { + // 如果没有小宝贝进来 则没有小宝贝出去 + if (!Array.isArray(arr)) { + return [] + } + + // 新来的小宝贝们先洗好澡澡哦 + const sourceArr = arr + + // 来开始我们的循环之旅吧 + sourceArr.forEach((curr) => { + // 获取可爱的小宝贝哦 + + if (curr.children?.length) { + // 如果小宝贝有小小宝贝 + + // 小宝贝们有孩子了,/(ㄒoㄒ)/~~ + routePromotion( + curr.children, + result, + path + (isRootPath(curr.path) ? curr.path : '/' + curr.path), + ) + } else { + // 小宝贝还是单身哦 + // 乖乖的小宝贝快快进入口袋 + curr.path = path + (isRootPath(curr.path) ? curr.path : '/' + curr.path) + + result.push(curr) + } + }) + + // 返回都是根节点的小宝贝们 + return result +} + +export const expandRoutes = (arr: AppRouteRecordRaw[]) => { + if (!Array.isArray(arr)) { + return [] + } + + return routePromotion(cloneDeep(arr)) +} diff --git a/src/router/helper/merge.ts b/src/router/helper/merge.ts new file mode 100644 index 00000000..c5e82731 --- /dev/null +++ b/src/router/helper/merge.ts @@ -0,0 +1,34 @@ +/** + * + * @author Ray + * + * @date 2023-06-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import type { AppRouteRecordRaw, AutoImportRouteModule } from '@/router/type' + +/** + * + * @returns 所有路由模块 + * + * @remark 自动合并所有路由模块, 每一个 ts 文件都视为一个 route module 与 views 一一对应 + */ +export const autoMergeRoute = () => { + const modulesFiles = import.meta.glob('../modules/**/*.ts', { + eager: true, + }) + + const modules = Object.keys(modulesFiles).reduce((modules, modulePath) => { + const value = modulesFiles[modulePath] as AutoImportRouteModule + + modules.push(value.default) + + return modules + }, [] as AppRouteRecordRaw[]) + + return modules +} diff --git a/src/router/helper/orderRoutes.ts b/src/router/helper/orderRoutes.ts new file mode 100644 index 00000000..95ba43fc --- /dev/null +++ b/src/router/helper/orderRoutes.ts @@ -0,0 +1,36 @@ +/** + * + * @author Ray + * + * @date 2023-06-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import type { AppRouteRecordRaw } from '@/router/type' + +/** + * + * @param routes 路由模块表(route 表) + * @returns 排序后的新路由表 + * + * @remark 必须配置 meta 属性, order 属性会影响页面菜单排序 + */ +export const orderRoutes = (routes: AppRouteRecordRaw[]) => { + return routes.sort((curr, next) => { + try { + const { + meta: { order: currOrder = 1 }, + } = curr + const { + meta: { order: nextOrder = 0 }, + } = next + + return currOrder - nextOrder + } catch (e) { + throw new Error('orderRoutes error: order must be number!') + } + }) +} diff --git a/src/router/index.ts b/src/router/index.ts index f9633303..e37d5bc5 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,5 +1,5 @@ import { createRouter, createWebHashHistory } from 'vue-router' -import { constantRoutes } from './routes' +import constantRoutes from './routes' import { permissionRouter as _permissionRouter } from './permission' import scrollViewToTop from '@/router/utils/viewScrollTop' diff --git a/src/router/modules/axios.ts b/src/router/modules/axios.ts index 8170be45..ad5aa22c 100644 --- a/src/router/modules/axios.ts +++ b/src/router/modules/axios.ts @@ -7,6 +7,8 @@ const axios: AppRouteRecordRaw = { meta: { i18nKey: 'Axios', icon: 'axios', + order: 3, + keepAlive: true, }, } diff --git a/src/router/modules/dashboard.ts b/src/router/modules/dashboard.ts index ccabbc0b..474898b2 100644 --- a/src/router/modules/dashboard.ts +++ b/src/router/modules/dashboard.ts @@ -7,6 +7,7 @@ const dashboard: AppRouteRecordRaw = { meta: { i18nKey: 'Dashboard', icon: 'dashboard', + order: 0, }, } diff --git a/src/router/modules/doc-local.ts b/src/router/modules/doc-local.ts index 3fff123a..e610e67b 100644 --- a/src/router/modules/doc-local.ts +++ b/src/router/modules/doc-local.ts @@ -8,6 +8,7 @@ const docLocal: AppRouteRecordRaw = { i18nKey: 'DocLocal', icon: 'doc', windowOpen: 'https://ray-template.yunkuangao.com/ray-template-doc/', + order: 6, }, } diff --git a/src/router/modules/doc.ts b/src/router/modules/doc.ts index 78cc8fd2..e8a76c62 100644 --- a/src/router/modules/doc.ts +++ b/src/router/modules/doc.ts @@ -8,6 +8,7 @@ const doc: AppRouteRecordRaw = { i18nKey: 'Doc', icon: 'doc', windowOpen: 'https://xiaodaigua-ray.github.io/ray-template-doc/', + order: 5, }, } diff --git a/src/router/modules/echart.ts b/src/router/modules/echart.ts index dafc7959..dd8c43f3 100644 --- a/src/router/modules/echart.ts +++ b/src/router/modules/echart.ts @@ -7,6 +7,7 @@ const echart: AppRouteRecordRaw = { meta: { i18nKey: 'Echart', icon: 'echart', + order: 1, }, } diff --git a/src/router/modules/multi-menu.ts b/src/router/modules/multi-menu.ts index 65867f51..dbfdff8e 100644 --- a/src/router/modules/multi-menu.ts +++ b/src/router/modules/multi-menu.ts @@ -9,6 +9,7 @@ const multiMenu: AppRouteRecordRaw = { meta: { i18nKey: 'MultiMenu', icon: 'table', + order: 4, }, children: [ { @@ -17,6 +18,7 @@ const multiMenu: AppRouteRecordRaw = { component: () => import('@/views/multi/views/multi-menu-one/index'), meta: { noLocalTitle: '多级菜单-1', + keepAlive: true, }, }, { @@ -34,7 +36,22 @@ const multiMenu: AppRouteRecordRaw = { import('@/views/multi/views/multi-menu-two/views/sub-menu/index'), meta: { noLocalTitle: '多级菜单-2-1', + keepAlive: true, }, + children: [ + { + path: 'sub-menu-one', + name: 'MultiMenuTwoOne', + component: () => + import( + '@/views/multi/views/multi-menu-two/views/sub-menu/views/multi-menu-two-one/index' + ), + meta: { + noLocalTitle: '多级菜单-2-1-1', + keepAlive: true, + }, + }, + ], }, ], }, diff --git a/src/router/modules/office.ts b/src/router/modules/office.ts index af9165d0..f98e3725 100644 --- a/src/router/modules/office.ts +++ b/src/router/modules/office.ts @@ -11,7 +11,7 @@ const office: AppRouteRecordRaw = { }, children: [ { - path: '/document', + path: 'document', name: 'Document', component: () => import('@/views/office/views/document/index'), meta: { @@ -19,7 +19,7 @@ const office: AppRouteRecordRaw = { }, }, { - path: '/presentation', + path: 'presentation', name: 'Presentation', component: () => import('@/views/office/views/presentation/index'), meta: { @@ -27,7 +27,7 @@ const office: AppRouteRecordRaw = { }, }, { - path: '/spreadsheet', + path: 'spreadsheet', name: 'Spreadsheet', component: () => import('@/views/office/views/spreadsheet/index'), meta: { diff --git a/src/router/modules/rely.ts b/src/router/modules/rely.ts index 450670ff..edb68c5a 100644 --- a/src/router/modules/rely.ts +++ b/src/router/modules/rely.ts @@ -9,10 +9,11 @@ const rely: AppRouteRecordRaw = { meta: { i18nKey: 'Rely', icon: 'rely', + order: 7, }, children: [ { - path: '/rely-about', + path: 'rely-about', name: 'RelyAbout', component: () => import('@/views/rely/views/rely-about/index'), meta: { diff --git a/src/router/modules/table.ts b/src/router/modules/table.ts index 066a691d..e1742fc6 100644 --- a/src/router/modules/table.ts +++ b/src/router/modules/table.ts @@ -7,6 +7,7 @@ const table: AppRouteRecordRaw = { meta: { i18nKey: 'Table', icon: 'table', + order: 2, }, } diff --git a/src/router/permission.ts b/src/router/permission.ts index b681f943..296afd76 100644 --- a/src/router/permission.ts +++ b/src/router/permission.ts @@ -24,16 +24,14 @@ import { getCache, setCache } from '@/utils/cache' import { useSignin } from '@/store' -import { APP_CATCH_KEY } from '@/appConfig/appConfig' +import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig' import type { Router, NavigationGuardNext } from 'vue-router' export const permissionRouter = (router: Router) => { const { beforeEach } = router - const { - rootRoute: { path }, - } = __APP_CFG__ + const { path } = ROOT_ROUTE /** 如果没有权限, 则重定向至首页 */ const redirectToDashboard = (next: NavigationGuardNext) => { diff --git a/src/router/route-module.ts b/src/router/route-module.ts deleted file mode 100644 index 8d8c5433..00000000 --- a/src/router/route-module.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { AppRouteRecordRaw } from '@/router/type' - -import dashboard from './modules/dashboard' -import reyl from './modules/rely' -import error from './modules/error' -import echart from './modules/echart' -import scrollReveal from './modules/scroll-reveal' -import axios from './modules/axios' -import table from './modules/table' -import doc from './modules/doc' -import multiMenu from './modules/multi-menu' -import docLocal from './modules/doc-local' -import office from './modules/office' - -const routes: AppRouteRecordRaw[] = [ - dashboard, - office, - echart, - table, - axios, - scrollReveal, - error, - multiMenu, - doc, - docLocal, - reyl, -] - -export default routes - -/** - * - * 弃用自动导入路由模块方式 - * - * 采用手动引入子路由模块方式 - * - * 因为自动导入路由方式在实际体验后还是有一些小问题, 综合考虑后, 还是自己手动挡吧 - */ diff --git a/src/router/routeModules.ts b/src/router/routeModules.ts new file mode 100644 index 00000000..bc091c92 --- /dev/null +++ b/src/router/routeModules.ts @@ -0,0 +1,30 @@ +/** + * + * @author Ray + * + * @date 2023-06-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** + * + * 描述: + * - 自动导入所有路由模块 + * - 平铺所有路由 + * + * modules 模块下每一个 ts 文件视为一个路由模块(route) + * 每个模块必须配置 meta 属性 + * 如果不设置 order 属性, 则会默认排在前面 + */ + +import { autoMergeRoute } from '@/router/helper/merge' +import { orderRoutes } from '@/router/helper/orderRoutes' + +import type { AppRouteRecordRaw } from '@/router/type' + +const routes: AppRouteRecordRaw[] = orderRoutes(autoMergeRoute()) + +export default routes diff --git a/src/router/routes.ts b/src/router/routes.ts index afec43cc..ddae6701 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -1,11 +1,11 @@ import Layout from '@/layout/index' -import childrenRoutes from './route-module' +import childrenRoutes from './routeModules' +import { ROOT_ROUTE } from '@/appConfig/appConfig' +import { expandRoutes } from '@/router/helper/expandRoutes' -const { - rootRoute: { path }, -} = __APP_CFG__ +const { path } = ROOT_ROUTE -export const constantRoutes = [ +export default [ { path: '/', name: 'login', @@ -16,7 +16,7 @@ export const constantRoutes = [ name: 'layout', redirect: path, component: Layout, - children: childrenRoutes, + children: expandRoutes(childrenRoutes), }, { /** 错误页面(404) */ @@ -26,9 +26,3 @@ export const constantRoutes = [ redirect: '/error', }, ] - -/** - * - * 主路由表配置 - * 例如: `login` `layout` 等 - */ diff --git a/src/router/type.ts b/src/router/type.ts index b4ee936a..ace690c9 100644 --- a/src/router/type.ts +++ b/src/router/type.ts @@ -17,6 +17,8 @@ export interface AppRouteMeta { hidden?: boolean noLocalTitle?: string | number ignoreAutoResetScroll?: boolean + order?: number + keepAlive?: boolean } // @ts-ignore @@ -29,3 +31,7 @@ export interface AppRouteRecordRaw extends Omit { props?: Recordable fullPath?: string } + +export interface AutoImportRouteModule extends Object { + default: AppRouteRecordRaw +} diff --git a/src/store/index.ts b/src/store/index.ts index 5204ce4c..c6c5deba 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -21,6 +21,7 @@ import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' export { useSetting } from './modules/setting/index' // import { useSetting } from '@/store' 即可使用 export { useMenu } from './modules/menu/index' export { useSignin } from './modules/signin/index' +export { useKeepAlive } from './modules/keep-alive/index' import type { App } from 'vue' diff --git a/src/store/modules/keep-alive/index.ts b/src/store/modules/keep-alive/index.ts new file mode 100644 index 00000000..4d1d8f4d --- /dev/null +++ b/src/store/modules/keep-alive/index.ts @@ -0,0 +1,72 @@ +/** + * + * @author Ray + * + * @date 2023-06-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +/** + * + * 缓存 + * + * 管理系统缓存 + * 基于 KeepAlive 组件实现 + * 依赖 APP_KEEP_ALIVE 配置 + */ + +import { APP_KEEP_ALIVE } from '@/appConfig/appConfig' + +import type { KeepAliveStoreState } from './type' + +export const useKeepAlive = defineStore( + 'keepAlive', + () => { + const { maxKeepAliveLength } = APP_KEEP_ALIVE + + const state = reactive({ + keepAliveInclude: [], + }) + + const getCurrentKeepAliveLength = () => state.keepAliveInclude.length + + /** + * + * @param option current menu option + * + * @remark 判断当前页面是否配置需要缓存, 并且判断当前缓存数量是否超过最大缓存数设置数量 + * @remark 如果超过最大阈值, 则会按照尾插头删方式维护该队列 + */ + const setKeepAliveInclude = (option: IMenuOptions) => { + const length = getCurrentKeepAliveLength() + const { + name, + meta: { keepAlive }, + } = option + + if (keepAlive) { + if (length >= maxKeepAliveLength) { + state.keepAliveInclude.splice(0, 1) + state.keepAliveInclude.push(name) + } else { + state.keepAliveInclude.push(name) + } + } + } + + return { + ...toRefs(state), + setKeepAliveInclude, + } + }, + { + persist: { + key: 'piniaKeepAliveStore', + storage: window.sessionStorage, + paths: ['keepAliveInclude'], + }, + }, +) diff --git a/src/store/modules/keep-alive/type.ts b/src/store/modules/keep-alive/type.ts new file mode 100644 index 00000000..2c99f3ec --- /dev/null +++ b/src/store/modules/keep-alive/type.ts @@ -0,0 +1,3 @@ +export interface KeepAliveStoreState { + keepAliveInclude: string[] +} diff --git a/src/store/modules/menu/index.ts b/src/store/modules/menu/index.ts index 4fb41907..e644e741 100644 --- a/src/store/modules/menu/index.ts +++ b/src/store/modules/menu/index.ts @@ -29,10 +29,12 @@ import { getCache, setCache } from '@/utils/cache' import { validRole } from '@/router/basic' import { parse, matchMenuOption, updateDocumentTitle } from './helper' import { useI18n } from '@/locales/useI18n' -import { MENU_COLLAPSED_CONFIG } from '@/appConfig/appConfig' +import { MENU_COLLAPSED_CONFIG, ROOT_ROUTE } from '@/appConfig/appConfig' +import routeModules from '@/router/routeModules' +import { useKeepAlive } from '@/store' import type { MenuOption } from 'naive-ui' -import type { RouteMeta } from 'vue-router' +import type { AppRouteMeta } from '@/router/type' export const useMenu = defineStore( 'menu', @@ -40,10 +42,9 @@ export const useMenu = defineStore( const router = useRouter() const route = useRoute() const { t } = useI18n() + const { setKeepAliveInclude } = useKeepAlive() - const { - rootRoute: { path }, - } = __APP_CFG__ + const { path } = ROOT_ROUTE const cacheMenuKey = getCache('menuKey') === 'no' ? path : getCache('menuKey') @@ -81,7 +82,7 @@ export const useMenu = defineStore( * @remark 修改后, 缓存当前选择 key 并且存储标签页与跳转页面(router push 操作) */ const menuModelValueChange = (key: string | number, item: MenuOption) => { - const meta = item.meta as RouteMeta + const meta = item.meta as AppRouteMeta if (meta.windowOpen) { window.open(meta.windowOpen) @@ -94,6 +95,7 @@ export const useMenu = defineStore( menuState.menuTagOptions, ) updateDocumentTitle(item as unknown as IMenuOptions) + setKeepAliveInclude(item as unknown as IMenuOptions) menuState.breadcrumbOptions = parse(menuState.options, 'key', key) // 获取面包屑 @@ -166,9 +168,6 @@ export const useMenu = defineStore( * @remark 如果权限发生变动, 则会触发强制弹出页面并且重新登陆 */ const setupAppRoutes = () => { - /** 取出所有 layout 下子路由 */ - const layout = router.getRoutes().find((route) => route.name === 'layout') - const resolveOption = (option: IMenuOptions) => { const { meta } = option @@ -231,7 +230,7 @@ export const useMenu = defineStore( } /** 缓存菜单列表 */ - menuState.options = resolveRoutes(layout?.children as IMenuOptions[], 0) + menuState.options = resolveRoutes(routeModules as IMenuOptions[], 0) /** 初始化后渲染面包屑 */ nextTick(() => { diff --git a/src/types/appConfig.ts b/src/types/appConfig.ts index 0e854e57..844e4137 100644 --- a/src/types/appConfig.ts +++ b/src/types/appConfig.ts @@ -6,3 +6,9 @@ export interface MenuCollapsedConfig { MENU_COLLAPSED_ICON_SIZE: number MENU_COLLAPSED_INDENT: number } + +export interface AppKeepAlive { + setupKeepAlive: boolean + keepAliveExclude?: string[] + maxKeepAliveLength: number +} diff --git a/src/types/cfg.ts b/src/types/cfg.ts index e2bce986..d61c1503 100644 --- a/src/types/cfg.ts +++ b/src/types/cfg.ts @@ -44,7 +44,6 @@ export interface Config { copyright?: LayoutCopyright sideBarLogo?: LayoutSideBarLogo mixinCSS?: string - rootRoute?: RootRoute preloadingConfig?: PreloadingConfig base?: string appPrimaryColor?: AppPrimaryColor @@ -70,7 +69,6 @@ export interface AppConfig { copyright?: LayoutCopyright sideBarLogo?: LayoutSideBarLogo } - rootRoute: RootRoute base?: string appPrimaryColor: AppPrimaryColor } diff --git a/src/types/store.d.ts b/src/types/store.d.ts index 8580f81e..044509db 100644 --- a/src/types/store.d.ts +++ b/src/types/store.d.ts @@ -3,7 +3,7 @@ export {} import type { RouteRecordRaw, RouteMeta } from 'vue-router' import type { MenuOption } from 'naive-ui' import type { VNode } from 'vue' -import type { AppRouteRecordRaw } from '@/router/type' +import type { AppRouteRecordRaw, AppRouteMeta } from '@/router/type' declare global { declare interface IMenuOptions extends AppRouteRecordRaw, MenuOption { @@ -13,7 +13,7 @@ declare global { label: string | Function show?: boolean children?: IMenuOptions[] - meta?: RouteMeta + meta: AppRouteMeta breadcrumbLabel?: string noLocalTitle?: string | number } diff --git a/src/utils/hook.ts b/src/utils/hook.ts index fe23aeef..553b5326 100644 --- a/src/utils/hook.ts +++ b/src/utils/hook.ts @@ -81,3 +81,10 @@ export const uuid = (length = 16, radix?: number) => { return arr.join('') } + +// // lodash 将字符串转为大驼峰 +// export const toCamelCase = (str: string) => { +// return str.replace(/-(\w)/g, (all, letter) => { +// return letter.toUpperCase() +// }) +// } diff --git a/src/views/error/index.tsx b/src/views/error/index.tsx index b0a35dd8..4251b2ef 100644 --- a/src/views/error/index.tsx +++ b/src/views/error/index.tsx @@ -1,14 +1,15 @@ import './index.scss' + import { NResult, NButton } from 'naive-ui' +import { ROOT_ROUTE } from '@/appConfig/appConfig' + const ErrorPage = defineComponent({ name: 'ErrorPage', setup() { const router = useRouter() - const { - rootRoute: { path }, - } = __APP_CFG__ + const { path } = ROOT_ROUTE const handleBack = () => { router.push(path) diff --git a/src/views/login/components/Signin/index.tsx b/src/views/login/components/Signin/index.tsx index ae6bf8e5..98285f8b 100644 --- a/src/views/login/components/Signin/index.tsx +++ b/src/views/login/components/Signin/index.tsx @@ -4,7 +4,7 @@ import { setCache } from '@/utils/cache' import { useSpin } from '@/spin' import { useSignin } from '@/store' import { useI18n } from '@/locales/useI18n' -import { APP_CATCH_KEY } from '@/appConfig/appConfig' +import { APP_CATCH_KEY, ROOT_ROUTE } from '@/appConfig/appConfig' import type { FormInst } from 'naive-ui' @@ -17,9 +17,7 @@ const Signin = defineComponent({ const signinStore = useSignin() const { signin } = signinStore - const { - rootRoute: { path }, - } = __APP_CFG__ + const { path } = ROOT_ROUTE const useSigninForm = () => ({ name: 'Ray Admin', diff --git a/src/views/multi/views/multi-menu-one/index.tsx b/src/views/multi/views/multi-menu-one/index.tsx index bf476cfd..f4f6983d 100644 --- a/src/views/multi/views/multi-menu-one/index.tsx +++ b/src/views/multi/views/multi-menu-one/index.tsx @@ -9,13 +9,24 @@ * @remark 今天也是元气满满撸代码的一天 */ +import { NInput } from 'naive-ui' + const MultiMenuOne = defineComponent({ name: 'MultiMenuOne', setup() { - return {} + const inputValue = ref(null) + + return { + inputValue, + } }, render() { - return
多级菜单-1
+ return ( +
+ 多级菜单-1 + +
+ ) }, }) diff --git a/src/views/multi/views/multi-menu-two/views/sub-menu/index.tsx b/src/views/multi/views/multi-menu-two/views/sub-menu/index.tsx index 5550f387..5c9d6e78 100644 --- a/src/views/multi/views/multi-menu-two/views/sub-menu/index.tsx +++ b/src/views/multi/views/multi-menu-two/views/sub-menu/index.tsx @@ -9,13 +9,24 @@ * @remark 今天也是元气满满撸代码的一天 */ +import { NInput } from 'naive-ui' + const SubMenu = defineComponent({ name: 'SubMenu', setup() { - return {} + const inputValue = ref(null) + + return { + inputValue, + } }, render() { - return
多级菜单-2-1
+ return ( +
+ 多级菜单-2-1 + +
+ ) }, }) diff --git a/src/views/multi/views/multi-menu-two/views/sub-menu/views/multi-menu-two-one/index.tsx b/src/views/multi/views/multi-menu-two/views/sub-menu/views/multi-menu-two-one/index.tsx new file mode 100644 index 00000000..01848989 --- /dev/null +++ b/src/views/multi/views/multi-menu-two/views/sub-menu/views/multi-menu-two-one/index.tsx @@ -0,0 +1,33 @@ +/** + * + * @author Ray + * + * @date 2023-03-01 + * + * @workspace ray-template + * + * @remark 今天也是元气满满撸代码的一天 + */ + +import { NInput } from 'naive-ui' + +const MultiMenuTwoOne = defineComponent({ + name: 'MultiMenuTwoOne', + setup() { + const inputValue = ref(null) + + return { + inputValue, + } + }, + render() { + return ( +
+ 多级菜单2-1-1 + +
+ ) + }, +}) + +export default MultiMenuTwoOne diff --git a/vite.config.ts b/vite.config.ts index 7e9dbcbb..cbbc6d1a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -32,7 +32,6 @@ const { copyright, sideBarLogo, mixinCSS, - rootRoute, appPrimaryColor, preloadingConfig, base, @@ -54,7 +53,6 @@ const __APP_CFG__ = { copyright, sideBarLogo, }, - rootRoute, appPrimaryColor, }