gerson
2025-03-25 982732e3aea8e429a9bbecc9e9927caa1d51a2fb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import { storeToRefs } from 'pinia';
import { RouteRecordRaw } from 'vue-router';
import { formatFlatteningRoutes, formatTwoStageRoutes, router } from '/@/router/index';
import { dynamicRoutes, notFoundAndNoPower } from '/@/router/route';
import pinia from '/@/stores/index';
import { useRoutesList } from '/@/stores/routesList';
import { useTagsViewRoutes } from '/@/stores/tagsViewRoutes';
import { useUserInfo } from '/@/stores/userInfo';
import { NextLoading } from '/@/utils/loading';
import { accessSessionKey } from '/@/utils/request';
import { Local } from '/@/utils/storage';
 
// 前端控制路由
 
/**
 * 前端控制路由:初始化方法,防止刷新时路由丢失
 * @method  NextLoading 界面 loading 动画开始执行
 * @method useUserInfo(pinia).setUserInfos() 触发初始化用户信息 pinia
 * @method setAddRoute 添加动态路由
 * @method setFilterMenuAndCacheTagsViewRoutes 设置递归过滤有权限的路由到 pinia routesList 中(已处理成多级嵌套路由)及缓存多级嵌套数组处理后的一维数组
 */
export async function initFrontEndControlRoutes() {
    // 界面 loading 动画开始执行
    if (window.nextLoading === undefined) NextLoading.start();
    // 无 token 停止执行下一步
    if (!Local.get(accessSessionKey)) return false;
    // 触发初始化用户信息 pinia
    // https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
    // await useUserInfo(pinia).setUserInfos();
    // 无登录权限时,添加判断
    // https://gitee.com/lyt-top/vue-next-admin/issues/I64HVO
    if (useUserInfo().userInfos.roles.length <= 0) return Promise.resolve(true);
    // 添加动态路由
    await setAddRoute();
    // 设置递归过滤有权限的路由到 pinia 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));
    // notFoundAndNoPower 防止 404、401 不在 layout 布局中,不设置的话,404、401 界面将全屏显示
    // 关联问题 No match found for location with path 'xxx'
    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);
}
 
/**
 * 设置递归过滤有权限的路由到 pinia 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;
}