xiaoxian521
3 years ago
25 changed files with 534 additions and 270 deletions
-
4.env.development
-
3.env.production
-
3build/index.ts
-
1mock/asyncRoutes.ts
-
2package.json
-
2pnpm-lock.yaml
-
1public/serverConfig.json
-
2src/directives/permission/index.ts
-
3src/layout/components/sidebar/horizontal.vue
-
3src/layout/components/sidebar/vertical.vue
-
2src/layout/components/tag/index.scss
-
88src/layout/components/tag/index.vue
-
15src/layout/routerView/parent.vue
-
3src/layout/types.ts
-
293src/router/index.ts
-
25src/router/modules/index.ts
-
9src/router/types.ts
-
289src/router/utils.ts
-
13src/store/modules/multiTags.ts
-
24src/store/modules/permission.ts
-
1src/store/modules/types.ts
-
2src/views/login.vue
-
6src/views/permission/button/index.vue
-
6src/views/permission/page/index.vue
-
2types/global.d.ts
@ -1,5 +1,8 @@ |
|||||
# 线上环境项目打包路径 |
# 线上环境项目打包路径 |
||||
VITE_PUBLIC_PATH = / |
VITE_PUBLIC_PATH = / |
||||
|
|
||||
|
# 线上环境路由历史模式 |
||||
|
VITE_ROUTER_HISTORY = "hash" |
||||
|
|
||||
# 线上环境后端地址 |
# 线上环境后端地址 |
||||
VITE_PROXY_DOMAIN_REAL = "" |
VITE_PROXY_DOMAIN_REAL = "" |
@ -0,0 +1,15 @@ |
|||||
|
<template> |
||||
|
<router-view> |
||||
|
<template #default="{ Component, route }"> |
||||
|
<transition appear name="fade-transform" mode="out-in"> |
||||
|
<component :is="Component" :key="route.fullPath" /> |
||||
|
</transition> |
||||
|
</template> |
||||
|
</router-view> |
||||
|
</template> |
||||
|
|
||||
|
<script lang="ts"> |
||||
|
export default { |
||||
|
name: "layoutParentView" |
||||
|
}; |
||||
|
</script> |
@ -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<RouteRecordRaw> = formatTwoStageRoutes( |
||||
|
formatFlatteningRoutes(ascending(routes)) |
||||
|
); |
||||
|
|
||||
|
// 用于渲染菜单,保持原始层级
|
||||
|
export const constantMenus: Array<RouteComponent> = ascending(routes).concat( |
||||
|
...remainingRouter |
||||
|
); |
@ -0,0 +1,9 @@ |
|||||
|
import { RouteLocationNormalized } from "vue-router"; |
||||
|
|
||||
|
export interface toRouteType extends RouteLocationNormalized { |
||||
|
meta: { |
||||
|
keepAlive: boolean; |
||||
|
refreshRedirect: string; |
||||
|
realPath: string; |
||||
|
}; |
||||
|
} |
@ -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<RouteConfigs>) => { |
||||
|
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<RouteRecordRaw>) => { |
||||
|
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<string>): 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 |
||||
|
}; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue