{"id":20037573,"url":"https://github.com/mrdulin/dm-utils","last_synced_at":"2026-01-07T05:17:44.070Z","repository":{"id":227013673,"uuid":"769092906","full_name":"mrdulin/dm-utils","owner":"mrdulin","description":"A dozen of utils for Front-End Development","archived":false,"fork":false,"pushed_at":"2025-12-19T09:05:06.000Z","size":463,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-22T02:26:36.379Z","etag":null,"topics":["clipboard","frontend","reactjs","utils","utils-library"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@d-matrix/utils","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mrdulin.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-08T10:29:08.000Z","updated_at":"2025-12-19T09:05:08.000Z","dependencies_parsed_at":"2024-06-11T09:55:24.833Z","dependency_job_id":"40fcd13f-a5f2-4f09-8f96-af54eb7b3aa0","html_url":"https://github.com/mrdulin/dm-utils","commit_stats":null,"previous_names":["mrdulin/dm-utils"],"tags_count":48,"template":false,"template_full_name":null,"purl":"pkg:github/mrdulin/dm-utils","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrdulin%2Fdm-utils","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrdulin%2Fdm-utils/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrdulin%2Fdm-utils/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrdulin%2Fdm-utils/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mrdulin","download_url":"https://codeload.github.com/mrdulin/dm-utils/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mrdulin%2Fdm-utils/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28232807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2026-01-07T02:00:05.975Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clipboard","frontend","reactjs","utils","utils-library"],"created_at":"2024-11-13T10:20:08.126Z","updated_at":"2026-01-07T05:17:44.064Z","avatar_url":"https://github.com/mrdulin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @d-matrix/utils\n\n![NPM Downloads](https://img.shields.io/npm/dw/%40d-matrix%2Futils)\n![npm bundle size](https://img.shields.io/bundlephobia/min/%40d-matrix%2Futils)\n\nA dozen of utils for Front-End Development\n\n## API\n\n- [clipboard](#clipboard)\n- [react](#react)\n- [dom](#dom)\n- [date](#date)\n- [types](#types)\n- [algorithm](#algorithm)\n- [file](#file)\n- [support](#support)\n- [timer](#timer)\n- [operator](#operator)\n- [decimal](#decimal)\n- [object](#object)\n- [array](#array)\n- [number](#number)\n- [echarts](#echarts)\n- [color](#color)\n\n### clipboard\n\n- `writeImage(element: HTMLImageElement | null | string): Promise\u003cvoid\u003e`\n\n复制图片到剪贴板\n\n- `writeText(text: string): Promise\u003cvoid\u003e`\n\n复制文本到剪切板\n\n### react\n\n- `render\u003cP\u003e(element: ReactElement\u003cP\u003e): Promise\u003cstring\u003e`\n\n渲染`React`组件，返回HTML字符串。\n\n- `cleanup(): void`\n\n清理函数，需要在调用`render()`函数后调用。\n\n- `useDisableContextMenu(target: ContextMenuTarget = defaultContextMenuTarget): void`\n\n在`target`函数返回的元素上禁用右键菜单，默认的`target`是`() =\u003e document`\n\n例1：在`id`是`test`的元素上禁用右键菜单\n\n```tsx\nimport { react } from '@d-matrix/utils';\n\nconst TestComp = () =\u003e {\n  react.useDisableContextMenu(() =\u003e document.getElementById('test'));\n\n  return (\n    \u003cdiv\u003e\n      \u003cdiv id='test'\u003e此元素的右键菜单被禁用\u003c/div\u003e\n    \u003c/div\u003e\n  )\n}\n```\n\n例2：在`document`上禁用右键菜单\n\n```tsx\nconst TestComp = () =\u003e {\n  react.useDisableContextMenu();\n\n  return \u003cdiv\u003e内容\u003c/div\u003e\n}\n```\n\n- `useStateCallback\u003cT\u003e(initialState: T): [T, (state: T, cb?: (state: T) =\u003e void) =\u003e void]`\n\n返回值`setState()`函数类似类组件中的`setState(updater[, callback])`,可以在`callback`中获取更新后的`state`\n\n- `useIsMounted(): () =\u003e boolean`\n\n获取当前组件是否已挂载的 Hook\n\n```ts\nconst Test = () =\u003e {\n  const isMounted = useIsMounted();\n\n  useEffect(() =\u003e {\n      if (isMounted()) {\n       console.log('component mounted')\n      }\n  }, [isMounted]);\n\n  return null\n};\n```\n\n- `useCopyToClipboard(props?: UseCopyToClipboardProps)`\n\n复制文本到剪切板, 用法见[测试](./tests/react.cy.tsx)\n\n- `EnhancedComponent.prototype.setStateAsync(state)`\n\n`setState()`方法的同步版本\n\n```ts\nimport { react } from '@d-matrix/utils';\n\n class TestComponent extends EnhancedComponent\u003cunknown, { pageIndex: number }\u003e {\n  state = {\n    pageIndex: 1,\n  };\n\n  async onClick() {\n    await this.setStateAsync({ pageIndex: 2 });\n    console.log(this.state.pageIndex); // 2\n  }\n\n  render() {\n    return (\n      \u003cbutton data-cy=\"test-button\" onClick={() =\u003e this.onClick()}\u003e\n        click\n      \u003c/button\u003e\n    );\n  }\n}\n```\n\n- `useDeepCompareRef(deps: DependencyList): React.MutableRefObject\u003cnumber\u003e`\n\n深比较`deps`。返回`ref`，`ref.current`是一个自增数字，每次`deps`变化，`ref.current`加`1`。用法见[测试](./tests/react.cy.tsx)\n\n- `InferRef\u003cT\u003e`\n\n推导子组件的`ref`类型，适用于组件没有导出其`ref`类型的场景, 更多用法见[测试](./tests/react-types.tsx)\n\n```tsx\ninterface ChildRefProps {\n  prop1: () =\u003e void;\n  prop2: () =\u003e void;\n}\n\ninterface ChildProps {\n  otherProp: string;\n}\n\nconst Child = React.forwardRef\u003cChildRefProps, ChildProps\u003e((props, ref) =\u003e {\n  React.useImperativeHandle(\n    ref,\n    () =\u003e ({\n      prop1() {},\n      prop2() {},\n    }),\n    [],\n  );\n\n  return null;\n});\n\ntype InferredChildRef = InferRef\u003ctypeof Child\u003e;  // 等价于ChildRefProps\n\nconst Parent = () =\u003e {\n  const childRef = React.useRef\u003cInferredChildRef\u003e(null);\n\n  return \u003cChild ref={childRef} otherProp=\"a\" /\u003e;\n};\n```\n\n- `useForwardRef = \u003cT\u003e(ref: ForwardedRef\u003cT\u003e, initialValue: any = null): React.MutableRefObject\u003cT\u003e`\n\n解决使用`React.forwardRef`后，在调用`ref.current.someMethod()`时, 出现`Property 'current' does not exist on type '(instance: HTMLInputElement | null) =\u003e void'` TS类型错误，具体问题见[这里](https://stackoverflow.com/questions/66060217/i-cant-type-the-ref-correctly-using-useref-hook-in-typescript)\n\n```ts\nconst Input = React.forwardRef\u003cHTMLInputElement, React.ComponentPropsWithRef\u003c'input'\u003e\u003e((props, ref) =\u003e {\n  const forwardRef = useForwardRef\u003cHTMLInputElement\u003e(ref);\n  useEffect(() =\u003e {\n    forwardRef.current.focus();\n  });\n  return \u003cinput type=\"text\" ref={forwardRef} value={props.value} /\u003e;\n});\n```\n\n- `useMediaQuery(query, options?): boolean`\n\n使用[Match Media API](https://developer.mozilla.org/en-US/docs/Web/API/Window/matchMedia) 检测当前document是否匹配media query\n\n```ts\nimport { useMediaQuery } from '@d-matrix/utils/react'\n\nexport default function Component() {\n  const matches = useMediaQuery('(min-width: 768px)')\n\n  return (\n    \u003cdiv\u003e\n      {`The view port is ${matches ? 'at least' : 'less than'} 768 pixels wide`}\n    \u003c/div\u003e\n  )\n}\n```\n\n- `useIsFirstRender(): boolean`\n\n对于确定当前渲染是否是组件的第一个渲染很有用。当你想在初始渲染时有条件地执行某些逻辑或渲染特定组件时，这个 hook 特别有价值，提供了一种有效的方法来区分第一次和后续渲染。\n\n### dom\n\n- `scrollToTop(element: Element | null | undefined): void`\n\n元素滚动条滚动到顶部，对老旧浏览器做了兼容，见[浏览器兼容性](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop#browser_compatibility)。\n\n- `strip(html: string): string`\n\n从字符串中去除 HTML 标签并返回纯文本内容。\n\n```ts\nimport { dom } from '@d-matrix/utils';\n\ndom.strip('测试\u003cem\u003e高亮\u003c/em\u003e测试'); // '测试高亮测试'\n```\n\n### date\n\n- `rangeOfYears(start: number, end: number = new Date().getFullYear()): number[]`\n\n创建`start`和`end`之间的年份数组。\n\n- `getYears()`\n\n```ts\nexport interface YearOption {\n  label: string;\n  value: number;\n}\n\nexport enum YearOptionKind {\n  Numbers,\n  Objects,\n}\n\nexport type GetYearsOptions = {\n  // 开始年份\n  startYear?: number;\n  // 最近几年\n  recentYears?: number;\n  // 截止年份\n  endYear?: number;\n  // 后缀，默认为'年'\n  suffix?: string;\n};\n\nexport function getYears(options: GetYearsOptions \u0026 { type: YearOptionKind.Numbers }): number[];\nexport function getYears(options: GetYearsOptions \u0026 { type: YearOptionKind.Objects }): YearOption[];\nexport function getYears(options: GetYearsOptions \u0026 { type: YearOptionKind }): number[] | YearOption[]\n```\n\n获取n年，`type`传`YearOptionKind.Numbers`，返回`[2023, 2022, 2021]`数字数组；`type`传`YearOptionKind.Objects`，返回如下的对象数组\n\n```sh\n[\n  { value: 2023, label: '2023年' },\n  { value: 2022, label: '2022年' },\n  { value: 2021, label: '2021年' },\n]\n```\n\n更多用法，见[测试用例](./tests/date.cy.ts)\n\n- `dayOfWeek(num: number, lang: keyof typeof i18n = 'zh'): string`\n\n返回星期几, `lang`仅支持`zh`和`en`, `num`必须为正整数,否则报错\n\n```js\ndayOfWeek(0) // \"日\"\n```\n\n### types\n\n- `WithOptional\u003cT, K extends keyof T\u003e`\n\n```ts\ntype A = { a: number; b: number; c: number; };\ntype T0 = WithOptional\u003cA, 'b' | 'c'\u003e;  // { a: number; b?: number; c?: number }\n```\n\n- `FunctionPropertyNames\u003cT\u003e`\n\n获取对象中的方法名称，返回union type\n\n```ts\nclass A {\n  add() {}\n  minus() {}\n  div() {}\n  public result: number = 0;\n}\ntype T0 = FunctionPropertyNames\u003cA\u003e; // 'add' | 'minus' | 'div'\n\nconst t1 = {\n  add() {},\n  minus() {},\n  div() {},\n  result: 0,\n};\ntype T1 =  FunctionPropertyNames\u003ctypeof t1\u003e; // 'add' | 'minus' | 'div'\n```\n\n- `NonFunctionPropertyNames\u003cT\u003e`\n\n获取对象中非函数属性名称，返回union type\n\n```ts\nclass A {\n  add() {}\n  minus() {}\n  div() {}\n  public result: number = 0;\n}\ntype T0 = FunctionPropertyNames\u003cA\u003e; // 'result'\n\nconst t1 = {\n  add() {},\n  minus() {},\n  div() {},\n  result: 0,\n};\ntype T1 =  FunctionPropertyNames\u003ctypeof t1\u003e; // 'result'\n```\n\n- `ValueOf\u003cT\u003e`\n\n获取对象中`key`的值，返回由这些值组成的union type\n\n```ts\nconst map = {\n  0: '0m',\n  1: '1m',\n  2: '2m',\n  3: '3m',\n  4: '4m',\n  5: '5m',\n  6: '6m',\n} as const;\n\ntype T0 = ValueOf\u003ctypeof map\u003e; // '0m' | '1m' | '2m' | '3m' | '4m' | '5m' | '6m'\n```\n\n- `WithRequired\u003cT, K extends keyof T\u003e`\n\n指定属性变为必选\n\n```ts\ntype Input = {\n  a: number;\n  b?: string;\n};\ntype Output = WithRequired\u003cInput, 'b'\u003e // { a: number; b: string }\n```\n\n### algorithm\n\n- `function nodeCountAtDepth(root: Record\u003cstring, any\u003e, depth: number, childrenKey: string = 'children'): number;`\n\n计算指定层级的节点数量\n\n```ts\nconst root = {\n  id: 1,\n  children: [\n    { id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },\n    { id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },\n  ],\n};\nexpect(tree.nodeCountAtDepth(root, 0)).to.be.equal(1);\nexpect(tree.nodeCountAtDepth(root, 1)).to.be.equal(2);\nexpect(tree.nodeCountAtDepth(root, 2)).to.be.equal(6);\n```\n\n- `const findNode = \u003cT extends Record\u003cstring, any\u003e\u003e(tree: T[], predicate: (node: T) =\u003e boolean, childrenKey = 'children'): T | null`\n\n找到符合条件的节点\n\n```ts\nconst root = {\n  id: 1,\n  children: [\n    { id: 2, children: [{ id: 21 }, { id: 22 }, { id: 23 }] },\n    { id: 3, children: [{ id: 31 }, { id: 32 }, { id: 33 }] },\n  ],\n};\nconst actual = tree.findNode([root], (node) =\u003e node.id === 3);\nexpect(actual).to.be.deep.equal(root.children[1]);\n\nconst actual2 = tree.findNode([root], (node) =\u003e node.id === 33);\nexpect(actual2).to.be.deep.equal(root.children[1].children[2]);\n```\n\n- `tree.findNode(tree, child, indentityKey, childrenKey)`\n\n根据子节点查找父节点\n\n```ts\nconst treeData = {\n  code: 1,\n  subs: [\n    { code: 2, subs: [{ code: 21 }, { code: 22 }, { code: 23 }] },\n    { code: 3, subs: [{ code: 31 }, { code: 32 }, { code: 33 }] },\n  ],\n};\n\nconst actual = tree.findParent(treeData, treeData.subs[1].subs[2], 'code', 'subs');\nexpect(actual).to.be.deep.equal(treeData.subs[1]);\n```\n\n- `function findPath\u003cT extends Record\u003cstring, any\u003e\u003e(tree: T[], func: (node: T) =\u003e boolean, childrenKey = 'children'): T[] | null`\n\n查找节点路径, 返回节点数组或`null`\n\n```ts\nconst treeData = {\n  code: 1,\n  subs: [\n    { code: 2, subs: [{ code: 21 }, { code: 22 }, { code: 23 }] },\n    { code: 3, subs: [{ code: 31 }, { code: 32 }, { code: 33 }] },\n  ],\n};\n\nconst actual = tree.findPath([root], (node) =\u003e node.id === 33);\nexpect(actual).to.be.deep.equal([root, root.children[1], root.children[1].children[2]]);\n```\n\n- `tree.flatten(tree, T[], childrenKey = 'children')`\n\n扁平化树结构, 返回的每个节点没有`children`属性\n\n### file\n\n- `toImage(file: BlobPart | FileURL, options?: BlobPropertyBag): Promise\u003cHTMLImageElement\u003e`\n\n转换BlobPart或者文件地址为图片对象\n\n- `validateImageSize(file: BlobPart | FileURL, limitSize: { width: number; height: number }, options?: BlobPropertyBag): Promise\u003cImageSizeValidationResult\u003e`\n\n返回值:\n\n```ts\ninterface ImageSizeValidationResult {\n  isOk: boolean;\n  width: number;\n  height: number;\n}\n```\n\n图片宽，高校验\n\n- `isImageExists(src: string, img: HTMLImageElement = new Image()): Promise\u003cboolean\u003e`\n\n检测图片地址是否可用\n\n```ts\nimport { file } from '@d-matrix/utils';\n\nconst url = 'https://picsum.photos/200/300';\nconst res = await file.isImageExists(url);\n```\n\n传入HTML中已经存在的`img`元素\n\n```js\nimport { file } from '@d-matrix/utils';\n\nconst $img = document.getElementById('img');\nconst res = await file.isImageExists(url, $img);\n```\n\n- `getFilenameFromContentDispositionHeader(header: { ['content-disposition']: string }): string`\n\n从`Content-Disposition` response header中获取`filename`\n\n```ts\nimport { file } from '@d-matrix/utils';\n\nconst header = {\n  'content-disposition': 'attachment;filename=%E5%A4%A7%E8%A1%8C%E6%8C%87%E5%AF%BC2024-06-27-2024-06-28.xlsx'\n};\nconst filename = file.getFilenameFromContentDispositionHeader(header);\n// '大行指导2024-06-27-2024-06-28.xlsx'\n```\n\n- `download(source: string | Blob, fileName = '', target?: HyperLinkTarget): void`\n\n文件下载，`source`是文件地址或`blob`对象。\n\n```ts\ntype HyperLinkTarget = \"_self\" | \"_blank\" | \"_parent\" | \"_top\"\n```\n\n- `downloadFileByIframe(source: string): boolean`\n\n通过创建`iframe`进行文件下载\n\n## support\n\n- `isBrowserEnv(): boolean`\n\n是否是浏览器环境\n\n- `isWebSocket(): boolean`\n\n是否支持WebSocket\n\n- `isSharedWorker(): boolean`\n\n是否支持SharedWorker\n\n## timer\n\n- `sleep(ms?: number): Promise\u003cunknown\u003e`\n\n使用`setTimeout`与`Promise`实现，暂停执行`ms`毫秒\n\n```ts\nawait sleep(3000); // 暂停3秒\nconsole.log('continue'); // 继续执行\n```\n\n## operator\n\n- `trueTypeOf = (obj: unknown): string`\n\n检查数据类型\n\n```ts\ntrueTypeOf([]); // array\ntrueTypeOf({}); // object\ntrueTypeOf(''); // string\ntrueTypeOf(new Date()); // date\ntrueTypeOf(1); // number\ntrueTypeOf(function () {}); // function\ntrueTypeOf(/test/i); // regexp\ntrueTypeOf(true); // boolean\ntrueTypeOf(null); // null\ntrueTypeOf(undefined); // undefined\n```\n\n## decimal\n\n- `format(value: number | string | undefined | null, options?: FormatOptions): string`\n\n格式化数字，默认保留3位小数，可添加前缀，后缀，默认值为'--'，用法见[测试](./tests//decimal.cy.ts)\n\n```ts\ntype FormatOptions = {\n  decimalPlaces?: number | false;\n  suffix?: string;\n  prefix?: string;\n  defaultValue?: string;\n  operation?: {\n    operator: 'add' | 'sub' | 'mul' | 'div' | 'toDecimalPlaces';\n    value: number;\n  }[];\n};\n```\n\n## object\n\n- `removeZeroValueKeys = \u003cT extends Record\u003cstring, any\u003e\u003e(obj: T, zeroValues = ZeroValues): T`\n\n移除零值的键, 默认的零值是：`undefined`、`null`, `''`, `NaN`, `[]`, `{}`\n\n```ts\nremoveZeroValueKeys({ a: '', b: 'abc', c: undefined, d: null, e: NaN, f: -1, g: [], h: {} })\n// { b: 'abc', f: -1 }\n```\n\n- `typedKeys(obj: T): Array\u003ckeyof T\u003e`\n\n返回`tuple`，而不是`string[]`\n\n```ts\nconst obj = { a: 1, b: '2' };\nObject.keys(obj) //  string[]\nobject.typedKeys({ a: 1, b: '2' }) // ('a' | 'b')[]\n```\n\n## array\n\n- `moveImmutable\u003cT\u003e(array: T[], fromIndex: number, toIndex: number): T[]`\n\n```js\nimport { array } from '@d-matrix/utils';\n\nconst input = ['a', 'b', 'c'];\n\nconst array1 = array.moveImmutable(input, 1, 2);\nconsole.log(array1);\n//=\u003e ['a', 'c', 'b']\n\nconst array2 = array.moveImmutable(input, -1, 0);\nconsole.log(array2);\n//=\u003e ['c', 'a', 'b']\n\nconst array3 = array.moveImmutable(input, -2, -3);\nconsole.log(array3);\n//=\u003e ['b', 'a', 'c']\n```\n\n- `moveMutable\u003cT\u003e(array: T[], fromIndex: number, toIndex: number): void`\n\n- `moveToStart\u003cT\u003e(array: T[], predicate: (item: T) =\u003e boolean): T[]`\n\n移动元素到数组首位，不会修改原数组\n\n```js\nimport { array } from '@d-matrix/utils';\n\nconst list = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }];\nconst newList = array.moveToStart(list, (item) =\u003e item.id === 4);\n\n// [{ id: 4 }, { id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]\n```\n\n- `moveMulti\u003cT extends unknown\u003e(arr: T[], indexes: number[], start: number): T[]`\n\n移动多个元素到数组中指定的位置,用法,见[测试用例](tests/array.cy.ts)\n\n- `getArrayOrUndefined\u003cT\u003e(array?: T[] | undefined | null): T[] | undefined`\n\n如果`array`是数组且不为空,返回该数组，否则返回`undefined`,见[测试用例](tests/array.cy.ts)\n\n- `calcUnusedMinSerialNumber = \u003cT extends Record\u003cstring, unknown\u003e\u003e(list: T[],options: { fieldName: keyof T; prefix: string; defaultNo?: number }): number`\n\n通用序号计算工具：从指定列表中提取「前缀+数字」格式的值，找到未被使用的最小正整数序号，用法见[测试用例](tests/array.cy.ts)\n\n## number\n\n- `randomInt(min: number, max: number): number`\n\n返回`min`, `max`之间的随机整数\n\n## echarts\n\n- `mergeOption(defaults: EChartsOption, overrides: EChartsOption, option?: deepmerge.Options): EChartsOption`\n\ndeep merge Echarts配置，用法见[测试用例](./tests//echarts/echarts.cy.ts)\n\n- `fill\u003cT extends Record\u003cstring, any\u003e, XAxisField extends keyof T, YAxisField extends keyof T\u003e(dataSource: T[], xAxisField:XAxisField, yAxisField: YAxisField): T[]`\n\n场景：后端接口返回某几个时间点的数据，需求是在接口数据的基础上每隔5分钟补一个点，以达到图中的效果: [折线图](https://raw.githubusercontent.com/mrdulin/pic-bucket-01/master/Dingtalk_20240724140535.jpg)。\n\n填充的点的Y轴值为前一个点的值, 时间示例: [9:23, 9:27] =\u003e [9:23, 9:25, 9:27, 9:30]，更多，见[测试用例](./tests/echarts/fill.cy.ts)\n\n- `calcYAxisRange\u003cT extends Record\u003cstring, any\u003e, Key extends keyof T\u003e(data: T[], key: Key, decimalPlaces = 2, splitNumber = 5): { max:number; min:number }`\n\n计算echarts YAxis的max和min属性，以达到根据实际数据动态调整，使折线图的波动明显。且第一个点始终在Y轴中间位置，[效果图](https://raw.githubusercontent.com/mrdulin/pic-bucket-01/master/Dingtalk_20240724140535.jpg)\n\n## color\n\n- `hexToRGBA(hex: string, alpha: number | string = 1): string`\n\n将十六进制颜色转换为 RGBA 颜色, 见[测试用例](./tests/color.cy.ts)\n\n## 测试\n\n运行全部组件测试\n\n```bash\nnpm run cy:component:all\n```\n\n运行单个组件测试\n\n```bash\nnpm run cy:component -- tests/date.cy.ts\n```\n\n运行E2E测试\n\n将`src`通过`tsc` build到`public/dist`目录\n\n```bash\nnpm run build:public\n```\n\n启动一个Web服务器来访问`public/index.html`文件，`dist`目录的脚本可以通过`\u003cscript type=\"module\"/\u003e`引入\n\n```bash\nnpm run serve\n```\n\n最后启动cypress GUI客户端，选择E2E测试\n\n```bash\nnpm run cy:open\n```\n\n## 发布\n\n更新package version:\n\n```bash\nnpm version \u003cminor\u003e or \u003cmajor\u003e...\n```\n\n构建:\n\n```bash\nnpm build\n```\n\n发布：\n\n```bash\nnpm publish --access public\n```\n\n网络原因导致连接registry服务器超时,可指定proxy\n\n```bash\nnpm --proxy http://127.0.0.1:7890 publish\n```\n\n镜像站查询版本与手动同步:\n\n[npm镜像站](https://npmmirror.com/package/@d-matrix/utils)\n\n通过`git log`命令获取changelogs，用于填写GitHub Release内容:\n\n```bash\ngit log --oneline --decorate\n```\n\n## 注意事项\n\n- [Before Publishing: Make Sure Your Package Installs and Works](https://docs.npmjs.com/cli/v10/using-npm/developers/#before-publishing-make-sure-your-package-installs-and-works)\n- [npm-link](https://docs.npmjs.com/cli/v9/commands/npm-link)\n- [Creating and publishing scoped public packages](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrdulin%2Fdm-utils","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmrdulin%2Fdm-utils","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmrdulin%2Fdm-utils/lists"}