This commit is contained in:
2025-12-18 16:37:33 +08:00
commit e974bf361d
4183 changed files with 497339 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
import type { Recordable } from '@vben/types';
export * from './rangePickerProps';
export * from './routerHelper';
/**
* 查找数组对象的某个下标
* @param {Array} ary 查找的数组
* @param {Function} fn 判断的方法
*/
type Fn<T = any> = (item: T, index: number, array: Array<T>) => boolean;
export const findIndex = <T = Recordable<any>>(
ary: Array<T>,
fn: Fn<T>,
): number => {
if (ary.findIndex) {
return ary.findIndex((item, index, array) => fn(item, index, array));
}
let index = -1;
ary.some((item: T, i: number, ary: Array<T>) => {
const ret: boolean = fn(item, i, ary);
if (ret) {
index = i;
return true;
}
return false;
});
return index;
};

View File

@@ -0,0 +1,74 @@
import dayjs from 'dayjs';
import { $t } from '#/locales';
/** 时间段选择器拓展 */
export function getRangePickerDefaultProps() {
return {
// 显示在输入框中的格式
format: 'YYYY-MM-DD HH:mm:ss',
// 绑定值的格式。 不指定则绑定值为 Date 对象
valueFormat: 'YYYY-MM-DD HH:mm:ss',
defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')],
// 输入框提示文字
startPlaceholder: $t('utils.rangePicker.beginTime'),
endPlaceholder: $t('utils.rangePicker.endTime'),
// 快捷时间范围
shortcuts: [
{
text: $t('utils.rangePicker.today'),
value: () => {
return [dayjs().startOf('day'), dayjs().endOf('day')];
},
},
{
text: $t('utils.rangePicker.yesterday'),
value: () => {
return [
dayjs().subtract(1, 'day').startOf('day'),
dayjs().subtract(1, 'day').endOf('day'),
];
},
},
{
text: $t('utils.rangePicker.last7Days'),
value: () => {
return [
dayjs().subtract(7, 'day').startOf('day'),
dayjs().endOf('day'),
];
},
},
{
text: $t('utils.rangePicker.last30Days'),
value: () => {
return [
dayjs().subtract(30, 'day').startOf('day'),
dayjs().endOf('day'),
];
},
},
{
text: $t('utils.rangePicker.thisWeek'),
value: () => {
return [dayjs().startOf('week'), dayjs().endOf('day')];
},
},
{
text: $t('utils.rangePicker.lastWeek'),
value: () => {
return [
dayjs().subtract(1, 'week').startOf('day'),
dayjs().endOf('day'),
];
},
},
{
text: $t('utils.rangePicker.thisMonth'),
value: () => {
return [dayjs().startOf('month'), dayjs().endOf('day')];
},
},
],
};
}

View File

@@ -0,0 +1,37 @@
import type {
RouteLocationNormalized,
RouteRecordNormalized,
} from 'vue-router';
import { defineAsyncComponent } from 'vue';
const modules = import.meta.glob('../views/**/*.{vue,tsx}');
/**
* 注册一个异步组件
* @param componentPath 例:/bpm/oa/leave/detail
*/
export function registerComponent(componentPath: string) {
for (const item in modules) {
if (item.includes(componentPath)) {
// 使用异步组件的方式来动态加载组件
return defineAsyncComponent(modules[item] as any);
}
}
}
export const getRawRoute = (
route: RouteLocationNormalized,
): RouteLocationNormalized => {
if (!route) return route;
const { matched, ...opt } = route;
return {
...opt,
matched: (matched
? matched.map((item) => ({
meta: item.meta,
name: item.name,
path: item.path,
}))
: undefined) as RouteRecordNormalized[],
};
};

View File

@@ -0,0 +1,442 @@
interface TreeHelperConfig {
id: string;
children: string;
pid: string;
}
const DEFAULT_CONFIG: TreeHelperConfig = {
id: 'id',
children: 'children',
pid: 'pid',
};
export const defaultProps = {
children: 'children',
label: 'name',
value: 'id',
isLeaf: 'leaf',
emitPath: false, // 用于 cascader 组件:在选中节点改变时,是否返回由该节点所在的各级菜单的值所组成的数组,若设置 false则只返回该节点的值
};
const getConfig = (config: Partial<TreeHelperConfig>) =>
Object.assign({}, DEFAULT_CONFIG, config);
// tree from list
export const listToTree = <T = any>(
list: any[],
config: Partial<TreeHelperConfig> = {},
): T[] => {
const conf = getConfig(config) as TreeHelperConfig;
const nodeMap = new Map();
const result: T[] = [];
const { id, children, pid } = conf;
for (const node of list) {
node[children] = node[children] || [];
nodeMap.set(node[id], node);
}
for (const node of list) {
const parent = nodeMap.get(node[pid]);
(parent ? parent.children : result).push(node);
}
return result;
};
export const treeToList = <T = any>(
tree: any,
config: Partial<TreeHelperConfig> = {},
): T => {
config = getConfig(config);
const { children } = config;
const result: any = [...tree];
for (let i = 0; i < result.length; i++) {
const childNodes = result[i][children];
if (!childNodes) continue;
result.splice(i + 1, 0, ...childNodes);
}
return result;
};
export const findNode = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {},
): null | T => {
config = getConfig(config);
const { children } = config;
const list = [...tree];
for (const node of list) {
if (func(node)) return node;
const childNodes = node[children];
if (childNodes) {
list.push(...childNodes);
}
}
return null;
};
export const findNodeAll = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {},
): T[] => {
config = getConfig(config);
const { children } = config;
const list = [...tree];
const result: T[] = [];
for (const node of list) {
func(node) && result.push(node);
const childNodes = node[children];
if (childNodes) {
list.push(...childNodes);
}
}
return result;
};
export const findPath = <T = any>(
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {},
): null | T | T[] => {
config = getConfig(config);
const path: T[] = [];
const list = [...tree];
const visitedSet = new Set();
const { children } = config;
while (list.length > 0) {
const node = list[0];
if (visitedSet.has(node)) {
path.pop();
list.shift();
} else {
visitedSet.add(node);
const childNodes = node[children];
if (childNodes) {
list.unshift(...childNodes);
}
path.push(node);
if (func(node)) {
return path;
}
}
}
return null;
};
export const findPathAll = (
tree: any,
func: Fn,
config: Partial<TreeHelperConfig> = {},
) => {
config = getConfig(config);
const path: any[] = [];
const list = [...tree];
const result: any[] = [];
const visitedSet = new Set();
const { children } = config;
while (list.length > 0) {
const node = list[0];
if (visitedSet.has(node)) {
path.pop();
list.shift();
} else {
visitedSet.add(node);
const childNodes = node[children];
if (childNodes) {
list.unshift(...childNodes);
}
path.push(node);
func(node) && result.push([...path]);
}
}
return result;
};
export const filter = <T = any>(
tree: T[],
func: (n: T) => boolean,
config: Partial<TreeHelperConfig> = {},
): T[] => {
config = getConfig(config);
const children = config.children as string;
function listFilter(list: T[]) {
return list
.map((node: any) => ({ ...node }))
.filter((node) => {
node[children] = node[children] && listFilter(node[children]);
return func(node) || node[children]?.length > 0;
});
}
return listFilter(tree);
};
export const forEach = <T = any>(
tree: T[],
func: (n: T) => any,
config: Partial<TreeHelperConfig> = {},
): void => {
config = getConfig(config);
const list: any[] = [...tree];
const { children } = config;
for (let i = 0; i < list.length; i++) {
// func 返回true就终止遍历避免大量节点场景下无意义循环引起浏览器卡顿
if (func(list[i])) {
return;
}
children &&
list[i][children] &&
list.splice(i + 1, 0, ...list[i][children]);
}
};
/**
* @description: Extract tree specified structure
*/
export const treeMap = <T = any>(
treeData: T[],
opt: { children?: string; conversion: Fn },
): T[] => {
return treeData.map((item) => treeMapEach(item, opt));
};
/**
* @description: Extract tree specified structure
*/
export const treeMapEach = (
data: any,
{ children = 'children', conversion }: { children?: string; conversion: Fn },
) => {
const haveChildren =
Array.isArray(data[children]) && data[children].length > 0;
const conversionData = conversion(data) || {};
return haveChildren
? {
...conversionData,
[children]: data[children].map((i: number) =>
treeMapEach(i, {
children,
conversion,
}),
),
}
: {
...conversionData,
};
};
/**
* 递归遍历树结构
* @param treeDatas 树
* @param callBack 回调
* @param parentNode 父节点
*/
export const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {
treeDatas.forEach((element) => {
const newNode = callBack(element, parentNode) || element;
if (element.children) {
eachTree(element.children, callBack, newNode);
}
});
};
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
*/
export const handleTree = (
data: any[],
id?: string,
parentId?: string,
children?: string,
) => {
if (!Array.isArray(data)) {
console.warn('data must be an array');
return [];
}
const config = {
id: id || 'id',
parentId: parentId || 'parentId',
childrenList: children || 'children',
};
const childrenListMap = {};
const nodeIds = {};
const tree: any[] = [];
for (const d of data) {
const parentId = d[config.parentId];
if (
childrenListMap[parentId] === null ||
childrenListMap[parentId] === undefined
) {
childrenListMap[parentId] = [];
}
nodeIds[d[config.id]] = d;
childrenListMap[parentId].push(d);
}
for (const d of data) {
const parentId = d[config.parentId];
if (nodeIds[parentId] === null || nodeIds[parentId] === undefined) {
tree.push(d);
}
}
for (const t of tree) {
adaptToChildrenList(t);
}
function adaptToChildrenList(o) {
if (childrenListMap[o[config.id]] !== null) {
o[config.childrenList] = childrenListMap[o[config.id]];
}
if (o[config.childrenList]) {
for (const c of o[config.childrenList]) {
adaptToChildrenList(c);
}
}
}
return tree;
};
/**
* 构造树型结构数据
* @param {*} data 数据源
* @param {*} id id字段 默认 'id'
* @param {*} parentId 父节点字段 默认 'parentId'
* @param {*} children 孩子节点字段 默认 'children'
* @param {*} rootId 根Id 默认 0
*/
// @ts-ignore: 遗留函数,保持原有逻辑不变
export const handleTree2 = (data, id, parentId, children, rootId) => {
id = id || 'id';
parentId = parentId || 'parentId';
// children = children || 'children'
rootId =
rootId ||
Math.min(
...data.map((item) => {
return item[parentId];
}),
) ||
0;
// 对源数据深度克隆
const cloneData = structuredClone(data);
// 循环所有项
const treeData = cloneData.filter((father) => {
const branchArr = cloneData.filter((child) => {
// 返回每一项的子级数组
return father[id] === child[parentId];
});
branchArr.length > 0 ? (father.children = branchArr) : '';
// 返回第一层
return father[parentId] === rootId;
});
return treeData === '' ? data : treeData;
};
/**
* 校验选中的节点,是否为指定 level
*
* @param tree 要操作的树结构数据
* @param nodeId 需要判断在什么层级的数据
* @param level 检查的级别, 默认检查到二级
* @return true 是false 否
*/
export const checkSelectedNode = (
tree: any[],
nodeId: any,
level = 2,
): boolean => {
if (tree === undefined || !Array.isArray(tree) || tree.length === 0) {
console.warn('tree must be an array');
return false;
}
// 校验是否是一级节点
if (tree.some((item) => item.id === nodeId)) {
return false;
}
// 递归计数
let count = 1;
// 深层次校验
function performAThoroughValidation(arr: any[]): boolean {
count += 1;
for (const item of arr) {
if (item.id === nodeId) {
return true;
} else if (
item.children !== undefined &&
item.children.length > 0 &&
performAThoroughValidation(item.children)
) {
return true;
}
}
return false;
}
for (const item of tree) {
count = 1;
if (
performAThoroughValidation(item.children) && // 找到后对比是否是期望的层级
count >= level
) {
return true;
}
}
return false;
};
/**
* 获取节点的完整结构
* @param tree 树数据
* @param nodeId 节点 id
*/
export const treeToString = (tree: any[], nodeId) => {
if (tree === undefined || !Array.isArray(tree) || tree.length === 0) {
console.warn('tree must be an array');
return '';
}
// 校验是否是一级节点
const node = tree.find((item) => item.id === nodeId);
if (node !== undefined) {
return node.name;
}
let str = '';
function performAThoroughValidation(arr) {
if (arr === undefined || !Array.isArray(arr) || arr.length === 0) {
return false;
}
for (const item of arr) {
if (item.id === nodeId) {
str += ` / ${item.name}`;
return true;
} else if (item.children !== undefined && item.children.length > 0) {
str += ` / ${item.name}`;
if (performAThoroughValidation(item.children)) {
return true;
}
}
}
return false;
}
for (const item of tree) {
str = `${item.name}`;
if (performAThoroughValidation(item.children)) {
break;
}
}
return str;
};

View File

@@ -0,0 +1,67 @@
import type { UploadRawFile } from 'element-plus';
import { ElMessage } from 'element-plus';
const message = ElMessage; // 消息
enum UploadType {
Image = 'image',
Video = 'video',
Voice = 'voice',
}
const useBeforeUpload = (type: UploadType, maxSizeMB: number) => {
const fn = (rawFile: UploadRawFile): boolean => {
let allowTypes: string[] = [];
let name = '';
switch (type) {
case UploadType.Image: {
allowTypes = [
'image/jpeg',
'image/png',
'image/gif',
'image/bmp',
'image/jpg',
];
maxSizeMB = 2;
name = '图片';
break;
}
case UploadType.Video: {
allowTypes = ['video/mp4'];
maxSizeMB = 10;
name = '视频';
break;
}
case UploadType.Voice: {
allowTypes = [
'audio/mp3',
'audio/mpeg',
'audio/wma',
'audio/wav',
'audio/amr',
];
maxSizeMB = 2;
name = '语音';
break;
}
}
// 格式不正确
if (!allowTypes.includes(rawFile.type)) {
message.error(`上传${name}格式不对!`);
return false;
}
// 大小不正确
if (rawFile.size / 1024 / 1024 > maxSizeMB) {
message.error(`上传${name}大小不能超过${maxSizeMB}M!`);
return false;
}
return true;
};
return fn;
};
export { UploadType, useBeforeUpload };