# @ithinkdt/common 通用工具库

## 许可证

MIT

## 安装

```bash
npm i @ithinkdt/common
```

> 部分功能需要您安装依赖 `vue@3`。

## 基础工具

### @ithinkdt/common/array 数组

#### `removeIf` 移除数组中符合条件的元素

```ts
declare function removeIf<T>(array: T[], condition: (data: T, index: number) => boolean, once: true): T | undefined

declare function removeIf<T>(array: T[], condition: (data: T, index: number) => boolean, once?: false): T[]
```

- 参数`array`：待处理的数组
- 参数`condition`：判断条件
- 参数`once`：是否只移除第一个符合条件的元素，默认 `false`
- 返回值：移除的元素

示例：

```ts
const array = [1, 2, 3, 4, 5]
removeIf(array, (item) => item > 2)
// [1, 2]

const array = [1, 2, 3, 4, 5]
removeIf(array, (item) => item > 2, true)
// 3
```

### @ithinkdt/common/code 代码

#### `formatCode` 格式化代码，使用 prettier

```ts
declare function formatCode(
    code: string,
    lang: 'js' | 'ts' | 'css' | 'html' | (string & {}),
    options?: import('prettier').Options,
): Promise<string>
```

- 参数`code`：待格式化的代码
- 参数`lang`：代码的语言
- 参数`options`：prettier 选项
- 返回值：格式化后的代码

示例：

```ts
await formatCode('console.log("Hello World")', 'js')
// console.log("Hello World");

await formatCode('console.log("Hello World")', 'js', { semi: false, singleQuote: true })
// console.log('Hello World');
```

### @ithinkdt/common/color 颜色

#### `isDarkColor` 判断颜色是否为暗色

```ts
declare function isDarkColor(color: string): boolean
```

- 参数`color`：待判断的颜色
- 返回值：是否为暗色

示例：

```ts
isDarkColor('#000000')
// true

isDarkColor('#ffffff')
// false
```

#### `brightness` 提亮颜色 (hsv 色彩空间的 v)

```ts
declare function brightness(color: string, amount: number): string
```

- 参数`color`：待处理的颜色
- 参数`amount`：提亮的度，负数时亮度降低
- 返回值：处理后的颜色

示例：

```ts
brightness('#18a058', 1)
// #36ad6a

brightness('#18a058', -1)
// #0c7a43
```

#### `toRGB` 颜色转 RGB

```ts
declare function toRGB(color: string): [r: number, g: number, b: number]
```

- 参数`color`：待转换的颜色
- 返回值：RGB 数组

示例：

```ts
toRGB('#18a058')
// [24, 160, 88]
```

#### `getLightness` 获取颜色的亮度（hsl 色彩空间的 l）

```ts
declare function getLightness(color: string): number
```

- 参数`color`：待处理的颜色
- 返回值：亮度

示例：

```ts
getLightness('#18a058')
// 36.07843137254902
```

#### `getAlpha` 获取颜色的透明度

```ts
declare function getAlpha(color: string): number
```

- 参数`color`：待处理的颜色
- 返回值：透明度

示例：

```ts
getAlpha('rgba(255, 0, 0, 0.5)')
// 0.5
```

### @ithinkdt/common/dom DOM

#### `string2dom` 字符串转 DOM（不安全版本，使用 innerHTML）

```ts
declare function string2dom<T extends HTMLElement = HTMLElement>(text: string): T
```

- 参数`text`：待转换的字符串
- 返回值：DOM

示例：

```ts
string2dom('<div>Hello World</div>')
// <div>Hello World</div>
```

#### `string2domSafely` 字符串转 DOM，（安全版本，使用 DOMParser）

```ts
declare function string2domSafely<T extends HTMLElement = HTMLElement>(text: string): T
```

- 参数`text`：待转换的字符串
- 返回值：DOM

示例：

```ts
string2domSafely('<div>Hello World</div>')
// <div>Hello World</div>
```

### @ithinkdt/common/excel Excel

#### `table2Excel` 表格转 Excel

> 拆分开 `thead` 与 `tbody`，可以更好的配合组件库的表格组件

```ts
declare function table2Excel(table: HTMLTableElement, sheetName?: string): Blob

declare function table2Excel(thead: HTMLTableSectionElement, tbody: HTMLTableSectionElement, sheetName?: string): Blob
```

- 参数`table`：待转换的表格
- 参数`thead`：待转换的表头
- 参数`tbody`：待转换的表格体
- 参数`sheetName`：工作表名称，默认 `Sheet1`
- 返回值：Blob

示例：

```ts
table2Excel(document.querySelector('table'))
table2Excel(document.querySelector('thead'), document.querySelector('tbody'))
// Blob { size: 1024, type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' }
```

### @ithinkdt/common/fn 函数工具

#### `isFunction` 判断是否为函数

```ts
declare function isFunction(val: unknown): val is Function
```

- 参数`val`：待判断的值
- 返回值：是否为函数

示例：

```ts
isFunction(() => {})
// true

isFunction({})
// false
```

#### `debounce` 创建一个去抖函数，延迟调用func直到wait之后

> 如果`leading`和`trailing`选项为`true`，则`func`为仅当去抖动函数在超时的后沿调用。
> 如果`wait`为 0 且`leading`为`false`，则推迟`func`调用直到下一个刻度，类似于超时为 0 的 setTimeout。
> 如果在有`requestAnimationFrame`的环境中省略`wait`，`func`调用将被推迟到绘制下一帧（通常约为 16 毫秒）。

```ts
declare function debounce<Fn extends (...args: any[]) => any>(
    fn: Fn,
    wait?: number,
    options?: {
        leading?: boolean
        trailing?: boolean
        maxWait?: number
    },
): Fn & {
    cancel: () => void
    flush: () => ReturnType<Fn>
    pending: boolean
}
```

- 参数`fn`：执行的函数
- 参数`wait`：防抖时间，单位毫秒，默认 `0`
- 参数`options`：选项
    - 参数`options.leading`：在超时前调用，默认 `false`
    - 参数`options.trailing`：在超时后调用，默认 `true`
    - 参数`options.maxWait`：最大等待时长
- 返回值：处理后的函数
    - `cancel`：取消执行
    - `flush`：立即执行
    - `pending`：是否正等待执行

示例：

```ts
const fn = debounce(() => console.log('hello world'), 1000)
fn()
setTimeout(fn, 500)
// 1.5s 后输出 'hello world'
```

#### `throttle` 创建一个节流函数，在每个`wait`时长内最多执行一次

> 如果`leading`和`trailing`选项为`true`，则`func`为仅当去抖动函数在超时的后沿调用。
> 如果`wait`为 0 且`leading`为`false`，则推迟`func`调用直到下一个刻度，类似于超时为 0 的 setTimeout。
> 如果在有`requestAnimationFrame`的环境中省略`wait`，`func`调用将被推迟到绘制下一帧（通常约为 16 毫秒）。

```ts
declare function throttle<Fn extends (...args: any[]) => unknown>(
    fn: Fn,
    wait?: number,
    options?: {
        leading?: boolean
        trailing?: boolean
    },
): Fn & {
    cancel: () => void
    flush: () => ReturnType<Fn>
    pending: boolean
}
```

- 参数`fn`：执行的函数
- 参数`wait`：节流时间，单位毫秒，默认 `0`
- 参数`options`：选项
    - 参数`options.leading`：在超时前调用，默认 `true`
    - 参数`options.trailing`：在超时后调用，默认 `true`
    - 参数`options.maxWait`：最大等待时长
- 返回值：处理后的函数
    - `cancel`：取消执行
    - `flush`：立即执行
    - `pending`：是否正等待执行

示例：

```ts
const fn = throttle(() => console.log('hello world'), 1000)
fn()
setTimeout(fn, 500)
// 1s 后输出 'hello world'
```

### @ithinkdt/common/mime MIME

#### `getType` 根据扩展名获取 MIME 类型

```ts
declare function getType(extensionOrPath: string): string | undefined
```

- 参数`extensionOrPath`扩展名或路径。如 `.png` `zip` `xxx.dat`
- 返回值：MIME类型。如：`text/plain`

示例：

```ts
getType('.png')
// 'image/png'
```

#### `getExtensions` 根据 MIME 类型获取扩展名

```ts
declare function getExtensions(type: string): string[] | undefined
```

- 参数`type`：MIME类型。如：`text/plain`
- 返回值：扩展名数组。如：`['.png']`

示例：

```ts
getExtensions('image/png')
// ['png']
```

### @ithinkdt/common/number 数字

#### `roundNum` 四舍五入到指定小数位

```ts
declare function roundNum(num: number, places?: number): number
```

- 参数`num`：待处理数字
- 参数`places`：保留的小数位数，默认 `2`
- 返回值：处理后的数字

示例：

```ts
roundNum(123.4562)
// 123.46

roundNum(123.4562, 3)
// 123.456

roundNum(123.0029)
// 123
```

### @ithinkdt/common/object 对象

#### `clone` 克隆对象（深拷贝）

> 包括原型、函数和循环引用

```ts
declare function clone<T extends {}>(obj: T): T
```

- 参数`obj`：待克隆的对象
- 返回值：克隆后的对象

示例：

```ts
const obj = { a: 1, b: { c: 2 } }
const cloned = clone(obj)
// { a: 1, b: { c: 2 } }

console.debug(cloned === obj, cloned.b === obj.b)
// false false
```

#### `copy` 简单的深拷贝

> 使用 `JSON.stringify` 和 `JSON.parse` 实现

```ts
declare function copy<T extends {}>(obj: T): T
```

- 参数`obj`：待复制的对象
- 返回值：复制后的对象

示例：

```ts
const obj = { a: 1, b: { c: 2 } }
const copied = copy(obj)
// { a: 1, b: { c: 2 } }

console.debug(copied === obj, copied.b === obj.b)
// false false
```

#### `omitProps` 移除对象的指定属性

```ts
declare function omitProps<T extends {}, Key extends keyof T | (string & {})>(obj: T, ...keys: Key[]): Omit<T, Key>
```

- 参数`obj`：待处理的对象
- 参数`keys`：待移除的属性
- 返回值：处理后的对象

示例：

```ts
const obj = { a: 1, b: 2, c: 3 }
omitProps(obj, 'a', 'b')
// { c: 3 }
```

#### `pickProps` 从对象中挑选指定属性

```ts
declare function pickProps<T extends {}, Key extends keyof T | (string & {})>(obj: T, ...keys: Key[]): Pick<T, Key>
```

- 参数`obj`：待处理的对象
- 参数`keys`：待挑选的属性
- 返回值：处理后的对象

示例：

```ts
const obj = { a: 1, b: 2, c: 3 }
pickProps(obj, 'a', 'b')
// { a: 1, b: 2 }
```

#### `isDiff` 比较两个对象是否有差异

```ts
declare function isDiff<O1, O2>(obj1: O1, obj2: O2, options?: IsDiffOptions): boolean

interface IsDiffOptions {
    ignore?: string[] | ((k: string) => boolean | undefined | void)
}
```

- 参数`obj1`：待比较的对象
- 参数`obj2`：待比较的对象
- 参数`options`：选项
    - 参数`options.ignore`：忽略的属性
- 返回值：是否有差异

示例：

```ts
isDiff({ a: 1, b: 2 }, { a: 1, b: 2 })
// false

isDiff({ a: 1, b: 2 }, { a: 1, b: 3 })
// true

isDiff({ a: 1, b: 2 }, { a: 1, b: 3 }, { ignore: ['b'] })
// false
```

#### `isNone` 判断对象是否为空

```ts
declare function isNone(val: unknown): boolean
```

- 参数`val`：待判断的值
- 返回值：是否为空

示例：

```ts
isNone(null)
// true

isNone(undefined)
// true

isNone('')
// true

isNone(' ')
// true

isNone(0)
// false

isNone(Infinity)
// false

isNone(NaN)
// true

isNone(false)
// false

isNone([])
// true

isNone([1])
// false

isNone({})
// true

isNone({ a: 1 })
// false
```

#### `isNullish` 判断对象是否为 nullish

```ts
declare function isNullish(val: unknown): val is null | undefined
```

- 参数`val`：待判断的值
- 返回值：是否为 nullish

示例：

```ts
isNullish(null)
// true

isNullish(undefined)
// true

isNullish('')
// false

isNullish(0)
// false

isNullish(Infinity)
// false

isNullish(NaN)
// false

isNullish([])
// false

isNullish({})
// false
```

#### `isObject` 判断对象是否为对象

```ts
declare const isObject: (val: unknown) => val is Record<any, any>
```

- 参数`val`：待判断的值
- 返回值：是否为对象

示例：

```ts
isObject({})
// true

isObject([])
// true

isObject(() => {})
// true

isObject('')
// false

isObject(0)
// false

isObject(Infinity)
// false

isObject(NaN)
// false
```

#### `isPlainObject` 判断对象是否为纯对象

```ts
declare function isPlainObject(val: unknown): val is object
```

- 参数`val`：待判断的值
- 返回值：是否为纯对象

示例：

```ts
isPlainObject({})
// true

isPlainObject([])
// false

isPlainObject(() => {})
// false

isPlainObject('')
// false

isPlainObject(0)
// false

isPlainObject(Infinity)
// false

isPlainObject(NaN)
// false
```

#### `merge` 合并对象

```ts
declare function merge(...objects: any[]): any
```

- 参数`objects`：待合并的对象
- 返回值：合并后的对象

示例：

```ts
const obj1 = { a: 1, b: 2 }
const obj2 = { c: 3, d: 4 }
merge(obj1, obj2)
// { a: 1, b: 2, c: 3, d: 4 }
```

### @ithinkdt/common/qs 查询字符串

#### `parse` 解析查询字符串

```ts
declare function parse(search: string): URLSearchParams
```

- 参数`search`：待解析的查询字符串，如：`a=1&b=xxx`
- 返回值：解析后的查询参数对象

示例：

```ts
parse('a=1&b=2&c=3')
// URLSearchParams { 'a' => '1', 'b' => '2', 'c' => '3' }
```

#### `stringify` 将查询参数对象转换为查询字符串

```ts
declare function stringify(object: Record<string, MaybeArray<string | number | boolean> | undefined | null>): string
```

- 参数`object`：待转换的查询参数对象
- 返回值：转换后的查询字符串

示例：

```ts
stringify({ a: 1, b: 2, c: 3 })
// 'a=1&b=2&c=3'
```

### @ithinkdt/common/save 数据保存

> `saveAs` 尝试使用 `showSaveFilePicker` 保存文件，如果该 API 未可用 则调用 `downloadAs`。

#### `saveAs` 保存为文件

```ts
declare function saveAs(content: MaybePromise<Response | Blob | ReadableStream>, name?: string): Promise<void>
```

- 参数`content`：待保存的内容
- 参数`name`：保存的文件名
- 返回值：promise

示例：

```ts
saveAs(new Blob(['Hello World'], { type: 'text/plain' }), 'hello.txt')

saveAs(fetch('http://www.ithinkdt.com/cloud/doc'), 'hello.txt')
```

#### `downloadAs` 下载为文件

```ts
declare function downloadAs(content: MaybePromise<Response | Blob | ReadableStream>, name?: string): Promise<void>
```

- 参数`content`：待下载的内容
- 参数`name`：保存的文件名
- 返回值：promise

示例：

```ts
downloadAs(new Blob(['Hello World'], { type: 'text/plain' }), 'hello.txt')

downloadAs(fetch('http://www.ithinkdt.com/cloud/doc'), 'hello.txt')
```

### @ithinkdt/common/string 字符串

#### `measureText` 测量文本宽度

```ts
declare function measureText(text: string, font: string): number
```

- 参数`text`：待测量的文本
- 参数`font`：文本的字体样式
- 返回值：文本的宽度

示例：

```ts
measureText('Hello World', '16px sans-serif')
// 100
```

#### `a2b` base64 转字符串，同 [globalThis.atob](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/atob)，支持 Unicode

```ts
declare function a2b(data: string): string
```

- 参数`data`：待转换的 base64 字符串
- 返回值：转换后的字符串

示例：

```ts
a2b('YSDEgCDwkICAIOaWhyDwn6aE')
// 'a Ā 𐀀 文 🦄'
```

#### `b2a` 字符串转 base64，同 [globalThis.btoa](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/btoa)，支持 Unicode

```ts
declare function b2a(str: string): string
```

- 参数`str`：待转换的字符串
- 返回值：转换后的 base64 字符串

示例：

```ts
b2a('a Ā 𐀀 文 🦄')
// 'YSDEgCDwkICAIOaWhyDwn6aE'
```

#### `hyphenate` 驼峰转连字符

```ts
declare function hyphenate(str: string): string
```

- 参数`str`：待处理的字符串
- 返回值：处理后的字符串

示例：

```ts
hyphenate('helloWorld')
// 'hello-world'
```

#### `camelize` 连字符转驼峰

```ts
declare function camelize(str: string): string
```

- 参数`str`：待处理的字符串
- 返回值：处理后的字符串

示例：

```ts
camelize('hello-world')
// 'helloWorld'
```

#### `capitalize` 首字母大写

```ts
declare function capitalize(str: string): string
```

- 参数`str`：待处理的字符串
- 返回值：处理后的字符串

示例：

```ts
capitalize('hello world')
// 'Hello world'
```

### @ithinkdt/common/tree 树

#### `flattenTree` 扁平化树

```ts
declare function flattenTree<T extends {}>(tree: T[], options?: FlattenTreeOptions<T>): T[]

interface FlattenTreeOptions<T extends {}> {
    childrenKey?: (keyof T) & string
}
```

- 参数`tree`：待处理的树
- 参数`options`：选项
    - 参数`options.childrenKey`：子节点键名，默认 `children`
- 返回值：处理后的树

示例：

```ts
const tree = [
    {
        id: 1,
        name: 'a',
        children: [
            {
                id: 2,
                name: 'b',
                children: [
                    {
                        id: 3,
                        name: 'c',
                    },
                ],
            },
        ],
    },
]
flattenTree(tree)
// [
//     { id: 1, name: 'a', children: [{ id: 2, name: 'b', children: [{ id: 3, name: 'c' }] }] },
//     { id: 2, name: 'b', children: [{ id: 3, name: 'c' }] },
//     { id: 3, name: 'c' },
// ]
```

#### `array2Tree` 数组转树

```ts
declare function array2Tree<T extends {}>(data: T[], options?: Array2TreeOptions<T>): T[]

interface Array2TreeOptions<T extends {}, KeyofT extends (keyof T) & string = (keyof T) & string> {
    rootId?: string
    idKey?: KeyofT
    parentKey?: KeyofT
    childrenKey?: KeyofT
    sortKey?: KeyofT
}
```

- 参数`data`：待处理的数组
- 参数`options`：选项
    - 参数`options.rootId`：根节点ID，默认自动计算
    - 参数`options.idKey`：数据的ID字段名，默认 `id`
    - 参数`options.parentKey`：数据的父节点ID字段名，默认 `parentId`
    - 参数`options.childrenKey`：数据的下级节点字段名，默认 `children`
    - 参数`options.sortKey`：数据的排序字段名
- 返回值：处理后的树

示例：

```ts
const data = [
    { id: 1, name: 'a', parentId: 0 },
    { id: 2, name: 'b', parentId: 1 },
    { id: 3, name: 'c', parentId: 2 },
]
array2Tree(data)
// [
//     {
//          id: 1,
//          name: 'a',
//          children: [
//              {
//                  id: 2,
//                  name: 'b',
//                  children: [
//                      {
//                          id: 3,
//                          name: 'c'
//                       }
//                  ]
//              }
//          ]
//      },
// ]
```

#### `walkTree` 遍历树

```ts
declare function walkTree<T extends {}>(
    data: T[],
    each: (item: T, index: number, parent: T | undefined, children: T[] | undefined) => MaybePromise<boolean | void>,
    options?: WalkTreeOptions<T> & {
        path?: false
        async: true
    },
): Promise<boolean>

declare function walkTree<T extends {}>(
    data: T[],
    each: (item: T, index: number, parents: T[], children: T[] | undefined) => MaybePromise<boolean | void>,
    options?: WalkTreeOptions<T> & {
        path: true
        async: true
    },
): Promise<boolean>

declare function walkTree<T extends {}>(
    data: T[],
    each: (item: T, index: number, parent: T | undefined, children: T[] | undefined) => boolean | void,
    options?: WalkTreeOptions<T> & {
        async?: false
        path?: false
    },
): boolean

declare function walkTree<T extends {}>(
    data: T[],
    each: (item: T, index: number, parents: T[], children: T[] | undefined) => boolean | void,
    options?: WalkTreeOptions<T> & {
        async?: false
        path: true
    },
): boolean

interface WalkTreeOptions<T extends {}, KeyofT extends (keyof T) & string = (keyof T) & string> {
    async?: boolean
    childrenKey?: KeyofT
    order?: 'pre' | 'post'
}
```

- 参数`data`：待处理的树
- 参数`each`：遍历函数
- 参数`options`：选项
    - 参数`options.async`：是否异步等待 each 的返回结果，默认 `false`
    - 参数`options.childrenKey`：数据的下级节点字段名，默认 `children`
    - 参数`options.order`：遍历顺序，默认 `pre`
    - 参数`options.path`：是否返回所有祖先路径，默认 `false`
- 返回值：遍历结果

示例：

```ts
const data = [{ id: 'a', children: [{ id: 'b' }] }, { id: 'c' }]
walkTree(data, (item, index, parent, children) => {
    console.log(item.id, index, parent?.id)
})
// a 0 undefined
// b 0 a
// c 1 undefined
```

### @ithinkdt/common/type 类型

#### `MaybeArray` 可能是数组的类型

```ts
declare type MaybeArray<T> = T | T[]
```

示例：

```ts
const a: MaybeArray<string> = 'a'
a = ['a', 'b'] // ok
```

#### `MaybePromise` 可能是Promise的类型

```ts
declare type MaybePromise<T> = T | Promise<T>
```

示例：

```ts
const a: MaybePromise<string> = 'a'
a = Promise.resolve('a') // ok
```

#### `Awaitable` 可等待的

```ts
declare type Awaitable<T> = T | PromiseLike<T>
```

示例：

```ts
const a: Awaitable<string> = 'a'
a = {
    then(onfulfilled, onrejected) {
        onfulfilled('a')
    },
} // ok
```

#### `Mutable` 将不可变转换为可变

```ts
declare type Mutable<T> = {
    -readonly [P in keyof T]: T[P]
}
```

示例：

```ts
const a: { readonly val: string } = { val: 'a' }
a.val = 'a2' // error
const b: Mutable<typeof a> = { val: 'b' }
b.val = 'b2' // ok
```

#### `RequiredKeys` 部分键设为必须

```ts
type RequiredKeys<T, K extends keyof T = keyof T> = T & {
    [P in K]-?: NonNullable<T[P]>
}
```

示例：

```ts
const a: { val?: string } = {}
const b: RequiredKeys<typeof a, 'val'> = {} // error
```

#### `PartialKeys` 部分键设为可选

```ts
type PartialKeys<T, K extends keyof T = keyof T> = T & {
    [P in K]?: T[P]
}
```

示例：

```ts
const a: { val: string } = { val: 'a' }
const b: PartialKeys<typeof a, 'val'> = {} // ok
```

#### `Union2Intersection` 联合类型转交叉类型

```ts
type Union2Intersection<Union> = (Union extends any ? (argument: Union) => void : never) extends (
    argument: infer I,
) => void
    ? I
    : never
```

示例：

```ts
type A = Union2Intersection<'a' | 'b' | 'c'>
// type A = "a" & "b" & "c"
```

#### `Union2Tuple` 联合类型转元组

```ts
/** 并集转元组 */
type Union2Tuple<Union> = Union2TupleRecurser<Union, []>

type Union2UoF<Union> = Union extends any ? (argument: Union) => void : never
type Union2IoF<Union> = Union2Intersection<Union2UoF<Union>>

type Firs2fUnion<Union> = Union2IoF<Union> extends { (argument: infer T): void } ? T : never

type Union2TupleRecurser<Union, Tuple extends any[]> = [
    Firs2fUnion<Union>,
    Exclude<Union, Firs2fUnion<Union>>,
] extends [infer First, ...infer Lasts]
    ? [First] extends [never]
        ? Tuple
        : Union2TupleRecurser<Lasts, [First, ...Tuple]>
    : never
```

示例：

```ts
type A = Union2Tuple<'a' | 'b' | 'c'>
// type A = ["a", "b", "c"]
```

### @ithinkdt/common/url URL处理

#### `normalizeUrl` 标准化URL

```ts
declare function normalizeUrl(url: string): string
```

- 参数`url`：待标准化的URL
- 返回值：标准化后的URL

示例：

```ts
normalizeUrl('http://www.ithinkdt.com/cloud//doc')
// 'http://www.ithinkdt.com/cloud/doc'
```

## Composables 组合式工具

### @ithinkdt/common/composables

#### `useClass` 响应式绑定 class

```ts
declare function useClass(element: MaybeElementRef, className: string, has?: MaybeRef<boolean>): Ref<boolean>
```

- 参数`element`：目标元素
- 参数`className`：类名
- 参数`has`：是否添加该类，默认 `true`
- 返回值：响应式状态

示例：

```ts
import { ref } from 'vue'
import { useClass } from '@ithinkdt/common/composables'

const el = ref<HTMLElement>()
const active = useClass(el, 'is-active', false)

active.value = true
// el.value?.classList.contains('is-active') === true
```

#### `promiseMounted` 挂载后返回 Promise

```ts
declare function promiseMounted(delay?: number): Promise<void>
```

- 参数`delay`：挂载后额外等待时间，单位毫秒
- 返回值：挂载完成后的 Promise

示例：

```ts
import { defineComponent } from 'vue'
import { promiseMounted } from '@ithinkdt/common/composables'

export default defineComponent({
    async setup() {
        await promiseMounted()
        console.log('组件已挂载')
    },
})
```

#### `useMounted` 获取组件挂载状态

```ts
declare function useMounted(delay?: number): Ref<boolean>
```

- 参数`delay`：挂载后额外等待时间，单位毫秒
- 返回值：是否已挂载

示例：

```ts
import { defineComponent } from 'vue'
import { useMounted } from '@ithinkdt/common/composables'

export default defineComponent({
    setup() {
        const mounted = useMounted(100)
        return { mounted }
    },
    template: `<div v-if="mounted">ready</div>`,
})
```

#### `useDataFilter` 过滤列表或树形数据

```ts
declare function useDataFilter<T extends object>(
    originData: MaybeRefOrGetter<T[] | undefined>,
    pattern: Ref<string>,
    fields: Array<keyof T>,
    options?: {
        debounce?: number
        ignoreCase?: boolean
        trimSpace?: boolean
        isTree?: boolean
        childrenField?: keyof T
        afterFilter?: (data: T[]) => void
    },
): Ref<T[]>
```

- 参数`originData`：原始数据
- 参数`pattern`：过滤关键字
- 参数`fields`：参与过滤的字段
- 参数`options`：过滤选项
- 返回值：过滤后的数据

示例：

```ts
import { ref } from 'vue'
import { useDataFilter } from '@ithinkdt/common/composables'

const keyword = ref('')
const users = ref([
    { name: 'Alice', dept: 'Design' },
    { name: 'Bob', dept: 'Dev' },
])

const filteredUsers = useDataFilter(users, keyword, ['name', 'dept'], {
    ignoreCase: true,
    trimSpace: true,
})

keyword.value = 'dev'
// filteredUsers.value => [{ name: 'Bob', dept: 'Dev' }]
```

#### `useHistoryStack` 管理撤销/重做

```ts
declare function useHistoryStack(options?: {
    keyboard?: boolean
    keyboardFilter?: (event: KeyboardEvent) => boolean
}): {
    history: {
        title: string
        date: number
        undo: () => unknown
        redo: () => unknown
    }[]
    current: Ref<number>
    revocable: Ref<boolean>
    redoable: Ref<boolean>
    clear: () => void
    record: (name: string, undo: () => unknown, redo: () => unknown) => Promise<void>
    undo: (pointer?: number) => Promise<void>
    redo: (pointer?: number) => Promise<void>
    busy: Ref<boolean>
}
```

- 参数`options`：配置项
- 返回值：历史栈状态与操作方法

示例：

```ts
import { ref } from 'vue'
import { useHistoryStack } from '@ithinkdt/common/composables'

const count = ref(0)
const history = useHistoryStack()

function plusOne() {
    const previous = count.value
    count.value += 1
    void history.record('count + 1', () => {
        count.value = previous
    }, () => {
        count.value = previous + 1
    })
}

await history.undo()
await history.redo()
```

#### `useIdleFn` 在浏览器空闲时执行回调

```ts
declare function useIdleFn(fn: () => unknown, options?: {
    timeout?: MaybeRefOrGetter<number>
    immediate?: boolean
}): Pausable
```

- 参数`fn`：回调函数
- 参数`options`：配置项
- 返回值：暂停/恢复控制器

示例：

```ts
import { useIdleFn } from '@ithinkdt/common/composables'

const { pause, resume, isActive } = useIdleFn(() => {
    console.log('空闲时预加载数据')
}, {
    timeout: 1000,
    immediate: false,
})

resume()
// isActive.value === true
pause()
```

#### `useElementIntersectionRect` 获取元素相交区域

```ts
declare function useElementIntersectionRect(
    element: MaybeElementRef,
    callback: (rect: { top: number, left: number, width: number, height: number }) => void,
    options?: {
        relativeElement?: MaybeElementRef
        interval?: MaybeRef<number>
    },
): {
    stop: () => void
    update: () => Promise<void>
}
```

- 参数`element`：目标元素
- 参数`callback`：相交区域变化回调
- 参数`options.relativeElement`：相对元素，默认视口
- 参数`options.interval`：轮询间隔，兜底使用
- 返回值：停止和手动更新方法

示例：

```ts
import { ref, onMounted } from 'vue'
import { useElementIntersectionRect } from '@ithinkdt/common/composables'

const cardRef = ref<HTMLElement>()

const { update, stop } = useElementIntersectionRect(cardRef, (rect) => {
    console.log('可见区域：', rect)
})

onMounted(() => {
    void update()
})

// stop()
```

## Request 请求

### @ithinkdt/common/request

#### `request` 发起 HTTP 请求

```ts
declare const request: HttpClient
```

- 支持 `request(url, method)`、`request.get(...)`、`request.postJSON(...)` 等调用方式
- 支持通过 `extend` 复用 `baseUrl`、拦截器和默认配置
- 默认返回 JSON；当 `responseType` 为 `response` 时返回原始 `Response`

示例：

```ts
import { request } from '@ithinkdt/common/request'

const users = await request.get<{ list: Array<{ id: number; name: string }> }>('/users', {
    page: 1,
    keyword: 'alice',
})
// { list: [...] }

await request.postJSON('/users', {
    name: 'Alice',
    role: 'admin',
})

const response = await request({
    url: '/report/export',
    responseType: 'response',
})
// Response
```

#### `request.extend` 创建带默认配置的请求实例

示例：

```ts
import { request } from '@ithinkdt/common/request'
import { error, timeout } from '@ithinkdt/common/request/interceptors'

const api = request.extend({
    baseUrl: '/api',
    timeout: 10_000,
    interceptors: [
        timeout,
        error({
            retry: 1,
        }),
    ],
})

const profile = await api.get('/profile')
```

### @ithinkdt/common/request/interceptors

#### `timeout` 请求超时拦截器

```ts
declare const timeout: HttpInterceptor<{
    timeout?: number
}>
```

示例：

```ts
import { request } from '@ithinkdt/common/request'
import { timeout } from '@ithinkdt/common/request/interceptors'

const api = request.extend({
    timeout: 3000,
    interceptors: [timeout],
})

await api.get('/slow-api')
```

#### `jwt` JWT 鉴权拦截器

```ts
declare function jwt(options: {
    headerField: string
    queryField?: string
    getAccessToken: () => MaybePromise<string | undefined>
    onHttpError?: (error: HttpError, retry: () => Promise<unknown>) => Promise<unknown>
    requestFilter?: (url: HttpRequest) => boolean
}): HttpInterceptor
```

示例：

```ts
import { request } from '@ithinkdt/common/request'
import { jwt } from '@ithinkdt/common/request/interceptors'

const api = request.extend({
    interceptors: [
        jwt({
            headerField: 'Authorization',
            getAccessToken: async () => `Bearer ${localStorage.getItem('token') ?? ''}`,
        }),
    ],
})

await api.get('/me')
```

#### `download` 自动下载拦截器

```ts
declare const download: HttpInterceptor<{
    download?: boolean | string
}>
```

示例：

```ts
import { request } from '@ithinkdt/common/request'
import { download } from '@ithinkdt/common/request/interceptors'

const api = request.extend<{ download?: boolean | string }>({
    interceptors: [download],
})

await api.get('/report/export', undefined, {
    responseType: 'response',
    download: 'report.xlsx',
})
```

#### `error` 错误处理与重试拦截器

```ts
declare function error(options: {
    onAlert?: (error: unknown, request: HttpRequest) => void | false | 'alerted' | 'ignore'
    retry?: number | false
    retryInterval?: number
}): HttpInterceptor<{
    retry?: number | false
    interval?: number
    alert?: boolean | ((error: unknown, request: HttpRequest) => void | false | 'alerted' | 'ignore')
}>
```

示例：

```ts
import { request } from '@ithinkdt/common/request'
import { error } from '@ithinkdt/common/request/interceptors'

const api = request.extend({
    interceptors: [
        error({
            retry: 2,
            retryInterval: 1000,
            onAlert(err) {
                console.error('请求失败：', err)
            },
        }),
    ],
})

await api.get('/unstable-api')
```

## Dict 字典

> 简化对数据字典的使用

### 注册插件

```ts
import { createApp } from 'vue'
import dict from '@ithinkdt/common/dict'

const app = createApp(...)

app.use(dict, {
    fetch: (dictTypes) => {
        // 从后端获取字典数据
        return fetch('/api/dict', {
            method: 'GET',
            params: {
                dictTypes: dictTypes.join(',')
            },
        }).then((res) => res.json())
    }
})

...
app.mount('#app')
```

### 字典资源使用

#### `useDict` 获取字典数据

```ts
declare function useDict<T extends object>(
    dictType: MaybeRef<DictTypeKey<T>>,
    options?: UseDictOptions<T>,
): UseDictReturn<DictItem<T>[]>

type DictTypeKey<T extends object = object> = (string | symbol) & {
    __T?: T
}

type UseDictOptions<T extends object> = {
    onGet?: (dicts: DictItem<T>[]) => void
}

type UseDictReturn<Result> = Result & {
    loading: boolean
} & [result: Result, loading: Ref<boolean>]

interface _DictItemBase {
    label: string
    value: string
    remark?: string
    disabled?: boolean
}

type DictItem<T extends object = object> = T & _DictItemBase
```

- 参数`dictType`：字典类型
- 参数`options`：选项
    - 参数`options.onGet`：获取到字典数据后回调
- 返回值：字典数据

示例

```ts
import { useDict } from '@ithinkdt/common/dict'

// 获取字典数据
const types = useDict('type', {
    onGet: (data) => {
        console.debug(`已获取到字典 type`, data)
    },
})
// [{ value: '1', label: '类型一' }, { value: '2', label: '类型二'}]

console.debug('字典 type 加载状态：', types.loading)
// true

// 也可以直接解构
// const [ types, typesLoading ] = useDict('type')
```

#### `useDictMap` 获取字典映射数据

```ts
declare function useDictMap<T extends object>(
    dictType: MaybeRef<DictTypeKey<T>>,
    options?: UseDictOptions<T>,
): UseDictReturn<DictValueMap<T>>

type DictValueMap<T extends object = object> = Map<string, DictItem<T>>
```

- 参数`dictType`：字典类型
- 参数`options`：选项
    - 参数`options.onGet`：获取到字典数据后回调
- 返回值：字典映射数据

示例

```ts
import { useDictMap } from '@ithinkdt/common/dict'

const typeMap = useDictMap('type', {
    onGet: (data) => {
        console.debug(`已获取到字典 type`, data)
    },
})
// Map {
//     '1' => { value: '1', label: '类型一' },
//     '2' => { value: '2', label: '类型二'}
// }

console.debug('字典 type 映射加载状态：', typeMap.loading)
// false

// 同样可以直接解构
// const [ typeMap, typeMapLoading ] = useDictMap('type')
```

#### `registerDict` 注册字典资源

```ts
declare function registerDict<T extends object>(
    dictType: DictTypeKey<T>,
    getter: (options: UseDictOptions<T>, getDict: <F extends object>(dictType: DictTypeKey<F>) => Promise<DictItem<F>[]>) => Promise<DictItem<T>[]>,
    cached?: boolean,
): Unregister

type Unregister = () => void

declare function unregisterDict<T extends object>(dictType: DictTypeKey<T>): void
```

- 参数`dictType`：字典类型
- 参数`getter`：资源加载函数
- 参数`cached`：是否缓存，默认 `true`
- 返回值：取消注册函数

示例

```ts
import { registerDict, unregisterDict } from '@ithinkdt/common/dict'

const someDictKey = Symbol()

// 注册资源加载函数
const unregister = registerDict(someDictKey, () => {
    return fetch('/api/some', {
        method: 'GET',
    }).then((res) => res.json())
})

// 使用字典
const options = useDict(someDictKey)

// 取消注册
unregister()
// or
unregisterDict(someDictKey)
```

可以使用此方法从系统字典中定制数据

```ts
import { registerDict } from '@ithinkdt/common/dict'

// 注册资源加载函数
registerDict('customKey', (params, getDict) => {
    return getDict('dictKey', params).then((data) => {
        return data.filter((item) => item.value !== 'some')
    })
})
```

## I18n 国际化

> 多语言处理

### 注册插件

```ts
import { createApp } from 'vue'
import i18n from '@ithinkdt/common/i18n'

const app = createApp(...)

const locale = ref('zh-CN')
app.use(i18n, {
    locale,
    // 未找到资源时回退的语言
    defaultLocale: 'en-US',
    // 关闭响应式，如果您在切换语言时刷新页面
    reactive: false
})

...
app.mount('#app')
```

### 定义并使用资源

使用 `defineI18n` 定义资源后使用

```ts
declare function defineI18n<Message extends object>(
    loader: (locale: Locale) => Promise<Message>,
    options?: {
        reactive?: boolean
    },
): I18nComposable<Message>

type I18nComposable<Message extends object> = (locale?: MaybeRef<Locale>) => I18nInstance<ParseMessage<Message>>

interface I18nInstance<ParsedMessage> {
    locale: Ref<Locale>
    t<Key extends keyof ParsedMessage>(this: void, key: Key): string
    t<Key extends keyof ParsedMessage, Parameter extends ParsedMessage[Key] extends (p: infer P) => string ? P : never>(
        this: void,
        key: Key,
        parameter: Parameter,
        locale?: Locale,
    ): string
}
```

- 参数`loader`：资源加载函数
- 参数`options`：选项
    - 参数`options.reactive`：是否响应式，默认跟随插件配置
- 返回值：国际化实例

示例

```tsx
// file locales/zh-CN.ts
export default {
    a: {
        b: 'a.b',
        c: (count: number) => `a.c: count is ${count}`,
    },
    d: (data: { p1: string; p2: boolean }) => `d: p1 is ${data.p1}, p2 is ${data.p2}`,
}

// file: use-abc-i18n.ts
import { defineI18n } from '@ithinkdt/common/i18n'

export default defineI18n(
    (locale) => {
        return import(`./locales/${locale}.ts`).then((m: typeof import('./locales/zh-CN')) => m.default)
    },
    {
        // 可单独控制资源是否为响应式
        reactive: true,
    },
)

// file: AbCde.tsx
import { defineComponent } from 'vue'
import useAbcI18n from './use-abc-i18n'

export default defineComponent({
    name: 'AbCde',
    setup() {
        const { t } = useAbcI18n()
        return (
            <div>
                <span>{t('a.b')}</span>
                <span>{t('a.c', 2)}</span>
                <span>{t('d', { p1: 'p1', p2: true })}</span>
            </div>
        )
    },
})
```
