@mi-avalon/libs 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,90 +1,135 @@
1
- export class PatternType {
2
- /**
3
- * 整数
4
- */
5
- static integerRegex = /^-?\d+$/;
6
- /**
7
- * 正整数
8
- */
9
- static positiveIntegerRegex = /^[1-9]\d*$/;
10
- /**
11
- * 负整数
12
- */
13
- static negativeIntegerRegex = /^-[1-9]\d*$/;
14
- /**
15
- * 浮点数
16
- */
17
- static floatRegex = /^-?\d+(\.\d+)?$/;
18
- /**
19
- * 字母
20
- */
21
- static letter = /^[a-zA-Z]+$/;
22
- /**
23
- * 汉字
24
- */
25
- static chinese = /^[\u4e00-\u9fa5]+$/;
26
- /**
27
- * 数字
28
- */
29
- static number = /^[0-9]*$/;
30
- /**
31
- * 用户名 字母开头,允许字母数字下划线,长度4-16
32
- */
33
- static username = /^[a-zA-Z]\w{3,15}$/;
34
- /**
35
- * 强用户名 必须包含大小写字母和数字,6-20位
36
- */
37
- static strongUsername = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,20}$/;
38
- /**
39
- * 密码 至少8位,包含字母和数字
40
- */
41
- static password = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
42
- /**
43
- * 强密码 至少8位,包含大小写字母、数字和特殊字符
44
- */
45
- static strongPassword = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
46
- /**
47
- * 中国大陆的手机号
48
- */
49
- static phone = /^1[3-9]\d{9}$/;
50
- /**
51
- * 带区号的手机号
52
- */
53
- static phoneWithAreaCode = /^\+?\d{2,3}-?\d{8,11}$/;
54
- /**
55
- * 邮箱
56
- */
57
- static email = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
58
- /**
59
- * 身份证
60
- */
61
- static idCard = /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/;
62
- /**
63
- * 银行卡
64
- */
65
- static bankCard = /^[1-9]\d{3,30}$/;
66
- /**
67
- * 邮政编码
68
- */
69
- static zipCode = /^[1-9]\d{5}$/;
70
- /**
71
- * IP
72
- */
73
- static ip = /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/;
74
- /**
75
- * URL
76
- */
77
- static url = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([/\w .-]*)*\/?$/;
78
- /**
79
- * 车牌
80
- */
81
- static carNumber = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/;
82
- /**
83
- * 时间 hh:mm:ss
84
- */
85
- static time = /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/;
86
- /**
87
- * 日期 YYYY-MM-DD
88
- */
89
- static date = /^(\d{4})-(\d{2})-(\d{2})$/;
1
+ /**
2
+ * 正则表达式模式常量 - 增强版本,提供验证函数和类型安全
3
+ */
4
+ export class PatternValidator {
5
+ // 基础数字模式
6
+ static patterns = {
7
+ /** 整数 */
8
+ integer: /^-?\d+$/,
9
+ /** 正整数 */
10
+ positiveInteger: /^[1-9]\d*$/,
11
+ /** 负整数 */
12
+ negativeInteger: /^-[1-9]\d*$/,
13
+ /** 浮点数 */
14
+ float: /^-?\d+(\.\d+)?$/,
15
+ /** 字母 */
16
+ letter: /^[a-zA-Z]+$/,
17
+ /** 汉字 */
18
+ chinese: /^[\u4e00-\u9fa5]+$/,
19
+ /** 数字 */
20
+ number: /^[0-9]*$/,
21
+ // 用户相关模式
22
+ /** 用户名:字母开头,允许字母数字下划线,长度4-16 */
23
+ username: /^[a-zA-Z]\w{3,15}$/,
24
+ /** 强用户名:必须包含大小写字母和数字,6-20位 */
25
+ strongUsername: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{6,20}$/,
26
+ /** 密码:至少8位,包含字母和数字 */
27
+ password: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/,
28
+ /** 强密码:至少8位,包含大小写字母、数字和特殊字符 */
29
+ strongPassword: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
30
+ // 联系方式模式
31
+ /** 中国大陆手机号 */
32
+ phone: /^1[3-9]\d{9}$/,
33
+ /** 带区号的手机号 */
34
+ phoneWithAreaCode: /^\+?\d{2,3}-?\d{8,11}$/,
35
+ /** 邮箱 */
36
+ email: /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
37
+ /** 身份证 */
38
+ idCard: /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/,
39
+ /** 银行卡 */
40
+ bankCard: /^[1-9]\d{3,30}$/,
41
+ /** 邮政编码 */
42
+ zipCode: /^[1-9]\d{5}$/,
43
+ // 技术模式
44
+ /** IP地址 */
45
+ ip: /^(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/,
46
+ /** URL */
47
+ url: /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})([\/\w .-]*)*\/?$/,
48
+ /** 车牌号 */
49
+ carNumber: /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-HJ-NP-Z][A-HJ-NP-Z0-9]{4,5}[A-HJ-NP-Z0-9挂学警港澳]$/,
50
+ // 时间日期模式
51
+ /** 时间格式 hh:mm:ss */
52
+ time: /^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])$/,
53
+ /** 日期格式 YYYY-MM-DD */
54
+ date: /^(\d{4})-(\d{2})-(\d{2})$/,
55
+ };
56
+ /**
57
+ * 验证字符串是否匹配指定模式
58
+ * @param type 模式类型
59
+ * @param value 要验证的值
60
+ * @returns 是否匹配
61
+ */
62
+ static validate(type, value) {
63
+ const pattern = this.patterns[type];
64
+ if (!pattern) {
65
+ throw new Error(`Unknown pattern type: ${type}`);
66
+ }
67
+ return pattern.test(value);
68
+ }
69
+ /**
70
+ * 获取指定模式的正则表达式
71
+ * @param type 模式类型
72
+ * @returns 正则表达式
73
+ */
74
+ static getPattern(type) {
75
+ const pattern = this.patterns[type];
76
+ if (!pattern) {
77
+ throw new Error(`Unknown pattern type: ${type}`);
78
+ }
79
+ return pattern;
80
+ }
81
+ /**
82
+ * 验证邮箱(特殊处理,支持更复杂的验证)
83
+ * @param email 邮箱地址
84
+ * @returns 是否有效
85
+ */
86
+ static validateEmail(email) {
87
+ const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
88
+ return emailRegex.test(email) && email.length <= 254;
89
+ }
90
+ /**
91
+ * 验证手机号(支持更多运营商)
92
+ * @param phone 手机号
93
+ * @returns 是否有效
94
+ */
95
+ static validatePhone(phone) {
96
+ const phoneRegex = /^1[3-9]\d{9}$/;
97
+ return phoneRegex.test(phone);
98
+ }
99
+ /**
100
+ * 验证身份证(包含校验位验证)
101
+ * @param idCard 身份证号
102
+ * @returns 是否有效
103
+ */
104
+ static validateIdCard(idCard) {
105
+ const pattern = this.patterns.idCard;
106
+ if (!pattern.test(idCard))
107
+ return false;
108
+ // 校验位验证
109
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
110
+ const codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
111
+ let sum = 0;
112
+ for (let i = 0; i < 17; i++) {
113
+ sum += parseInt(idCard[i]) * weights[i];
114
+ }
115
+ const checkCode = codes[sum % 11];
116
+ return idCard[17].toUpperCase() === checkCode;
117
+ }
118
+ /**
119
+ * 创建自定义验证函数
120
+ * @param pattern 正则表达式
121
+ * @param errorMessage 错误消息
122
+ * @returns 验证函数
123
+ */
124
+ static createValidator(pattern, errorMessage) {
125
+ return (value) => {
126
+ const valid = pattern.test(value);
127
+ return {
128
+ valid,
129
+ message: valid ? undefined : (errorMessage || 'Invalid format')
130
+ };
131
+ };
132
+ }
90
133
  }
134
+ // 保持向后兼容的别名
135
+ export const PatternType = PatternValidator;
@@ -5,6 +5,7 @@ export type UseFuncRequestOptions<T extends (...args: any[]) => Promise<any>> =
5
5
  onSuccess?: (data: UnwrapPromise<ReturnType<T>>) => void | Promise<void>;
6
6
  onError?: (error: Error) => void | Promise<void>;
7
7
  onFinally?: () => void | Promise<void>;
8
+ autoRunDeps?: any[];
8
9
  };
9
10
  export declare function useFuncRequest<T extends (...args: any[]) => Promise<any>>(asyncFunc: T, options?: UseFuncRequestOptions<T>): {
10
11
  run: (...args: Parameters<T>) => Promise<any>;
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, useState } from 'react';
1
+ import { useEffect, useRef, useState, useMemo, useCallback } from 'react';
2
2
  export function useFuncRequest(
3
3
  // 异步函数
4
4
  asyncFunc,
@@ -9,12 +9,12 @@ options) {
9
9
  const [data, setData] = useState(null);
10
10
  const abortControllerRef = useRef(undefined);
11
11
  const requestIdRef = useRef(0);
12
- const cancel = () => {
12
+ const cancel = useCallback(() => {
13
13
  abortControllerRef.current?.abort();
14
14
  setLoading(false);
15
15
  setError(null);
16
- };
17
- const run = async (...args) => {
16
+ }, []);
17
+ const run = useCallback(async (...args) => {
18
18
  const currentRequestId = ++requestIdRef.current;
19
19
  cancel();
20
20
  const abortController = new AbortController();
@@ -56,12 +56,14 @@ options) {
56
56
  await options?.onFinally?.();
57
57
  }
58
58
  }
59
- };
59
+ }, [asyncFunc, options, cancel]);
60
+ // 使用 useMemo 优化依赖项,避免 JSON.stringify 的性能问题
61
+ const autoRunDeps = useMemo(() => [...(options?.autoRunDeps || []), options?.autoRunArgs], [options?.autoRunDeps, options?.autoRunArgs]);
60
62
  useEffect(() => {
61
63
  if (options?.autoRunArgs) {
62
64
  run(...options.autoRunArgs).catch(() => { });
63
65
  }
64
66
  return cancel;
65
- }, [JSON.stringify(options?.autoRunArgs)]);
66
- return { run, cancel, loading, error, data };
67
+ }, autoRunDeps);
68
+ return useMemo(() => ({ run, cancel, loading, error, data }), [run, cancel, loading, error, data]);
67
69
  }
@@ -1,8 +1,8 @@
1
1
  type Callback = () => void;
2
- interface UseTimeoutReturn {
2
+ interface UseIntervalReturn {
3
3
  start: Callback;
4
4
  isRunning: boolean;
5
5
  clear: Callback;
6
6
  }
7
- declare function useInterval(callback: Callback, delay: number | null, immediate?: boolean): UseTimeoutReturn;
7
+ declare function useInterval(callback: Callback, delay: number | null, immediate?: boolean): UseIntervalReturn;
8
8
  export { useInterval };
@@ -1,7 +1,8 @@
1
- import { useCallback, useEffect, useRef } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  function useInterval(callback, delay, immediate = false) {
3
3
  const timerRef = useRef(undefined);
4
4
  const savedCallback = useRef(callback);
5
+ const [isRunning, setIsRunning] = useState(false);
5
6
  useEffect(() => {
6
7
  savedCallback.current = callback;
7
8
  }, [callback]);
@@ -9,6 +10,7 @@ function useInterval(callback, delay, immediate = false) {
9
10
  if (timerRef.current) {
10
11
  clearInterval(timerRef.current);
11
12
  timerRef.current = undefined;
13
+ setIsRunning(false);
12
14
  }
13
15
  }, []);
14
16
  const start = useCallback(() => {
@@ -18,6 +20,7 @@ function useInterval(callback, delay, immediate = false) {
18
20
  if (immediate) {
19
21
  savedCallback.current();
20
22
  }
23
+ setIsRunning(true);
21
24
  timerRef.current = setInterval(() => savedCallback.current(), delay);
22
25
  }
23
26
  }, [delay, clear, immediate]);
@@ -25,6 +28,6 @@ function useInterval(callback, delay, immediate = false) {
25
28
  useEffect(() => {
26
29
  return clear;
27
30
  }, [clear]);
28
- return { start, clear, isRunning: !!timerRef.current };
31
+ return { start, clear, isRunning };
29
32
  }
30
33
  export { useInterval };
@@ -1,3 +1 @@
1
- export declare function useQuery(): {
2
- [k: string]: string;
3
- };
1
+ export declare function useQuery(): Record<string, string>;
@@ -1,6 +1,53 @@
1
+ import { useMemo, useEffect, useState } from 'react';
1
2
  export function useQuery() {
2
- const search = window.location.href.split('?')[1] || '';
3
- const params = new URLSearchParams(search);
4
- const query = Object.fromEntries(params.entries());
5
- return query;
3
+ const [query, setQuery] = useState(() => {
4
+ // SSR兼容性检查
5
+ if (typeof window === 'undefined') {
6
+ return {};
7
+ }
8
+ const search = window.location.search;
9
+ if (!search)
10
+ return {};
11
+ const params = new URLSearchParams(search);
12
+ return Object.fromEntries(params.entries());
13
+ });
14
+ // 使用 useMemo 优化性能,避免每次渲染都重新计算
15
+ const memoizedQuery = useMemo(() => query, [query]);
16
+ // 监听路由变化(popstate, pushstate, replacestate)
17
+ useEffect(() => {
18
+ if (typeof window === 'undefined')
19
+ return;
20
+ const updateQuery = () => {
21
+ const search = window.location.search;
22
+ if (!search) {
23
+ setQuery({});
24
+ return;
25
+ }
26
+ const params = new URLSearchParams(search);
27
+ const newQuery = Object.fromEntries(params.entries());
28
+ setQuery(newQuery);
29
+ };
30
+ // 监听浏览器前进/后退
31
+ window.addEventListener('popstate', updateQuery);
32
+ // 拦截 pushState 和 replaceState
33
+ const originalPushState = history.pushState;
34
+ const originalReplaceState = history.replaceState;
35
+ history.pushState = function (...args) {
36
+ originalPushState.apply(history, args);
37
+ setTimeout(updateQuery, 0);
38
+ };
39
+ history.replaceState = function (...args) {
40
+ originalReplaceState.apply(history, args);
41
+ setTimeout(updateQuery, 0);
42
+ };
43
+ // 监听 hashchange(对于 hash 路由)
44
+ window.addEventListener('hashchange', updateQuery);
45
+ return () => {
46
+ window.removeEventListener('popstate', updateQuery);
47
+ window.removeEventListener('hashchange', updateQuery);
48
+ history.pushState = originalPushState;
49
+ history.replaceState = originalReplaceState;
50
+ };
51
+ }, []);
52
+ return memoizedQuery;
6
53
  }
@@ -1,14 +1,16 @@
1
- import { useCallback, useEffect, useRef } from 'react';
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
2
  function useTimeout(callback, delay, immediate = false) {
3
3
  const timerRef = useRef(undefined);
4
4
  const savedCallback = useRef(callback);
5
+ const [isRunning, setIsRunning] = useState(false);
5
6
  useEffect(() => {
6
7
  savedCallback.current = callback;
7
8
  }, [callback]);
8
9
  const clear = useCallback(() => {
9
10
  if (timerRef.current) {
10
- clearInterval(timerRef.current);
11
+ clearTimeout(timerRef.current);
11
12
  timerRef.current = undefined;
13
+ setIsRunning(false);
12
14
  }
13
15
  }, []);
14
16
  const start = useCallback(() => {
@@ -18,13 +20,18 @@ function useTimeout(callback, delay, immediate = false) {
18
20
  if (immediate) {
19
21
  savedCallback.current();
20
22
  }
21
- timerRef.current = setTimeout(() => savedCallback.current(), delay);
23
+ setIsRunning(true);
24
+ timerRef.current = setTimeout(() => {
25
+ savedCallback.current();
26
+ setIsRunning(false);
27
+ timerRef.current = undefined;
28
+ }, delay);
22
29
  }
23
30
  }, [delay, clear, immediate]);
24
31
  // 只负责清理
25
32
  useEffect(() => {
26
33
  return clear;
27
34
  }, [clear]);
28
- return { start, clear, isRunning: !!timerRef.current };
35
+ return { start, clear, isRunning };
29
36
  }
30
37
  export { useTimeout };