Files
fe.ems.vue3/src/store/modules/router.ts

250 lines
7.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { defineStore } from 'pinia';
import type {
RouteComponent,
RouteLocationRaw,
RouteMeta,
RouteRecord,
RouteRecordRaw,
} from 'vue-router';
import { getRouters } from '@/api/router';
import useNeInfoStore from '@/store/modules/neinfo';
import BasicLayout from '@/layouts/BasicLayout.vue';
import BlankLayout from '@/layouts/BlankLayout.vue';
import LinkLayout from '@/layouts/LinkLayout.vue';
import {
MENU_COMPONENT_LAYOUT_BASIC,
MENU_COMPONENT_LAYOUT_BLANK,
MENU_COMPONENT_LAYOUT_LINK,
} from '@/constants/menu-constants';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
/**路由构建参数类型 */
type RouterStore = {
/**初始的根路由数据 */
rootRouterData: RouteRecordRaw[];
/**动态路由数据 */
buildRouterData: RouteRecordRaw[];
};
const useRouterStore = defineStore('router', {
state: (): RouterStore => ({
rootRouterData: [],
buildRouterData: [],
}),
actions: {
/**
* 记录初始根节点菜单数据
* @param data 初始数据
* @returns 初始数据
*/
setRootRouterData(data: RouteRecordRaw[]) {
if (this.rootRouterData.length <= 0) {
this.rootRouterData = data;
}
return this.rootRouterData;
},
/**
* 动态路由列表数据生成
* @returns 生成的路由菜单
*/
async generateRoutes() {
const res = await getRouters();
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 获取当前网元类型
const neTypes = useNeInfoStore().getNeSelectOtions.map(v => v.value);
// 先过滤
const filteredRoutes = this.clearMenuItemByNeList(res.data.concat(), neTypes);
// 再 build
const buildRoutes = buildRouters(filteredRoutes);
this.buildRouterData = buildRoutes;
return buildRoutes;
}
return [];
},
/**
* 根据网元类型过滤菜单
* @param routes 经过clearMenuItem(router.getRoutes())处理
* @param neTypes 网元类型
* @returns 过滤后的菜单
*/
clearMenuItemByNeList(
routes: RouteRecord[] | RouteRecordRaw[],
neTypes: string[]
): RouteRecordRaw[] {
return routes
.map((item: RouteRecord | RouteRecordRaw) => {
const finalItem = { ...item };
// 过滤网元类型
if (
Array.isArray(finalItem.meta?.neType) &&
finalItem.meta?.neType.length > 0
) {
let metaNeType: string[] = finalItem.meta.neType;
let match = false; // 匹配
for (const netype of metaNeType) {
if (netype.indexOf('+') > -1) {
metaNeType = netype.split('+');
match = true;
break;
}
}
if (match && !metaNeType.every(item => neTypes.includes(item))) {
// 同时匹配
return null;
} else if (!metaNeType.some(item => neTypes.includes(item))) {
// 有一种
return null;
}
}
// 有子菜单进行递归
if (finalItem.children && finalItem.children.length > 0) {
const children = this.clearMenuItemByNeList(
finalItem.children,
neTypes
);
// 如果子菜单都被过滤掉了,就不显示
if (children.length === 0) {
return null;
}
finalItem.children = children;
// 只重定向到第一个可用的子菜单
// finalItem.redirect = finalItem.children[0].path;
// console.log(`finalItem.redirect`, finalItem.redirect);
// 如果有子菜单,且没有重定向,则设置重定向到第一个子菜单
if (children.length > 0) {
let childPath = children[0].path;
if (!childPath.startsWith('/')) {
// 确保父路径以 / 结尾,子路径不以 / 开头
const parentPath = finalItem.path.replace(/\/$/, '');
childPath = parentPath + '/' + childPath.replace(/^\//, '');
}
finalItem.redirect = childPath;
}
return finalItem;
}
delete finalItem.children;
return finalItem;
})
.filter(item => item) as RouteRecordRaw[];
},
},
});
/**异步路由类型 */
type RecordRaws = {
path: string;
name?: string;
meta: RouteMeta;
redirect: RouteLocationRaw;
component: string;
children: RecordRaws[];
};
/**
* 构建动态路由
*
* 遍历后台配置的路由菜单,转换为组件路由菜单
*
* @param recordRaws 异步路由列表
* @returns 可添加的路由列表
*/
function buildRouters(recordRaws: RouteRecordRaw[]): RouteRecordRaw[] {
const routers: RouteRecordRaw[] = [];
for (const item of recordRaws) {
// 过滤旧前端菜单 是layui的菜单跳过
if (['', '/page"'].includes(item.path)) {
continue;
}
// 路由页面组件
let component: RouteComponent = {};
if (item.component) {
const comp = item.component as unknown as string; // 添加类型断言
if (comp === MENU_COMPONENT_LAYOUT_BASIC) {
component = BasicLayout;
} else if (comp === MENU_COMPONENT_LAYOUT_BLANK) {
component = BlankLayout;
} else if (comp === MENU_COMPONENT_LAYOUT_LINK) {
component = LinkLayout;
} else {
// 指定页面视图,一般用于显示子菜单
component = findView(comp);
}
}
// 有子菜单进行递归
let children: RouteRecordRaw[] = [];
if (item.children && item.children.length > 0) {
children = buildRouters(item.children);
// 如果没有 redirect 但有子菜单,设置 redirect 到第一个子菜单
if (!item.redirect && children.length > 0) {
let childPath = children[0].path;
if (!childPath?.startsWith('/')) {
childPath = item.path.replace(/\/$/, '') + '/' + (childPath || '').replace(/^\//, '');
}
// 修改 item 的 redirect需要类型断言
(item as any).redirect = childPath;
}
}
// 对元数据特殊参数进行处理
let metaIcon = (item.meta?.icon as string) || '';
if (!metaIcon.startsWith('icon-')) {
metaIcon = '';
}
// 更新 meta需要类型断言
(item as any).meta = Object.assign(item.meta || {}, {
icon: metaIcon,
});
// 构建路由
const router: RouteRecordRaw = {
path: item.path,
name: item.name,
meta: item.meta,
redirect: item.redirect,
component: component,
children: children,
};
routers.push(router);
}
return routers;
}
/**匹配views里面所有的.vue或.tsx文件 */
const views = import.meta.glob('./../../views/**/*.{vue,tsx}');
/**
* 查找页面模块
*
* 查找 `/views/system/menu/index.vue` 或 `/views/system/menu/index.tsx`
*
* 参数值为 `system/menu/index`
*
* @param dirName 组件路径
* @returns 路由懒加载函数
*/
function findView(dirName: string) {
for (const dir in views) {
let viewDirName = '';
const component = dir.match(/views\/(.+)\.(vue|tsx)/);
if (component && component.length === 3) {
viewDirName = component[1];
}
if (viewDirName === dirName) {
return () => views[dir]();
}
}
return () => import('@/views/error/404.vue');
}
export default useRouterStore;