@dofe/sso-hooks 0.1.5

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.
package/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # @repo/hooks - React Hooks 工具库
2
+
3
+ 通用的 React Hooks 工具集合,支持国际化验证、错误处理、键盘快捷键等场景。
4
+
5
+ ## 安装
6
+
7
+ ### Workspace 内使用
8
+
9
+ ```json
10
+ {
11
+ "dependencies": {
12
+ "@repo/hooks": "workspace:*"
13
+ }
14
+ }
15
+ ```
16
+
17
+ ### 从 npmjs 使用
18
+
19
+ ```json
20
+ {
21
+ "dependencies": {
22
+ "@dofe/sso-hooks": "^0.1.0"
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## 包含的 Hooks
28
+
29
+ | Hook | 描述 | 依赖 |
30
+ | ------------------------ | --------------------------------------------- | ------------------------ |
31
+ | `useDebouncedValue` | 防抖值更新 | react |
32
+ | `useHotkeys` | 键盘快捷键绑定 | react |
33
+ | `usePermissions` | 权限管理 | react |
34
+ | `useAspectRatioSize` | 宽高比尺寸计算 | react |
35
+ | `useOperationFeedback` | 统一的操作反馈(toast) | react, sonner, next-intl |
36
+ | `useErrorHandler` | 统一错误处理,自动分类显示 | react, sonner, next-intl |
37
+ | `useI18nValidation` | 国际化表单验证 Zod Schema 生成 | react, zod, next-intl |
38
+
39
+ ## 使用示例
40
+
41
+ ### useDebouncedValue
42
+
43
+ ```typescript
44
+ import { useDebouncedValue } from '@repo/hooks';
45
+
46
+ const [search, setSearch] = useState('');
47
+ const debouncedSearch = useDebouncedValue(search, 500);
48
+
49
+ // debouncedSearch 会在 500ms 后更新
50
+ ```
51
+
52
+ ### useHotkeys
53
+
54
+ ```typescript
55
+ import { useHotkeys } from '@repo/hooks';
56
+
57
+ useHotkeys('ctrl+s', (event) => {
58
+ event.preventDefault();
59
+ handleSave();
60
+ }, { scope: 'editor' });
61
+ ```
62
+
63
+ ### useI18nValidation
64
+
65
+ ```typescript
66
+ import { useI18nValidation, createPasswordConfirmSchema } from '@repo/hooks';
67
+
68
+ const v = useI18nValidation();
69
+
70
+ // 使用预定义验证器
71
+ const schema = z.object({
72
+ email: v.email(),
73
+ phone: v.phone(),
74
+ password: v.password({ min: 6, requireStrong: true }),
75
+ username: v.required('username'),
76
+ bio: v.string('description', { max: 200 }),
77
+ });
78
+
79
+ // 密码确认 schema
80
+ const passwordSchema = createPasswordConfirmSchema(v, {
81
+ passwordField: 'password',
82
+ confirmField: 'confirmPassword',
83
+ minLength: 8,
84
+ });
85
+ ```
86
+
87
+ ### useOperationFeedback
88
+
89
+ ```typescript
90
+ import { useOperationFeedback } from '@repo/hooks';
91
+
92
+ const { success, error, warning, info, loading, promise } = useOperationFeedback();
93
+
94
+ success('操作成功');
95
+ error('操作失败');
96
+
97
+ // Promise toast
98
+ const result = await promise(
99
+ saveData(),
100
+ {
101
+ loading: '保存中...',
102
+ success: '保存成功',
103
+ error: '保存失败',
104
+ }
105
+ );
106
+ ```
107
+
108
+ ### useErrorHandler
109
+
110
+ ```typescript
111
+ import { useErrorHandler, NetworkError, ValidationError } from '@repo/hooks';
112
+
113
+ const handleError = useErrorHandler();
114
+
115
+ try {
116
+ await fetchData();
117
+ } catch (error) {
118
+ if (!response.ok) {
119
+ handleError(new NetworkError('网络错误'));
120
+ } else {
121
+ handleError(new ValidationError('验证失败'));
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## 目录结构
127
+
128
+ ```
129
+ src/
130
+ ├── useDebouncedValue.ts # 防抖 hook
131
+ ├── useHotkeys.ts # 键盘快捷键 hook
132
+ ├── usePermissions.ts # 权限 hook
133
+ ├── useAspectRatioSize.ts # 宽高比尺寸 hook
134
+ ├── useOperationFeedback.ts # 操作反馈 hook
135
+ ├── useErrorHandler.ts # 错误处理 hook
136
+ ├── useI18nValidation.ts # 国际化验证 hook
137
+ └── index.ts # 导出汇总
138
+ ```
139
+
140
+ ## 构建
141
+
142
+ ```bash
143
+ pnpm --filter @repo/hooks build
144
+ ```
145
+
146
+ ---
147
+
148
+ ## NPM 发布流程
149
+
150
+ ### 发布说明
151
+
152
+ 本包在 workspace 内使用名称 `@repo/hooks`,发布到 npmjs 时使用名称 `@dofe/sso-hooks`。
153
+
154
+ 发布脚本会自动处理名称转换:
155
+
156
+ - `prepack` - 发布前将名称改为 `@dofe/sso-hooks`,移除 workspace 依赖
157
+ - `postpack` - 发布后恢复为 `@repo/hooks`,恢复 workspace 依赖
158
+
159
+ ### 发布命令
160
+
161
+ ```bash
162
+ # 进入 hooks 目录
163
+ cd sso.dofe.ai/packages/hooks
164
+
165
+ # 发布 patch 版本 (0.1.0 → 0.1.1)
166
+ pnpm release:patch
167
+
168
+ # 发布 minor 版本 (0.1.0 → 0.2.0)
169
+ pnpm release:minor
170
+
171
+ # 发布 major 版本 (0.1.0 → 1.0.0)
172
+ pnpm release:major
173
+
174
+ # 带 OTP 发布(如果启用了 2FA)
175
+ pnpm publish:npm:otp --otp=<你的OTP码>
176
+ ```
177
+
178
+ ### 发布流程详解
179
+
180
+ ```bash
181
+ pnpm release:patch
182
+ ```
183
+
184
+ 执行步骤:
185
+
186
+ 1. `npm version patch` - 升级版本号
187
+ 2. `pnpm run prepack` - 修改 package.json(名称、移除 workspace 依赖)
188
+ 3. `npm publish --access public` - 发布到 npmjs
189
+ 4. `pnpm run postpack` - 恢复 package.json 原始状态
190
+
191
+ ### 手动发布步骤(推荐)
192
+
193
+ 由于 npm lifecycle hooks 的复杂性,建议使用以下手动流程:
194
+
195
+ ```bash
196
+ # 1. 构建包
197
+ pnpm build
198
+
199
+ # 2. 运行 prepack 修改 package.json
200
+ node scripts/prepack.mjs
201
+
202
+ # 3. 发布(使用 --ignore-scripts 避免重复运行 lifecycle hooks)
203
+ npm publish --access public --ignore-scripts --otp=<你的OTP码>
204
+
205
+ # 4. 运行 postpack 恢复 package.json
206
+ node scripts/postpack.mjs
207
+ ```
208
+
209
+ ### 前置条件
210
+
211
+ 1. npm 账户登录:
212
+
213
+ ```bash
214
+ npm login
215
+ ```
216
+
217
+ 2. 验证登录状态:
218
+
219
+ ```bash
220
+ npm whoami
221
+ ```
222
+
223
+ 3. 确保有 `@dofe` organization 的发布权限
224
+
225
+ ### Peer Dependencies
226
+
227
+ 使用此包的项目需要安装以下依赖:
228
+
229
+ ```json
230
+ {
231
+ "dependencies": {
232
+ "next-intl": "^4.0.0",
233
+ "react": "^19.0.0",
234
+ "sonner": "^2.0.0",
235
+ "zod": "^3.24.0"
236
+ }
237
+ }
238
+ ```
239
+
240
+ ### 常见问题
241
+
242
+ #### 发布失败 "Cannot resolve @repo/constants"
243
+
244
+ 确保运行了 `prepack` 脚本,它会移除 workspace 依赖。
245
+
246
+ #### 本地安装失败 "Cannot resolve @repo/hooks"
247
+
248
+ 这是因为 workspace 依赖问题。确保在 sso.dofe.ai 项目根目录运行 `pnpm install`。
@@ -0,0 +1,8 @@
1
+ export { useDebouncedValue } from './useDebouncedValue';
2
+ export { useErrorHandler, NetworkError, ValidationError, AuthenticationError, PermissionError } from './useErrorHandler';
3
+ export { useHotkeys } from './useHotkeys';
4
+ export { useI18nValidation, createPasswordConfirmSchema } from './useI18nValidation';
5
+ export { useOperationFeedback } from './useOperationFeedback';
6
+ export { usePermissions } from './usePermissions';
7
+ export { useAspectRatioSize } from './useAspectRatioSize';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ export { useDebouncedValue } from './useDebouncedValue';
2
+ export { useErrorHandler, NetworkError, ValidationError, AuthenticationError, PermissionError } from './useErrorHandler';
3
+ export { useHotkeys } from './useHotkeys';
4
+ export { useI18nValidation, createPasswordConfirmSchema } from './useI18nValidation';
5
+ export { useOperationFeedback } from './useOperationFeedback';
6
+ export { usePermissions } from './usePermissions';
7
+ export { useAspectRatioSize } from './useAspectRatioSize';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AACrF,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,18 @@
1
+ interface UseAspectRatioSizeOptions {
2
+ initialAspectRatio?: string;
3
+ initialResolution?: string;
4
+ initialRatioLocked?: boolean;
5
+ onSizeChange?: (width: number, height: number) => void;
6
+ }
7
+ interface UseAspectRatioSizeReturn {
8
+ width: number;
9
+ height: number;
10
+ ratioLocked: boolean;
11
+ setRatioLocked: (locked: boolean) => void;
12
+ handleWidthChange: (value: string, aspectRatio: string) => void;
13
+ handleHeightChange: (value: string, aspectRatio: string) => void;
14
+ updateSizeFromRatio: (aspectRatio: string, resolution: string) => void;
15
+ }
16
+ export declare function useAspectRatioSize({ initialAspectRatio, initialResolution, initialRatioLocked, onSizeChange, }: UseAspectRatioSizeOptions): UseAspectRatioSizeReturn;
17
+ export {};
18
+ //# sourceMappingURL=useAspectRatioSize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAspectRatioSize.d.ts","sourceRoot":"","sources":["../src/useAspectRatioSize.ts"],"names":[],"mappings":"AA2EA,UAAU,yBAAyB;IACjC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACxD;AAED,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,KAAK,IAAI,CAAC;IACjE,mBAAmB,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;CACxE;AAED,wBAAgB,kBAAkB,CAAC,EACjC,kBAA0B,EAC1B,iBAAwB,EACxB,kBAAyB,EACzB,YAAY,GACb,EAAE,yBAAyB,GAAG,wBAAwB,CA2FtD"}
@@ -0,0 +1,135 @@
1
+ 'use client';
2
+ import { useState, useCallback, useEffect } from 'react';
3
+ // 解析宽高比字符串为数字
4
+ const parseAspectRatio = (ratio) => {
5
+ const [w, h] = ratio.split(':').map(Number);
6
+ return [w ?? 0, h ?? 0];
7
+ };
8
+ // 根据分辨率获取基准尺寸
9
+ const getBaseSize = (resolution) => {
10
+ switch (resolution) {
11
+ case '1K':
12
+ return 1024;
13
+ case '2K':
14
+ return 2048;
15
+ case '4K':
16
+ return 4096;
17
+ default:
18
+ return 2048;
19
+ }
20
+ };
21
+ // 根据比例计算最大尺寸限制
22
+ const getMaxDimensions = (aspectRatio) => {
23
+ const [w, h] = parseAspectRatio(aspectRatio);
24
+ const maxDimension = 6240;
25
+ if (w > h) {
26
+ // 横向比例:以高度为基准计算宽度
27
+ const maxHeight = maxDimension;
28
+ const maxWidth = Math.round(maxDimension * (w / h));
29
+ // 如果计算出的宽度超过最大限制,则以宽度为基准重新计算
30
+ return maxWidth > maxDimension
31
+ ? [maxDimension, Math.round(maxDimension * (h / w))]
32
+ : [maxWidth, maxHeight];
33
+ }
34
+ else {
35
+ // 纵向比例:以宽度为基准计算高度
36
+ const maxWidth = maxDimension;
37
+ const maxHeight = Math.round(maxDimension * (h / w));
38
+ // 如果计算出的高度超过最大限制,则以高度为基准重新计算
39
+ return maxHeight > maxDimension
40
+ ? [Math.round(maxDimension * (w / h)), maxDimension]
41
+ : [maxWidth, maxHeight];
42
+ }
43
+ };
44
+ // 根据宽高比和分辨率计算尺寸
45
+ const calculateSize = (aspectRatio, resolution) => {
46
+ const [w, h] = parseAspectRatio(aspectRatio);
47
+ const baseSize = getBaseSize(resolution);
48
+ const [maxWidth, maxHeight] = getMaxDimensions(aspectRatio);
49
+ // 计算最适合的尺寸,保持比例
50
+ if (w > h) {
51
+ // 横向比例:宽度为主导
52
+ const width = Math.min(baseSize, maxWidth, Math.round(baseSize * (w / h)));
53
+ const height = Math.round(width * (h / w));
54
+ return [width, height];
55
+ }
56
+ else {
57
+ // 纵向比例:高度为主导
58
+ const height = Math.min(baseSize, maxHeight, Math.round(baseSize * (h / w)));
59
+ const width = Math.round(height * (w / h));
60
+ return [width, height];
61
+ }
62
+ };
63
+ export function useAspectRatioSize({ initialAspectRatio = '3:4', initialResolution = '2K', initialRatioLocked = true, onSizeChange, }) {
64
+ const [width, setWidth] = useState(() => {
65
+ const [w] = calculateSize(initialAspectRatio, initialResolution);
66
+ return w;
67
+ });
68
+ const [height, setHeight] = useState(() => {
69
+ const [, h] = calculateSize(initialAspectRatio, initialResolution);
70
+ return h;
71
+ });
72
+ const [ratioLocked, setRatioLocked] = useState(initialRatioLocked);
73
+ // 更新尺寸的方法
74
+ const updateSizeFromRatio = useCallback((aspectRatio, resolution) => {
75
+ const [newWidth, newHeight] = calculateSize(aspectRatio, resolution);
76
+ setWidth(newWidth);
77
+ setHeight(newHeight);
78
+ }, []);
79
+ // 处理宽度输入 - 需要外部传入当前aspectRatio
80
+ const handleWidthChange = useCallback((value, currentAspectRatio) => {
81
+ const numValue = parseInt(value);
82
+ // 如果输入不是有效数字,忽略
83
+ if (isNaN(numValue) || value === '')
84
+ return;
85
+ // 根据比例获取最大尺寸限制
86
+ const [maxWidth] = getMaxDimensions(currentAspectRatio);
87
+ // 限制在有效范围内,如果超出范围则修正
88
+ const clampedValue = Math.max(1, Math.min(maxWidth, numValue));
89
+ setWidth(clampedValue);
90
+ if (ratioLocked) {
91
+ const [w, h] = parseAspectRatio(currentAspectRatio);
92
+ const newHeight = Math.round(clampedValue * (h / w));
93
+ // 根据比例获取最大高度限制
94
+ const [, maxHeight] = getMaxDimensions(currentAspectRatio);
95
+ const clampedHeight = Math.max(1, Math.min(maxHeight, newHeight));
96
+ setHeight(clampedHeight);
97
+ }
98
+ }, [ratioLocked]);
99
+ // 处理高度输入 - 需要外部传入当前aspectRatio
100
+ const handleHeightChange = useCallback((value, currentAspectRatio) => {
101
+ const numValue = parseInt(value);
102
+ // 如果输入不是有效数字,忽略
103
+ if (isNaN(numValue) || value === '')
104
+ return;
105
+ // 根据比例获取最大尺寸限制
106
+ const [, maxHeight] = getMaxDimensions(currentAspectRatio);
107
+ // 限制在有效范围内,如果超出范围则修正
108
+ const clampedValue = Math.max(1, Math.min(maxHeight, numValue));
109
+ setHeight(clampedValue);
110
+ if (ratioLocked) {
111
+ const [w, h] = parseAspectRatio(currentAspectRatio);
112
+ const newWidth = Math.round(clampedValue * (w / h));
113
+ // 根据比例获取最大宽度限制
114
+ const [maxWidth] = getMaxDimensions(currentAspectRatio);
115
+ const clampedWidth = Math.max(1, Math.min(maxWidth, newWidth));
116
+ setWidth(clampedWidth);
117
+ }
118
+ }, [ratioLocked]);
119
+ // 同步尺寸变化到外部(如 form)
120
+ useEffect(() => {
121
+ if (onSizeChange) {
122
+ onSizeChange(width, height);
123
+ }
124
+ }, [width, height, onSizeChange]);
125
+ return {
126
+ width,
127
+ height,
128
+ ratioLocked,
129
+ setRatioLocked,
130
+ handleWidthChange,
131
+ handleHeightChange,
132
+ updateSizeFromRatio,
133
+ };
134
+ }
135
+ //# sourceMappingURL=useAspectRatioSize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useAspectRatioSize.js","sourceRoot":"","sources":["../src/useAspectRatioSize.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 防抖 hook,延迟更新值
3
+ * @param value 需要防抖的值
4
+ * @param delay 延迟时间(毫秒),默认 500ms
5
+ * @returns 防抖后的值
6
+ */
7
+ export declare function useDebouncedValue<T>(value: T, delay?: number): T;
8
+ //# sourceMappingURL=useDebouncedValue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDebouncedValue.d.ts","sourceRoot":"","sources":["../src/useDebouncedValue.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,SAAM,GAAG,CAAC,CAc7D"}
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+ import { useState, useEffect } from 'react';
3
+ /**
4
+ * 防抖 hook,延迟更新值
5
+ * @param value 需要防抖的值
6
+ * @param delay 延迟时间(毫秒),默认 500ms
7
+ * @returns 防抖后的值
8
+ */
9
+ export function useDebouncedValue(value, delay = 500) {
10
+ const [debouncedValue, setDebouncedValue] = useState(value);
11
+ useEffect(() => {
12
+ const timer = setTimeout(() => {
13
+ setDebouncedValue(value);
14
+ }, delay);
15
+ return () => {
16
+ clearTimeout(timer);
17
+ };
18
+ }, [value, delay]);
19
+ return debouncedValue;
20
+ }
21
+ //# sourceMappingURL=useDebouncedValue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useDebouncedValue.js","sourceRoot":"","sources":["../src/useDebouncedValue.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5C;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAI,KAAQ,EAAE,KAAK,GAAG,GAAG;IACxD,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAI,KAAK,CAAC,CAAC;IAE/D,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAC3B,CAAC,EAAE,KAAK,CAAC,CAAC;QAEV,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAEnB,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 错误类型定义
3
+ */
4
+ export declare class NetworkError extends Error {
5
+ constructor(message: string);
6
+ }
7
+ export declare class ValidationError extends Error {
8
+ constructor(message: string);
9
+ }
10
+ export declare class AuthenticationError extends Error {
11
+ constructor(message: string);
12
+ }
13
+ export declare class PermissionError extends Error {
14
+ constructor(message: string);
15
+ }
16
+ /**
17
+ * 统一错误处理 Hook
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const handleError = useErrorHandler();
22
+ *
23
+ * try {
24
+ * await someOperation();
25
+ * } catch (error) {
26
+ * handleError(error);
27
+ * }
28
+ * ```
29
+ */
30
+ export declare function useErrorHandler(): (err: unknown, customMessage?: string) => void;
31
+ //# sourceMappingURL=useErrorHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useErrorHandler.d.ts","sourceRoot":"","sources":["../src/useErrorHandler.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,UAKrB,OAAO,kBAAkB,MAAM,UAqDxC"}
@@ -0,0 +1,96 @@
1
+ 'use client';
2
+ /**
3
+ * useErrorHandler - 统一的错误处理 Hook
4
+ * 提供友好的错误提示和错误分类处理
5
+ */
6
+ import { useCallback } from 'react';
7
+ import { useOperationFeedback } from './useOperationFeedback';
8
+ import { useTranslations } from 'next-intl';
9
+ /**
10
+ * 错误类型定义
11
+ */
12
+ export class NetworkError extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = 'NetworkError';
16
+ }
17
+ }
18
+ export class ValidationError extends Error {
19
+ constructor(message) {
20
+ super(message);
21
+ this.name = 'ValidationError';
22
+ }
23
+ }
24
+ export class AuthenticationError extends Error {
25
+ constructor(message) {
26
+ super(message);
27
+ this.name = 'AuthenticationError';
28
+ }
29
+ }
30
+ export class PermissionError extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = 'PermissionError';
34
+ }
35
+ }
36
+ /**
37
+ * 统一错误处理 Hook
38
+ *
39
+ * @example
40
+ * ```tsx
41
+ * const handleError = useErrorHandler();
42
+ *
43
+ * try {
44
+ * await someOperation();
45
+ * } catch (error) {
46
+ * handleError(error);
47
+ * }
48
+ * ```
49
+ */
50
+ export function useErrorHandler() {
51
+ const { error } = useOperationFeedback();
52
+ const t = useTranslations('errors');
53
+ const handleError = useCallback((err, customMessage) => {
54
+ // 如果有自定义消息,直接使用
55
+ if (customMessage) {
56
+ error(customMessage);
57
+ return;
58
+ }
59
+ // 根据错误类型显示不同的提示
60
+ if (err instanceof NetworkError) {
61
+ error(t('network', {
62
+ defaultValue: '网络连接失败,请检查网络后重试',
63
+ }));
64
+ }
65
+ else if (err instanceof ValidationError) {
66
+ error(t('validation', {
67
+ defaultValue: '输入数据格式不正确,请检查后重试',
68
+ }));
69
+ }
70
+ else if (err instanceof AuthenticationError) {
71
+ error(t('authentication', {
72
+ defaultValue: '登录已过期,请重新登录',
73
+ }));
74
+ }
75
+ else if (err instanceof PermissionError) {
76
+ error(t('permission', {
77
+ defaultValue: '您没有权限执行此操作',
78
+ }));
79
+ }
80
+ else if (err instanceof Error) {
81
+ // 普通 Error 对象,显示错误消息
82
+ error(err.message ||
83
+ t('unknown', {
84
+ defaultValue: '操作失败,请稍后重试',
85
+ }));
86
+ }
87
+ else {
88
+ // 未知错误类型
89
+ error(t('unknown', {
90
+ defaultValue: '操作失败,请稍后重试',
91
+ }));
92
+ }
93
+ }, [error, t]);
94
+ return handleError;
95
+ }
96
+ //# sourceMappingURL=useErrorHandler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useErrorHandler.js","sourceRoot":"","sources":["../src/useErrorHandler.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IACrC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,EAAE,KAAK,EAAE,GAAG,oBAAoB,EAAE,CAAC;IACzC,MAAM,CAAC,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAEpC,MAAM,WAAW,GAAG,WAAW,CAC7B,CAAC,GAAY,EAAE,aAAsB,EAAE,EAAE;QACvC,gBAAgB;QAChB,IAAI,aAAa,EAAE,CAAC;YAClB,KAAK,CAAC,aAAa,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,gBAAgB;QAChB,IAAI,GAAG,YAAY,YAAY,EAAE,CAAC;YAChC,KAAK,CACH,CAAC,CAAC,SAAS,EAAE;gBACX,YAAY,EAAE,iBAAiB;aAChC,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;YAC1C,KAAK,CACH,CAAC,CAAC,YAAY,EAAE;gBACd,YAAY,EAAE,kBAAkB;aACjC,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,YAAY,mBAAmB,EAAE,CAAC;YAC9C,KAAK,CACH,CAAC,CAAC,gBAAgB,EAAE;gBAClB,YAAY,EAAE,aAAa;aAC5B,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,YAAY,eAAe,EAAE,CAAC;YAC1C,KAAK,CACH,CAAC,CAAC,YAAY,EAAE;gBACd,YAAY,EAAE,YAAY;aAC3B,CAAC,CACH,CAAC;QACJ,CAAC;aAAM,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YAChC,qBAAqB;YACrB,KAAK,CACH,GAAG,CAAC,OAAO;gBACT,CAAC,CAAC,SAAS,EAAE;oBACX,YAAY,EAAE,YAAY;iBAC3B,CAAC,CACL,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,SAAS;YACT,KAAK,CACH,CAAC,CAAC,SAAS,EAAE;gBACX,YAAY,EAAE,YAAY;aAC3B,CAAC,CACH,CAAC;QACJ,CAAC;IACH,CAAC,EACD,CAAC,KAAK,EAAE,CAAC,CAAC,CACX,CAAC;IAEF,OAAO,WAAW,CAAC;AACrB,CAAC"}