工具
前言
@vben/utils 是多个 app 共用的工具包。它通过重新导出(re-export)继承了 @vben-core/shared 的 cache、color、utils 三个子模块的全部能力,并在其上补充了 UI 等业务工具。
TIP
@vben/utils 已在各 app 下统一引入,无需单独安装。@vben-core/shared/constants 子模块未被重新导出,其导出(如 ELEMENT_ID_LAYOUT_SCROLL)需直接从 @vben-core/shared/constants 引入。
日期
格式化日期
formatDate:按 dayjs 格式化日期。time 支持 Date / Dayjs / number / string;空值返回 '';解析失败回退为 String(time)。结果按当前时区输出。
import { formatDate } from '@vben/utils';
formatDate('2024-01-01'); // '2024-01-01'
formatDate(new Date(), 'YYYY/MM/DD'); // '2024/01/01'
formatDate(undefined); // ''格式化日期时间
formatDateTime:等价于 formatDate(time, 'YYYY-MM-DD HH:mm:ss')。
import { formatDateTime } from '@vben/utils';
formatDateTime(new Date('2024-01-01T12:34:56')); // '2024-01-01 12:34:56'判断 Date 实例
isDate:类型守卫,判断是否为 Date 实例。
import { isDate } from '@vben/utils';
isDate(new Date()); // true
isDate('2024-01-01'); // false判断 dayjs 实例
isDayjsObject:类型守卫,判断是否为 dayjs 实例。
import { isDayjsObject } from '@vben/utils';
import dayjs from 'dayjs';
isDayjsObject(dayjs()); // true
isDayjsObject(new Date()); // false数组比较
数组相等比较
arraysEqual:比较两个数组是否相等(忽略顺序,按元素计数)。
import { arraysEqual } from '@vben/utils';
arraysEqual([1, 2], [2, 1]); // true
arraysEqual([1, 2], [1, 2, 3]); // false数组严格比较
arraysStrictEqual:比较两个数组是否严格相等(顺序敏感)。
import { arraysStrictEqual } from '@vben/utils';
arraysStrictEqual([1, 2], [2, 1]); // false
arraysStrictEqual([1, 2], [1, 2]); // true对象差异
对象差异比较
diff:深度对比两个对象,返回差异对象(仅含变化的字段),数组比较忽略顺序。
import { diff } from '@vben/utils';
diff({ a: 1, b: 2 }, { a: 1, b: 3 }); // { b: 3 }
diff({ list: [1, 2] }, { list: [2, 1] }); // {} (忽略顺序,无差异)对象严格差异比较
diffStrict:深度对比两个对象,数组比较顺序敏感。
import { diffStrict } from '@vben/utils';
diffStrict({ a: [1, 2] }, { a: [2, 1] }); // { a: [2, 1] }文件下载
按 URL 下载文件
downloadFileFromUrl:通过 URL 下载,支持跨域。Chrome/Safari 用 <a download> 触发;其余浏览器走 openWindow。仅当 source 为 falsy 或非字符串时抛错;不校验 URL 语法。
import { downloadFileFromUrl } from '@vben/utils';
await downloadFileFromUrl({ source: 'https://example.com/file.pdf' });
await downloadFileFromUrl({
source: 'https://example.com/report.pdf',
fileName: 'report.pdf',
});按 Base64 下载文件
downloadFileFromBase64:通过 Base64 数据触发下载。
import { downloadFileFromBase64 } from '@vben/utils';
downloadFileFromBase64({
source: 'data:text/plain;base64,aGVsbG8=',
fileName: 'hello.txt',
});按图片 URL 下载
downloadFileFromImageUrl:先用 urlToBase64 把图片 URL 转成 Base64,再下载。
import { downloadFileFromImageUrl } from '@vben/utils';
await downloadFileFromImageUrl({
source: 'https://example.com/logo.png',
fileName: 'logo.png',
});按 Blob 下载文件
downloadFileFromBlob:通过 Blob 触发下载(创建 Object URL)。非 Blob 入参抛 TypeError。
import { downloadFileFromBlob } from '@vben/utils';
downloadFileFromBlob({
source: new Blob(['hello'], { type: 'text/plain' }),
fileName: 'hello.txt',
});按 BlobPart 下载文件
downloadFileFromBlobPart:下载 BlobPart 类型数据;非 Blob 会被包成 application/octet-stream 的 Blob 再下载。
import { downloadFileFromBlobPart } from '@vben/utils';
downloadFileFromBlobPart({ source: ['hello', 'world'], fileName: 'data.txt' });图片 URL 转 Base64
urlToBase64:通过 canvas 将图片 URL 转为 Base64 dataURL(默认 image/png)。
import { urlToBase64 } from '@vben/utils';
const dataURL = await urlToBase64('https://example.com/logo.png');
// 'data:image/png;base64,iVBORw0KGgo...'触发下载
triggerDownload:插入隐藏 <a>、点击、移除,并在 revokeDelay 毫秒后 URL.revokeObjectURL 释放内存。
import { triggerDownload } from '@vben/utils';
triggerDownload('https://example.com/file.pdf', 'file.pdf');类型判断
判断 undefined
isUndefined:是否为 undefined。
import { isUndefined } from '@vben/utils';
isUndefined(undefined); // true
isUndefined(null); // false判断布尔值
isBoolean:是否为布尔值。
import { isBoolean } from '@vben/utils';
isBoolean(true); // true
isBoolean(0); // false判断空值
isEmpty:是否为空:null / undefined / 空串 / 长度为 0 的数组 / 空 Map、Set / 无属性对象。
import { isEmpty } from '@vben/utils';
isEmpty(''); // true
isEmpty([]); // true
isEmpty({}); // true
isEmpty(null); // true
isEmpty('a'); // false判断 HTTP URL
isHttpUrl:是否以 http:// 或 https:// 开头。
import { isHttpUrl } from '@vben/utils';
isHttpUrl('https://a.com'); // true
isHttpUrl('/api/user'); // false判断 window 对象
isWindow:是否为 window 对象。
import { isWindow } from '@vben/utils';
isWindow(window); // true
isWindow(document); // false判断 macOS 环境
isMacOs:当前环境是否为 macOS(依据 userAgent)。
import { isMacOs } from '@vben/utils';
isMacOs(); // macOS 下返回 true判断 Windows 环境
isWindowsOs:当前环境是否为 Windows。
import { isWindowsOs } from '@vben/utils';
isWindowsOs(); // Windows 下返回 true判断数字
isNumber:是否为有限数字。
import { isNumber } from '@vben/utils';
isNumber(1); // true
isNumber(Infinity); // false
isNumber('1'); // false判断函数
isFunction:来自 @vue/shared 的类型守卫。
import { isFunction } from '@vben/utils';
isFunction(() => {}); // true
isFunction('fn'); // false判断对象
isObject:来自 @vue/shared 的类型守卫。
import { isObject } from '@vben/utils';
isObject({}); // true
isObject([1]); // true
isObject(null); // false判断字符串
isString:来自 @vue/shared 的类型守卫。
import { isString } from '@vben/utils';
isString('a'); // true
isString(1); // false取首个非空值
getFirstNonNullOrUndefined:返回入参列表中第一个非 null、非 undefined 的值,全部为空时返回 undefined。
import { getFirstNonNullOrUndefined } from '@vben/utils';
getFirstNonNullOrUndefined(undefined, null, 42, 'hello'); // 42
getFirstNonNullOrUndefined(undefined, null); // undefined字符串转换
首字母大写
capitalizeFirstLetter:首字母大写。
import { capitalizeFirstLetter } from '@vben/utils';
capitalizeFirstLetter('abc'); // 'Abc'首字母小写
toLowerCaseFirstLetter:首字母小写。
import { toLowerCaseFirstLetter } from '@vben/utils';
toLowerCaseFirstLetter('Abc'); // 'abc'转驼峰键名
toCamelCase:生成驼峰键名(无父级时返回 key,否则拼接父级 + 首字母大写子键)。
import { toCamelCase } from '@vben/utils';
toCamelCase('name'); // 'name'
toCamelCase('name', 'user'); // 'userName'kebab 转驼峰
kebabToCamelCase:kebab-case 转 camelCase。
import { kebabToCamelCase } from '@vben/utils';
kebabToCamelCase('my-var-name'); // 'myVarName'加载脚本
loadScript:动态加载 JS 文件,已存在同 src 的 <script> 时直接 resolve;加载失败 reject。
import { loadScript } from '@vben/utils';
await loadScript('https://cdn.example.com/lib.js');异步与流程控制
栈结构
Stack:栈数据结构。dedup 为 true 时入栈自动去重(移除旧位置);maxSize 限制最大容量,超出则丢弃最早元素。
import { Stack } from '@vben/utils';
const stack = new Stack<string>(true, 3);
stack.push('a', 'b', 'c');
stack.push('a'); // 去重后栈为 ['b', 'c', 'a']
stack.peek(); // 'a'
stack.pop(); // 'a'
stack.size; // 2
stack.toArray(); // ['b', 'c']创建栈
createStack:工厂函数,等价于 new Stack(...)。
import { createStack } from '@vben/utils';
const stack = createStack<string>(false, 5);
stack.push('a', 'b');
stack.toArray(); // ['a', 'b']Promise 转 err/data 元组
to:把 Promise 转成 Go 风格的 [err, data] 元组。失败时若传入 errorExt,会合并到错误对象上。
import { to } from '@vben/utils';
const [err, data] = await to(fetchUser(id));
if (err) return;
console.log(data);树形遍历
遍历树收集值
traverseTreeValues:深度遍历,收集每个节点经 getValue 映射后的值,过滤掉 falsy 值。
import { traverseTreeValues } from '@vben/utils';
const tree = [{ id: 1, children: [{ id: 2 }] }];
traverseTreeValues(tree, (n) => n.id); // [1, 2]过滤树
filterTree:按条件过滤树节点,保留命中节点(含命中子节点),原顺序返回。会修改入参树。
import { filterTree } from '@vben/utils';
const tree = [{ id: 1, children: [{ id: 2 }] }];
filterTree(tree, (n) => n.id === 1); // 保留 id=1 节点映射树
mapTree:递归映射树节点。mapper 接收当前节点与父节点,返回新节点;其 childProps 子数组会被递归映射。
import { mapTree } from '@vben/utils';
const tree = [{ id: 1, children: [{ id: 2 }] }];
const mapped = mapTree(tree, (n) => ({ ...n, id: n.id * 10 }));
// [{ id: 10, children: [{ id: 20 }] }]排序树
sortTree:递归对树排序,返回新结构(不修改原树)。
import { sortTree } from '@vben/utils';
const tree = [
{ id: 2, children: [] },
{ id: 1, children: [] },
];
const sorted = sortTree(tree, (a, b) => a.id - b.id); // 按 id 升序,返回新结构数据处理
按字段去重
uniqueByField:根据指定字段对对象数组去重。
import { uniqueByField } from '@vben/utils';
uniqueByField([{ id: 1 }, { id: 1 }, { id: 2 }], 'id'); // [{ id: 1 }, { id: 2 }]窗口操作
打开新窗口
openWindow:以指定参数打开新窗口。默认 noopener = noreferrer = true、target = '_blank'。
import { openWindow } from '@vben/utils';
openWindow('https://example.com');
openWindow('https://example.com', { target: '_self' });新窗口打开路由
openRouteInNewWindow:在新窗口打开应用内路由 path,自动补全 origin 与 hash 前缀。
import { openRouteInNewWindow } from '@vben/utils';
openRouteInNewWindow('/dashboard');防抖
debounce:来自 es-toolkit/compat 的防抖函数。
import { debounce } from '@vben/utils';
const fn = debounce(() => console.log('exec'), 200);
fn();路径取值与设值
按路径取值
get:来自 es-toolkit/compat,按路径获取对象值。
import { get } from '@vben/utils';
get({ a: { b: 1 } }, 'a.b'); // 1按路径设值
set:来自 es-toolkit/compat,按路径设置对象值。
import { set } from '@vben/utils';
const obj = {};
set(obj, 'a.b.c', 1); // obj -> { a: { b: { c: 1 } } }深度比较与深拷贝
深度相等比较
isEqual:来自 es-toolkit/compat 的深度相等比较。
import { isEqual } from '@vben/utils';
isEqual({ a: 1 }, { a: 1 }); // true深拷贝
cloneDeep:来自 lodash.clonedeep 的深拷贝。
import { cloneDeep } from '@vben/utils';
const copy = cloneDeep({ a: { b: 1 } });颜色工具
基于 @ctrl/tinycolor 与 theme-colors,提供颜色判断与格式转换。
判断深色
isDarkColor:判断颜色是否为深色。
import { isDarkColor } from '@vben/utils';
isDarkColor('#000'); // true
isDarkColor('#fff'); // false判断浅色
isLightColor:判断颜色是否为浅色。
import { isLightColor } from '@vben/utils';
isLightColor('#fff'); // true
isLightColor('#000'); // false转 HSL 字符串
convertToHsl:转为 hsl(h s% l%) 字符串;透明度 < 1 时追加 alpha。
import { convertToHsl } from '@vben/utils';
convertToHsl('#ff0000'); // 'hsl(0 100% 50%)'转 HSL CSS 变量值
convertToHslCssVar:转为适合作为 CSS 变量值的 h s% l%(不带 hsl() 包裹);透明度 < 1 时格式为 h s% l% / a。
import { convertToHslCssVar } from '@vben/utils';
convertToHslCssVar('#ff0000'); // '0 100% 50%'转 RGB 字符串
convertToRgb:转为 RGB 字符串。会先剔除 deg/grad/rad/turn 单位(TinyColor 无法解析含这些单位的 hsl 字符串),无效颜色返回 rgb(0, 0, 0)。
import { convertToRgb } from '@vben/utils';
convertToRgb('hsl(0deg 100% 50%)'); // 'rgb(255, 0, 0)'
convertToRgb('xxx'); // 'rgb(0, 0, 0)'校验颜色有效性
isValidColor:检查颜色字符串是否有效(基于 TinyColor.isValid),空值返回 false。
import { isValidColor } from '@vben/utils';
isValidColor('#fff'); // true
isValidColor('xxx'); // falseTinyColor 颜色类
TinyColor:颜色处理类,来自第三方库 @ctrl/tinycolor,可直接 new TinyColor('#fff') 使用。
import { TinyColor } from '@vben/utils';
new TinyColor('#ff0000').toHslString(); // 'hsl(0, 100%, 50%)'@vben/utils 仅作再导出,实例上的全部方法(lighten、darken、saturate、desaturate、brighten、toHexString、toHslString、toRgbString、isValid 等)均来自 @ctrl/tinycolor,完整 API 见 官方文档。
UI 工具
获取弹出层容器
getPopupContainer:用于 el-select / el-picker 等组件的 getPopupContainer 属性。节点位于表单内返回所在 form 元素,否则返回节点父节点,未传入节点时返回 document.body。
<script setup lang="ts">
import { getPopupContainer } from '@vben/utils';
</script>
<template>
<el-select :get-popup-container="getPopupContainer" />
</template>
Liu