diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000..b1e138b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,6 @@ +#!/bin/sh +export NVM_DIR="$HOME/.nvm" + +[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" + +npx lint-staged \ No newline at end of file diff --git a/README.md b/README.md index 2ed0967..88cf7ac 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ ui-utils-kit 是一个高效的偏业务前端工具函数库。 - **海报制作** 将 DOM 元素转换为图片(canvas),支持自动下载或返回 Blob 格式数据。 > 针对跨域图片问题,内置 `html2canvas` proxy 解决方案。 +- **业务工具类函数** + 提供一些常用的业务工具函数,如 JSON 安全解析、敏感信息脱敏等。 --- @@ -45,12 +47,14 @@ ui-utils-kit 的工具函数分为三大类:`tree`、`business`、`common`, **方式一:** ```javascript import { tree } from "ui-utils-kit"; + const result = tree.buildTree(nodes); ``` **方式二:** ```javascript import { buildTree } from "ui-utils-kit"; + const result = buildTree(nodes); ``` @@ -83,6 +87,9 @@ const result = buildTree(nodes); **`tree.treeToArr`** 将树形数据转换为扁平化数组,便于遍历与处理。 +- **参数:** + - `tree` (`TreeNode`):包含 `id` 与 `name`、`children`的树形结构数据。 + - `node` (`TreeNode`):包含 `id` 与 `pid` 的节点数据。 - **示例:** ```typescript import { treeToArr } from 'ui-utils-kit'; @@ -96,6 +103,8 @@ const result = buildTree(nodes); **`tree.updateTreeCheckStatus`** 更新树中节点的选中状态(含子节点与父节点联动)。 +- **参数:** + - `tree` (`TreeNode`):包含 `id` 与 `name`、`children`的树形结构数据。 - **示例:** ```typescript import { updateTreeCheckStatus } from 'ui-utils-kit'; @@ -172,7 +181,97 @@ const result = buildTree(nodes); ## 🎨 公共通用函数 (common) -> 当前暂无公共通用函数,后续版本将持续更新。 +### 1. safeJsonParse(jsonString: string, defaultValue: T): [Error \| null, T] + +**功能描述:** 安全地解析 JSON 字符串,如果解析出错则返回默认值和错误对象。 + +**参数** +- `jsonString: string` — 要解析的 JSON 格式字符串。 + +- `defaultValue: T` — 当解析失败时返回的默认值,类型与期待的解析结果相同。 + +**返回值** + `[Error | null, T]` — 一个元组: + +- 示例 +```typescript +import { safeJsonParse } from 'ui-utils-kit'; + +const [err, data] = safeJsonParse('{"foo": 42}', { foo: 0 }); +if (err) { + // 处理解析错误 +} else { + console.log(data.foo); // 42 +} +``` +### 2. desensitize(value: string, type: "mobile" | "idcard"): string +**功能描述**:对敏感信息(手机号或身份证号)进行脱敏处理,隐藏中间部分。 + +**参数** +- `value: string` — 原始字符串,如手机号或身份证号。 +- `type: "mobile" \| "idcard"` — 数据类型,"mobile" 脱敏手机号,"idcard" 脱敏身份证号。 + +**返回值** +string — 脱敏后字符串,如果输入非字符串则返回空字符串。 + +- 示例 +```typescript +import { desensitize } from 'ui-utils-kit'; + +console.log(desensitize('13812345678', 'mobile')); // 输出:138****5678 +console.log(desensitize('110105199001011234', 'idcard')); // 输出:110105********1234 +``` + +### 3. Mutex 类 +功能描述:模拟互斥锁机制,用于控制异步操作对共享资源的访问,确保同一时刻只有一个操作进入临界区。 +> ##### 以下是一些应用场景 +> - 防止按钮重复点击:避免用户多次点击同一按钮导致重复网络请求或状态混乱 +> - 强制 API 调用顺序:确保一组异步接口按预期顺序依次执行,防止乱序带来的逻辑错误 +> - 多标签页 localStorage 访问:在多个浏览器标签或窗口同时操作同一 localStorage 时,避免数据竞争和丢失 +> - 分片上传(Chunked Upload):在大文件上传时,按顺序上传每个分片,确保断点续传或失败重试时不会错乱 +> - Web Worker 任务同步:在主线程与 Worker 线程之间同步访问共享内存(如 SharedArrayBuffer )时,保证原子性 + + +- 示例 + +```typescript + + + + +``` --- diff --git a/package.json b/package.json index 3904318..ee271d6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@ui-utils-kit/monorepo", "type": "module", - "version": "1.1.12", + "version": "1.1.13", "private": true, "packageManager": "pnpm@10.4.1", "description": "ui-utils-kit 是一个高效的偏业务前端工具函数库", @@ -16,12 +16,10 @@ "release": "bumpp -r", "lint": "eslint --cache .", "lint:fix": "nr lint --fix", - "lint:type": "tsc --noEmit", - "test": "vitest", - "test:ui": "vitest --ui", - "test:coverage": "vitest run --coverage" + "stylelint:fix": "stylelint **/*.{css,scss,html,vue} --fix" }, "dependencies": { + "husky": "^9.1.7", "ui-utils-kit": "^1.1.0" }, "devDependencies": { @@ -39,10 +37,7 @@ "vite": "^5.4.11", "vitest": "^2.1.5" }, - "simple-git-hooks": { - "pre-commit": "npx lint-staged" - }, "lint-staged": { - "*": "eslint --fix" + "*.{js,ts,tsx,vue}": "eslint --fix" } } \ No newline at end of file diff --git a/packages/core/README.md b/packages/core/README.md index 2ed0967..b86be4f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -172,7 +172,89 @@ const result = buildTree(nodes); ## 🎨 公共通用函数 (common) -> 当前暂无公共通用函数,后续版本将持续更新。 +### 1. safeJsonParse(jsonString: string, defaultValue: T): [Error \| null, T] + +**功能描述:** +安全地解析 JSON 字符串,如果解析出错则返回默认值和错误对象。 + +```typescript +export function safeJsonParse(jsonString: string, defaultValue: T): [Error | null, T] { + try { + const parsed = JSON.parse(jsonString) as T; + return [null, parsed]; + } catch (error) { + console.error("JSON 解析错误:", error); + return [error instanceof Error ? error : new Error(String(error)), defaultValue]; + } +} +``` +**参数** +- `jsonString: string` — 要解析的 JSON 格式字符串。 + +- `defaultValue: T` — 当解析失败时返回的默认值,类型与期待的解析结果相同。 + +**返回值** +`[Error | null, T]` — 一个元组: + +- 示例 +```typescript +const [err, data] = safeJsonParse('{"foo": 42}', { foo: 0 }); +if (err) { + // 处理解析错误 +} else { + console.log(data.foo); // 42 +} +``` +### 2. desensitize(value: string, type: "mobile" | "idcard"): string +功能描述:对敏感信息(手机号或身份证号)进行脱敏处理,隐藏中间部分。 + +**参数** +- `value: string` — 原始字符串,如手机号或身份证号。 + +- `type: "mobile" \| "idcard"` — 数据类型,"mobile" 脱敏手机号,"idcard" 脱敏身份证号。 + +**返回值** +string — 脱敏后字符串,如果输入非字符串则返回空字符串。 +- 示例 +```typescript +console.log(desensitize('13812345678', 'mobile')); // 输出:138****5678 +console.log(desensitize('110105199001011234', 'idcard')); // 输出:110105********1234 +``` + +### 3. Mutex 类 +功能描述:模拟互斥锁机制,用于控制异步操作对共享资源的访问,确保同一时刻只有一个操作进入临界区。 +- 示例 + +```typescript +import Mutex from './Mutex'; + +const mutex = new Mutex(); + +// 初始状态 +console.log('初始状态', mutex.isLocked(), mutex.queueLength()); // 初始状态 false 0 + +// 获取锁 +await mutex.lock(); +console.log('获取锁后', mutex.isLocked(), mutex.queueLength()); // 获取锁后 true 0 + +// 第二次请求锁,不会立即获取,加入队列 +const pending = mutex.lock().then(() => { + console.log('第二次获取锁', mutex.isLocked(), mutex.queueLength()); +}); +console.log('请求队列长度', mutex.queueLength()); // 请求队列长度 1 + +// 执行临界区代码 +// ... + +// 释放锁,自动唤醒队列中的下一个请求 +mutex.unlock(); +console.log('释放锁后', mutex.isLocked(), mutex.queueLength()); // 释放锁后 true 0 + +await pending; // 等待第二个请求获取锁 +// 最后释放锁 +mutex.unlock(); +console.log('全部完成', mutex.isLocked(), mutex.queueLength()); // 全部完成 false 0 +``` --- diff --git a/packages/core/package.json b/packages/core/package.json index c855b06..2ffad31 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "ui-utils-kit", "type": "module", - "version": "1.1.12", + "version": "1.1.13", "description": "ui-utils-kit 是一个高效的偏业务前端工具函数库", "author": "Ethan Yin", "license": "MIT", diff --git a/packages/core/src/common.ts b/packages/core/src/common.ts index 17e5612..0a39f0b 100644 --- a/packages/core/src/common.ts +++ b/packages/core/src/common.ts @@ -2,4 +2,118 @@ * ===================== * =======公共函数======= * ===================== - */ \ No newline at end of file + */ + + +/** + * 安全地解析 JSON 字符串。 + * + * @param jsonString - 要解析的 JSON 字符串。 + * @param defaultValue - 解析失败时返回的默认值。 + * @returns 一个元组,第一个元素为错误对象(若无错误则为 null),第二个元素为解析结果或默认值。 + */ +export function safeJsonParse(jsonString: string, defaultValue: T): [Error | null, T] { + try { + const parsed = JSON.parse(jsonString) as T; + return [null, parsed]; + } catch (error) { + console.error("JSON 解析错误:", error); + return [error instanceof Error ? error : new Error(String(error)), defaultValue]; + } +} + +/** + * 对敏感信息进行脱敏处理 + * + * @param value - 原始字符串(如手机号、身份证号) + * @param type - 数据类型,可选值为 "mobile"(手机号)或 "idcard"(身份证号) + * @returns 脱敏后的字符串 + */ +export function desensitize(value: string, type: "mobile" | "idcard"): string { + if (typeof value !== "string") { + return ""; + } + + switch (type) { + case "mobile": + // 手机号脱敏:保留前3位和后4位,中间4位替换为星号 + // 示例:13812345678 => 138****5678 + return value.replace(/^(\d{3})\d{4}(\d{4})$/, "$1****$2"); + + case "idcard": + // 身份证号脱敏:保留前6位和后4位,中间位数替换为星号 + // 示例:110105199001011234 => 110105********1234 + return value.replace(/^(\d{6})\d+(\d{4})$/, (_, p1, p2) => { + const middleLength = value.length - p1.length - p2.length; + const masked = "*".repeat(middleLength); + return `${p1}${masked}${p2}`; + }); + + default: + return value; + } +} + +/** + * 模拟互斥锁(Mutex)机制,用于控制异步操作对共享资源的访问 + */ +export default class Mutex { + + /** + * 锁的状态:true 表示已锁定,false 表示未锁定 + */ + #locked = false; + + /** + * 等待获取锁的 Promise resolve 函数队列 + */ + #queue: Array<() => void> = []; + + /** + * 获取锁。如果当前已锁定,则将请求加入队列,等待解锁后依次获取。 + * @returns Promise - 当获取到锁时,Promise 被 resolve。 + */ + public lock(): Promise { + if (this.#locked) { + // 已锁定,将请求加入队列 + return new Promise((resolve) => { + this.#queue.push(resolve); + }); + } + + // 未锁定,立即获取锁 + this.#locked = true; + return Promise.resolve(); + } + + /** + * 释放锁。如果有等待队列,则唤醒队列中的下一个请求;否则将锁状态设为未锁定。 + */ + public unlock(): void { + if (this.#queue.length > 0) { + // 唤醒队列中的下一个请求 + const nextResolve = this.#queue.shift(); + nextResolve?.(); + } else { + // 没有等待请求,释放锁 + this.#locked = false; + } + } + + /** + * 查询当前锁的状态 + * @returns boolean - true 表示已锁定,false 表示未锁定 + */ + public isLocked(): boolean { + return this.#locked; + } + + /** + * 获取当前等待队列的长度 + * @returns number - 等待队列中的请求数量 + */ + public queueLength(): number { + return this.#queue.length; + } + +} \ No newline at end of file diff --git a/playground/README.md b/playground/README.md index 2ed0967..b86be4f 100644 --- a/playground/README.md +++ b/playground/README.md @@ -172,7 +172,89 @@ const result = buildTree(nodes); ## 🎨 公共通用函数 (common) -> 当前暂无公共通用函数,后续版本将持续更新。 +### 1. safeJsonParse(jsonString: string, defaultValue: T): [Error \| null, T] + +**功能描述:** +安全地解析 JSON 字符串,如果解析出错则返回默认值和错误对象。 + +```typescript +export function safeJsonParse(jsonString: string, defaultValue: T): [Error | null, T] { + try { + const parsed = JSON.parse(jsonString) as T; + return [null, parsed]; + } catch (error) { + console.error("JSON 解析错误:", error); + return [error instanceof Error ? error : new Error(String(error)), defaultValue]; + } +} +``` +**参数** +- `jsonString: string` — 要解析的 JSON 格式字符串。 + +- `defaultValue: T` — 当解析失败时返回的默认值,类型与期待的解析结果相同。 + +**返回值** +`[Error | null, T]` — 一个元组: + +- 示例 +```typescript +const [err, data] = safeJsonParse('{"foo": 42}', { foo: 0 }); +if (err) { + // 处理解析错误 +} else { + console.log(data.foo); // 42 +} +``` +### 2. desensitize(value: string, type: "mobile" | "idcard"): string +功能描述:对敏感信息(手机号或身份证号)进行脱敏处理,隐藏中间部分。 + +**参数** +- `value: string` — 原始字符串,如手机号或身份证号。 + +- `type: "mobile" \| "idcard"` — 数据类型,"mobile" 脱敏手机号,"idcard" 脱敏身份证号。 + +**返回值** +string — 脱敏后字符串,如果输入非字符串则返回空字符串。 +- 示例 +```typescript +console.log(desensitize('13812345678', 'mobile')); // 输出:138****5678 +console.log(desensitize('110105199001011234', 'idcard')); // 输出:110105********1234 +``` + +### 3. Mutex 类 +功能描述:模拟互斥锁机制,用于控制异步操作对共享资源的访问,确保同一时刻只有一个操作进入临界区。 +- 示例 + +```typescript +import Mutex from './Mutex'; + +const mutex = new Mutex(); + +// 初始状态 +console.log('初始状态', mutex.isLocked(), mutex.queueLength()); // 初始状态 false 0 + +// 获取锁 +await mutex.lock(); +console.log('获取锁后', mutex.isLocked(), mutex.queueLength()); // 获取锁后 true 0 + +// 第二次请求锁,不会立即获取,加入队列 +const pending = mutex.lock().then(() => { + console.log('第二次获取锁', mutex.isLocked(), mutex.queueLength()); +}); +console.log('请求队列长度', mutex.queueLength()); // 请求队列长度 1 + +// 执行临界区代码 +// ... + +// 释放锁,自动唤醒队列中的下一个请求 +mutex.unlock(); +console.log('释放锁后', mutex.isLocked(), mutex.queueLength()); // 释放锁后 true 0 + +await pending; // 等待第二个请求获取锁 +// 最后释放锁 +mutex.unlock(); +console.log('全部完成', mutex.isLocked(), mutex.queueLength()); // 全部完成 false 0 +``` --- diff --git a/playground/package.json b/playground/package.json index 5b4bea4..69e36e5 100644 --- a/playground/package.json +++ b/playground/package.json @@ -1,7 +1,7 @@ { "name": "@ui-utils-kit/playground", "type": "module", - "version": "1.1.12", + "version": "1.1.13", "private": true, "repository": { "type": "git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0debf7e..7e81ccd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + husky: + specifier: ^9.1.7 + version: 9.1.7 ui-utils-kit: specifier: ^1.1.0 version: 1.1.0 @@ -945,55 +948,46 @@ packages: resolution: {integrity: sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.27.4': resolution: {integrity: sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.27.4': resolution: {integrity: sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.27.4': resolution: {integrity: sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-powerpc64le-gnu@4.27.4': resolution: {integrity: sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.27.4': resolution: {integrity: sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-s390x-gnu@4.27.4': resolution: {integrity: sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.27.4': resolution: {integrity: sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.27.4': resolution: {integrity: sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-win32-arm64-msvc@4.27.4': resolution: {integrity: sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==} @@ -2045,6 +2039,11 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -5327,6 +5326,8 @@ snapshots: human-signals@5.0.0: {} + husky@9.1.7: {} + ignore@5.3.2: {} immutable@5.0.3: {}