提交新版本
This commit is contained in:
27
packages/utils/package.json
Normal file
27
packages/utils/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@vben/utils",
|
||||
"version": "5.5.9",
|
||||
"homepage": "https://github.com/vbenjs/vue-vben-admin",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "packages/utils"
|
||||
},
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"sideEffects": [
|
||||
"**/*.css"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./src/index.ts",
|
||||
"default": "./src/index.ts"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vben-core/shared": "workspace:*",
|
||||
"@vben-core/typings": "workspace:*",
|
||||
"vue-router": "catalog:"
|
||||
}
|
||||
}
|
||||
178
packages/utils/src/helpers/generate-menus.ts
Normal file
178
packages/utils/src/helpers/generate-menus.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import type { Router, RouteRecordRaw } from 'vue-router';
|
||||
|
||||
import type {
|
||||
AppRouteRecordRaw,
|
||||
ExRouteRecordRaw,
|
||||
MenuRecordRaw,
|
||||
RouteMeta,
|
||||
RouteRecordStringComponent,
|
||||
} from '@vben-core/typings';
|
||||
|
||||
import { filterTree, isHttpUrl, mapTree } from '@vben-core/shared/utils';
|
||||
|
||||
/**
|
||||
* 根据 routes 生成菜单列表
|
||||
* @param routes - 路由配置列表
|
||||
* @param router - Vue Router 实例
|
||||
* @returns 生成的菜单列表
|
||||
*/
|
||||
function generateMenus(
|
||||
routes: RouteRecordRaw[],
|
||||
router: Router,
|
||||
): MenuRecordRaw[] {
|
||||
// 将路由列表转换为一个以 name 为键的对象映射
|
||||
const finalRoutesMap: { [key: string]: string } = Object.fromEntries(
|
||||
router.getRoutes().map(({ name, path }) => [name, path]),
|
||||
);
|
||||
|
||||
let menus = mapTree<ExRouteRecordRaw, MenuRecordRaw>(routes, (route) => {
|
||||
// 获取最终的路由路径
|
||||
const path = finalRoutesMap[route.name as string] ?? route.path ?? '';
|
||||
|
||||
const {
|
||||
meta = {} as RouteMeta,
|
||||
name: routeName,
|
||||
redirect,
|
||||
children = [],
|
||||
} = route;
|
||||
const {
|
||||
activeIcon,
|
||||
badge,
|
||||
badgeType,
|
||||
badgeVariants,
|
||||
hideChildrenInMenu = false,
|
||||
icon,
|
||||
link,
|
||||
order,
|
||||
title = '',
|
||||
} = meta;
|
||||
|
||||
// 确保菜单名称不为空
|
||||
const name = (title || routeName || '') as string;
|
||||
|
||||
// 处理子菜单
|
||||
const resultChildren = hideChildrenInMenu
|
||||
? []
|
||||
: ((children as MenuRecordRaw[]) ?? []);
|
||||
|
||||
// 设置子菜单的父子关系
|
||||
if (resultChildren.length > 0) {
|
||||
resultChildren.forEach((child) => {
|
||||
child.parents = [...(route.parents ?? []), path];
|
||||
child.parent = path;
|
||||
});
|
||||
}
|
||||
|
||||
// 确定最终路径
|
||||
const resultPath = hideChildrenInMenu ? redirect || path : link || path;
|
||||
|
||||
return {
|
||||
activeIcon,
|
||||
badge,
|
||||
badgeType,
|
||||
badgeVariants,
|
||||
icon,
|
||||
name,
|
||||
order,
|
||||
parent: route.parent,
|
||||
parents: route.parents,
|
||||
path: resultPath,
|
||||
show: !meta.hideInMenu,
|
||||
children: resultChildren,
|
||||
};
|
||||
});
|
||||
|
||||
// 对菜单进行排序,避免order=0时被替换成999的问题
|
||||
menus = menus.toSorted((a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
||||
|
||||
// 过滤掉隐藏的菜单项
|
||||
return filterTree(menus, (menu) => !!menu.show);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换后端菜单数据为路由数据
|
||||
* @param menuList 后端菜单数据
|
||||
* @param parent 父级菜单
|
||||
* @param nameSet 用于跟踪已使用的 name,防止重复
|
||||
* @returns 路由数据
|
||||
*/
|
||||
function convertServerMenuToRouteRecordStringComponent(
|
||||
menuList: AppRouteRecordRaw[],
|
||||
parent = '',
|
||||
nameSet: Set<string> = new Set(),
|
||||
): RouteRecordStringComponent[] {
|
||||
const menus: RouteRecordStringComponent[] = [];
|
||||
menuList.forEach((menu) => {
|
||||
// 处理顶级链接菜单
|
||||
if (isHttpUrl(menu.path) && menu.parentId === 0) {
|
||||
const urlMenu: RouteRecordStringComponent = {
|
||||
component: 'IFrameView',
|
||||
meta: {
|
||||
hideInMenu: !menu.visible,
|
||||
icon: menu.icon,
|
||||
link: menu.path,
|
||||
orderNo: menu.sort,
|
||||
title: menu.name,
|
||||
},
|
||||
name: menu.name,
|
||||
path: `/${menu.path}/index`,
|
||||
};
|
||||
menus.push(urlMenu);
|
||||
return;
|
||||
} else if (menu.children && menu.parentId === 0) {
|
||||
menu.component = 'BasicLayout';
|
||||
} else if (!menu.children) {
|
||||
menu.component = menu.component as string;
|
||||
}
|
||||
if (menu.component === 'Layout') {
|
||||
menu.component = 'BasicLayout';
|
||||
}
|
||||
|
||||
if (menu.children && menu.parentId !== 0) {
|
||||
menu.component = '';
|
||||
}
|
||||
|
||||
// path
|
||||
if (parent) {
|
||||
menu.path = `${parent}/${menu.path}`;
|
||||
}
|
||||
|
||||
if (!menu.path.startsWith('/')) {
|
||||
menu.path = `/${menu.path}`;
|
||||
}
|
||||
|
||||
// add by 芋艿:防止 name 重复,只有在 name 重复时,才自动添加 id
|
||||
let finalName = menu.componentName || menu.name;
|
||||
if (nameSet.has(finalName)) {
|
||||
finalName = menu.name + menu.id;
|
||||
console.error(`menu name duplicate: ${menu.name}, id: ${menu.id}`, menu);
|
||||
}
|
||||
nameSet.add(finalName);
|
||||
|
||||
const buildMenu: RouteRecordStringComponent = {
|
||||
component: menu.component,
|
||||
meta: {
|
||||
hideInMenu: !menu.visible,
|
||||
icon: menu.icon,
|
||||
keepAlive: menu.keepAlive,
|
||||
orderNo: menu.sort,
|
||||
title: menu.name,
|
||||
},
|
||||
name: finalName,
|
||||
path: menu.path,
|
||||
};
|
||||
|
||||
if (menu.children && menu.children.length > 0) {
|
||||
buildMenu.children = convertServerMenuToRouteRecordStringComponent(
|
||||
menu.children,
|
||||
menu.path,
|
||||
nameSet,
|
||||
);
|
||||
}
|
||||
|
||||
menus.push(buildMenu);
|
||||
});
|
||||
return menus;
|
||||
}
|
||||
|
||||
export { convertServerMenuToRouteRecordStringComponent, generateMenus };
|
||||
5
packages/utils/src/index.ts
Normal file
5
packages/utils/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './helpers';
|
||||
export * from './validator';
|
||||
export * from '@vben-core/shared/cache';
|
||||
export * from '@vben-core/shared/color';
|
||||
export * from '@vben-core/shared/utils';
|
||||
2
packages/utils/src/validator/index.ts
Normal file
2
packages/utils/src/validator/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './regex';
|
||||
export * from './validator';
|
||||
18
packages/utils/src/validator/regex.ts
Normal file
18
packages/utils/src/validator/regex.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/** 手机号正则表达式(中国) */
|
||||
const MOBILE_REGEX = /(?:0|86|\+86)?1[3-9]\d{9}/;
|
||||
/** 身份证号正则表达式 */
|
||||
const ID_CARD_REGEX = /^\d{15}|\d{18}$/;
|
||||
/** 邮箱正则表达式 */
|
||||
const EMAIL_REGEX = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
|
||||
/** 密码正则表达式 以字母开头,长度在6~18之间,只能包含字母、数字和下划线 */
|
||||
const PASSWORD_REGEX = /^[a-z]\w{5,17}$/i;
|
||||
/** 强密码 必须包含大小写字母和数字的组合,不能使用特殊字符,长度在8-10之间 */
|
||||
const STRONG_PASSWORD_REGEX = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$/;
|
||||
|
||||
export {
|
||||
EMAIL_REGEX,
|
||||
ID_CARD_REGEX,
|
||||
MOBILE_REGEX,
|
||||
PASSWORD_REGEX,
|
||||
STRONG_PASSWORD_REGEX,
|
||||
};
|
||||
16
packages/utils/src/validator/validator.ts
Normal file
16
packages/utils/src/validator/validator.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { MOBILE_REGEX } from './regex';
|
||||
|
||||
/**
|
||||
* 验证是否为手机号码(中国)
|
||||
*
|
||||
* @param value 值
|
||||
* @returns 是否为手机号码(中国)
|
||||
*/
|
||||
function isMobile(value?: null | string): boolean {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
return MOBILE_REGEX.test(value);
|
||||
}
|
||||
|
||||
export { isMobile };
|
||||
Reference in New Issue
Block a user