@bigflower/utils
v1.1.0
Published
Common utility functions and constants
Readme
@bigflower/utils
通用工具函数与常量库。仅提供 ESM 产物,自带类型声明,支持 tree-shaking。
安装
pnpm add @bigflower/utils使用
所有函数与常量均从包根部按需导入:
import { isEmail, thousand, DATE_FORMAT } from "@bigflower/utils";
isEmail("[email protected]"); // true
thousand(1234567.89, "¥", 2); // "¥1,234,567.89"
DATE_FORMAT; // "YYYY-MM-DD"目录
- 规则判断 —
isNilisStringisEmptyisNumericisUrlisEmailisIpv4isIpv6isIpisBankNoisIdNoisMobileisSmsisUsciisPassword - 格式化 —
separatorthousanddecimalformatSizeasteriskdisplay - 解析 —
parseIdNo - 对象 —
merge - 文件 —
getMimeTypebase64ToDataUrlbytesToBase64base64ToBytesbase64ToFileblobToFiledownloadFilezipFilesunzipFiles - DOM —
openWithFormloadScript - 常量
规则判断
所有判断函数都接受 unknown,传入非预期类型时返回 false,无需调用前额外做类型检查。
isNil
isNil(value: unknown): value is null | undefined;判断值是否为 null 或 undefined,可用作类型守卫。
isNil(null); // true
isNil(undefined); // true
isNil(0); // false
isNil(""); // falseisString
isString(value: unknown): value is string;判断值是否为原始字符串,可用作类型守卫。
isString("abc"); // true
isString(123); // false
isString(new String("abc")); // false,包装对象不算原始字符串isEmpty
isEmpty(value: unknown): boolean;判断值是否为空。判定规则依次为:null / undefined 为空;字符串和数组按 length;Map 和 Set 按 size;其余对象按自身可枚举键的数量;其他类型一律不为空。
isEmpty(null); // true
isEmpty(""); // true
isEmpty([]); // true
isEmpty(new Map()); // true
isEmpty({}); // true
isEmpty(" "); // false,空白字符不算空
isEmpty([1]); // false
isEmpty({ a: 1 }); // false
isEmpty(0); // false
isEmpty(false); // false注意:
Date、RegExp、函数等没有可枚举自有属性的对象会被判为空(isEmpty(new Date()) === true)。如需判断这类值,请自行加类型判断。
isNumeric
isNumeric(value: unknown): boolean;判断值是否可转换为有限数值。数字类型要求为有限数;字符串会去除首尾空白后转换,空字符串不通过。
isNumeric(1.5); // true
isNumeric("12.5"); // true
isNumeric(" 12 "); // true
isNumeric(NaN); // false
isNumeric(Infinity); // false
isNumeric(""); // false
isNumeric("abc"); // false
isNumeric(null); // false注意:沿用
Number()的解析规则,"0x10"、"1e3"均返回true。
isUrl
isUrl(value: unknown, loose?: boolean): boolean; // loose 默认 false判断值是否为合法 URL。默认仅接受 http: 和 https:;loose 为 true 时接受任意协议。
isUrl("https://a.com/b?c=1"); // true
isUrl("ftp://a.com"); // false
isUrl("ftp://a.com", true); // true
isUrl("a.com"); // false,缺少协议
isUrl("/path"); // false,相对路径不是合法 URL注意:
loose为true时javascript:alert(1)也会通过。若结果会写入href等位置,请勿使用loose。
isEmail
isEmail(value: unknown): boolean;判断值是否符合常见邮箱格式:@ 前后均非空且不含空白,域名部分至少含一个 .。
isEmail("[email protected]"); // true
isEmail("user@example"); // false,域名缺少点号
isEmail("a [email protected]"); // false,含空白注意:规则较宽松,
[email protected]也会通过;不校验国际化域名和实际可达性。
isIpv4
isIpv4(value: unknown): boolean;判断值是否为 IPv4 地址。要求四段十进制数、每段不超过 255 且无前导零。
isIpv4("192.168.1.1"); // true
isIpv4("0.0.0.0"); // true
isIpv4("256.1.1.1"); // false,超出范围
isIpv4("192.168.01.1"); // false,含前导零
isIpv4("1.2.3"); // false,段数不足isIpv6
isIpv6(value: unknown): boolean;判断值是否为 IPv6 地址。支持 :: 零压缩写法,每组为 1~4 位十六进制。
isIpv6("2001:db8:0:0:0:0:2:1"); // true
isIpv6("2001:db8::2:1"); // true
isIpv6("::1"); // true
isIpv6("2001:db8"); // false,分组不足
isIpv6("::ffff:192.168.1.1"); // false,不支持 IPv4 映射格式isIp
isIp(value: unknown): boolean;判断值是否为 IPv4 或 IPv6 地址,等价于 isIpv4(value) || isIpv6(value)。
isIp("192.168.1.1"); // true
isIp("::1"); // true
isIp("abc"); // falseisBankNo
isBankNo(value: unknown, loose?: boolean): boolean; // loose 默认 false使用 Luhn 算法校验银行卡号。校验前会自动去除空格和连字符。默认接受 9~~20 位数字,loose 为 true 时放宽到 8~~30 位。
isBankNo("4242424242424242"); // true
isBankNo("4242 4242 4242 4242"); // true,忽略空格
isBankNo("4242-4242-4242-4242"); // true,忽略连字符
isBankNo("4242424242424243"); // false,校验位不匹配
isBankNo("4242424242", true); // true,loose 放宽长度下限isIdNo
isIdNo(value: unknown): boolean;校验中国大陆 18 位居民身份证号码,同时验证出生日期的真实性和末位校验码。末位 x 不区分大小写。
isIdNo("11010519491231002X"); // true
isIdNo("11010519491231002x"); // true
isIdNo("110105194912310021"); // false,校验码错误
isIdNo("110105194913310021"); // false,出生月份非法
isIdNo("11010519491231"); // false,位数不足注意:不支持 15 位一代身份证;出生日期按运行环境的本地时区解析。
isMobile
isMobile(value: unknown): boolean;判断值是否为中国大陆手机号:以 13~19 开头的 11 位数字。
isMobile("13800138000"); // true
isMobile("12800138000"); // false,第二位非法
isMobile("1380013800"); // false,位数不足isSms
isSms(value: unknown): boolean;判断值是否为 6 位数字短信验证码。
isSms("123456"); // true
isSms("12345"); // falseisUsci
isUsci(value: unknown): boolean;校验 18 位统一社会信用代码,含末位校验码验证。字母不区分大小写,且自动排除易混淆的 I、O、S、V、Z。
isUsci("91350100M000100Y43"); // true
isUsci("91350100m000100y43"); // true,忽略大小写
isUsci("91350100M000100Y44"); // false,校验码错误
isUsci("91350100I000100Y43"); // false,含非法字符 IisPassword
isPassword(value: unknown, options?: PasswordOptions): boolean;
type PasswordOptions = {
alpha?: boolean; // 是否必须含字母,默认 true
sensitive?: boolean; // 是否必须同时含大小写字母,默认 false
number?: boolean; // 是否必须含数字,默认 true
special?: string; // 允许且必须出现的特殊字符集,默认 SPECIAL_CHARS
min?: number; // 最小长度,默认 8
max?: number; // 最大长度,默认 16
};按长度和字符组成规则校验密码。密码只能由字母、数字和 special 中的字符构成,出现其他字符(如中文)一律不通过。
alpha、number、special 都是双向约束:设为 true(或非空字符串)表示必须包含,设为 false(或空字符串)表示必须不包含。
isPassword("Abcd123!"); // true
isPassword("abcd1234"); // false,默认要求包含特殊字符
isPassword("abcd1234", { special: "" }); // true,关闭特殊字符要求
isPassword("abcd123!中"); // false,含允许范围外的字符
isPassword("abcd123!", { sensitive: true }); // false,缺少大写字母
isPassword("Abcd123!", { sensitive: true }); // true
isPassword("abc1!", { min: 4, max: 8 }); // true,自定义长度区间
isPassword("12345678", { alpha: false, special: "" }); // true,纯数字密码格式化
separator
separator(value: unknown, sign?: string, length?: number, reverse?: boolean): string;
// sign 默认 " ",length 默认 4,reverse 默认 false将值的字符串形式按固定长度分组并插入分隔符。reverse 为 true 时从右往左分组,适合数字场景。length 非正整数时回退为 4。
separator("1234567890"); // "1234 5678 90"
separator("1234567890", "-"); // "1234-5678-90"
separator("1234567890", " ", 3); // "123 456 789 0"
separator("1234567890", " ", 4, true); // "12 3456 7890",从右往左
separator(1234567890, ",", 3, true); // "1,234,567,890"thousand
thousand(value: unknown, currency?: string, fixed?: number): string;
// currency 默认 ""按千分位格式化数值,可附加货币符号并固定小数位数。非数值返回空字符串。fixed 需为非负整数,否则忽略并保留原有小数位。负号位于货币符号之前。
thousand(1234567.891); // "1,234,567.891"
thousand(1234567.891, "¥"); // "¥1,234,567.891"
thousand(1234567.891, "¥", 2); // "¥1,234,567.89"
thousand("1234.5", "", 0); // "1,235"
thousand(-1234.5, "$", 2); // "-$1,234.50"
thousand("abc"); // ""
thousand(null); // ""注意:固定使用
en-US千分位规则;数值经Number转换,超大数可能损失精度。
decimal
decimal(value: unknown, scale?: number, fixed?: number): string;
// scale 默认 0按 10^scale 缩放十进制值并返回字符串,即结果为 value × 10^scale。内部使用 BigInt 计算,不受浮点误差影响,适合金额分/元换算。
fixed 指定保留的小数位数,省略时保留缩放后的原始精度。舍入方式为远离零方向的四舍五入。无法解析的值返回空字符串。
decimal("1.23", 2); // "123",元转分
decimal("123", -2); // "1.23",分转元
decimal(12.3456); // "12.3456",保留原始精度
decimal("1.239", 0, 2); // "1.24"
decimal("1.235", 0, 2); // "1.24",远离零方向舍入
decimal("-1.235", 0, 2); // "-1.24"
decimal("1.005", 0, 2); // "1.01",不受浮点误差影响
decimal(0.1 + 0.2, 0, 2); // "0.30"
decimal("1e3"); // "1000",支持科学计数法
decimal("abc"); // ""formatSize
formatSize(value: unknown, from?: SizeUnit, to?: SizeUnit, fixed?: number): string;
// from 默认 "B",fixed 默认 2
type SizeUnit = "B" | "KB" | "MB" | "GB" | "TB";按 1024 进制在字节单位之间换算,返回带单位的字符串(数值与单位间有一个空格)。省略 to 时自动选取使数值落在 [1, 1024) 的单位,上下限为 B 和 TB。
fixed 为保留的小数位数,采用 toFixed 的四舍五入。值无法转为有限数值、单位不在枚举内、或 fixed 不是 0~100 的整数时返回空字符串。
formatSize(1536); // "1.50 KB",自动选取单位
formatSize(1048576); // "1.00 MB"
formatSize(0); // "0.00 B"
formatSize(1536, "B", "KB"); // "1.50 KB"
formatSize(1, "GB", "MB"); // "1024.00 MB"
formatSize(2, "KB", "GB"); // "0.00 GB",指定目标单位时不做上下限调整
formatSize(1536, "B", "KB", 0); // "2 KB"
formatSize(1.5, "MB", undefined, 3); // "1.500 MB"
formatSize(1023.999); // "1.00 KB",进位后自动切换单位
formatSize(1536, "b"); // "",单位大小写敏感
formatSize("abc"); // ""注意:使用 1024 进制和
KB/MB记法(即通常所说的 KiB/MiB),不支持 1000 进制的硬盘厂商口径。
asterisk
asterisk(value: unknown, start?: number, end?: number, sign?: string): string;
// start 默认 0,sign 默认 "*"将字符串 [start, end) 区间内的字符替换为掩码符号,区间语义与 String.prototype.slice 一致,省略 end 表示到字符串末尾。非字符串返回空字符串;sign 为空字符串时原样返回。
asterisk("13800138000", 3, 7); // "138****8000"
asterisk("abcdef", 1, 3); // "a**def"
asterisk("abcdef", 1, 3, "#"); // "a##def"
asterisk("abcdef"); // "******",全部替换
asterisk("abcdef", 2); // "ab****",从下标 2 到末尾
asterisk("abcdef", -2); // "abcd**",负数下标从末尾计算
asterisk("abcdef", 10); // "abcdef",区间为空时原样返回
asterisk(123456, 1, 3); // "",非字符串注意:按 UTF-16 码元计数,emoji 等字符可能被截断。
display
display(value: unknown, unit?: string, empty?: string): string;
// empty 默认 "-"用于展示层的空值兜底:值为空时返回占位符,否则转为字符串并追加单位。空值判定复用 isEmpty。
display("abc"); // "abc"
display(12, "元"); // "12元"
display(0, "元"); // "0元",0 不算空
display(null); // "-"
display(undefined, "元"); // "-",占位时不追加单位
display("", "", "暂无"); // "暂无"
display([], "", "空"); // "空"解析
parseIdNo
parseIdNo(value: unknown): IdNoInfo | null;
type IdNoInfo = {
gender: "male" | "female"; // 性别,由顺序码奇偶决定
birthday: string; // 出生日期,格式为 YYYY-MM-DD
region: string; // 6 位行政区划代码
};从中国大陆 18 位居民身份证号码中提取性别、出生日期和地区码。解析前先用 isIdNo 校验,号码非法时返回 null。
parseIdNo("11010519491231002X");
// { gender: "female", birthday: "1949-12-31", region: "110105" }
parseIdNo("110105194912310011");
// { gender: "male", birthday: "1949-12-31", region: "110105" }
parseIdNo("110105194912310021"); // null,校验码错误
parseIdNo(undefined); // null注意:只返回 6 位地区码本身,不含省市区名称;行政区划代码会随区划调整变更,历史号码可能对应已撤销的代码。
对象
merge
merge<T extends Mergeable>(target: T, ...sources: (Mergeable | undefined)[]): T;
type Mergeable = Record<PropertyKey, unknown> | unknown[];深度合并多个对象或数组,返回全新的值,不修改任何入参。
合并规则:两侧同为普通对象时逐键递归;同为数组时按下标合并;其余情况后者整体覆盖前者。sources 中的 undefined 会被跳过。已内置原型链污染防护,__proto__、constructor、prototype 键会被忽略。
merge({ a: { b: 1 } }, { a: { c: 2 } }); // { a: { b: 1, c: 2 } }
merge({ a: 1 }, { a: 2 }, { a: 3 }); // { a: 3 }
merge({ a: [1, 2, 3] }, { a: [9] }); // { a: [9, 2, 3] },数组按下标合并
merge({ a: 1 }, undefined, { b: 2 }); // { a: 1, b: 2 },跳过 undefined 源
// 深拷贝,修改结果不影响源对象
const source = { a: { b: 1 } };
const result = merge({}, source);
result.a.b = 99;
source.a.b; // 1注意:跳过的只是整个为
undefined的源对象;源对象内部的undefined属性值仍会覆盖已有值,merge({ a: 1 }, { a: undefined })得到{ a: undefined }。
文件
getMimeType
getMimeType(value: unknown): string;根据文件名或后缀查询 MIME 类型,取最后一个 . 之后的部分并忽略大小写。未收录的后缀返回空字符串。完整映射表见常量 MIME_TYPES。
getMimeType("a.pdf"); // "application/pdf"
getMimeType(".png"); // "image/png"
getMimeType("PNG"); // "image/png",可直接传后缀
getMimeType("a.tar.gz"); // "application/gzip",只取最后一段
getMimeType("a.xyz"); // ""
getMimeType(123); // ""base64ToDataUrl
base64ToDataUrl(value: string, name?: string): string;将 Base64 字符串拼接为 Data URL。MIME 类型由 name(文件名或后缀)推断,无法识别或未传时回退为 image/png。传入的已经是 Data URL 时原样返回,不会重复添加前缀。
base64ToDataUrl("aGVsbG8=", "a.pdf"); // "data:application/pdf;base64,aGVsbG8="
base64ToDataUrl("aGVsbG8=", "png"); // "data:image/png;base64,aGVsbG8="
base64ToDataUrl("aGVsbG8="); // "data:image/png;base64,aGVsbG8=",回退默认类型
base64ToDataUrl("aGVsbG8=", "a.xyz"); // "data:image/png;base64,aGVsbG8="
base64ToDataUrl("data:image/webp;base64,aGVsbG8=", "a.pdf");
// "data:image/webp;base64,aGVsbG8=",已是 Data URL 时原样返回bytesToBase64
bytesToBase64(data: Uint8Array): string;将字节数组编码为 Base64 字符串。内部分块处理,可安全处理大数组而不会栈溢出。
bytesToBase64(new Uint8Array([104, 101, 108, 108, 111])); // "aGVsbG8="base64ToBytes
base64ToBytes(base64: string): Uint8Array<ArrayBuffer>;将 Base64 字符串解码为字节数组。入参需为纯 Base64,不接受 Data URL 前缀。
base64ToBytes("aGVsbG8="); // Uint8Array [104, 101, 108, 108, 111]注意:非法 Base64 会让底层
atob抛出异常。
base64ToFile
base64ToFile(value: string, name: string): File;将 Base64 或 Data URL 转换为 File 对象。MIME 类型优先取自 Data URL 中声明的类型,其次由文件名推断,都不可用时回退为 image/png。
base64ToFile("aGVsbG8=", "a.png");
// File { name: "a.png", type: "image/png", size: 5 }
base64ToFile("data:image/webp;base64,aGVsbG8=", "a.png");
// File { name: "a.png", type: "image/webp" },Data URL 中的类型优先
base64ToFile("aGVsbG8=", "a.xyz");
// File { name: "a.xyz", type: "image/png" },回退默认类型blobToFile
blobToFile(blob: Blob, name: string): File;为 Blob 补上文件名转换成 File,常用于处理 fetch 或 canvas.toBlob 的结果。MIME 类型优先取自 blob.type,其次由文件名推断,都不可用时回退为 image/png。
blobToFile(new Blob(["hello"], { type: "image/webp" }), "a.pdf");
// File { name: "a.pdf", type: "image/webp" },blob.type 优先
blobToFile(new Blob(["hello"]), "a.pdf");
// File { name: "a.pdf", type: "application/pdf" },由文件名推断
blobToFile(new Blob(["hello"]), "a.xyz");
// File { name: "a.xyz", type: "image/png" },回退默认类型downloadFile
downloadFile(url: string, name: string): void;触发浏览器下载:创建隐藏的 <a download> 并自动点击,结束后移除。url 可以是普通链接、Data URL 或 URL.createObjectURL 生成的地址。
downloadFile("https://example.com/report.pdf", "报表.pdf");
// 配合 Blob 使用
const url = URL.createObjectURL(new Blob(["a,b\n1,2"], { type: "text/csv" }));
downloadFile(url, "data.csv");
URL.revokeObjectURL(url);注意:仅可在浏览器环境调用。跨域地址的
download属性会被浏览器忽略,此时通常会直接打开而非下载。
zipFiles
zipFiles(files: File | File[], zipName?: string): Promise<File>;
// zipName 默认 "files.zip"将单个或多个 File 打包成一个 ZIP File(类型为 application/zip)。条目名取自 file.name,重名时在扩展名前追加 (1)、(2) 序号,文件名为空时回退为 file-1。名称中的 / 会在 ZIP 内形成目录层级。
// 单个文件,无需包成数组
await zipFiles(new File(["hello"], "a.txt"));
// File { name: "files.zip", type: "application/zip" }
await zipFiles([new File(["a"], "a.txt"), new File(["b"], "b.txt")], "包.zip");
// File { name: "包.zip" },内含 a.txt、b.txt
// 重名自动去重
await zipFiles([new File(["a"], "a.png"), new File(["b"], "a.png")]);
// 内含 a.png、a (1).png
// 配合 downloadFile 直接下载
const pack = await zipFiles(files);
const url = URL.createObjectURL(pack);
downloadFile(url, pack.name);
URL.revokeObjectURL(url);unzipFiles
unzipFiles(zipFile: File | Blob): Promise<File[]>;将 ZIP 解压成 File 数组,目录条目会被跳过。文件名保留 ZIP 内的相对路径(如 dir/a.txt),MIME 类型由该路径推断,无法识别时为空字符串。
await unzipFiles(new File([bytes], "pack.zip"));
// [File { name: "a.txt", type: "text/plain" }, File { name: "dir/b.pdf", type: "application/pdf" }]
// 也接受 Blob,可直接处理 fetch 结果
await unzipFiles(await (await fetch("/pack.zip")).blob());注意:
zipFiles与unzipFiles基于 fflate,已作为依赖随包安装,无需额外引入。传入非法 ZIP 数据时unzipFiles会 reject(invalid zip data)。两者都会把整包读入内存,超大文件请谨慎使用。
DOM
openWithForm
openWithForm(url: string, data?: Record<string, FormValue>, method?: "POST" | "GET"): void;
// data 默认 {},method 默认 "POST"以动态表单的方式跳转到目标地址,适用于需要携带参数、又无法用普通链接表达的页面跳转。
参数值会被序列化为隐藏域:对象序列化为 JSON 字符串,其他类型转为字符串,值为 null 或 undefined 的字段会被跳过。
openWithForm("https://example.com/pay", {
orderNo: "202601010001",
amount: 100,
extra: { source: "web" }, // 自动 JSON.stringify
remark: null, // 跳过,不生成隐藏域
});
openWithForm("https://example.com/search", { keyword: "abc" }, "GET");注意:仅可在浏览器环境调用。
loadScript
loadScript(url: string, attributes?: ScriptAttributes): Promise<void>;
// attributes 默认 {}
type ScriptAttributes = Record<string, string | number | boolean | null | undefined>;创建 script 标签并插入 document.head 来加载远程脚本,适合按需加载第三方 SDK。onload 后 resolve,标签保留在页面上;onerror 后先移除标签再 reject。
attributes 会通过 setAttribute 写到标签上:值为 true 时作为布尔属性写入(如 async),值为 false、null、undefined 的项会被跳过,其余转为字符串。async 默认为 true,传 async: false 可关闭。src 始终取自 url,不会被 attributes 覆盖。
// 加载成功后 resolve,标签保留在页面上;默认带 async
await loadScript("https://cdn.example.com/sdk.js"); // <script async src="..."></script>
// 传入标签属性
await loadScript("https://cdn.example.com/sdk.js", {
crossorigin: "anonymous",
"data-app-id": 10086,
});
// 关闭默认的 async
await loadScript("https://cdn.example.com/sdk.js", { async: false, defer: true });
// 加载失败时 reject,并同时移除标签
try {
await loadScript("https://cdn.example.com/404.js");
} catch (error) {
console.error(error); // Error: Failed to load script: https://cdn.example.com/404.js
}注意:仅可在浏览器环境调用。不会对相同
url去重,重复调用会插入多个标签。
常量
| 常量 | 值 | 说明 |
| ------------------ | --------------------------------- | ---------------------------- |
| MONTH_FORMAT | "YYYY-MM" | 月份格式 |
| DATE_FORMAT | "YYYY-MM-DD" | 日期格式 |
| TIME_FORMAT | "HH:mm:ss" | 时间格式 |
| DATE_TIME_FORMAT | "YYYY-MM-DD HH:mm:ss" | 日期时间格式 |
| LONG_DATE | Date | 长期有效日期,9999-12-31 |
| LONG_DATE_LABEL | "长期" | 长期有效的展示文案 |
| MIN_DATE | Date | 日期选择下限,1900-01-01 |
| MAX_DATE | Date | 日期选择上限,2099-12-31 |
| SPECIAL_CHARS | "!@#$%^&*()-=_+[]\\\|{},./?<>~" | 密码默认允许的特殊字符集 |
| MIME_TYPES | Record<string, string> | 常见后缀到 MIME 类型的映射表 |
| PALETTE | Record<string, string> | 图表配色用的调色板 |
import { DATE_FORMAT, LONG_DATE, MIME_TYPES, PALETTE } from "@bigflower/utils";
DATE_FORMAT; // "YYYY-MM-DD"
LONG_DATE; // Date 9999-12-31
MIME_TYPES.pdf; // "application/pdf"
PALETTE.bananaRed; // "#D4042D"
Object.values(PALETTE); // 按顺序取色,适合图表系列配色PALETTE 共 8 色,键为驼峰色名,值为十六进制色值:
| 键 | 色值 | 中文名 |
| ---------------- | --------- | -------- |
| bananaRed | #D4042D | 蕉红 |
| sunlitOrange | #DB7A0E | 暖阳橙 |
| cornflowerBlue | #5A92E5 | 矢车菊蓝 |
| emeraldMist | #5E8034 | 翠微 |
| merlotPink | #EB6D98 | 梅洛粉 |
| jadeCyan | #41B5C2 | 碧青 |
| lemonYellow | #FACA2E | 柠檬黄 |
| royalPurple | #722169 | 皇家紫 |
注意:
LONG_DATE、MIN_DATE、MAX_DATE是共享的Date实例,请勿直接修改,需要变更时先复制一份。MIME_TYPES与PALETTE都用as const声明,取值为字面量类型且属性只读。
开发
pnpm install
pnpm build
pnpm lint发布
pnpm publish