diff --git a/.env.development b/.env.development index 23c4c72..c3f4bbd 100644 --- a/.env.development +++ b/.env.development @@ -7,6 +7,8 @@ VITE_PUBLIC_PATH = / # 开发环境代理 VITE_PROXY_DOMAIN = /api -# 开发环境后端地址 -VITE_PROXY_DOMAIN_REAL = "http://127.0.0.1:3000" +# 开发环境路由历史模式 +VITE_ROUTER_HISTORY = "hash" +# 开发环境后端地址 +VITE_PROXY_DOMAIN_REAL = "http://127.0.0.1:3000" \ No newline at end of file diff --git a/.env.production b/.env.production index eace0a9..5537b0c 100644 --- a/.env.production +++ b/.env.production @@ -1,5 +1,8 @@ # 线上环境项目打包路径 VITE_PUBLIC_PATH = / +# 线上环境路由历史模式 +VITE_ROUTER_HISTORY = "hash" + # 线上环境后端地址 VITE_PROXY_DOMAIN_REAL = "" diff --git a/build/index.ts b/build/index.ts index c70fb1c..7280839 100644 --- a/build/index.ts +++ b/build/index.ts @@ -5,7 +5,8 @@ const warpperEnv = (envConf: Recordable): ViteEnv => { VITE_PORT: 8848, VITE_PUBLIC_PATH: "", VITE_PROXY_DOMAIN: "", - VITE_PROXY_DOMAIN_REAL: "" + VITE_PROXY_DOMAIN_REAL: "", + VITE_ROUTER_HISTORY: "" }; for (const envName of Object.keys(envConf)) { diff --git a/mock/asyncRoutes.ts b/mock/asyncRoutes.ts index 6954db3..b944aa8 100644 --- a/mock/asyncRoutes.ts +++ b/mock/asyncRoutes.ts @@ -1,7 +1,6 @@ // 根据角色动态生成路由 import { MockMethod } from "vite-plugin-mock"; -// http://mockjs.com/examples.html#Object const permissionRouter = { path: "/permission", name: "permission", diff --git a/package.json b/package.json index 7c47a73..a1ab3dd 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "typescript-cookie": "^1.0.0", "vue": "^3.2.24", "vue-i18n": "^9.2.0-beta.3", - "vue-router": "^4.0.11", + "vue-router": "^4.0.12", "vue-types": "^4.1.0" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a4b2e8b..624692c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,7 @@ specifiers: vue: ^3.2.24 vue-eslint-parser: 7.10.0 vue-i18n: ^9.2.0-beta.3 - vue-router: ^4.0.11 + vue-router: ^4.0.12 vue-types: ^4.1.0 dependencies: diff --git a/public/serverConfig.json b/public/serverConfig.json index 7d28a5b..2462e96 100644 --- a/public/serverConfig.json +++ b/public/serverConfig.json @@ -13,7 +13,6 @@ "HideTabs": false, "MapConfigure": { "amapKey": "97b3248d1553172e81f168cf94ea667e", - "baiduKey": "wTHbkkEweiFqZLKunMIjcrb2RcqNXkhc", "options": { "resizeEnable": true, "center": [113.6401, 34.72468], diff --git a/src/directives/permission/index.ts b/src/directives/permission/index.ts index 649e8d0..c311508 100644 --- a/src/directives/permission/index.ts +++ b/src/directives/permission/index.ts @@ -9,7 +9,7 @@ export const auth: Directive = { const authRoles = value; const hasAuth = usePermissionStoreHook().buttonAuth.includes(authRoles); if (!hasAuth) { - el.style.display = "none"; + el.parentNode.removeChild(el); } } else { throw new Error("need roles! Like v-auth=\"['admin','test']\""); diff --git a/src/layout/components/sidebar/horizontal.vue b/src/layout/components/sidebar/horizontal.vue index bee3976..56e814d 100644 --- a/src/layout/components/sidebar/horizontal.vue +++ b/src/layout/components/sidebar/horizontal.vue @@ -29,7 +29,6 @@ const title = getCurrentInstance().appContext.config.globalProperties.$config?.Title; const menuRef = templateRef("menu", null); -const routeStore = usePermissionStoreHook(); const route = useRoute(); const router = useRouter(); const routers = useRouter().options.routes; @@ -133,7 +132,7 @@ onMounted(() => { @select="menuSelect" > { return !pureApp.getSidebarStatus; @@ -72,7 +71,7 @@ onBeforeMount(() => { @select="menuSelect" > > = computed(() => { return useMultiTagsStoreHook()?.multiTags; }); +const linkIsActive = computed(() => { + return item => { + if (Object.keys(route.query).length === 0) { + if (route.path === item.path) { + return "is-active"; + } else { + return ""; + } + } else { + if (isEqual(route?.query, item?.query)) { + return "is-active"; + } else { + return ""; + } + } + }; +}); + +const scheduleIsActive = computed(() => { + return item => { + if (Object.keys(route.query).length === 0) { + if (route.path === item.path) { + return "schedule-active"; + } else { + return ""; + } + } else { + if (isEqual(route?.query, item?.query)) { + return "schedule-active"; + } else { + return ""; + } + } + }; +}); + +const iconIsActive = computed(() => { + return (item, index) => { + if (index === 0) return; + if (Object.keys(route.query).length === 0) { + if (route.path === item.path) { + return true; + } else { + return false; + } + } else { + if (isEqual(route?.query, item?.query)) { + return true; + } else { + return false; + } + } + }; +}); + const dynamicTagView = () => { const index = multiTags.value.findIndex(item => { return item.path === route.path; @@ -228,7 +285,13 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) { // 存放被删除的缓存路由 let delAliveRouteList = []; let valueIndex: number = multiTags.value.findIndex((item: any) => { - return item.path === obj.path; + if (item.query) { + if (item.path === obj.path) { + return item.query === obj.query; + } + } else { + return item.path === obj.path; + } }); const spliceRoute = ( @@ -279,7 +342,8 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) { if (tag === "left") return; nextTick(() => { router.push({ - path: newRoute[0].path + path: newRoute[0].path, + query: newRoute[0].query }); }); } else { @@ -291,7 +355,8 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) { }); !isHasActiveTag && router.push({ - path: newRoute[0].path + path: newRoute[0].path, + query: newRoute[0].query }); } } @@ -477,7 +542,8 @@ function openMenu(tag, e) { // 触发tags标签切换 function tagOnClick(item) { router.push({ - path: item?.path + path: item?.path, + query: item?.query }); showMenuModel(item?.path); } @@ -563,7 +629,7 @@ onBeforeMount(() => { :key="index" :class="[ 'scroll-item is-closable', - $route.path === item.path ? 'is-active' : '', + linkIsActive(item), $route.path === item.path && showModel === 'card' ? 'card-active' : '' @@ -573,12 +639,12 @@ onBeforeMount(() => { @mouseleave.prevent="onMouseleave(item, index)" @click="tagOnClick(item)" > - {{ - transformI18n(item.meta.title, item.meta.i18n) - }} + {{ transformI18n(item.meta.title, item.meta.i18n) }} + {
diff --git a/src/layout/routerView/parent.vue b/src/layout/routerView/parent.vue new file mode 100644 index 0000000..6a0c9f4 --- /dev/null +++ b/src/layout/routerView/parent.vue @@ -0,0 +1,15 @@ + + + diff --git a/src/layout/types.ts b/src/layout/types.ts index 4222e5c..72c7f87 100644 --- a/src/layout/types.ts +++ b/src/layout/types.ts @@ -14,13 +14,16 @@ export const routerArrays: Array = [ export type RouteConfigs = { path?: string; parentPath?: string; + query?: object; meta?: { title?: string; i18n?: boolean; icon?: string; showLink?: boolean; savedPosition?: boolean; + authority?: Array; }; + children?: RouteConfigs[]; name?: string; }; diff --git a/src/router/index.ts b/src/router/index.ts index bc763f0..e714dce 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -1,135 +1,27 @@ -import { - Router, - RouteMeta, - createRouter, - RouteComponent, - RouteRecordName, - createWebHashHistory, - RouteRecordNormalized -} from "vue-router"; +import { toRouteType } from "./types"; import { openLink } from "/@/utils/link"; import NProgress from "/@/utils/progress"; -import { split, uniqBy } from "lodash-es"; -import { useTimeoutFn } from "@vueuse/core"; -import { RouteConfigs } from "/@/layout/types"; +import { constantRoutes } from "./modules"; import { transformI18n } from "/@/plugins/i18n"; +import remainingRouter from "./modules/remaining"; +import { split, find, findIndex } from "lodash-es"; import { storageSession, storageLocal } from "/@/utils/storage"; -import { usePermissionStoreHook } from "/@/store/modules/permission"; import { useMultiTagsStoreHook } from "/@/store/modules/multiTags"; - -// 静态路由 -import homeRouter from "./modules/home"; -import Layout from "/@/layout/index.vue"; -import errorRouter from "./modules/error"; -import externalLink from "./modules/externalLink"; -import remainingRouter from "./modules/remaining"; -// 动态路由 -import { getAsyncRoutes } from "/@/api/routes"; - -// https://cn.vitejs.dev/guide/features.html#glob-import -const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue"); - -const constantRoutes: Array = [ - homeRouter, - externalLink, - errorRouter -]; - -// 按照路由中meta下的rank等级升序来排序路由 -export const ascending = arr => { - return arr.sort((a: any, b: any) => { - return a?.meta?.rank - b?.meta?.rank; - }); -}; - -// 将所有静态路由导出 -export const constantRoutesArr: Array = ascending( - constantRoutes -).concat(...remainingRouter); - -// 过滤meta中showLink为false的路由 -export const filterTree = data => { - const newTree = data.filter(v => v.meta.showLink); - newTree.forEach(v => v.children && (v.children = filterTree(v.children))); - return newTree; -}; - -// 从路由中提取keepAlive为true的name组成数组(此处本项目中并没有用到,只是暴露个方法) -export const getAliveRoute = () => { - const alivePageList = []; - const recursiveSearch = treeLists => { - if (!treeLists || !treeLists.length) { - return; - } - for (let i = 0; i < treeLists.length; i++) { - if (treeLists[i]?.meta?.keepAlive) alivePageList.push(treeLists[i].name); - recursiveSearch(treeLists[i].children); - } - }; - recursiveSearch(router.options.routes); - return alivePageList; -}; - -// 批量删除缓存路由 -export const delAliveRoutes = (delAliveRouteList: Array) => { - delAliveRouteList.forEach(route => { - usePermissionStoreHook().cacheOperate({ - mode: "delete", - name: route?.name - }); - }); -}; - -// 处理缓存路由(添加、删除、刷新) -export const handleAliveRoute = ( - matched: RouteRecordNormalized[], - mode?: string -) => { - switch (mode) { - case "add": - matched.forEach(v => { - usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name }); - }); - break; - case "delete": - usePermissionStoreHook().cacheOperate({ - mode: "delete", - name: matched[matched.length - 1].name - }); - break; - default: - usePermissionStoreHook().cacheOperate({ - mode: "delete", - name: matched[matched.length - 1].name - }); - useTimeoutFn(() => { - matched.forEach(v => { - usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name }); - }); - }, 100); - } -}; - -// 过滤后端传来的动态路由 重新生成规范路由 -export const addAsyncRoutes = (arrRoutes: Array) => { - if (!arrRoutes || !arrRoutes.length) return; - arrRoutes.forEach((v: any) => { - if (v.redirect) { - v.component = Layout; - } else { - v.component = modulesRoutes[`/src/views${v.path}/index.vue`]; - } - if (v.children) { - addAsyncRoutes(v.children); - } - }); - return arrRoutes; -}; +import { usePermissionStoreHook } from "/@/store/modules/permission"; +import { Router, RouteMeta, createRouter, RouteRecordName } from "vue-router"; +import { + initRouter, + getHistoryMode, + getParentPaths, + findRouteByPath, + handleAliveRoute +} from "./utils"; // 创建路由实例 export const router: Router = createRouter({ - history: createWebHashHistory(), - routes: ascending(constantRoutes).concat(...remainingRouter), + history: getHistoryMode(), + routes: constantRoutes.concat(...remainingRouter), + strict: true, scrollBehavior(to, from, savedPosition) { return new Promise(resolve => { if (savedPosition) { @@ -145,96 +37,10 @@ export const router: Router = createRouter({ } }); -// 初始化路由 -export const initRouter = name => { - return new Promise(resolve => { - getAsyncRoutes({ name }).then(({ info }) => { - if (info.length === 0) { - usePermissionStoreHook().changeSetting(info); - } else { - addAsyncRoutes(info).map((v: any) => { - // 防止重复添加路由 - if ( - router.options.routes.findIndex(value => value.path === v.path) !== - -1 - ) { - return; - } else { - // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 - router.options.routes.push(v); - // 最终路由进行升序 - ascending(router.options.routes); - router.addRoute(v.name, v); - usePermissionStoreHook().changeSetting(info); - } - resolve(router); - }); - } - router.addRoute({ - path: "/:pathMatch(.*)", - redirect: "/error/404" - }); - }); - }); -}; - -// 重置路由 -export function resetRouter() { - router.getRoutes().forEach(route => { - const { name } = route; - if (name) { - router.hasRoute(name) && router.removeRoute(name); - } - }); -} - -function findRouteByPath(path, routes) { - let res = routes.find(item => item.path == path); - if (res) { - return res; - } else { - for (let i = 0; i < routes.length; i++) { - if ( - routes[i].children instanceof Array && - routes[i].children.length > 0 - ) { - res = findRouteByPath(path, routes[i].children); - if (res) { - return res; - } - } - } - return null; - } -} - -function getParentPaths(path, routes) { - // 深度遍历查找 - function dfs(routes, path, parents) { - for (let i = 0; i < routes.length; i++) { - const item = routes[i]; - // 找到path则返回父级path - if (item.path === path) return parents; - // children不存在或为空则不递归 - if (!item.children || !item.children.length) continue; - // 往下查找时将当前path入栈 - parents.push(item.path); - - if (dfs(item.children, path, parents).length) return parents; - // 深度遍历查找未找到时当前path 出栈 - parents.pop(); - } - // 未找到时返回空数组 - return []; - } - - return dfs(routes, path, []); -} - // 路由白名单 const whiteList = ["/login"]; -router.beforeEach((to, _from, next) => { +router.beforeEach((to: toRouteType, _from, next) => { if (to.meta?.keepAlive) { const newMatched = to.matched; handleAliveRoute(newMatched, "add"); @@ -266,7 +72,7 @@ router.beforeEach((to, _from, next) => { } } else { // 刷新 - if (usePermissionStoreHook().wholeRoutes.length === 0) + if (usePermissionStoreHook().wholeMenus.length === 0) initRouter(name.username).then((router: Router) => { if (!useMultiTagsStoreHook().getMultiTagsCache) { const handTag = ( @@ -282,29 +88,60 @@ router.beforeEach((to, _from, next) => { meta }); }; - const parentPath = to.matched[0]?.path; + // 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对静态路由) if (to.meta?.realPath) { - const { path, name, meta } = to.matched[0]?.children[0]; - handTag(path, parentPath, name, meta); - return router.push(path); + const routes = router.options.routes; + const { refreshRedirect } = to.meta; + const { name, meta } = findRouteByPath(refreshRedirect, routes); + handTag( + refreshRedirect, + getParentPaths(refreshRedirect, routes)[1], + name, + meta + ); + return router.push(refreshRedirect); } else { const { path } = to; - const routes = router.options.routes; + const index = findIndex(remainingRouter, v => { + return v.path == path; + }); + const routes = + index === -1 + ? router.options.routes[0].children + : router.options.routes; const route = findRouteByPath(path, routes); const routePartent = getParentPaths(path, routes); - handTag( - route.path, - routePartent[routePartent.length - 1], - route.name, - route.meta - ); - return router.push(path); + // 未开启标签页缓存,刷新页面重定向到顶级路由(参考标签页操作例子,只针对动态路由) + if (routePartent.length === 0) { + const { name, meta } = findRouteByPath( + route?.meta?.refreshRedirect, + routes + ); + handTag( + route.meta?.refreshRedirect, + getParentPaths(route.meta?.refreshRedirect, routes)[0], + name, + meta + ); + return router.push(route.meta?.refreshRedirect); + } else { + handTag( + route.path, + routePartent[routePartent.length - 1], + route.name, + route.meta + ); + return router.push(path); + } } } - router.push(to.path); + router.push(to.fullPath); // 刷新页面更新标签栏与页面路由匹配 const localRoutes = storageLocal.getItem("responsive-tags"); - const optionsRoutes = router.options?.routes; + const home = find(router.options?.routes, route => { + return route.path === "/"; + }); + const optionsRoutes = [home, ...router.options?.routes[0].children]; const newLocalRoutes = []; optionsRoutes.forEach(ors => { localRoutes.forEach(lrs => { @@ -313,10 +150,6 @@ router.beforeEach((to, _from, next) => { } }); }); - storageLocal.setItem( - "responsive-tags", - uniqBy(newLocalRoutes, "path") - ); }); next(); } diff --git a/src/router/modules/index.ts b/src/router/modules/index.ts new file mode 100644 index 0000000..49e8f88 --- /dev/null +++ b/src/router/modules/index.ts @@ -0,0 +1,25 @@ +// 静态路由 +import homeRouter from "./home"; +import errorRouter from "./error"; +import externalLink from "./externalLink"; +import remainingRouter from "./remaining"; +import { RouteRecordRaw, RouteComponent } from "vue-router"; + +import { + ascending, + formatTwoStageRoutes, + formatFlatteningRoutes +} from "../utils"; + +// 原始静态路由(未做任何处理) +const routes = [homeRouter, errorRouter, externalLink]; + +// 导出处理后的静态路由(三级及以上的路由全部拍成二级) +export const constantRoutes: Array = formatTwoStageRoutes( + formatFlatteningRoutes(ascending(routes)) +); + +// 用于渲染菜单,保持原始层级 +export const constantMenus: Array = ascending(routes).concat( + ...remainingRouter +); diff --git a/src/router/types.ts b/src/router/types.ts new file mode 100644 index 0000000..0577e04 --- /dev/null +++ b/src/router/types.ts @@ -0,0 +1,9 @@ +import { RouteLocationNormalized } from "vue-router"; + +export interface toRouteType extends RouteLocationNormalized { + meta: { + keepAlive: boolean; + refreshRedirect: string; + realPath: string; + }; +} diff --git a/src/router/utils.ts b/src/router/utils.ts new file mode 100644 index 0000000..af9f6ed --- /dev/null +++ b/src/router/utils.ts @@ -0,0 +1,289 @@ +import { + RouterHistory, + RouteRecordRaw, + RouteComponent, + createWebHistory, + createWebHashHistory, + RouteRecordNormalized +} from "vue-router"; +import { router } from "./index"; +import { loadEnv } from "../../build"; +import Layout from "/@/layout/index.vue"; +import { useTimeoutFn } from "@vueuse/core"; +import { RouteConfigs } from "/@/layout/types"; +import { usePermissionStoreHook } from "/@/store/modules/permission"; +// https://cn.vitejs.dev/guide/features.html#glob-import +const modulesRoutes = import.meta.glob("/src/views/*/*/*.vue"); + +// 动态路由 +import { getAsyncRoutes } from "/@/api/routes"; + +// 按照路由中meta下的rank等级升序来排序路由 +const ascending = (arr: any[]) => { + return arr.sort( + (a: { meta: { rank: number } }, b: { meta: { rank: number } }) => { + return a?.meta?.rank - b?.meta?.rank; + } + ); +}; + +// 过滤meta中showLink为false的路由 +const filterTree = (data: RouteComponent[]) => { + const newTree = data.filter( + (v: { meta: { showLink: boolean } }) => v.meta.showLink + ); + newTree.forEach( + (v: { children }) => v.children && (v.children = filterTree(v.children)) + ); + return newTree; +}; + +// 批量删除缓存路由(keepalive) +const delAliveRoutes = (delAliveRouteList: Array) => { + delAliveRouteList.forEach(route => { + usePermissionStoreHook().cacheOperate({ + mode: "delete", + name: route?.name + }); + }); +}; + +// 通过path获取父级路径 +const getParentPaths = (path: string, routes: RouteRecordRaw[]) => { + // 深度遍历查找 + function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) { + for (let i = 0; i < routes.length; i++) { + const item = routes[i]; + // 找到path则返回父级path + if (item.path === path) return parents; + // children不存在或为空则不递归 + if (!item.children || !item.children.length) continue; + // 往下查找时将当前path入栈 + parents.push(item.path); + + if (dfs(item.children, path, parents).length) return parents; + // 深度遍历查找未找到时当前path 出栈 + parents.pop(); + } + // 未找到时返回空数组 + return []; + } + + return dfs(routes, path, []); +}; + +// 查找对应path的路由信息 +const findRouteByPath = (path: string, routes: RouteRecordRaw[]) => { + let res = routes.find((item: { path: string }) => item.path == path); + if (res) { + return res; + } else { + for (let i = 0; i < routes.length; i++) { + if ( + routes[i].children instanceof Array && + routes[i].children.length > 0 + ) { + res = findRouteByPath(path, routes[i].children); + if (res) { + return res; + } + } + } + return null; + } +}; + +// 重置路由 +const resetRouter = (): void => { + router.getRoutes().forEach(route => { + const { name } = route; + if (name) { + router.hasRoute(name) && router.removeRoute(name); + } + }); +}; + +// 初始化路由 +const initRouter = (name: string) => { + return new Promise(resolve => { + getAsyncRoutes({ name }).then(({ info }) => { + if (info.length === 0) { + usePermissionStoreHook().changeSetting(info); + } else { + formatFlatteningRoutes(addAsyncRoutes(info)).map( + (v: RouteRecordRaw) => { + // 防止重复添加路由 + if ( + router.options.routes[0].children.findIndex( + value => value.path === v.path + ) !== -1 + ) { + return; + } else { + // 切记将路由push到routes后还需要使用addRoute,这样路由才能正常跳转 + router.options.routes[0].children.push(v); + // 最终路由进行升序 + ascending(router.options.routes[0].children); + if (!router.hasRoute(v?.name)) router.addRoute(v); + } + resolve(router); + } + ); + usePermissionStoreHook().changeSetting(info); + } + router.addRoute({ + path: "/:pathMatch(.*)", + redirect: "/error/404" + }); + }); + }); +}; + +/** + * 将多级嵌套路由处理成一维数组 + * @param routesList 传入路由 + * @returns 返回处理后的一维路由 + */ +const formatFlatteningRoutes = (routesList: RouteRecordRaw[]) => { + if (routesList.length <= 0) return routesList; + for (let i = 0; i < routesList.length; i++) { + if (routesList[i].children) { + routesList = routesList + .slice(0, i + 1) + .concat(routesList[i].children, routesList.slice(i + 1)); + } + } + return routesList; +}; + +/** + * 一维数组处理成多级嵌套数组(三级及以上的路由全部拍成二级,keep-alive 只支持到二级缓存) + * https://github.com/xiaoxian521/vue-pure-admin/issues/67 + * @param routesList 处理后的一维路由菜单数组 + * @returns 返回将一维数组重新处理成规定路由的格式 + */ +const formatTwoStageRoutes = (routesList: RouteRecordRaw[]) => { + if (routesList.length <= 0) return routesList; + const newRoutesList: RouteRecordRaw[] = []; + routesList.forEach((v: RouteRecordRaw) => { + if (v.path === "/") { + newRoutesList.push({ + component: v.component, + name: v.name, + path: v.path, + redirect: v.redirect, + meta: v.meta, + children: [] + }); + } else { + newRoutesList[0].children.push({ ...v }); + } + }); + return newRoutesList; +}; + +// 处理缓存路由(添加、删除、刷新) +const handleAliveRoute = (matched: RouteRecordNormalized[], mode?: string) => { + switch (mode) { + case "add": + matched.forEach(v => { + usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name }); + }); + break; + case "delete": + usePermissionStoreHook().cacheOperate({ + mode: "delete", + name: matched[matched.length - 1].name + }); + break; + default: + usePermissionStoreHook().cacheOperate({ + mode: "delete", + name: matched[matched.length - 1].name + }); + useTimeoutFn(() => { + matched.forEach(v => { + usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name }); + }); + }, 100); + } +}; + +// 过滤后端传来的动态路由 重新生成规范路由 +const addAsyncRoutes = (arrRoutes: Array) => { + if (!arrRoutes || !arrRoutes.length) return; + arrRoutes.forEach((v: RouteRecordRaw) => { + if (v.redirect) { + v.component = Layout; + } else { + if (v.meta.realPath) { + v.component = modulesRoutes[`/src/views${v.meta.realPath}/index.vue`]; + } else { + v.component = modulesRoutes[`/src/views${v.path}/index.vue`]; + } + } + if (v.children) { + addAsyncRoutes(v.children); + } + }); + return arrRoutes; +}; + +// 获取路由历史模式 https://next.router.vuejs.org/zh/guide/essentials/history-mode.html +const getHistoryMode = (): RouterHistory => { + const routerHistory = loadEnv().VITE_ROUTER_HISTORY; + // len为1 代表只有历史模式 为2 代表历史模式中存在base参数 https://next.router.vuejs.org/zh/api/#%E5%8F%82%E6%95%B0-1 + const historyMode = routerHistory.split(","); + const leftMode = historyMode[0]; + const rightMode = historyMode[1]; + // no param + if (historyMode.length === 1) { + if (leftMode === "hash") { + return createWebHashHistory(""); + } else if (leftMode === "h5") { + return createWebHistory(""); + } + } //has param + else if (historyMode.length === 2) { + if (leftMode === "hash") { + return createWebHashHistory(rightMode); + } else if (leftMode === "h5") { + return createWebHistory(rightMode); + } + } +}; + +// 是否有权限 +const hasPermissions = (value: Array): boolean => { + if (value && value instanceof Array && value.length > 0) { + const roles = usePermissionStoreHook().buttonAuth; + const permissionRoles = value; + + const hasPermission = roles.some(role => { + return permissionRoles.includes(role); + }); + + if (!hasPermission) { + return false; + } + return true; + } else { + return false; + } +}; + +export { + ascending, + filterTree, + initRouter, + resetRouter, + hasPermissions, + getHistoryMode, + addAsyncRoutes, + delAliveRoutes, + getParentPaths, + findRouteByPath, + handleAliveRoute, + formatTwoStageRoutes, + formatFlatteningRoutes +}; diff --git a/src/store/modules/multiTags.ts b/src/store/modules/multiTags.ts index 8969435..4ad0529 100644 --- a/src/store/modules/multiTags.ts +++ b/src/store/modules/multiTags.ts @@ -1,5 +1,6 @@ import { defineStore } from "pinia"; import { store } from "/@/store"; +import { isEqual } from "lodash-es"; import { storageLocal } from "/@/utils/storage"; import { multiType, positionType } from "./types"; @@ -54,12 +55,18 @@ export const useMultiTagsStore = defineStore({ case "push": { const tagVal = value as multiType; - // 判断tag是否已存在: + // 判断tag是否已存在 const tagHasExits = this.multiTags.some(tag => { return tag.path === tagVal?.path; }); - if (tagHasExits) return; + // 判断tag中的query键值是否相等 + const tagQueryHasExits = this.multiTags.some(tag => { + return isEqual(tag.query, tagVal.query); + }); + + if (tagHasExits && tagQueryHasExits) return; + const meta = tagVal?.meta; const dynamicLevel = meta?.dynamicLevel ?? -1; if (dynamicLevel > 0) { @@ -85,10 +92,8 @@ export const useMultiTagsStore = defineStore({ this.multiTags.splice(position?.startIndex, position?.length); this.tagsCache(this.multiTags); return this.multiTags; - break; case "slice": return this.multiTags.slice(-1); - break; } } } diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index aa4061e..608bc30 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -1,28 +1,32 @@ import { defineStore } from "pinia"; import { store } from "/@/store"; import { cacheType } from "./types"; -import { constantRoutesArr, ascending, filterTree } from "/@/router/index"; +import { RouteConfigs } from "/@/layout/types"; +import { constantMenus } from "/@/router/modules"; +import { ascending, filterTree } from "/@/router/utils"; export const usePermissionStore = defineStore({ id: "pure-permission", state: () => ({ - // 静态路由 - constantRoutes: constantRoutesArr, - wholeRoutes: [], + // 静态路由生成的菜单 + constantMenus, + // 整体路由生成的菜单(静态、动态) + wholeMenus: [], buttonAuth: [], // 缓存页面keepAlive cachePageList: [] }), actions: { + // 获取异步路由菜单 asyncActionRoutes(routes) { - if (this.wholeRoutes.length > 0) return; - this.wholeRoutes = filterTree( - ascending(this.constantRoutes.concat(routes)) + if (this.wholeMenus.length > 0) return; + this.wholeMenus = filterTree( + ascending(this.constantMenus.concat(routes)) ); - const getButtonAuth = (arrRoutes: Array) => { + const getButtonAuth = (arrRoutes: Array) => { if (!arrRoutes || !arrRoutes.length) return; - arrRoutes.forEach((v: any) => { + arrRoutes.forEach((v: RouteConfigs) => { if (v.meta && v.meta.authority) { this.buttonAuth.push(...v.meta.authority); } @@ -32,7 +36,7 @@ export const usePermissionStore = defineStore({ }); }; - getButtonAuth(this.wholeRoutes); + getButtonAuth(this.wholeMenus); }, async changeSetting(routes) { await this.asyncActionRoutes(routes); diff --git a/src/store/modules/types.ts b/src/store/modules/types.ts index d6c1128..644ce86 100644 --- a/src/store/modules/types.ts +++ b/src/store/modules/types.ts @@ -25,6 +25,7 @@ export type multiType = { path: string; parentPath: string; name: string; + query: object; meta: any; }; diff --git a/src/views/login.vue b/src/views/login.vue index fcae896..1f76ff9 100644 --- a/src/views/login.vue +++ b/src/views/login.vue @@ -1,7 +1,7 @@ + +