mirror of
https://github.com/XiaoDaiGua-Ray/ray-template.git
synced 2025-04-05 19:42:07 +08:00
v3.3.0 发布
This commit is contained in:
parent
d0847ee6f9
commit
49725bbed5
16
CHANGELOG.md
16
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
|
||||
|
||||
### 特征
|
||||
|
21
COMMONPROBLEM.md
Normal file
21
COMMONPROBLEM.md
Normal file
@ -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) 方法,首选该方法作为国际化语言切换方法。
|
@ -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)
|
||||
|
||||
## 功能
|
||||
|
||||
- 主题切换
|
||||
|
5
cfg.ts
5
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,
|
||||
/**
|
||||
*
|
||||
|
@ -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<AppKeepAlive> = {
|
||||
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<RootRoute> = {
|
||||
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<MenuCollapsedConfig> = {
|
||||
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
|
||||
|
@ -6,12 +6,23 @@
|
||||
:mode="transitionMode"
|
||||
:appear="transitionAppear"
|
||||
>
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
<keep-alive
|
||||
v-if="setupKeepAlive"
|
||||
:max="maxKeepAliveLength"
|
||||
:include="keepAliveInclude"
|
||||
:exclude="keepAliveExclude"
|
||||
>
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
<component :is="Component" v-else :key="route.fullPath" />
|
||||
</transition>
|
||||
</template>
|
||||
</router-view>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { useKeepAlive } from '@/store'
|
||||
import { APP_KEEP_ALIVE } from '@/appConfig/appConfig'
|
||||
|
||||
import type { PropType } from 'vue'
|
||||
|
||||
defineProps({
|
||||
@ -28,8 +39,8 @@ defineProps({
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
// 带过渡动画 `RouterView` 组件
|
||||
// 如果子路由需要做动画切换,则需要此组件
|
||||
// 为什么必须为 `vue` 文件,因为 `tsx` 文件在解析的时候会抛出警告不好看
|
||||
// 只需要像使用 `RouterView` 组件时一样使用即可, 但是不能对于子路由生效, 所以需要在子路由显示的地方替换 `RouterView` 组件
|
||||
|
||||
const keepAliveStore = useKeepAlive()
|
||||
const { keepAliveInclude } = storeToRefs(keepAliveStore)
|
||||
const { setupKeepAlive, maxKeepAliveLength, keepAliveExclude } = APP_KEEP_ALIVE
|
||||
</script>
|
||||
|
@ -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 // 当前右键标签页索引位置
|
||||
|
@ -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) {
|
||||
|
@ -7,7 +7,7 @@
|
||||
"scrollReveal": "Scroll Reveal",
|
||||
"Axios": "Axios Request",
|
||||
"Table": "Table",
|
||||
"MultiMenu": "MultiMenu",
|
||||
"MultiMenu": "MultiMenu(catch)",
|
||||
"Doc": "Doc",
|
||||
"DocLocal": "Doc (China)",
|
||||
"Office": "Office",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"scrollReveal": "滚动动画",
|
||||
"Axios": "请求",
|
||||
"Table": "表格",
|
||||
"MultiMenu": "多级菜单",
|
||||
"MultiMenu": "多级菜单(缓存)",
|
||||
"Doc": "文档",
|
||||
"DocLocal": "文档 (国内地址)",
|
||||
"Office": "办公",
|
||||
|
@ -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 启用才有效)
|
||||
```
|
||||
|
78
src/router/helper/expandRoutes.ts
Normal file
78
src/router/helper/expandRoutes.ts
Normal file
@ -0,0 +1,78 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-Ray>
|
||||
*
|
||||
* @date 2023-06-01
|
||||
*
|
||||
* @workspace ray-template
|
||||
*
|
||||
* @remark 今天也是元气满满撸代码的一天
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* 该功能基于 <https://me.yka.moe/> 代码改进实现
|
||||
* 自动展开所有路由
|
||||
*/
|
||||
|
||||
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))
|
||||
}
|
34
src/router/helper/merge.ts
Normal file
34
src/router/helper/merge.ts
Normal file
@ -0,0 +1,34 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-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
|
||||
}
|
36
src/router/helper/orderRoutes.ts
Normal file
36
src/router/helper/orderRoutes.ts
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-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!')
|
||||
}
|
||||
})
|
||||
}
|
@ -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'
|
||||
|
@ -7,6 +7,8 @@ const axios: AppRouteRecordRaw = {
|
||||
meta: {
|
||||
i18nKey: 'Axios',
|
||||
icon: 'axios',
|
||||
order: 3,
|
||||
keepAlive: true,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ const dashboard: AppRouteRecordRaw = {
|
||||
meta: {
|
||||
i18nKey: 'Dashboard',
|
||||
icon: 'dashboard',
|
||||
order: 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ const docLocal: AppRouteRecordRaw = {
|
||||
i18nKey: 'DocLocal',
|
||||
icon: 'doc',
|
||||
windowOpen: 'https://ray-template.yunkuangao.com/ray-template-doc/',
|
||||
order: 6,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ const doc: AppRouteRecordRaw = {
|
||||
i18nKey: 'Doc',
|
||||
icon: 'doc',
|
||||
windowOpen: 'https://xiaodaigua-ray.github.io/ray-template-doc/',
|
||||
order: 5,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ const echart: AppRouteRecordRaw = {
|
||||
meta: {
|
||||
i18nKey: 'Echart',
|
||||
icon: 'echart',
|
||||
order: 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -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: {
|
||||
|
@ -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: {
|
||||
|
@ -7,6 +7,7 @@ const table: AppRouteRecordRaw = {
|
||||
meta: {
|
||||
i18nKey: 'Table',
|
||||
icon: 'table',
|
||||
order: 2,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
||||
/**
|
||||
*
|
||||
* 弃用自动导入路由模块方式
|
||||
*
|
||||
* 采用手动引入子路由模块方式
|
||||
*
|
||||
* 因为自动导入路由方式在实际体验后还是有一些小问题, 综合考虑后, 还是自己手动挡吧
|
||||
*/
|
30
src/router/routeModules.ts
Normal file
30
src/router/routeModules.ts
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-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
|
@ -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` 等
|
||||
*/
|
||||
|
@ -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<RouteRecordRaw, 'meta'> {
|
||||
props?: Recordable
|
||||
fullPath?: string
|
||||
}
|
||||
|
||||
export interface AutoImportRouteModule extends Object {
|
||||
default: AppRouteRecordRaw
|
||||
}
|
||||
|
@ -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'
|
||||
|
||||
|
72
src/store/modules/keep-alive/index.ts
Normal file
72
src/store/modules/keep-alive/index.ts
Normal file
@ -0,0 +1,72 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-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<KeepAliveStoreState>({
|
||||
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'],
|
||||
},
|
||||
},
|
||||
)
|
3
src/store/modules/keep-alive/type.ts
Normal file
3
src/store/modules/keep-alive/type.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export interface KeepAliveStoreState {
|
||||
keepAliveInclude: string[]
|
||||
}
|
@ -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(() => {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
4
src/types/store.d.ts
vendored
4
src/types/store.d.ts
vendored
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
// })
|
||||
// }
|
||||
|
@ -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)
|
||||
|
@ -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',
|
||||
|
@ -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 <div>多级菜单-1</div>
|
||||
return (
|
||||
<div>
|
||||
多级菜单-1
|
||||
<NInput v-model={this.inputValue} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -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 <div>多级菜单-2-1</div>
|
||||
return (
|
||||
<div>
|
||||
多级菜单-2-1
|
||||
<NInput v-model={this.inputValue} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
*
|
||||
* @author Ray <https://github.com/XiaoDaiGua-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 (
|
||||
<div>
|
||||
多级菜单2-1-1
|
||||
<NInput v-model={this.inputValue} />
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export default MultiMenuTwoOne
|
@ -32,7 +32,6 @@ const {
|
||||
copyright,
|
||||
sideBarLogo,
|
||||
mixinCSS,
|
||||
rootRoute,
|
||||
appPrimaryColor,
|
||||
preloadingConfig,
|
||||
base,
|
||||
@ -54,7 +53,6 @@ const __APP_CFG__ = {
|
||||
copyright,
|
||||
sideBarLogo,
|
||||
},
|
||||
rootRoute,
|
||||
appPrimaryColor,
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user