npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@d-matrix/utils

v1.41.0

Published

A dozen of utils for Front-End Development

Readme

@d-matrix/utils

codecov NPM Downloads npm bundle size NPM version

@d-matrix/utils 是一个面向前端开发的 TypeScript 工具函数集合,按命名空间导出浏览器、React、日期、数组、数值、文件与图表等常用能力。

文档同时保留了详细 API 说明、测试入口和发布命令,既适合作为包使用说明,也适合作为仓库维护入口。

特性

  • 按命名空间导出,导入路径稳定,适合在业务项目中按模块组织调用。
  • 覆盖前端常见场景,包括剪贴板、DOM、React Hook、文件处理、ECharts、数组与数值计算。
  • 提供 Cypress 组件测试、类型测试和示例页面,便于验证行为与查看用法。

快速开始

安装

npm install @d-matrix/utils

如果你会使用 reactecharts 相关能力,请根据项目实际情况安装对应的 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); // 3
import 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:在 idtest 的元素上禁用右键菜单。

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 切换、定时器或延迟任务返回时组件上下文已变化。

使用步骤:

  1. 将能代表当前业务上下文的值作为依赖数组传入,例如 [query][routeId][userId, filter]
  2. 在发起异步任务前调用 captureVersion(),记录当前版本快照。
  3. 在异步回调返回后调用 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

创建 startend 之间的年份数组。

获取最近若干年。typeYearOptionKind.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 仅支持 zhennum 需要是整数,内部按 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.tsbinary.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 的异步等待工具。

相关测试:暂无专用测试

使用 setTimeoutPromise 实现暂停执行若干毫秒。

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); // undefined

decimal

提供基于 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

移除零值的键。默认零值包括 undefinednull''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([]); // undefined

number

提供随机整数、字符串转数值与安全四则运算函数。

相关测试:number.cy.ts

返回 minmax 之间的随机整数。

安全减法。支持数字和可转为数字的字符串。任一参数为 undefinednull、空字符串、空白字符串或非法数字字符串时返回 undefined

number.safeMinus(5, 2); // 3
number.safeMinus('5', '2'); // 3
number.safeMinus(undefined, 2); // undefined
number.safeMinus('abc', 2); // undefined

安全加法。支持数字和可转为数字的字符串。任一参数为 undefinednull、空字符串、空白字符串或非法数字字符串时返回 undefined

number.safePlus(5, 2); // 7
number.safePlus('5', '2'); // 7
number.safePlus(null, 2); // undefined
number.safePlus('', 2); // undefined

安全乘法。支持数字和可转为数字的字符串。任一参数为 undefinednull、空字符串、空白字符串或非法数字字符串时返回 undefined

number.safeMultiply(5, 2); // 10
number.safeMultiply('5', '2'); // 10
number.safeMultiply(5, undefined); // undefined
number.safeMultiply('   ', 2); // undefined

安全除法。支持数字和可转为数字的字符串。任一参数为 undefinednull、空字符串、空白字符串、非法数字字符串,或者除数为 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); // undefined

echarts

提供 ECharts 配置合并、时间序列补点和 Y 轴范围计算工具。

相关测试:mergeOption.cy.tsfill.cy.tscalcYAxisRange.cy.ts

deep merge ECharts 配置。更多用法见测试用例

适用于后端接口只返回部分时间点数据时,按 5 分钟粒度补点。填充点的 Y 轴值沿用前一个点的值。

时间示例:[9:23, 9:27] => [9:23, 9:25, 9:27, 9:30]

更多用法见测试用例,效果图见折线图

计算 ECharts YAxismaxmin,使折线图能够根据实际数据动态调整波动范围,并让第一个点始终位于 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;

ECharts 图标预览

中国地图 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.tssortRecordsBySortStateNilLast.cy.tskeyedDebounceScheduler.cy.ts

相关源码:keyedDebounceScheduler.tsd3LeanLabeler.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 数组中的 xy 坐标。
  • 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.htmldist 目录脚本可通过 <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

镜像站查询版本与手动同步:

npm 镜像站

GitHub Release 默认由 Actions 自动创建,并启用 GitHub 的自动 release notes 分类;只有在需要补充说明时,才需要手动编辑 release 内容。

git log --oneline --decorate

补充链接

与发布和包维护相关的补充资料: