diff --git a/CHANGELOG.md b/CHANGELOG.md index 5463de4b..11a18ad5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,7 +16,7 @@ - 🎯 优化 去掉开发环境 i18n 控制台警告,页面代码:[i18n/index.ts](https://gitee.com/lyt-top/vue-next-admin/blob/master/src/i18n/index.ts) - 🎯 优化 `NextLoading.start()` 方法,防止第一次进入界面时出现短暂空白 - 🎯 优化 地址栏有参数退出登录,再次登录不跳之前界面问题 `src/layout/navBars/breadcrumb/user.vue` -- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题 +- 🎯 优化 `SvgIcon` 组件,防止 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题,工作流不可连线问题 - 🎯 优化 [wangEditor](https://www.wangeditor.com/) 更新到 v5,[vue3 版本线上示例中 wangeditor 富文本编辑器 demo 实例,无法换行#I5565B](https://gitee.com/lyt-top/vue-next-admin/issues/I5565B),感谢@[jenchih](https://gitee.com/jenchih) - 🎯 优化 [在关闭 tagview 时,高度刷新时会会变化,出现滚动条](https://gitee.com/lyt-top/vue-next-admin/issues/I55FHM),感谢[张松](https://gitee.com/zs310071113) - 🎉 新增 [vuex](https://vuex.vuejs.org/) 替换成 [pinia](https://pinia.vuejs.org/getting-started.html) @@ -31,6 +31,7 @@ - 🐞 修复 `开启 Tagsview 图标` 时,`tagsView 右键菜单关闭` 报错问题 - 🐞 修复 `router.push` 路径找不到时报错问题,`404、401 界面` 已移入到 `main` 主布局里(之前全屏) - 🐞 修复 [全局修改组件大小失效了](https://gitee.com/lyt-top/vue-next-admin/issues/I551RP),感谢[lg_boy](https://gitee.com/lg_boy) +- 🌈 重构 路由(`/src/router/index.ts`)解决 No match found for location with path "xxx"(前端控制,后端控制未解决) 问题 ## 2.0.2 diff --git a/src/components/svgIcon/index.vue b/src/components/svgIcon/index.vue index 430d2426..74b101a8 100644 --- a/src/components/svgIcon/index.vue +++ b/src/components/svgIcon/index.vue @@ -1,11 +1,9 @@ - - diff --git a/src/i18n/lang/en.ts b/src/i18n/lang/en.ts index 6647f957..46ae8302 100644 --- a/src/i18n/lang/en.ts +++ b/src/i18n/lang/en.ts @@ -105,7 +105,6 @@ export default { logOutConfirm: 'determine', logOutCancel: 'cancel', logOutExit: 'Exiting', - logOutSuccess: 'Exit successfully!', }, tagsView: { refresh: 'refresh', diff --git a/src/i18n/lang/zh-cn.ts b/src/i18n/lang/zh-cn.ts index 38fc84a4..79ef328d 100644 --- a/src/i18n/lang/zh-cn.ts +++ b/src/i18n/lang/zh-cn.ts @@ -105,7 +105,6 @@ export default { logOutConfirm: '确定', logOutCancel: '取消', logOutExit: '退出中', - logOutSuccess: '安全退出成功!', }, tagsView: { refresh: '刷新', diff --git a/src/i18n/lang/zh-tw.ts b/src/i18n/lang/zh-tw.ts index 18edb1cd..d900abb5 100644 --- a/src/i18n/lang/zh-tw.ts +++ b/src/i18n/lang/zh-tw.ts @@ -105,7 +105,6 @@ export default { logOutConfirm: '確定', logOutCancel: '取消', logOutExit: '退出中', - logOutSuccess: '安全退出成功!', }, tagsView: { refresh: '重繪', diff --git a/src/layout/navBars/breadcrumb/user.vue b/src/layout/navBars/breadcrumb/user.vue index ac1fe5d2..b307311b 100644 --- a/src/layout/navBars/breadcrumb/user.vue +++ b/src/layout/navBars/breadcrumb/user.vue @@ -82,7 +82,6 @@ import { useRouter } from 'vue-router'; import { ElMessageBox, ElMessage } from 'element-plus'; import screenfull from 'screenfull'; import { useI18n } from 'vue-i18n'; -import { resetRoute } from '/@/router/index'; import { storeToRefs } from 'pinia'; import { useUserInfo } from '/@/stores/userInfo'; import { useThemeConfig } from '/@/stores/themeConfig'; @@ -162,8 +161,7 @@ export default defineComponent({ }) .then(async () => { Session.clear(); // 清除缓存/token等 - await resetRoute(); // 删除/重置路由 - ElMessage.success(t('message.user.logOutSuccess')); + // 使用 reload 时,不需要调用 resetRoute() 重置路由 window.location.reload(); }) .catch(() => {}); diff --git a/src/router/backEnd.ts b/src/router/backEnd.ts index fcae0055..5355fb84 100644 --- a/src/router/backEnd.ts +++ b/src/router/backEnd.ts @@ -1,15 +1,23 @@ +import { RouteRecordRaw } from 'vue-router'; +import { storeToRefs } from 'pinia'; +import pinia from '/@/stores/index'; import { useUserInfo } from '/@/stores/userInfo'; import { useRequestOldRoutes } from '/@/stores/requestOldRoutes'; import { Session } from '/@/utils/storage'; import { NextLoading } from '/@/utils/loading'; -import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index'; -import { dynamicRoutes } from '/@/router/route'; +import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route'; +import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index'; +import { useRoutesList } from '/@/stores/routesList'; +import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; import { useMenuApi } from '/@/api/menu/index'; const menuApi = useMenuApi(); const layouModules: any = import.meta.glob('../layout/routerView/*.{vue,tsx}'); const viewsModules: any = import.meta.glob('../views/**/*.{vue,tsx}'); + +// 后端控制路由 + /** * 获取目录下的 .vue、.tsx 全部文件 * @method import.meta.glob @@ -21,7 +29,7 @@ const dynamicViewsModules: Record = Object.assign({}, { ...lay * 后端控制路由:初始化方法,防止刷新时路由丢失 * @method NextLoading 界面 loading 动画开始执行 * @method useUserInfo().setUserInfos() 触发初始化用户信息 pinia - * @method useRequestOldRoutes().setCacheKeepAlive() 存储接口原始路由(未处理component),根据需求选择使用 + * @method useRequestOldRoutes().setRequestOldRoutes() 存储接口原始路由(未处理component),根据需求选择使用 * @method setAddRoute 添加动态路由 * @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 */ @@ -36,13 +44,59 @@ export async function initBackEndControlRoutes() { // 获取路由菜单数据 const res = await getBackEndControlRoutes(); // 存储接口原始路由(未处理component),根据需求选择使用 - useRequestOldRoutes().setCacheKeepAlive(JSON.parse(JSON.stringify(res.data))); + useRequestOldRoutes().setRequestOldRoutes(JSON.parse(JSON.stringify(res.data))); // 处理路由(component),替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由 dynamicRoutes[0].children = await backEndComponent(res.data); // 添加动态路由 await setAddRoute(); - // 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 - setFilterMenuAndCacheTagsViewRoutes(); + // // 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 + await setFilterMenuAndCacheTagsViewRoutes(); +} + +/** + * 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 + * @description 用于左侧菜单、横向菜单的显示 + * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) + */ +export function setFilterMenuAndCacheTagsViewRoutes() { + const storesRoutesList = useRoutesList(pinia); + storesRoutesList.setRoutesList(dynamicRoutes[0].children as any); + setCacheTagsViewRoutes(); +} + +/** + * 缓存多级嵌套数组处理后的一维数组 + * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) + */ +export function setCacheTagsViewRoutes() { + // 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示 + const storesTagsView = useTagsViewRoutes(pinia); + // 添加到 pinia setTagsViewRoutes 中 + storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes))[0].children); +} + +/** + * 获取有当前用户权限标识的路由数组,进行对原路由的替换 + * @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由 + * @returns 返回替换后的路由数组 + */ +export function setFilterRouteEnd() { + // console.log(dynamicRoutes); + let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)); + filterRouteEnd[0].children = [...filterRouteEnd[0].children, ...notFoundAndNoPower]; + return filterRouteEnd; +} + +/** + * 添加动态路由 + * @method router.addRoute + * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套 + * @link 参考:https://next.router.vuejs.org/zh/api/#addroute + */ +export async function setAddRoute() { + await setFilterRouteEnd().forEach((route: RouteRecordRaw) => { + router.addRoute(route); + }); } /** @@ -52,8 +106,9 @@ export async function initBackEndControlRoutes() { */ export function getBackEndControlRoutes() { // 模拟 admin 与 test - const stores = useUserInfo(); - const auth = stores.userInfos.roles[0]; + const stores = useUserInfo(pinia); + const { userInfos } = storeToRefs(stores); + const auth = userInfos.value.roles[0]; // 管理员 admin if (auth === 'admin') return menuApi.getMenuAdmin(); // 其它用户 test diff --git a/src/router/frontEnd.ts b/src/router/frontEnd.ts index 52941f53..8877514a 100644 --- a/src/router/frontEnd.ts +++ b/src/router/frontEnd.ts @@ -1,8 +1,15 @@ +import { RouteRecordRaw } from 'vue-router'; +import { storeToRefs } from 'pinia'; +import { formatTwoStageRoutes, formatFlatteningRoutes, router } from '/@/router/index'; +import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route'; import pinia from '/@/stores/index'; -import { useUserInfo } from '/@/stores/userInfo'; import { Session } from '/@/utils/storage'; +import { useUserInfo } from '/@/stores/userInfo'; +import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; +import { useRoutesList } from '/@/stores/routesList'; import { NextLoading } from '/@/utils/loading'; -import { setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index'; + +// 前端控制路由 /** * 前端控制路由:初始化方法,防止刷新时路由丢失 @@ -23,3 +30,118 @@ export async function initFrontEndControlRoutes() { // 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 await setFilterMenuAndCacheTagsViewRoutes(); } + +/** + * 添加动态路由 + * @method router.addRoute + * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套 + * @link 参考:https://next.router.vuejs.org/zh/api/#addroute + */ +export async function setAddRoute() { + await setFilterRouteEnd().forEach((route: RouteRecordRaw) => { + router.addRoute(route); + }); +} + +/** + * 删除/重置路由 + * @method router.removeRoute + * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套 + * @link 参考:https://next.router.vuejs.org/zh/api/#push + */ +export async function frontEndsResetRoute() { + await setFilterRouteEnd().forEach((route: RouteRecordRaw) => { + const routeName: any = route.name; + router.hasRoute(routeName) && router.removeRoute(routeName); + }); +} + +/** + * 获取有当前用户权限标识的路由数组,进行对原路由的替换 + * @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由 + * @returns 返回替换后的路由数组 + */ +export function setFilterRouteEnd() { + let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)); + filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower]; + return filterRouteEnd; +} + +/** + * 获取当前用户权限标识去比对路由表(未处理成多级嵌套路由) + * @description 这里主要用于动态路由的添加,router.addRoute + * @link 参考:https://next.router.vuejs.org/zh/api/#addroute + * @param chil dynamicRoutes(/@/router/route)第一个顶级 children 的下路由集合 + * @returns 返回有当前用户权限标识的路由数组 + */ +export function setFilterRoute(chil: any) { + const stores = useUserInfo(pinia); + const { userInfos } = storeToRefs(stores); + let filterRoute: any = []; + chil.forEach((route: any) => { + if (route.meta.roles) { + route.meta.roles.forEach((metaRoles: any) => { + userInfos.value.roles.forEach((roles: any) => { + if (metaRoles === roles) filterRoute.push({ ...route }); + }); + }); + } + }); + return filterRoute; +} + +/** + * 缓存多级嵌套数组处理后的一维数组 + * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) + */ +export function setCacheTagsViewRoutes() { + // 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示 + const stores = useUserInfo(pinia); + const storesTagsView = useTagsViewRoutes(pinia); + const { userInfos } = storeToRefs(stores); + let rolesRoutes = setFilterHasRolesMenu(dynamicRoutes, userInfos.value.roles); + // 添加到 pinia setTagsViewRoutes 中 + storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children); +} + +/** + * 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 + * @description 用于左侧菜单、横向菜单的显示 + * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) + */ +export function setFilterMenuAndCacheTagsViewRoutes() { + const stores = useUserInfo(pinia); + const storesRoutesList = useRoutesList(pinia); + const { userInfos } = storeToRefs(stores); + storesRoutesList.setRoutesList(setFilterHasRolesMenu(dynamicRoutes[0].children, userInfos.value.roles)); + setCacheTagsViewRoutes(); +} + +/** + * 判断路由 `meta.roles` 中是否包含当前登录用户权限字段 + * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组 + * @param route 当前循环时的路由项 + * @returns 返回对比后有权限的路由项 + */ +export function hasRoles(roles: any, route: any) { + if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role)); + else return true; +} + +/** + * 获取当前用户权限标识去比对路由表,设置递归过滤有权限的路由 + * @param routes 当前路由 children + * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组 + * @returns 返回有权限的路由数组 `meta.roles` 中控制 + */ +export function setFilterHasRolesMenu(routes: any, roles: any) { + const menu: any = []; + routes.forEach((route: any) => { + const item = { ...route }; + if (hasRoles(roles, item)) { + if (item.children) item.children = setFilterHasRolesMenu(item.children, roles); + menu.push(item); + } + }); + return menu; +} diff --git a/src/router/index.ts b/src/router/index.ts index e7ed858d..4a51bb61 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,53 +1,41 @@ -import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'; +import { createRouter, createWebHashHistory } from 'vue-router'; import NProgress from 'nprogress'; import 'nprogress/nprogress.css'; import pinia from '/@/stores/index'; import { storeToRefs } from 'pinia'; -import { useUserInfo } from '/@/stores/userInfo'; import { useKeepALiveNames } from '/@/stores/keepAliveNames'; -import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes'; import { useRoutesList } from '/@/stores/routesList'; import { useThemeConfig } from '/@/stores/themeConfig'; import { Session } from '/@/utils/storage'; -import { staticRoutes, dynamicRoutes } from '/@/router/route'; +import { staticRoutes } from '/@/router/route'; import { initFrontEndControlRoutes } from '/@/router/frontEnd'; import { initBackEndControlRoutes } from '/@/router/backEnd'; +/** + * 1、前端控制路由时:isRequestRoutes 为 false,需要写 roles,需要走 setFilterRoute 方法。 + * 2、后端控制路由时:isRequestRoutes 为 true,不需要写 roles,不需要走 setFilterRoute 方法), + * 相关方法已拆解到对应的 `backEnd.ts` 与 `frontEnd.ts`(他们互不影响,不需要同时改 2 个文件)。 + * 特别说明: + * 1、前端控制:路由菜单由前端去写(无菜单管理界面,有角色管理界面),角色管理中有 roles 属性,需返回到 userInfo 中。 + * 2、后端控制:路由菜单由后端返回(有菜单管理界面、有角色管理界面) + */ + +// 读取 `/src/stores/themeConfig.ts` 是否开启后端控制路由配置 +const storesThemeConfig = useThemeConfig(pinia); +const { themeConfig } = storeToRefs(storesThemeConfig); +const { isRequestRoutes } = themeConfig.value; +if (isRequestRoutes) staticRoutes.splice(0, 1); + /** * 创建一个可以被 Vue 应用程序使用的路由实例 * @method createRouter(options: RouterOptions): Router * @link 参考:https://next.router.vuejs.org/zh/api/#createrouter */ -const router = createRouter({ +export const router = createRouter({ history: createWebHashHistory(), routes: staticRoutes, }); -/** - * 定义404、401界面 - * @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify - */ -const notFoundAndNoPower = [ - { - path: '/:path(.*)*', - name: 'notFound', - component: () => import('/@/views/error/404.vue'), - meta: { - title: 'message.staticRoutes.notFound', - isHide: true, - }, - }, - { - path: '/401', - name: 'noPower', - component: () => import('/@/views/error/401.vue'), - meta: { - title: 'message.staticRoutes.noPower', - isHide: true, - }, - }, -]; - /** * 路由多级嵌套数组处理成一维数组 * @param arr 传入路由菜单数据数组 @@ -97,127 +85,7 @@ export function formatTwoStageRoutes(arr: any) { return newArr; } -/** - * 缓存多级嵌套数组处理后的一维数组 - * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) - */ -export function setCacheTagsViewRoutes() { - // 获取有权限的路由,否则 tagsView、菜单搜索中无权限的路由也将显示 - const stores = useUserInfo(pinia); - const storesTagsView = useTagsViewRoutes(pinia); - const { userInfos } = storeToRefs(stores); - let rolesRoutes = setFilterHasRolesMenu(dynamicRoutes, userInfos.value.roles); - // 添加到 pinia setTagsViewRoutes 中 - storesTagsView.setTagsViewRoutes(formatTwoStageRoutes(formatFlatteningRoutes(rolesRoutes))[0].children); -} - -/** - * 判断路由 `meta.roles` 中是否包含当前登录用户权限字段 - * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组 - * @param route 当前循环时的路由项 - * @returns 返回对比后有权限的路由项 - */ -export function hasRoles(roles: any, route: any) { - if (route.meta && route.meta.roles) return roles.some((role: any) => route.meta.roles.includes(role)); - else return true; -} - -/** - * 获取当前用户权限标识去比对路由表,设置递归过滤有权限的路由 - * @param routes 当前路由 children - * @param roles 用户权限标识,在 userInfos(用户信息)的 roles(登录页登录时缓存到浏览器)数组 - * @returns 返回有权限的路由数组 `meta.roles` 中控制 - */ -export function setFilterHasRolesMenu(routes: any, roles: any) { - const menu: any = []; - routes.forEach((route: any) => { - const item = { ...route }; - if (hasRoles(roles, item)) { - if (item.children) item.children = setFilterHasRolesMenu(item.children, roles); - menu.push(item); - } - }); - return menu; -} - -/** - * 设置递归过滤有权限的路由到 vuex routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组 - * @description 用于左侧菜单、横向菜单的显示 - * @description 用于 tagsView、菜单搜索中:未过滤隐藏的(isHide) - */ -export function setFilterMenuAndCacheTagsViewRoutes() { - const stores = useUserInfo(pinia); - const storesRoutesList = useRoutesList(pinia); - const { userInfos } = storeToRefs(stores); - storesRoutesList.setRoutesList(setFilterHasRolesMenu(dynamicRoutes[0].children, userInfos.value.roles)); - setCacheTagsViewRoutes(); -} - -/** - * 获取当前用户权限标识去比对路由表(未处理成多级嵌套路由) - * @description 这里主要用于动态路由的添加,router.addRoute - * @link 参考:https://next.router.vuejs.org/zh/api/#addroute - * @param chil dynamicRoutes(/@/router/route)第一个顶级 children 的下路由集合 - * @returns 返回有当前用户权限标识的路由数组 - */ -export function setFilterRoute(chil: any) { - const stores = useUserInfo(pinia); - const { userInfos } = storeToRefs(stores); - let filterRoute: any = []; - chil.forEach((route: any) => { - if (route.meta.roles) { - route.meta.roles.forEach((metaRoles: any) => { - userInfos.value.roles.forEach((roles: any) => { - if (metaRoles === roles) filterRoute.push({ ...route }); - }); - }); - } - }); - return filterRoute; -} - -/** - * 获取有当前用户权限标识的路由数组,进行对原路由的替换 - * @description 替换 dynamicRoutes(/@/router/route)第一个顶级 children 的路由 - * @returns 返回替换后的路由数组 - */ -export function setFilterRouteEnd() { - let filterRouteEnd: any = formatTwoStageRoutes(formatFlatteningRoutes(dynamicRoutes)); - filterRouteEnd[0].children = [...setFilterRoute(filterRouteEnd[0].children), ...notFoundAndNoPower]; - return filterRouteEnd; -} - -/** - * 添加动态路由 - * @method router.addRoute - * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套 - * @link 参考:https://next.router.vuejs.org/zh/api/#addroute - */ -export async function setAddRoute() { - await setFilterRouteEnd().forEach((route: RouteRecordRaw) => { - const routeName: any = route.name; - if (!router.hasRoute(routeName)) router.addRoute(route); - }); -} - -/** - * 删除/重置路由 - * @method router.removeRoute - * @description 此处循环为 dynamicRoutes(/@/router/route)第一个顶级 children 的路由一维数组,非多级嵌套 - * @link 参考:https://next.router.vuejs.org/zh/api/#push - */ -export async function resetRoute() { - await setFilterRouteEnd().forEach((route: RouteRecordRaw) => { - const routeName: any = route.name; - router.hasRoute(routeName) && router.removeRoute(routeName); - }); -} - // isRequestRoutes 为 true,则开启后端控制路由,路径:`/src/stores/themeConfig.ts` -const storesThemeConfig = useThemeConfig(pinia); -const { themeConfig } = storeToRefs(storesThemeConfig); -const { isRequestRoutes } = themeConfig.value; -// 前端控制路由:初始化方法,防止刷新时路由丢失 if (!isRequestRoutes) initFrontEndControlRoutes(); // 路由加载前 @@ -232,7 +100,6 @@ router.beforeEach(async (to, from, next) => { if (!token) { next(`/login?redirect=${to.path}¶ms=${JSON.stringify(to.query ? to.query : to.params)}`); Session.clear(); - resetRoute(); NProgress.done(); } else if (token && to.path === '/login') { next('/home'); diff --git a/src/router/route.ts b/src/router/route.ts index 6aaefa1f..1d3c8d3a 100644 --- a/src/router/route.ts +++ b/src/router/route.ts @@ -16,6 +16,7 @@ import { RouteRecordRaw } from 'vue-router'; /** * 定义动态路由 + * 前端添加路由,请在顶级节点的 `children 数组` 里添加 * @description 未开启 isRequestRoutes 为 true 时使用(前端控制路由),开启时第一个顶级 children 的路由将被替换成接口请求回来的路由数据 * @description 各字段请查看 `/@/views/system/menu/component/addMenu.vue 下的 ruleForm` * @returns 返回路由菜单数据 @@ -1132,11 +1133,49 @@ export const dynamicRoutes: Array = [ ]; /** - * 定义静态路由 + * 定义404、401界面 + * @link 参考:https://next.router.vuejs.org/zh/guide/essentials/history-mode.html#netlify + */ +export const notFoundAndNoPower = [ + { + path: '/:path(.*)*', + name: 'notFound', + component: () => import('/@/views/error/404.vue'), + meta: { + title: 'message.staticRoutes.notFound', + isHide: true, + }, + }, + { + path: '/401', + name: 'noPower', + component: () => import('/@/views/error/401.vue'), + meta: { + title: 'message.staticRoutes.noPower', + isHide: true, + }, + }, +]; + +/** + * 定义静态路由(默认路由) + * 此路由不要动,前端添加路由的话,请在 `dynamicRoutes 数组` 中添加 * @description 前端控制直接改 dynamicRoutes 中的路由,后端控制不需要修改,请求接口路由数据时,会覆盖 dynamicRoutes 第一个顶级 children 的内容(全屏,不包含 layout 中的路由出口) * @returns 返回路由菜单数据 */ export const staticRoutes: Array = [ + { + path: '/', + name: '/', + component: () => import('/@/layout/index.vue'), + meta: { + title: '布局界面', + }, + children: [ + // 请不要往这里 `children` 中添加内容,此内容为了防止 No match found for location with path "xxx" 问题 + ...notFoundAndNoPower, + ], + }, { path: '/login', name: 'login', diff --git a/src/stores/requestOldRoutes.ts b/src/stores/requestOldRoutes.ts index 31eb3d1b..be9b5cd4 100644 --- a/src/stores/requestOldRoutes.ts +++ b/src/stores/requestOldRoutes.ts @@ -10,7 +10,7 @@ export const useRequestOldRoutes = defineStore('requestOldRoutes', { requestOldRoutes: [], }), actions: { - async setCacheKeepAlive(routes: Array) { + async setRequestOldRoutes(routes: Array) { this.requestOldRoutes = routes; }, }, diff --git a/src/theme/media/error.scss b/src/theme/media/error.scss index 727b7c41..f35015fd 100644 --- a/src/theme/media/error.scss +++ b/src/theme/media/error.scss @@ -33,3 +33,13 @@ } } } + +/* 页面宽度小于1200px +------------------------------- */ +@media screen and (max-width: $lg) { + .error { + .error-flex { + padding: 0 30px; + } + } +} diff --git a/src/theme/mixins/index.scss b/src/theme/mixins/index.scss index 31667af2..61f3c6bd 100644 --- a/src/theme/mixins/index.scss +++ b/src/theme/mixins/index.scss @@ -7,6 +7,7 @@ margin-right: 5px; width: 24px; text-align: center; + justify-content: center; } /* 文本不换行 diff --git a/src/views/limits/frontEnd/page/index.vue b/src/views/limits/frontEnd/page/index.vue index c61c8f4d..c77c4ded 100644 --- a/src/views/limits/frontEnd/page/index.vue +++ b/src/views/limits/frontEnd/page/index.vue @@ -26,7 +26,7 @@ import { toRefs, reactive, onMounted, defineComponent } from 'vue'; import { storeToRefs } from 'pinia'; import { useUserInfo } from '/@/stores/userInfo'; -import { resetRoute, setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/index'; +import { frontEndsResetRoute, setAddRoute, setFilterMenuAndCacheTagsViewRoutes } from '/@/router/frontEnd'; import { Session } from '/@/utils/storage'; export default defineComponent({ @@ -45,7 +45,7 @@ export default defineComponent({ // 用户权限改变时 const onRadioChange = async () => { // 模拟数据 - resetRoute(); + frontEndsResetRoute(); let defaultRoles: string[] = []; let defaultAuthBtnList: string[] = []; // admin 页面权限标识,对应路由 meta.roles,用于控制路由的显示/隐藏 diff --git a/src/views/visualizing/demo1.vue b/src/views/visualizing/demo1.vue index 871b7b10..8e6a00e4 100644 --- a/src/views/visualizing/demo1.vue +++ b/src/views/visualizing/demo1.vue @@ -93,6 +93,7 @@ import { toRefs, reactive, onMounted, onUnmounted, getCurrentInstance, defineCom import * as echarts from 'echarts'; import 'echarts/extension/bmap/bmap'; import { formatDate } from '/@/utils/formatTime'; +import { NextLoading } from '/@/utils/loading'; import { echartsMapList, echartsMapData, echartsMapImgs } from './mock/demo1'; // 定义接口来定义对象的类型 @@ -934,6 +935,7 @@ export default defineComponent({ }; // 页面加载时 onMounted(async () => { + NextLoading.done(); initTime(); await initEchartsMap(); await initVisualizingContentLeftTop(); diff --git a/src/views/visualizing/demo2.vue b/src/views/visualizing/demo2.vue index 284064b6..89eb40c7 100644 --- a/src/views/visualizing/demo2.vue +++ b/src/views/visualizing/demo2.vue @@ -233,6 +233,7 @@ import { toRefs, reactive, onMounted, onUnmounted, getCurrentInstance, defineCom import * as echarts from 'echarts'; import 'echarts-gl'; import { formatDate } from '/@/utils/formatTime'; +import { NextLoading } from '/@/utils/loading'; import { dropdownList, skyList, dBtnList, earth3DBtnList, chartData4List } from './mock/demo2'; import worldImg from './images/world.jpg'; import bathymetryImg from './images/bathymetry.jpg'; @@ -765,6 +766,7 @@ export default defineComponent({ }; // 页面加载时 onMounted(async () => { + NextLoading.done(); initTime(); await initRightChartData1(); await initRightChartData4();