@qhr123/sa2kit 0.1.1
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/LICENSE +22 -0
- package/README.md +113 -0
- package/dist/chunk-3R73CUAY.js +80 -0
- package/dist/chunk-3R73CUAY.js.map +1 -0
- package/dist/chunk-4JTIYLS6.js +88 -0
- package/dist/chunk-4JTIYLS6.js.map +1 -0
- package/dist/chunk-6PRFP5EG.js +171 -0
- package/dist/chunk-6PRFP5EG.js.map +1 -0
- package/dist/chunk-C7VOMO3L.mjs +77 -0
- package/dist/chunk-C7VOMO3L.mjs.map +1 -0
- package/dist/chunk-KQGP6BTS.mjs +165 -0
- package/dist/chunk-KQGP6BTS.mjs.map +1 -0
- package/dist/chunk-M7CA3DTF.mjs +86 -0
- package/dist/chunk-M7CA3DTF.mjs.map +1 -0
- package/dist/chunk-WFWG4UZF.mjs +334 -0
- package/dist/chunk-WFWG4UZF.mjs.map +1 -0
- package/dist/chunk-WO3H4BMN.js +342 -0
- package/dist/chunk-WO3H4BMN.js.map +1 -0
- package/dist/hooks/index.d.mts +30 -0
- package/dist/hooks/index.d.ts +30 -0
- package/dist/hooks/index.js +17 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/index.mjs +4 -0
- package/dist/hooks/index.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +71 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +6 -0
- package/dist/index.mjs.map +1 -0
- package/dist/logger/index.d.mts +125 -0
- package/dist/logger/index.d.ts +125 -0
- package/dist/logger/index.js +28 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/index.mjs +3 -0
- package/dist/logger/index.mjs.map +1 -0
- package/dist/storage/index.d.mts +20 -0
- package/dist/storage/index.d.ts +20 -0
- package/dist/storage/index.js +12 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/index.mjs +3 -0
- package/dist/storage/index.mjs.map +1 -0
- package/dist/types-BaZccpvk.d.mts +48 -0
- package/dist/types-BaZccpvk.d.ts +48 -0
- package/dist/utils/index.d.mts +170 -0
- package/dist/utils/index.d.ts +170 -0
- package/dist/utils/index.js +37 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/index.mjs +4 -0
- package/dist/utils/index.mjs.map +1 -0
- package/package.json +106 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 React Utils Kit Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# React Utils Kit
|
|
2
|
+
|
|
3
|
+
A modern, type-safe React utility library with cross-platform support for building scalable applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🚀 **Modern TypeScript** - Full type safety and IntelliSense support
|
|
8
|
+
- 📦 **Tree-shakeable** - Optimized bundle size with ESM support
|
|
9
|
+
- 🔄 **Cross-platform** - Works in browser and Node.js environments
|
|
10
|
+
- ⚡ **Zero dependencies** - Minimal footprint (React as peer dependency)
|
|
11
|
+
- 🧩 **Modular** - Import only what you need
|
|
12
|
+
- 🎯 **React Hooks** - Custom hooks for common patterns
|
|
13
|
+
- 📝 **Logger System** - Unified logging with multiple adapters
|
|
14
|
+
- 💾 **Storage Adapters** - Universal storage abstraction
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @react-utils-kit/core
|
|
20
|
+
# or
|
|
21
|
+
yarn add @react-utils-kit/core
|
|
22
|
+
# or
|
|
23
|
+
pnpm add @react-utils-kit/core
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Logger
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { logger, createLogger, LogLevel } from '@react-utils-kit/core/logger';
|
|
32
|
+
|
|
33
|
+
// Use default logger
|
|
34
|
+
logger.info('Application started');
|
|
35
|
+
logger.debug('Debug information', { user: 'John' });
|
|
36
|
+
logger.error('Something went wrong', new Error('Error details'));
|
|
37
|
+
|
|
38
|
+
// Create custom logger with context
|
|
39
|
+
const apiLogger = createLogger('API', {
|
|
40
|
+
minLevel: LogLevel.INFO,
|
|
41
|
+
enableTimestamp: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
apiLogger.info('API request completed');
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Utility Functions
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { stringUtils, arrayUtils, fileUtils } from '@react-utils-kit/core/utils';
|
|
51
|
+
|
|
52
|
+
// String utilities
|
|
53
|
+
const capitalized = stringUtils.capitalize('hello world');
|
|
54
|
+
const truncated = stringUtils.truncate('Long text...', 10);
|
|
55
|
+
|
|
56
|
+
// Array utilities
|
|
57
|
+
const unique = arrayUtils.unique([1, 2, 2, 3, 3, 4]);
|
|
58
|
+
const grouped = arrayUtils.groupBy(items, 'category');
|
|
59
|
+
|
|
60
|
+
// File utilities
|
|
61
|
+
const size = fileUtils.formatFileSize(1024000);
|
|
62
|
+
const isValid = fileUtils.isValidFilename('document.pdf');
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### React Hooks
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { useLocalStorage, useAsyncStorage } from '@react-utils-kit/core/hooks';
|
|
69
|
+
|
|
70
|
+
function MyComponent() {
|
|
71
|
+
// Persistent state with localStorage
|
|
72
|
+
const [theme, setTheme] = useLocalStorage('theme', 'light');
|
|
73
|
+
|
|
74
|
+
// Async storage operations
|
|
75
|
+
const { data, loading, error } = useAsyncStorage('user-data');
|
|
76
|
+
|
|
77
|
+
return <div>Theme: {theme}</div>;
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Documentation
|
|
82
|
+
|
|
83
|
+
- [Logger Documentation](./docs/logger.md)
|
|
84
|
+
- [Utility Functions](./docs/utils.md)
|
|
85
|
+
- [React Hooks](./docs/hooks.md)
|
|
86
|
+
- [Storage Adapters](./docs/storage.md)
|
|
87
|
+
|
|
88
|
+
## Examples
|
|
89
|
+
|
|
90
|
+
Check out the [examples](./examples) directory for complete working examples:
|
|
91
|
+
|
|
92
|
+
- React App Example
|
|
93
|
+
- Next.js Integration
|
|
94
|
+
- TypeScript Configuration
|
|
95
|
+
|
|
96
|
+
## API Reference
|
|
97
|
+
|
|
98
|
+
Full API documentation is available at [https://react-utils-kit.dev](https://react-utils-kit.dev)
|
|
99
|
+
|
|
100
|
+
## Contributing
|
|
101
|
+
|
|
102
|
+
We welcome contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for details.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT © [Your Name](LICENSE)
|
|
107
|
+
|
|
108
|
+
## Support
|
|
109
|
+
|
|
110
|
+
- 🐛 [Report a bug](https://github.com/your-org/react-utils-kit/issues)
|
|
111
|
+
- 💡 [Request a feature](https://github.com/your-org/react-utils-kit/issues)
|
|
112
|
+
- 📖 [Documentation](https://react-utils-kit.dev)
|
|
113
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk4JTIYLS6_js = require('./chunk-4JTIYLS6.js');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
|
|
6
|
+
function useStorage(storage, key, defaultValue) {
|
|
7
|
+
const [value, setValue] = react.useState(defaultValue);
|
|
8
|
+
const [loading, setLoading] = react.useState(true);
|
|
9
|
+
react.useEffect(() => {
|
|
10
|
+
const loadValue = async () => {
|
|
11
|
+
try {
|
|
12
|
+
const stored = await storage.getItem(key);
|
|
13
|
+
if (stored !== null) {
|
|
14
|
+
setValue(JSON.parse(stored));
|
|
15
|
+
}
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error(`Error reading storage key "${key}":`, error);
|
|
18
|
+
} finally {
|
|
19
|
+
setLoading(false);
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
void loadValue();
|
|
23
|
+
}, [storage, key]);
|
|
24
|
+
const updateValue = react.useCallback(
|
|
25
|
+
async (newValue) => {
|
|
26
|
+
try {
|
|
27
|
+
setValue(newValue);
|
|
28
|
+
await storage.setItem(key, JSON.stringify(newValue));
|
|
29
|
+
if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
|
|
30
|
+
storage.dispatchChange(key, JSON.stringify(newValue));
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`Error setting storage key "${key}":`, error);
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
[storage, key]
|
|
37
|
+
);
|
|
38
|
+
const removeValue = react.useCallback(async () => {
|
|
39
|
+
try {
|
|
40
|
+
setValue(defaultValue);
|
|
41
|
+
await storage.removeItem(key);
|
|
42
|
+
if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
|
|
43
|
+
storage.dispatchChange(key, null);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error(`Error removing storage key "${key}":`, error);
|
|
47
|
+
}
|
|
48
|
+
}, [storage, key, defaultValue]);
|
|
49
|
+
react.useEffect(() => {
|
|
50
|
+
if (!storage.addChangeListener) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const cleanup = storage.addChangeListener((changedKey, newValue) => {
|
|
54
|
+
if (changedKey === key) {
|
|
55
|
+
try {
|
|
56
|
+
if (newValue === null) {
|
|
57
|
+
setValue(defaultValue);
|
|
58
|
+
} else {
|
|
59
|
+
setValue(JSON.parse(newValue));
|
|
60
|
+
}
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error("Error parsing storage change event:", error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
return cleanup;
|
|
67
|
+
}, [storage, key, defaultValue]);
|
|
68
|
+
return [value, updateValue, removeValue, loading];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/hooks/useLocalStorage.ts
|
|
72
|
+
var webStorage = new chunk4JTIYLS6_js.WebStorageAdapter();
|
|
73
|
+
function useLocalStorage(key, defaultValue) {
|
|
74
|
+
return useStorage(webStorage, key, defaultValue);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.useLocalStorage = useLocalStorage;
|
|
78
|
+
exports.useStorage = useStorage;
|
|
79
|
+
//# sourceMappingURL=chunk-3R73CUAY.js.map
|
|
80
|
+
//# sourceMappingURL=chunk-3R73CUAY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useStorage.ts","../src/hooks/useLocalStorage.ts"],"names":["useState","useEffect","useCallback","WebStorageAdapter"],"mappings":"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","file":"chunk-3R73CUAY.js","sourcesContent":["/**\n * 通用存储 Hook\n * 支持多平台:Web、React Native、小程序\n *\n * 优点:\n * 1. 异步读取,不阻塞渲染\n * 2. 统一的错误处理\n * 3. 类型安全\n * 4. 跨平台支持\n * 5. 自动同步(支持的平台)\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { StorageAdapter } from '../storage/types';\n\nexport function useStorage<T>(\n storage: StorageAdapter,\n key: string,\n defaultValue: T\n): [T, (value: T) => void, () => void, boolean] {\n const [value, setValue] = useState<T>(defaultValue);\n const [loading, setLoading] = useState(true);\n\n // 初始化时从存储读取\n useEffect(() => {\n const loadValue = async () => {\n try {\n const stored = await storage.getItem(key);\n if (stored !== null) {\n setValue(JSON.parse(stored));\n }\n } catch (error) {\n console.error(`Error reading storage key \"${key}\":`, error);\n } finally {\n setLoading(false);\n }\n };\n\n void loadValue();\n }, [storage, key]);\n\n // 更新值并同步到存储\n const updateValue = useCallback(\n async (newValue: T) => {\n try {\n setValue(newValue);\n await storage.setItem(key, JSON.stringify(newValue));\n\n // 如果是 Web 适配器,触发自定义事件\n if ('dispatchChange' in storage && typeof storage.dispatchChange === 'function') {\n (storage as any).dispatchChange(key, JSON.stringify(newValue));\n }\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [storage, key]\n );\n\n // 删除值\n const removeValue = useCallback(async () => {\n try {\n setValue(defaultValue);\n await storage.removeItem(key);\n\n // 如果是 Web 适配器,触发自定义事件\n if ('dispatchChange' in storage && typeof storage.dispatchChange === 'function') {\n (storage as any).dispatchChange(key, null);\n }\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [storage, key, defaultValue]);\n\n // 监听存储变化\n useEffect(() => {\n if (!storage.addChangeListener) {\n return;\n }\n\n const cleanup = storage.addChangeListener((changedKey: string, newValue: string | null) => {\n if (changedKey === key) {\n try {\n if (newValue === null) {\n setValue(defaultValue);\n } else {\n setValue(JSON.parse(newValue));\n }\n } catch (error) {\n console.error('Error parsing storage change event:', error);\n }\n }\n });\n\n return cleanup;\n }, [storage, key, defaultValue]);\n\n return [value, updateValue, removeValue, loading];\n}\n\n","/**\n * Web 平台 localStorage Hook\n * 基于通用 useStorage 的便捷封装\n */\n\nimport { useStorage } from './useStorage';\nimport { WebStorageAdapter } from '../storage';\n\n// 创建单例适配器\nconst webStorage = new WebStorageAdapter();\n\n/**\n * Web 平台的 localStorage Hook\n *\n * @param key - 存储键名\n * @param defaultValue - 默认值\n * @returns [value, setValue, removeValue, isLoading]\n */\nexport function useLocalStorage<T>(\n key: string,\n defaultValue: T\n): [T, (value: T) => void, () => void, boolean] {\n return useStorage(webStorage, key, defaultValue);\n}\n\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/storage/web-adapter.ts
|
|
4
|
+
var isBrowser = typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
5
|
+
var WebStorageAdapter = class {
|
|
6
|
+
async getItem(key) {
|
|
7
|
+
if (!isBrowser) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return localStorage.getItem(key);
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error(`[WebStorage] Error getting item "${key}":`, error);
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
async setItem(key, value) {
|
|
18
|
+
if (!isBrowser) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
localStorage.setItem(key, value);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(`[WebStorage] Error setting item "${key}":`, error);
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async removeItem(key) {
|
|
29
|
+
if (!isBrowser) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
localStorage.removeItem(key);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error(`[WebStorage] Error removing item "${key}":`, error);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async clear() {
|
|
40
|
+
if (!isBrowser) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
localStorage.clear();
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.error("[WebStorage] Error clearing storage:", error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
addChangeListener(callback) {
|
|
51
|
+
if (!isBrowser) {
|
|
52
|
+
return () => {
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const handleStorageEvent = (e) => {
|
|
56
|
+
if (e.key) {
|
|
57
|
+
callback(e.key, e.newValue);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const handleCustomEvent = (e) => {
|
|
61
|
+
const customEvent = e;
|
|
62
|
+
callback(customEvent.detail.key, customEvent.detail.value);
|
|
63
|
+
};
|
|
64
|
+
window.addEventListener("storage", handleStorageEvent);
|
|
65
|
+
window.addEventListener("local-storage-change", handleCustomEvent);
|
|
66
|
+
return () => {
|
|
67
|
+
window.removeEventListener("storage", handleStorageEvent);
|
|
68
|
+
window.removeEventListener("local-storage-change", handleCustomEvent);
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 触发自定义事件,通知同标签页的其他组件
|
|
73
|
+
*/
|
|
74
|
+
dispatchChange(key, value) {
|
|
75
|
+
if (!isBrowser) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
window.dispatchEvent(
|
|
79
|
+
new CustomEvent("local-storage-change", {
|
|
80
|
+
detail: { key, value }
|
|
81
|
+
})
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
exports.WebStorageAdapter = WebStorageAdapter;
|
|
87
|
+
//# sourceMappingURL=chunk-4JTIYLS6.js.map
|
|
88
|
+
//# sourceMappingURL=chunk-4JTIYLS6.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/storage/web-adapter.ts"],"names":[],"mappings":"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","file":"chunk-4JTIYLS6.js","sourcesContent":["/**\n * Web 平台存储适配器 (localStorage)\n */\n\nimport type { StorageAdapter } from './types';\n\n/**\n * 检查是否在浏览器环境中\n */\nconst isBrowser = typeof window !== 'undefined' && typeof window.localStorage !== 'undefined';\n\nexport class WebStorageAdapter implements StorageAdapter {\n async getItem(key: string): Promise<string | null> {\n // SSR 环境下返回 null\n if (!isBrowser) {\n return null;\n }\n\n try {\n return localStorage.getItem(key);\n } catch (error) {\n console.error(`[WebStorage] Error getting item \"${key}\":`, error);\n return null;\n }\n }\n\n async setItem(key: string, value: string): Promise<void> {\n // SSR 环境下静默忽略\n if (!isBrowser) {\n return;\n }\n\n try {\n localStorage.setItem(key, value);\n } catch (error) {\n console.error(`[WebStorage] Error setting item \"${key}\":`, error);\n throw error;\n }\n }\n\n async removeItem(key: string): Promise<void> {\n // SSR 环境下静默忽略\n if (!isBrowser) {\n return;\n }\n\n try {\n localStorage.removeItem(key);\n } catch (error) {\n console.error(`[WebStorage] Error removing item \"${key}\":`, error);\n throw error;\n }\n }\n\n async clear(): Promise<void> {\n // SSR 环境下静默忽略\n if (!isBrowser) {\n return;\n }\n\n try {\n localStorage.clear();\n } catch (error) {\n console.error('[WebStorage] Error clearing storage:', error);\n throw error;\n }\n }\n\n addChangeListener(callback: (key: string, value: string | null) => void): () => void {\n // SSR 环境下返回空函数\n if (!isBrowser) {\n return () => {};\n }\n\n // 监听 storage 事件(跨标签页)\n const handleStorageEvent = (e: StorageEvent) => {\n if (e.key) {\n callback(e.key, e.newValue);\n }\n };\n\n // 监听自定义事件(同标签页)\n const handleCustomEvent = (e: Event) => {\n const customEvent = e as CustomEvent;\n callback(customEvent.detail.key, customEvent.detail.value);\n };\n\n window.addEventListener('storage', handleStorageEvent);\n window.addEventListener('local-storage-change', handleCustomEvent);\n\n // 返回清理函数\n return () => {\n window.removeEventListener('storage', handleStorageEvent);\n window.removeEventListener('local-storage-change', handleCustomEvent);\n };\n }\n\n /**\n * 触发自定义事件,通知同标签页的其他组件\n */\n dispatchChange(key: string, value: string | null): void {\n // SSR 环境下静默忽略\n if (!isBrowser) {\n return;\n }\n\n window.dispatchEvent(\n new CustomEvent('local-storage-change', {\n detail: { key, value },\n })\n );\n }\n}\n\n"]}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/logger/types.ts
|
|
4
|
+
var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
|
|
5
|
+
LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
|
|
6
|
+
LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
|
|
7
|
+
LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
|
|
8
|
+
LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
|
|
9
|
+
LogLevel2[LogLevel2["NONE"] = 4] = "NONE";
|
|
10
|
+
return LogLevel2;
|
|
11
|
+
})(LogLevel || {});
|
|
12
|
+
|
|
13
|
+
// src/logger/console-adapter.ts
|
|
14
|
+
var ConsoleLoggerAdapter = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.colors = {
|
|
17
|
+
DEBUG: "\x1B[36m",
|
|
18
|
+
// Cyan
|
|
19
|
+
INFO: "\x1B[32m",
|
|
20
|
+
// Green
|
|
21
|
+
WARN: "\x1B[33m",
|
|
22
|
+
// Yellow
|
|
23
|
+
ERROR: "\x1B[31m",
|
|
24
|
+
// Red
|
|
25
|
+
RESET: "\x1B[0m"
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
log(entry) {
|
|
29
|
+
const { level, message, timestamp, data, context, error } = entry;
|
|
30
|
+
let logMessage = "";
|
|
31
|
+
if (timestamp) {
|
|
32
|
+
logMessage += `[${this.formatTimestamp(timestamp)}] `;
|
|
33
|
+
}
|
|
34
|
+
const levelName = this.getLevelName(level);
|
|
35
|
+
logMessage += `${levelName}: `;
|
|
36
|
+
if (context) {
|
|
37
|
+
logMessage += `[${context}] `;
|
|
38
|
+
}
|
|
39
|
+
logMessage += message;
|
|
40
|
+
switch (level) {
|
|
41
|
+
case 0:
|
|
42
|
+
console.debug(this.colorize(logMessage, "DEBUG"), data || "");
|
|
43
|
+
break;
|
|
44
|
+
case 1:
|
|
45
|
+
console.info(this.colorize(logMessage, "INFO"), data || "");
|
|
46
|
+
break;
|
|
47
|
+
case 2:
|
|
48
|
+
console.warn(this.colorize(logMessage, "WARN"), data || "");
|
|
49
|
+
break;
|
|
50
|
+
case 3:
|
|
51
|
+
console.error(this.colorize(logMessage, "ERROR"), data || "");
|
|
52
|
+
if (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
formatTimestamp(date) {
|
|
59
|
+
return date.toISOString();
|
|
60
|
+
}
|
|
61
|
+
getLevelName(level) {
|
|
62
|
+
const names = ["DEBUG", "INFO", "WARN", "ERROR", "NONE"];
|
|
63
|
+
return names[level] || "UNKNOWN";
|
|
64
|
+
}
|
|
65
|
+
colorize(message, level) {
|
|
66
|
+
if (typeof process !== "undefined" && process.stdout?.isTTY) {
|
|
67
|
+
return `${this.colors[level]}${message}${this.colors.RESET}`;
|
|
68
|
+
}
|
|
69
|
+
return message;
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/logger/Logger.ts
|
|
74
|
+
var Logger = class _Logger {
|
|
75
|
+
constructor(config, context) {
|
|
76
|
+
const isProduction = typeof process !== "undefined" ? process.env.NODE_ENV === "production" : false;
|
|
77
|
+
this.config = {
|
|
78
|
+
minLevel: config?.minLevel ?? (isProduction ? 1 : 0),
|
|
79
|
+
// INFO in prod, DEBUG in dev
|
|
80
|
+
enableTimestamp: config?.enableTimestamp ?? true,
|
|
81
|
+
enableContext: config?.enableContext ?? true,
|
|
82
|
+
environment: config?.environment ?? (isProduction ? "production" : "development"),
|
|
83
|
+
adapter: config?.adapter ?? new ConsoleLoggerAdapter()
|
|
84
|
+
};
|
|
85
|
+
this.adapter = this.config.adapter;
|
|
86
|
+
this.context = context;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 创建带上下文的子 Logger
|
|
90
|
+
*/
|
|
91
|
+
createChild(context) {
|
|
92
|
+
return new _Logger(this.config, context);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 调试日志
|
|
96
|
+
*/
|
|
97
|
+
debug(message, data) {
|
|
98
|
+
this.log(0, message, data);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 信息日志
|
|
102
|
+
*/
|
|
103
|
+
info(message, data) {
|
|
104
|
+
this.log(1, message, data);
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 警告日志
|
|
108
|
+
*/
|
|
109
|
+
warn(message, data) {
|
|
110
|
+
this.log(2, message, data);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 错误日志
|
|
114
|
+
*/
|
|
115
|
+
error(message, error) {
|
|
116
|
+
this.log(
|
|
117
|
+
3,
|
|
118
|
+
// LogLevel.ERROR
|
|
119
|
+
message,
|
|
120
|
+
error instanceof Error ? void 0 : error,
|
|
121
|
+
error instanceof Error ? error : void 0
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* 核心日志方法
|
|
126
|
+
*/
|
|
127
|
+
log(level, message, data, error) {
|
|
128
|
+
if (level < this.config.minLevel) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
|
|
132
|
+
const loggerDebug = localStorage.getItem("logger-debug");
|
|
133
|
+
if (loggerDebug === "false" && level < 3) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const entry = {
|
|
138
|
+
level,
|
|
139
|
+
message,
|
|
140
|
+
timestamp: this.config.enableTimestamp ? /* @__PURE__ */ new Date() : void 0,
|
|
141
|
+
data,
|
|
142
|
+
context: this.config.enableContext ? this.context : void 0,
|
|
143
|
+
error
|
|
144
|
+
};
|
|
145
|
+
this.adapter.log(entry);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 设置日志级别
|
|
149
|
+
*/
|
|
150
|
+
setLevel(level) {
|
|
151
|
+
this.config.minLevel = level;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 获取当前日志级别
|
|
155
|
+
*/
|
|
156
|
+
getLevel() {
|
|
157
|
+
return this.config.minLevel;
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
var logger = new Logger();
|
|
161
|
+
function createLogger(context, config) {
|
|
162
|
+
return new Logger(config, context);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
exports.ConsoleLoggerAdapter = ConsoleLoggerAdapter;
|
|
166
|
+
exports.LogLevel = LogLevel;
|
|
167
|
+
exports.Logger = Logger;
|
|
168
|
+
exports.createLogger = createLogger;
|
|
169
|
+
exports.logger = logger;
|
|
170
|
+
//# sourceMappingURL=chunk-6PRFP5EG.js.map
|
|
171
|
+
//# sourceMappingURL=chunk-6PRFP5EG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/logger/types.ts","../src/logger/console-adapter.ts","../src/logger/Logger.ts"],"names":["LogLevel"],"mappings":";;;AAGO,IAAK,QAAA,qBAAAA,SAAAA,KAAL;AACL,EAAAA,SAAAA,CAAAA,SAAAA,CAAA,WAAQ,CAAA,CAAA,GAAR,OAAA;AACA,EAAAA,SAAAA,CAAAA,SAAAA,CAAA,UAAO,CAAA,CAAA,GAAP,MAAA;AACA,EAAAA,SAAAA,CAAAA,SAAAA,CAAA,UAAO,CAAA,CAAA,GAAP,MAAA;AACA,EAAAA,SAAAA,CAAAA,SAAAA,CAAA,WAAQ,CAAA,CAAA,GAAR,OAAA;AACA,EAAAA,SAAAA,CAAAA,SAAAA,CAAA,UAAO,CAAA,CAAA,GAAP,MAAA;AALU,EAAA,OAAAA,SAAAA;AAAA,CAAA,EAAA,QAAA,IAAA,EAAA;;;ACGL,IAAM,uBAAN,MAAoD;AAAA,EAApD,WAAA,GAAA;AACL,IAAA,IAAA,CAAiB,MAAA,GAAS;AAAA,MACxB,KAAA,EAAO,UAAA;AAAA;AAAA,MACP,IAAA,EAAM,UAAA;AAAA;AAAA,MACN,IAAA,EAAM,UAAA;AAAA;AAAA,MACN,KAAA,EAAO,UAAA;AAAA;AAAA,MACP,KAAA,EAAO;AAAA,KACT;AAAA,EAAA;AAAA,EAEA,IAAI,KAAA,EAAuB;AACzB,IAAA,MAAM,EAAE,KAAA,EAAO,OAAA,EAAS,WAAW,IAAA,EAAM,OAAA,EAAS,OAAM,GAAI,KAAA;AAG5D,IAAA,IAAI,UAAA,GAAa,EAAA;AAGjB,IAAA,IAAI,SAAA,EAAW;AACb,MAAA,UAAA,IAAc,CAAA,CAAA,EAAI,IAAA,CAAK,eAAA,CAAgB,SAAS,CAAC,CAAA,EAAA,CAAA;AAAA,IACnD;AAGA,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,YAAA,CAAa,KAAK,CAAA;AACzC,IAAA,UAAA,IAAc,GAAG,SAAS,CAAA,EAAA,CAAA;AAG1B,IAAA,IAAI,OAAA,EAAS;AACX,MAAA,UAAA,IAAc,IAAI,OAAO,CAAA,EAAA,CAAA;AAAA,IAC3B;AAGA,IAAA,UAAA,IAAc,OAAA;AAGd,IAAA,QAAQ,KAAA;AAAO,MACb,KAAK,CAAA;AACH,QAAA,OAAA,CAAQ,MAAM,IAAA,CAAK,QAAA,CAAS,YAAY,OAAO,CAAA,EAAG,QAAQ,EAAE,CAAA;AAC5D,QAAA;AAAA,MACF,KAAK,CAAA;AACH,QAAA,OAAA,CAAQ,KAAK,IAAA,CAAK,QAAA,CAAS,YAAY,MAAM,CAAA,EAAG,QAAQ,EAAE,CAAA;AAC1D,QAAA;AAAA,MACF,KAAK,CAAA;AACH,QAAA,OAAA,CAAQ,KAAK,IAAA,CAAK,QAAA,CAAS,YAAY,MAAM,CAAA,EAAG,QAAQ,EAAE,CAAA;AAC1D,QAAA;AAAA,MACF,KAAK,CAAA;AACH,QAAA,OAAA,CAAQ,MAAM,IAAA,CAAK,QAAA,CAAS,YAAY,OAAO,CAAA,EAAG,QAAQ,EAAE,CAAA;AAC5D,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,MAAM,KAAK,CAAA;AAAA,QACrB;AACA,QAAA;AAAA;AACJ,EACF;AAAA,EAEQ,gBAAgB,IAAA,EAAoB;AAC1C,IAAA,OAAO,KAAK,WAAA,EAAY;AAAA,EAC1B;AAAA,EAEQ,aAAa,KAAA,EAAyB;AAC5C,IAAA,MAAM,QAAQ,CAAC,OAAA,EAAS,MAAA,EAAQ,MAAA,EAAQ,SAAS,MAAM,CAAA;AACvD,IAAA,OAAO,KAAA,CAAM,KAAK,CAAA,IAAK,SAAA;AAAA,EACzB;AAAA,EAEQ,QAAA,CAAS,SAAiB,KAAA,EAAyC;AAEzE,IAAA,IAAI,OAAO,OAAA,KAAY,WAAA,IAAe,OAAA,CAAQ,QAAQ,KAAA,EAAO;AAC3D,MAAA,OAAO,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,KAAK,CAAC,GAAG,OAAO,CAAA,EAAG,IAAA,CAAK,MAAA,CAAO,KAAK,CAAA,CAAA;AAAA,IAC5D;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AACF;;;ACpEO,IAAM,MAAA,GAAN,MAAM,OAAA,CAAO;AAAA,EAKlB,WAAA,CAAY,QAAgC,OAAA,EAAkB;AAC5D,IAAA,MAAM,eACJ,OAAO,OAAA,KAAY,cAAc,OAAA,CAAQ,GAAA,CAAI,aAAa,YAAA,GAAe,KAAA;AAE3E,IAAA,IAAA,CAAK,MAAA,GAAS;AAAA,MACZ,QAAA,EAAU,MAAA,EAAQ,QAAA,KAAa,YAAA,GAAe,CAAA,GAAI,CAAA,CAAA;AAAA;AAAA,MAClD,eAAA,EAAiB,QAAQ,eAAA,IAAmB,IAAA;AAAA,MAC5C,aAAA,EAAe,QAAQ,aAAA,IAAiB,IAAA;AAAA,MACxC,WAAA,EAAa,MAAA,EAAQ,WAAA,KAAgB,YAAA,GAAe,YAAA,GAAe,aAAA,CAAA;AAAA,MACnE,OAAA,EAAS,MAAA,EAAQ,OAAA,IAAW,IAAI,oBAAA;AAAqB,KACvD;AACA,IAAA,IAAA,CAAK,OAAA,GAAU,KAAK,MAAA,CAAO,OAAA;AAC3B,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,OAAA,EAAyB;AACnC,IAAA,OAAO,IAAI,OAAA,CAAO,IAAA,CAAK,MAAA,EAAQ,OAAO,CAAA;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,CAAM,SAAiB,IAAA,EAAkB;AACvC,IAAA,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,EAAS,IAAI,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,CAAK,SAAiB,IAAA,EAAkB;AACtC,IAAA,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,EAAS,IAAI,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAA,CAAK,SAAiB,IAAA,EAAkB;AACtC,IAAA,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,OAAA,EAAS,IAAI,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,CAAM,SAAiB,KAAA,EAA2B;AAChD,IAAA,IAAA,CAAK,GAAA;AAAA,MACH,CAAA;AAAA;AAAA,MACA,OAAA;AAAA,MACA,KAAA,YAAiB,QAAQ,MAAA,GAAY,KAAA;AAAA,MACrC,KAAA,YAAiB,QAAQ,KAAA,GAAQ;AAAA,KACnC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKQ,GAAA,CAAI,KAAA,EAAiB,OAAA,EAAiB,IAAA,EAAY,KAAA,EAAqB;AAE7E,IAAA,IAAI,KAAA,GAAQ,IAAA,CAAK,MAAA,CAAO,QAAA,EAAU;AAChC,MAAA;AAAA,IACF;AAGA,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,iBAAiB,WAAA,EAAa;AACxE,MAAA,MAAM,WAAA,GAAc,YAAA,CAAa,OAAA,CAAQ,cAAc,CAAA;AAEvD,MAAA,IAAI,WAAA,KAAgB,OAAA,IAAW,KAAA,GAAQ,CAAA,EAAG;AAExC,QAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAkB;AAAA,MACtB,KAAA;AAAA,MACA,OAAA;AAAA,MACA,WAAW,IAAA,CAAK,MAAA,CAAO,eAAA,mBAAkB,IAAI,MAAK,GAAK,MAAA;AAAA,MACvD,IAAA;AAAA,MACA,OAAA,EAAS,IAAA,CAAK,MAAA,CAAO,aAAA,GAAgB,KAAK,OAAA,GAAU,MAAA;AAAA,MACpD;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,OAAA,CAAQ,IAAI,KAAK,CAAA;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAA,EAAuB;AAC9B,IAAA,IAAA,CAAK,OAAO,QAAA,GAAW,KAAA;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAA,GAAqB;AACnB,IAAA,OAAO,KAAK,MAAA,CAAO,QAAA;AAAA,EACrB;AACF;AAKO,IAAM,MAAA,GAAS,IAAI,MAAA;AAKnB,SAAS,YAAA,CAAa,SAAiB,MAAA,EAAwC;AACpF,EAAA,OAAO,IAAI,MAAA,CAAO,MAAA,EAAQ,OAAO,CAAA;AACnC","file":"chunk-6PRFP5EG.js","sourcesContent":["/**\n * 日志级别\n */\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4, // 禁用所有日志\n}\n\n/**\n * 日志条目\n */\nexport interface LogEntry {\n level: LogLevel;\n message: string;\n timestamp: Date;\n data?: any;\n context?: string;\n error?: Error;\n}\n\n/**\n * 日志适配器接口\n * 不同平台实现不同的日志输出方式\n */\nexport interface LoggerAdapter {\n /**\n * 输出日志\n */\n log(entry: LogEntry): void;\n\n /**\n * 批量输出日志(可选)\n */\n logBatch?(entries: LogEntry[]): void;\n}\n\n/**\n * 日志配置\n */\nexport interface LoggerConfig {\n /**\n * 最小日志级别\n * 只有大于等于此级别的日志才会输出\n */\n minLevel: LogLevel;\n\n /**\n * 是否启用时间戳\n */\n enableTimestamp?: boolean;\n\n /**\n * 是否启用上下文(模块名)\n */\n enableContext?: boolean;\n\n /**\n * 环境(development/production)\n */\n environment?: 'development' | 'production';\n\n /**\n * 自定义适配器\n */\n adapter?: LoggerAdapter;\n}\n\n","import type { LoggerAdapter, LogEntry, LogLevel } from './types';\n\n/**\n * 控制台日志适配器\n * 使用 console.info/warn/error 输出日志\n */\nexport class ConsoleLoggerAdapter implements LoggerAdapter {\n private readonly colors = {\n DEBUG: '\\x1b[36m', // Cyan\n INFO: '\\x1b[32m', // Green\n WARN: '\\x1b[33m', // Yellow\n ERROR: '\\x1b[31m', // Red\n RESET: '\\x1b[0m',\n };\n\n log(entry: LogEntry): void {\n const { level, message, timestamp, data, context, error } = entry;\n\n // 构建日志消息\n let logMessage = '';\n\n // 添加时间戳\n if (timestamp) {\n logMessage += `[${this.formatTimestamp(timestamp)}] `;\n }\n\n // 添加日志级别\n const levelName = this.getLevelName(level);\n logMessage += `${levelName}: `;\n\n // 添加上下文\n if (context) {\n logMessage += `[${context}] `;\n }\n\n // 添加消息\n logMessage += message;\n\n // 根据日志级别选择输出方式\n switch (level) {\n case 0: // DEBUG\n console.debug(this.colorize(logMessage, 'DEBUG'), data || '');\n break;\n case 1: // INFO\n console.info(this.colorize(logMessage, 'INFO'), data || '');\n break;\n case 2: // WARN\n console.warn(this.colorize(logMessage, 'WARN'), data || '');\n break;\n case 3: // ERROR\n console.error(this.colorize(logMessage, 'ERROR'), data || '');\n if (error) {\n console.error(error);\n }\n break;\n }\n }\n\n private formatTimestamp(date: Date): string {\n return date.toISOString();\n }\n\n private getLevelName(level: LogLevel): string {\n const names = ['DEBUG', 'INFO', 'WARN', 'ERROR', 'NONE'];\n return names[level] || 'UNKNOWN';\n }\n\n private colorize(message: string, level: keyof typeof this.colors): string {\n // 只在支持颜色的环境中使用颜色\n if (typeof process !== 'undefined' && process.stdout?.isTTY) {\n return `${this.colors[level]}${message}${this.colors.RESET}`;\n }\n return message;\n }\n}\n\n","import type { LogLevel, LogEntry, LoggerConfig, LoggerAdapter } from './types';\nimport { ConsoleLoggerAdapter } from './console-adapter';\n\n/**\n * 统一日志管理类\n */\nexport class Logger {\n private config: Required<LoggerConfig>;\n private adapter: LoggerAdapter;\n private context?: string;\n\n constructor(config?: Partial<LoggerConfig>, context?: string) {\n const isProduction =\n typeof process !== 'undefined' ? process.env.NODE_ENV === 'production' : false;\n\n this.config = {\n minLevel: config?.minLevel ?? (isProduction ? 1 : 0), // INFO in prod, DEBUG in dev\n enableTimestamp: config?.enableTimestamp ?? true,\n enableContext: config?.enableContext ?? true,\n environment: config?.environment ?? (isProduction ? 'production' : 'development'),\n adapter: config?.adapter ?? new ConsoleLoggerAdapter(),\n };\n this.adapter = this.config.adapter;\n this.context = context;\n }\n\n /**\n * 创建带上下文的子 Logger\n */\n createChild(context: string): Logger {\n return new Logger(this.config, context);\n }\n\n /**\n * 调试日志\n */\n debug(message: string, data?: any): void {\n this.log(0, message, data); // LogLevel.DEBUG\n }\n\n /**\n * 信息日志\n */\n info(message: string, data?: any): void {\n this.log(1, message, data); // LogLevel.INFO\n }\n\n /**\n * 警告日志\n */\n warn(message: string, data?: any): void {\n this.log(2, message, data); // LogLevel.WARN\n }\n\n /**\n * 错误日志\n */\n error(message: string, error?: Error | any): void {\n this.log(\n 3, // LogLevel.ERROR\n message,\n error instanceof Error ? undefined : error,\n error instanceof Error ? error : undefined\n );\n }\n\n /**\n * 核心日志方法\n */\n private log(level: LogLevel, message: string, data?: any, error?: Error): void {\n // 检查日志级别\n if (level < this.config.minLevel) {\n return;\n }\n\n // 检查动态调试配置(仅在浏览器环境)\n if (typeof window !== 'undefined' && typeof localStorage !== 'undefined') {\n const loggerDebug = localStorage.getItem('logger-debug');\n // 如果明确设置为 false,则不输出(但 Error 级别始终输出)\n if (loggerDebug === 'false' && level < 3) {\n // level < ERROR\n return;\n }\n }\n\n const entry: LogEntry = {\n level,\n message,\n timestamp: this.config.enableTimestamp ? new Date() : (undefined as any),\n data,\n context: this.config.enableContext ? this.context : undefined,\n error,\n };\n\n this.adapter.log(entry);\n }\n\n /**\n * 设置日志级别\n */\n setLevel(level: LogLevel): void {\n this.config.minLevel = level;\n }\n\n /**\n * 获取当前日志级别\n */\n getLevel(): LogLevel {\n return this.config.minLevel;\n }\n}\n\n/**\n * 默认全局 Logger 实例\n */\nexport const logger = new Logger();\n\n/**\n * 创建带上下文的 Logger\n */\nexport function createLogger(context: string, config?: Partial<LoggerConfig>): Logger {\n return new Logger(config, context);\n}\n\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { WebStorageAdapter } from './chunk-M7CA3DTF.mjs';
|
|
2
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
function useStorage(storage, key, defaultValue) {
|
|
5
|
+
const [value, setValue] = useState(defaultValue);
|
|
6
|
+
const [loading, setLoading] = useState(true);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const loadValue = async () => {
|
|
9
|
+
try {
|
|
10
|
+
const stored = await storage.getItem(key);
|
|
11
|
+
if (stored !== null) {
|
|
12
|
+
setValue(JSON.parse(stored));
|
|
13
|
+
}
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Error reading storage key "${key}":`, error);
|
|
16
|
+
} finally {
|
|
17
|
+
setLoading(false);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
void loadValue();
|
|
21
|
+
}, [storage, key]);
|
|
22
|
+
const updateValue = useCallback(
|
|
23
|
+
async (newValue) => {
|
|
24
|
+
try {
|
|
25
|
+
setValue(newValue);
|
|
26
|
+
await storage.setItem(key, JSON.stringify(newValue));
|
|
27
|
+
if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
|
|
28
|
+
storage.dispatchChange(key, JSON.stringify(newValue));
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`Error setting storage key "${key}":`, error);
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
[storage, key]
|
|
35
|
+
);
|
|
36
|
+
const removeValue = useCallback(async () => {
|
|
37
|
+
try {
|
|
38
|
+
setValue(defaultValue);
|
|
39
|
+
await storage.removeItem(key);
|
|
40
|
+
if ("dispatchChange" in storage && typeof storage.dispatchChange === "function") {
|
|
41
|
+
storage.dispatchChange(key, null);
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.error(`Error removing storage key "${key}":`, error);
|
|
45
|
+
}
|
|
46
|
+
}, [storage, key, defaultValue]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (!storage.addChangeListener) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const cleanup = storage.addChangeListener((changedKey, newValue) => {
|
|
52
|
+
if (changedKey === key) {
|
|
53
|
+
try {
|
|
54
|
+
if (newValue === null) {
|
|
55
|
+
setValue(defaultValue);
|
|
56
|
+
} else {
|
|
57
|
+
setValue(JSON.parse(newValue));
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error("Error parsing storage change event:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
return cleanup;
|
|
65
|
+
}, [storage, key, defaultValue]);
|
|
66
|
+
return [value, updateValue, removeValue, loading];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/hooks/useLocalStorage.ts
|
|
70
|
+
var webStorage = new WebStorageAdapter();
|
|
71
|
+
function useLocalStorage(key, defaultValue) {
|
|
72
|
+
return useStorage(webStorage, key, defaultValue);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { useLocalStorage, useStorage };
|
|
76
|
+
//# sourceMappingURL=chunk-C7VOMO3L.mjs.map
|
|
77
|
+
//# sourceMappingURL=chunk-C7VOMO3L.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/useStorage.ts","../src/hooks/useLocalStorage.ts"],"names":[],"mappings":"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","file":"chunk-C7VOMO3L.mjs","sourcesContent":["/**\n * 通用存储 Hook\n * 支持多平台:Web、React Native、小程序\n *\n * 优点:\n * 1. 异步读取,不阻塞渲染\n * 2. 统一的错误处理\n * 3. 类型安全\n * 4. 跨平台支持\n * 5. 自动同步(支持的平台)\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { StorageAdapter } from '../storage/types';\n\nexport function useStorage<T>(\n storage: StorageAdapter,\n key: string,\n defaultValue: T\n): [T, (value: T) => void, () => void, boolean] {\n const [value, setValue] = useState<T>(defaultValue);\n const [loading, setLoading] = useState(true);\n\n // 初始化时从存储读取\n useEffect(() => {\n const loadValue = async () => {\n try {\n const stored = await storage.getItem(key);\n if (stored !== null) {\n setValue(JSON.parse(stored));\n }\n } catch (error) {\n console.error(`Error reading storage key \"${key}\":`, error);\n } finally {\n setLoading(false);\n }\n };\n\n void loadValue();\n }, [storage, key]);\n\n // 更新值并同步到存储\n const updateValue = useCallback(\n async (newValue: T) => {\n try {\n setValue(newValue);\n await storage.setItem(key, JSON.stringify(newValue));\n\n // 如果是 Web 适配器,触发自定义事件\n if ('dispatchChange' in storage && typeof storage.dispatchChange === 'function') {\n (storage as any).dispatchChange(key, JSON.stringify(newValue));\n }\n } catch (error) {\n console.error(`Error setting storage key \"${key}\":`, error);\n }\n },\n [storage, key]\n );\n\n // 删除值\n const removeValue = useCallback(async () => {\n try {\n setValue(defaultValue);\n await storage.removeItem(key);\n\n // 如果是 Web 适配器,触发自定义事件\n if ('dispatchChange' in storage && typeof storage.dispatchChange === 'function') {\n (storage as any).dispatchChange(key, null);\n }\n } catch (error) {\n console.error(`Error removing storage key \"${key}\":`, error);\n }\n }, [storage, key, defaultValue]);\n\n // 监听存储变化\n useEffect(() => {\n if (!storage.addChangeListener) {\n return;\n }\n\n const cleanup = storage.addChangeListener((changedKey: string, newValue: string | null) => {\n if (changedKey === key) {\n try {\n if (newValue === null) {\n setValue(defaultValue);\n } else {\n setValue(JSON.parse(newValue));\n }\n } catch (error) {\n console.error('Error parsing storage change event:', error);\n }\n }\n });\n\n return cleanup;\n }, [storage, key, defaultValue]);\n\n return [value, updateValue, removeValue, loading];\n}\n\n","/**\n * Web 平台 localStorage Hook\n * 基于通用 useStorage 的便捷封装\n */\n\nimport { useStorage } from './useStorage';\nimport { WebStorageAdapter } from '../storage';\n\n// 创建单例适配器\nconst webStorage = new WebStorageAdapter();\n\n/**\n * Web 平台的 localStorage Hook\n *\n * @param key - 存储键名\n * @param defaultValue - 默认值\n * @returns [value, setValue, removeValue, isLoading]\n */\nexport function useLocalStorage<T>(\n key: string,\n defaultValue: T\n): [T, (value: T) => void, () => void, boolean] {\n return useStorage(webStorage, key, defaultValue);\n}\n\n"]}
|