@d-matrix/utils
v1.41.0
Published
A dozen of utils for Front-End Development
Readme
@d-matrix/utils
@d-matrix/utils 是一个面向前端开发的 TypeScript 工具函数集合,按命名空间导出浏览器、React、日期、数组、数值、文件与图表等常用能力。
文档同时保留了详细 API 说明、测试入口和发布命令,既适合作为包使用说明,也适合作为仓库维护入口。
特性
- 按命名空间导出,导入路径稳定,适合在业务项目中按模块组织调用。
- 覆盖前端常见场景,包括剪贴板、DOM、React Hook、文件处理、ECharts、数组与数值计算。
- 提供 Cypress 组件测试、类型测试和示例页面,便于验证行为与查看用法。
快速开始
安装
npm install @d-matrix/utils如果你会使用 react 或 echarts 相关能力,请根据项目实际情况安装对应的 peer dependencies。
导入方式
包的公开入口与 src/index.ts 保持一致,推荐按命名空间导入:
import { array, date, number, react } from '@d-matrix/utils';基本示例
import { date, number } from '@d-matrix/utils';
const years = date.getYears({ type: date.YearOptionKind.Numbers, recentYears: 3 });
const ratio = number.safeDivide('12', '4');
console.log(years); // [2026, 2025, 2024]
console.log(ratio); // 3import React from 'react';
import { react } from '@d-matrix/utils';
export function ResponsivePanel() {
const isDesktopViewport = react.useMediaQuery('(min-width: 1024px)');
const isMobileViewport = react.useMediaQuery('(max-width: 767px)');
return <div>{isDesktopViewport ? 'desktop' : isMobileViewport ? 'mobile' : 'tablet'}</div>;
}API 导航
每个 API 默认收起,点击展开可查看说明、示例和测试入口。
- clipboard:剪贴板读写。
- react:React 渲染、Hook 与类型辅助。
- dom:DOM 操作与 HTML 字符串处理。
- date:年份与星期相关工具。
- types:TypeScript 类型工具。
- algorithm:树结构与二分相关算法。
- file:图片、下载与文件辅助。
- support:运行环境能力检测。
- timer:异步等待工具。
- operator:运行时类型判断。
- decimal:十进制格式化。
- object:对象清理与键名辅助。
- array:数组移动、统计与排名。
- number:安全数值运算。
- echarts:ECharts 配置与数据处理。
- color:颜色转换。
- scene:业务场景选择态与排序工具。
API 详情
clipboard
提供剪贴板写入能力,支持文本和图片两类常见前端交互场景。
相关测试:clipboard.cy.tsx
复制图片到剪贴板。
复制文本到剪贴板。
react
提供 React 渲染、常用 Hook、类组件增强能力与类型辅助工具。
相关测试:tests/react
渲染 React 组件并返回 HTML 字符串。
清理函数,需要在调用 render() 后执行。
在 target 函数返回的元素上禁用右键菜单,默认 target 为 () => document。
例 1:在 id 是 test 的元素上禁用右键菜单。
import { react } from '@d-matrix/utils';
const TestComp = () => {
react.useDisableContextMenu(() => document.getElementById('test'));
return (
<div>
<div id="test">此元素的右键菜单被禁用</div>
</div>
);
};例 2:在 document 上禁用右键菜单。
const TestComp = () => {
react.useDisableContextMenu();
return <div>内容</div>;
};返回值中的 setState() 类似类组件里的 setState(updater[, callback]),可在 callback 中获取更新后的 state。
获取当前组件是否已挂载的 Hook。
const Test = () => {
const isMounted = useIsMounted();
useEffect(() => {
if (isMounted()) {
console.log('component mounted');
}
}, [isMounted]);
return null;
};复制文本到剪贴板。更多用法见测试。
setState() 方法的同步版本。
import { react } from '@d-matrix/utils';
class TestComponent extends EnhancedComponent<unknown, { pageIndex: number }> {
state = {
pageIndex: 1,
};
async onClick() {
await this.setStateAsync({ pageIndex: 2 });
console.log(this.state.pageIndex); // 2
}
render() {
return (
<button data-cy="test-button" onClick={() => this.onClick()}>
click
</button>
);
}
}深比较 deps。返回的 ref.current 是自增数字,每次 deps 变化时加 1。更多用法见测试。
推导子组件的 ref 类型,适用于组件没有导出 ref 类型的场景。更多用法见测试。
interface ChildRefProps {
prop1: () => void;
prop2: () => void;
}
interface ChildProps {
otherProp: string;
}
const Child = React.forwardRef<ChildRefProps, ChildProps>((props, ref) => {
React.useImperativeHandle(
ref,
() => ({
prop1() {},
prop2() {},
}),
[],
);
return null;
});
type InferredChildRef = InferRef<typeof Child>; // 等价于 ChildRefProps
const Parent = () => {
const childRef = React.useRef<InferredChildRef>(null);
return <Child ref={childRef} otherProp="a" />;
};解决 React.forwardRef 场景下调用 ref.current.someMethod() 时出现 Property 'current' does not exist on type '(instance: HTMLInputElement | null) => void' 的 TypeScript 类型错误。问题背景见这里。
const Input = React.forwardRef<HTMLInputElement, React.ComponentPropsWithRef<'input'>>((props, ref) => {
const forwardRef = useForwardRef<HTMLInputElement>(ref);
useEffect(() => {
forwardRef.current.focus();
});
return <input type="text" ref={forwardRef} value={props.value} />;
});使用 Match Media API 检测当前 document 是否匹配 media query。
import { useMediaQuery } from '@d-matrix/utils/react';
export default function Component() {
const isViewportAtLeast768PixelsWide = useMediaQuery('(min-width: 768px)');
const isViewportAtMost767PixelsWide = useMediaQuery('(max-width: 767px)');
return (
<div>
<div>{`The viewport is at least 768 pixels wide: ${isViewportAtLeast768PixelsWide}`}</div>
<div>{`The viewport is at most 767 pixels wide: ${isViewportAtMost767PixelsWide}`}</div>
</div>
);
}用于判断当前渲染是否为组件的第一次渲染,适合在初始渲染与后续渲染之间做逻辑区分。
基于依赖列表维护一个自增版本号,用来解决「异步结果晚于上下文变化返回」的问题。典型场景包括:搜索关键字连续变化、路由参数切换、详情页资源 ID 切换、定时器或延迟任务返回时组件上下文已变化。
使用步骤:
- 将能代表当前业务上下文的值作为依赖数组传入,例如
[query]、[routeId]、[userId, filter]。 - 在发起异步任务前调用
captureVersion(),记录当前版本快照。 - 在异步回调返回后调用
isCurrentVersion(version),只在返回true时写入状态。
搜索请求示例
当用户快速输入多个搜索关键字时,旧请求可能比新请求更晚返回。useVersionGuard() 可以避免旧请求覆盖新结果。
import React from 'react';
import { react } from '@d-matrix/utils';
export function SearchResult({ query }: { query: string }) {
const [result, setResult] = React.useState('');
const { captureVersion, isCurrentVersion } = react.useVersionGuard([query]);
React.useEffect(() => {
const version = captureVersion();
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then((response) => response.text())
.then((text) => {
if (isCurrentVersion(version)) {
setResult(text);
}
});
}, [captureVersion, isCurrentVersion, query]);
return <div>{result}</div>;
}路由资源示例
当详情页从 userId=1 切到 userId=2 时,userId=1 的请求结果如果晚返回,不应该覆盖 userId=2 的页面状态。
import React from 'react';
import { react } from '@d-matrix/utils';
interface UserProfileProps {
userId: string;
}
export function UserProfile({ userId }: UserProfileProps) {
const [name, setName] = React.useState('');
const { captureVersion, isCurrentVersion } = react.useVersionGuard([userId]);
React.useEffect(() => {
const version = captureVersion();
fetch(`/api/users/${userId}`)
.then((response) => response.json())
.then((user: { name: string }) => {
if (isCurrentVersion(version)) {
setName(user.name);
}
});
}, [captureVersion, isCurrentVersion, userId]);
return <div>{name}</div>;
}注意事项
- 依赖数组应该包含能代表当前上下文的值,不需要在外部手动拼接字符串 key。
- 依赖值会使用深比较判断是否变化;复杂对象建议保持不可变更新,避免原地修改导致历史依赖快照同时被改写。
useVersionGuard()只负责判断结果是否过期,不会取消已经发出的请求;如果需要真正取消网络请求,可以结合AbortController使用。captureVersion()和isCurrentVersion()引用稳定,可安全放入useEffect依赖数组。更多行为验证见测试。
dom
提供 DOM 滚动、纯文本提取、HTML 颜色值转换和文本尺寸测量等浏览器侧工具。
相关测试:dom.cy.tsx
将元素滚动条滚动到顶部,并兼容老旧浏览器。浏览器兼容性见MDN。
从字符串中去除 HTML 标签并返回纯文本内容。
import { dom } from '@d-matrix/utils';
dom.strip('测试<em>高亮</em>测试'); // '测试高亮测试'将 HTML 字符串中的 RGB / RGBA 颜色值转换为十六进制颜色值。
const html = '<div style="color: rgb(255, 0, 0)">Red text</div>';
dom.convertRgbToHexInHtml(html); // <div style="color: #ff0000">Red text</div>创建文本测量器。测量器内部会复用一个隐藏的 DOM 容器,适合在表格列宽、图表标签或浮层布局中反复测量文本尺寸。
使用完成后调用 dispose(),释放内部缓存的测量容器。
import { dom } from '@d-matrix/utils';
const measurer = dom.createTextMeasurer();
const size = measurer.measure({
text: '测试文本',
fontSize: '14px',
fontFamily: 'Arial',
fontWeight: 'normal',
maxWidth: 120,
minWidth: 0,
lineHeight: '20px',
});
console.log(size.width, size.height);
measurer.dispose();React 函数组件中也可以这样使用:
import React from 'react';
import { dom } from '@d-matrix/utils';
export function TextWidthPreview() {
const measurer = React.useMemo(() => dom.createTextMeasurer(), []);
const [width, setWidth] = React.useState(0);
React.useEffect(() => {
const size = measurer.measure({
text: '示例文本',
fontSize: '14px',
fontFamily: 'Arial',
maxWidth: 120,
lineHeight: '20px',
});
setWidth(size.width);
return () => {
measurer.dispose();
};
}, [measurer]);
return <div>{`width: ${width}px`}</div>;
}date
提供年份选项构造和星期展示等日期辅助能力。
相关测试:date.cy.ts
创建 start 到 end 之间的年份数组。
获取最近若干年。type 传 YearOptionKind.Numbers 时返回数字数组;传 YearOptionKind.Objects 时返回对象数组。
export interface YearOption {
label: string;
value: number;
}
export enum YearOptionKind {
Numbers,
Objects,
}
export type GetYearsOptions = {
startYear?: number;
recentYears?: number;
endYear?: number;
suffix?: string;
};
export function getYears(options: GetYearsOptions & { type: YearOptionKind.Numbers }): number[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind.Objects }): YearOption[];
export function getYears(options: GetYearsOptions & { type: YearOptionKind }): number[] | YearOption[];[
{ value: 2023, label: '2023年' },
{ value: 2022, label: '2022年' },
{ value: 2021, label: '2021年' },
]更多用法见测试用例。
返回星期几。lang 仅支持 zh 和 en,num 需要是整数,内部按 7 取模。
dayOfWeek(0); // '日'types
提供常用的 TypeScript 类型变换工具,适合在业务类型声明中复用。
相关测试:types.ts
将指定属性设为可选。
type A = { a: number; b: number; c: number };
type T0 = WithOptional<A, 'b' | 'c'>; // { a: number; b?: number; c?: number }将指定属性的值类型扩展为 undefined,并保留原属性的必选或可选修饰。
type A = { a: number; b: string; c?: boolean };
type T0 = WithUndefinable<A, 'b' | 'c'>; // { a: number; b: string | undefined; c?: boolean | undefined }为任意类型 T 扩展 undefined | null。
type T0 = Nullishable<string>; // string | undefined | null为任意类型 T 扩展 undefined。
type T0 = Optional<number>; // number | undefined为任意类型 T 扩展 null。
type T0 = NullableValue<boolean>; // boolean | null获取对象中的方法名称,返回 union type。
class A {
add() {}
minus() {}
div() {}
public result: number = 0;
}
type T0 = FunctionPropertyNames<A>; // 'add' | 'minus' | 'div'
const t1 = {
add() {},
minus() {},
div() {},
result: 0,
};
type T1 = FunctionPropertyNames<typeof t1>; // 'add' | 'minus' | 'div'获取对象中的非函数属性名称,返回 union type。
class A {
add() {}
minus() {}
div() {}
public result: number = 0;
}
type T0 = NonFunctionPropertyNames<A>; // 'result'
const t1 = {
add() {},
minus() {},
div() {},
result: 0,
};
type T1 = NonFunctionPropertyNames<typeof t1>; // 'result'获取对象中所有值组成的 union type。
const map = {
0: '0m',
1: '1m',
2: '2m',
3: '3m',
4: '4m',
5: '5m',
6: '6m',
} as const;
type T0 = ValueOf<typeof map>; // '0m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m'将指定属性变为必选。
type Input = {
a: number;
b?: string;
};
type Output = WithRequired<Input, 'b'>; // { a: number; b: string }将交叉类型或映射类型展开为单个更易读的对象类型,便于编辑器展示解析后的结构。
type A = { name: string };
type B = { age: number };
type User = Simplify<A & B>; // { name: string; age: number }移除对象类型中所有属性的 readonly 修饰。
type ReadonlyUser = {
readonly id: number;
readonly name: string;
};
type User = Mutable<ReadonlyUser>; // { id: number; name: string }递归提取对象中所有字符串属性值类型,并组成联合类型;非字符串叶子节点会被忽略。
type Bond = {
code: '240215';
issuer: {
name: '国家开发银行';
type: '政策性银行';
};
duration: number;
};
type BondText = DeepStringLeafValues<Bond>; // '240215' | '国家开发银行' | '政策性银行'algorithm
收录树结构与二分相关的算法辅助能力。
相关测试:tree.cy.ts、binary.cy.ts
计算指定层级的节点数量。
const root = {
id: 1,
children: [
{ id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },
{ id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },
],
};
expect(tree.nodeCountAtDepth(root, 0)).to.be.equal(1);
expect(tree.nodeCountAtDepth(root, 1)).to.be.equal(2);
expect(tree.nodeCountAtDepth(root, 2)).to.be.equal(6);找到符合条件的节点。
const root = {
id: 1,
children: [
{ id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },
{ id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },
],
};
const actual = tree.findNode([root], (node) => node.id === 3);
expect(actual).to.be.deep.equal(root.children[1]);
const actual2 = tree.findNode([root], (node) => node.id === 33);
expect(actual2).to.be.deep.equal(root.children[1].children[2]);根据子节点查找父节点。
const treeData = {
code: 1,
subs: [
{ code: 2, subs: [{ code: 21 }, { code: 22 }, { code: 23 }] },
{ code: 3, subs: [{ code: 31 }, { code: 32 }, { code: 33 }] },
],
};
const actual = tree.findParent(treeData, treeData.subs[1].subs[2], 'code', 'subs');
expect(actual).to.be.deep.equal(treeData.subs[1]);查找节点路径,返回节点数组或 null。
const root = {
id: 1,
children: [
{ id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },
{ id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },
],
};
const actual = tree.findPath([root], (node) => node.id === 33);
expect(actual).to.be.deep.equal([root, root.children[1], root.children[1].children[2]]);扁平化树结构,返回的每个节点都不包含 children 属性。
file
提供图片加载、尺寸校验、文件下载和响应头文件名解析等工具。
相关测试:file.cy.ts
将 BlobPart 或文件地址转换为图片对象。
图片宽高校验。
返回值:
interface ImageSizeValidationResult {
isOk: boolean;
width: number;
height: number;
}检测图片地址是否可用。
import { file } from '@d-matrix/utils';
const url = 'https://picsum.photos/200/300';
const res = await file.isImageExists(url);传入 HTML 中已存在的 img 元素:
import { file } from '@d-matrix/utils';
const $img = document.getElementById('img');
const res = await file.isImageExists(url, $img as HTMLImageElement);从 Content-Disposition response header 中获取 filename。
import { file } from '@d-matrix/utils';
const header = {
'content-disposition': 'attachment;filename=%E5%A4%A7%E8%A1%8C%E6%8C%87%E5%AF%BC2024-06-27-2024-06-28.xlsx',
};
const filename = file.getFilenameFromContentDispositionHeader(header);
// '大行指导2024-06-27-2024-06-28.xlsx'文件下载,source 可以是文件地址或 blob 对象。
type HyperLinkTarget = '_self' | '_blank' | '_parent' | '_top';通过创建 iframe 进行文件下载。
support
提供浏览器、WebSocket、SharedWorker 等运行环境能力检测函数。
相关测试:暂无专用测试
判断当前是否为浏览器环境。
判断当前环境是否支持 WebSocket。
判断当前环境是否支持 SharedWorker。
timer
提供基于 Promise 的异步等待工具。
相关测试:暂无专用测试
使用 setTimeout 与 Promise 实现暂停执行若干毫秒。
await sleep(3000); // 暂停 3 秒
console.log('continue'); // 继续执行operator
提供运行时值类型判断工具。
相关测试:operator.cy.ts
检查数据类型。
trueTypeOf([]); // array
trueTypeOf({}); // object
trueTypeOf(''); // string
trueTypeOf(new Date()); // date
trueTypeOf(1); // number
trueTypeOf(function () {}); // function
trueTypeOf(/test/i); // regexp
trueTypeOf(true); // boolean
trueTypeOf(null); // null
trueTypeOf(undefined); // undefineddecimal
提供基于 decimal.js-light 的数值格式化能力。
相关测试:decimal.cy.ts
格式化数字,默认保留 3 位小数,可添加前缀、后缀,默认值为 '--'。更多用法见测试。
type FormatOptions = {
decimalPlaces?: number | false;
suffix?: string;
prefix?: string;
defaultValue?: string;
operation?: {
operator: 'add' | 'sub' | 'mul' | 'div' | 'toDecimalPlaces';
value: number;
}[];
};object
提供对象零值清理与类型安全键名访问辅助函数。
相关测试:object.cy.ts
移除零值的键。默认零值包括 undefined、null、''、NaN、[]、{}。
removeZeroValueKeys({ a: '', b: 'abc', c: undefined, d: null, e: NaN, f: -1, g: [], h: {} });
// { b: 'abc', f: -1 }返回 tuple 类型,而不是 string[]。
const obj = { a: 1, b: '2' };
Object.keys(obj); // string[]
object.typedKeys({ a: 1, b: '2' }); // ('a' | 'b')[]array
提供数组移动、序号计算、百分位排名和标准差等实用函数。
相关测试:array.cy.ts
移动数组元素位置,返回新数组,不修改原数组。
import { array } from '@d-matrix/utils';
const input = ['a', 'b', 'c'];
const array1 = array.moveImmutable(input, 1, 2);
console.log(array1);
//=> ['a', 'c', 'b']
const array2 = array.moveImmutable(input, -1, 0);
console.log(array2);
//=> ['c', 'a', 'b']
const array3 = array.moveImmutable(input, -2, -3);
console.log(array3);
//=> ['b', 'a', 'c']移动数组元素位置,直接修改原数组。
将满足条件的元素移动到数组首位,不修改原数组。
import { array } from '@d-matrix/utils';
const list = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];
const newList = array.moveToStart(list, (item) => item.id === 4);
// [{ id: 4 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]将多个元素移动到数组中的指定位置。更多用法见测试用例。
如果 array 是非空数组则返回该数组,否则返回 undefined。更多用法见测试用例。
从指定列表中提取“前缀 + 数字”格式的值,找到未被使用的最小正整数序号。更多用法见测试用例。
计算给定值在数组中的百分位排名。函数内部会先对数组做升序排序,再计算结果。
const arr = [10, 20, 40, 50];
array.percentRank(arr, 30); // 0.5
array.percentRank(arr, 10); // 0
array.percentRank(arr, 50); // 1计算给定值在已排序数组中的百分位排名,适用于输入已经是升序数组的场景,避免重复排序。支持精确命中和区间插值,超出范围时返回 undefined。更多用法见测试用例。
const sorted = [10, 20, 40, 50];
array.percentRankOfSorted(sorted, 30); // 0.5
array.percentRankOfSorted(sorted, 10); // 0
array.percentRankOfSorted(sorted, 50); // 1计算数字数组的总体标准差,空数组时返回 undefined。更多用法见测试用例。
array.standardDeviation([1, 2, 3, 4, 5]); // 1.4142135623730951
array.standardDeviation([5]); // 0
array.standardDeviation([]); // undefinednumber
提供随机整数、字符串转数值与安全四则运算函数。
相关测试:number.cy.ts
返回 min 到 max 之间的随机整数。
安全减法。支持数字和可转为数字的字符串。任一参数为 undefined、null、空字符串、空白字符串或非法数字字符串时返回 undefined。
number.safeMinus(5, 2); // 3
number.safeMinus('5', '2'); // 3
number.safeMinus(undefined, 2); // undefined
number.safeMinus('abc', 2); // undefined安全加法。支持数字和可转为数字的字符串。任一参数为 undefined、null、空字符串、空白字符串或非法数字字符串时返回 undefined。
number.safePlus(5, 2); // 7
number.safePlus('5', '2'); // 7
number.safePlus(null, 2); // undefined
number.safePlus('', 2); // undefined安全乘法。支持数字和可转为数字的字符串。任一参数为 undefined、null、空字符串、空白字符串或非法数字字符串时返回 undefined。
number.safeMultiply(5, 2); // 10
number.safeMultiply('5', '2'); // 10
number.safeMultiply(5, undefined); // undefined
number.safeMultiply(' ', 2); // undefined安全除法。支持数字和可转为数字的字符串。任一参数为 undefined、null、空字符串、空白字符串、非法数字字符串,或者除数为 0(包括字符串 '0')时返回 undefined。
number.safeDivide(6, 2); // 3
number.safeDivide('6', '2'); // 3
number.safeDivide(1, 0); // undefined
number.safeDivide(1, '0'); // undefined
number.safeDivide(6, null); // undefinedecharts
提供 ECharts 配置合并、时间序列补点和 Y 轴范围计算工具。
相关测试:mergeOption.cy.ts、fill.cy.ts、calcYAxisRange.cy.ts
deep merge ECharts 配置。更多用法见测试用例。
适用于后端接口只返回部分时间点数据时,按 5 分钟粒度补点。填充点的 Y 轴值沿用前一个点的值。
时间示例:[9:23, 9:27] => [9:23, 9:25, 9:27, 9:30]
计算 ECharts YAxis 的 max 和 min,使折线图能够根据实际数据动态调整波动范围,并让第一个点始终位于 Y 轴中间位置。效果图见这里。
confinePosition 会根据鼠标位置与 tooltip 尺寸计算提示框左上角坐标,避免提示框超出图表视图右侧和底部。默认偏移量为右下方各 20px;空间不足时会尝试显示在鼠标左侧或上方。
在线示例:CodeSandbox。
推荐直接使用 ECharts 原生的 tooltip.confine: true 配置。它会将 tooltip 限制在图表容器中,无需维护自定义位置回调。
import type { EChartsOption } from 'echarts';
const option: EChartsOption = {
tooltip: {
trigger: 'axis',
confine: true,
},
};在迁移完成前,仍可通过深路径导入已弃用的回调:
import type { EChartsOption } from 'echarts';
import { confinePosition } from '@d-matrix/utils/dist/echarts/tooltip';
const option: EChartsOption = {
tooltip: {
trigger: 'axis',
position: confinePosition,
},
};自定义图标
自定义 ECharts symbol 定义位于 src/echarts/symbols/index.ts,可通过深路径导入单个图标或 symbols 集合。
import { ChevronLeftOutline, symbols } from '@d-matrix/utils/dist/echarts/symbols';
const option = {
series: [
{
type: 'scatter',
symbol: ChevronLeftOutline,
symbolSize: 24,
data: [[0, 0]],
},
],
};
symbols.StarFilled;
中国地图 GeoJSON
中国地图数据位于 src/echarts/geo-json/china.json。在创建图表前使用 registerMap() 注册数据;geo.map 与地图系列的 map 使用相同名称。
import * as echarts from 'echarts';
import chinaJson from './src/echarts/geo-json/china.json';
echarts.registerMap('china', chinaJson);
const option: echarts.EChartsOption = {
geo: {
map: 'china',
},
series: [
{
type: 'map',
map: 'china',
},
],
};更多参数见 ECharts registerMap API。
color
提供十六进制颜色与 RGBA 之间的转换能力。
相关测试:color.cy.ts
将十六进制颜色转换为 RGBA 颜色。更多用法见测试用例。
scene
提供多选默认项切换、带空值兜底的排序和按键防抖调度等场景工具。
相关测试:toggleSelectionValue.cy.ts、sortRecordsBySortStateNilLast.cy.ts、keyedDebounceScheduler.cy.ts
相关源码:keyedDebounceScheduler.ts、d3LeanLabeler.ts
切换多选值,并在取消最后一个普通项时回退到默认值。适用于“全部”与普通选项互斥的场景。
import { scene } from '@d-matrix/utils';
const defaultValue = 'all';
const isDefaultSelected = (value: string[] | undefined) => scene.hasSameSelectionValues(value, [defaultValue]);
scene.toggleSelectionValue({
selectedValues: [defaultValue],
toggledValue: 'A',
selected: true,
defaultValue,
isDefaultSelected,
}); // ['A']
scene.toggleSelectionValue({
selectedValues: ['A'],
toggledValue: 'A',
selected: false,
defaultValue,
isDefaultSelected,
}); // ['all']创建按键管理的防抖调度器。同一个键重复调度时只执行最后一次回调,不同键的回调互不影响。
import { scene } from '@d-matrix/utils';
const scheduler = scene.createKeyedDebounceScheduler<string>(300);
const fetchContacts = (groupId: string) => {
console.log(`获取分组 ${groupId} 下的联系人`);
};
scheduler.schedule('record-1', () => {
fetchContacts('record-1');
});
scheduler.cancel('record-1');
scheduler.dispose();dispose() 用于在组件卸载或页面离开时结束调度器的生命周期:
- 会取消所有 key 对应的待执行定时器,因此这些回调不会再执行;
- 已经执行完成的回调不受影响;
- 调用后调度器不可恢复,再次调用
schedule()只会直接忽略,不会创建新的定时器; - 重复调用
dispose()是安全的,不会产生额外副作用。
如果只是想取消某个分组当前尚未执行的任务,应使用 cancel(key);取消后调度器仍可继续调用 schedule():
scheduler.cancel('record-1');
scheduler.schedule('record-1', () => {
fetchContacts('record-1');
});如果已经调用 dispose(),原调度器就不能再次复用。后续仍需要调度任务时,必须重新创建实例:
scheduler.dispose();
const nextScheduler = scene.createKeyedDebounceScheduler<string>(300);
nextScheduler.schedule('record-1', () => {
fetchContacts('record-1');
});判断两次选中值是否一致。undefined 会按空数组处理。
scene.hasSameSelectionValues(undefined, []); // true
scene.hasSameSelectionValues(['A', 'B'], ['A', 'B']); // true
scene.hasSameSelectionValues(['A', 'B'], ['B', 'A']); // false根据排序状态对记录排序,并将 null / undefined 放到结果末尾。
import { scene } from '@d-matrix/utils';
const records = [{ value: 3 }, { value: undefined }, { value: 1 }];
scene.sortRecordsBySortStateNilLast(records, {
field: 'value',
direction: scene.VirtualTableSortDirection.ASC,
});
// [{ value: 1 }, { value: 3 }, { value: undefined }]基于模拟退火的标签避让器,用于减少标签重叠、标签与锚点重叠,以及引导线相交。
注意事项:
- 这是
src/scene/d3LeanLabeler.ts的命名导出函数,当前不会通过根入口的scene命名空间暴露。 label()与anchor()传入的数组需要等长,且按索引一一对应。start()会原地修改label数组中的x、y坐标。alt_schedule()目前只保留兼容签名,实际仍使用内置的线性降温策略。
import { d3LeanLabeler, type Anchor, type Label } from '@d-matrix/utils/dist/scene/d3LeanLabeler';
const labels: Label[] = [
{ x: 120, y: 110, width: 80, height: 20, id: 'A' },
{ x: 160, y: 118, width: 80, height: 20, id: 'B' },
];
const anchors: Anchor[] = [
{ x: 100, y: 100, r: 4, id: 'A' },
{ x: 140, y: 108, r: 4, id: 'B' },
];
const labeler = d3LeanLabeler().width(800).height(600).label(labels).anchor(anchors);
labeler.start(200);
console.log(labels); // 坐标已被原地更新链式 API:
width(width)/height(height):设置布局边界。label(labels)/anchor(anchors):注入标签与锚点数据。alt_energy(fn):覆盖默认能量函数。alt_schedule(fn):兼容接口,当前不会真正生效。start(nsweeps):执行退火布局。
排序方向常量。
scene.VirtualTableSortDirection.NONE; // 0
scene.VirtualTableSortDirection.ASC; // 1
scene.VirtualTableSortDirection.DESC; // -1排序状态类型。
type VirtualTableSort = {
field: string;
direction: ValueOf<typeof scene.VirtualTableSortDirection>;
};测试与开发
测试
运行全部组件测试:
npm run cy:component:all运行单个组件测试:
npm run cy:component -- tests/date.cy.ts运行类型测试:
npm run test:tsd运行 E2E 测试前,先将 src 通过 tsc 构建到 public/dist 目录:
npm run build:public启动一个 Web 服务器访问 public/index.html,dist 目录脚本可通过 <script type="module" /> 引入:
npm run start最后启动 Cypress GUI 客户端并选择 E2E 测试:
npm run cy:open本地开发
构建 npm 包输出到 dist 目录:
npm run build启动本地示例页服务:
npm run start如果需要先为示例页准备 public/dist 下的构建产物,请先执行:
npm run build:public发布说明
默认发布路径改为“打版本 tag 触发 GitHub Actions 自动发布”。发布前建议先在本地执行一次完整校验:
npm run release:verify更新 package 版本:
npm version <minor> or <major>...npm version 会更新版本、创建 git tag,并通过 postversion 自动 push commit 与 tag。tag 推送后,GitHub Actions 会执行:
npm ci
npm run build
npm run test:types
npm run cy:component:all
npm pack --dry-run
npm publish --access public
自动创建 GitHub Release如果只想本地验证产物而不发布,可单独执行:
npm run pack:check不再建议把 npm publish 作为常规发布入口;除非自动发布链路不可用且你明确要走人工兜底。
网络原因导致连接 registry 服务器超时时,可在人工兜底发布时指定 proxy:
npm --proxy http://127.0.0.1:7890 publish镜像站查询版本与手动同步:
GitHub Release 默认由 Actions 自动创建,并启用 GitHub 的自动 release notes 分类;只有在需要补充说明时,才需要手动编辑 release 内容。
git log --oneline --decorate补充链接
与发布和包维护相关的补充资料:
