@melt-js/libs-vben 0.0.0-stage → 2026.11.8-2
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 +10 -0
- package/README.md +86 -2
- package/package.json +98 -3
- package/src/access/access-control.vue +47 -0
- package/src/access/accessible.ts +236 -0
- package/src/access/directive.ts +42 -0
- package/src/access/index.ts +4 -0
- package/src/access/use-access.ts +53 -0
- package/src/composables/index.ts +14 -0
- package/src/composables/use-is-mobile.ts +7 -0
- package/src/composables/use-layout-style.ts +51 -0
- package/src/composables/use-layout-viewport-height.ts +71 -0
- package/src/composables/use-namespace.ts +106 -0
- package/src/composables/use-priority-value.ts +96 -0
- package/src/composables/use-scroll-lock.ts +151 -0
- package/src/composables/use-simple-locale/README.md +3 -0
- package/src/composables/use-simple-locale/index.ts +27 -0
- package/src/composables/use-simple-locale/messages.ts +31 -0
- package/src/composables/use-sortable.ts +32 -0
- package/src/constants/core.ts +76 -0
- package/src/constants/index.ts +2 -0
- package/src/core-icons/create-icon.ts +14 -0
- package/src/core-icons/index.ts +11 -0
- package/src/core-icons/lucide.ts +95 -0
- package/src/core-preferences/config.ts +172 -0
- package/src/core-preferences/constants.ts +116 -0
- package/src/core-preferences/index.ts +24 -0
- package/src/core-preferences/preferences.ts +503 -0
- package/src/core-preferences/types.ts +471 -0
- package/src/core-preferences/update-css-variables.ts +129 -0
- package/src/core-preferences/use-preferences.ts +282 -0
- package/src/design/css/global.css +1 -0
- package/src/design/css/nprogress.css +61 -0
- package/src/design/css/transition.css +238 -0
- package/src/design/css/ui.css +101 -0
- package/src/design/design-tokens/dark.css +446 -0
- package/src/design/design-tokens/default.css +491 -0
- package/src/design/design-tokens/index.ts +2 -0
- package/src/design/index.ts +6 -0
- package/src/design/scss-bem/bem.scss +34 -0
- package/src/design/scss-bem/constants.scss +5 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/use-app-config.ts +39 -0
- package/src/hooks/use-content-maximize.ts +24 -0
- package/src/hooks/use-design-tokens.ts +421 -0
- package/src/hooks/use-hover-toggle.ts +163 -0
- package/src/hooks/use-pagination.ts +72 -0
- package/src/hooks/use-refresh.ts +16 -0
- package/src/hooks/use-tabs.ts +143 -0
- package/src/hooks/use-watermark.ts +84 -0
- package/src/icons/iconify/index.ts +5 -0
- package/src/icons/icons/empty-icon.vue +27 -0
- package/src/icons/index.ts +3 -0
- package/src/icons/svg/icons/antdv-logo.svg +29 -0
- package/src/icons/svg/icons/antdv-next-logo.svg +1 -0
- package/src/icons/svg/icons/avatar-1.svg +1 -0
- package/src/icons/svg/icons/avatar-2.svg +1 -0
- package/src/icons/svg/icons/avatar-3.svg +1 -0
- package/src/icons/svg/icons/avatar-4.svg +1 -0
- package/src/icons/svg/icons/bell.svg +1 -0
- package/src/icons/svg/icons/cake.svg +1 -0
- package/src/icons/svg/icons/card.svg +1 -0
- package/src/icons/svg/icons/dingding.svg +1 -0
- package/src/icons/svg/icons/download.svg +1 -0
- package/src/icons/svg/icons/github.svg +1 -0
- package/src/icons/svg/icons/google.svg +1 -0
- package/src/icons/svg/icons/qqchat.svg +1 -0
- package/src/icons/svg/icons/tdesign-logo.svg +39 -0
- package/src/icons/svg/icons/wechat.svg +1 -0
- package/src/icons/svg/index.ts +39 -0
- package/src/icons/svg/load.ts +81 -0
- package/src/locales/i18n.ts +147 -0
- package/src/locales/index.ts +30 -0
- package/src/locales/langs/en-US/authentication.json +65 -0
- package/src/locales/langs/en-US/common.json +27 -0
- package/src/locales/langs/en-US/preferences.json +245 -0
- package/src/locales/langs/en-US/profile.json +4 -0
- package/src/locales/langs/en-US/ui.json +165 -0
- package/src/locales/langs/zh-CN/authentication.json +65 -0
- package/src/locales/langs/zh-CN/common.json +27 -0
- package/src/locales/langs/zh-CN/preferences.json +245 -0
- package/src/locales/langs/zh-CN/profile.json +4 -0
- package/src/locales/langs/zh-CN/ui.json +165 -0
- package/src/locales/langs/zh-TW/authentication.json +65 -0
- package/src/locales/langs/zh-TW/common.json +27 -0
- package/src/locales/langs/zh-TW/preferences.json +245 -0
- package/src/locales/langs/zh-TW/profile.json +4 -0
- package/src/locales/langs/zh-TW/ui.json +165 -0
- package/src/locales/typing.ts +27 -0
- package/src/preferences/index.ts +42 -0
- package/src/request/index.ts +2 -0
- package/src/request/request-client/index.ts +3 -0
- package/src/request/request-client/modules/downloader.ts +60 -0
- package/src/request/request-client/modules/interceptor.ts +40 -0
- package/src/request/request-client/modules/sse.ts +134 -0
- package/src/request/request-client/modules/uploader.ts +79 -0
- package/src/request/request-client/preset-interceptors.ts +178 -0
- package/src/request/request-client/request-client.ts +163 -0
- package/src/request/request-client/types.ts +90 -0
- package/src/shared/cache/README.md +418 -0
- package/src/shared/cache/index.ts +5 -0
- package/src/shared/cache/indexeddb-driver.ts +137 -0
- package/src/shared/cache/local-storage-driver.ts +70 -0
- package/src/shared/cache/memory-storage-driver.ts +32 -0
- package/src/shared/cache/storage-manager.ts +146 -0
- package/src/shared/cache/types.ts +39 -0
- package/src/shared/color/color.ts +9 -0
- package/src/shared/color/convert.ts +62 -0
- package/src/shared/color/generator.ts +45 -0
- package/src/shared/color/index.ts +3 -0
- package/src/shared/constants/globals.ts +20 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/constants/vben.ts +30 -0
- package/src/shared/global-state.ts +45 -0
- package/src/shared/store.ts +1 -0
- package/src/shared/utils/cn.ts +10 -0
- package/src/shared/utils/date.ts +80 -0
- package/src/shared/utils/diff.ts +114 -0
- package/src/shared/utils/dom.ts +126 -0
- package/src/shared/utils/download.ts +186 -0
- package/src/shared/utils/error.ts +19 -0
- package/src/shared/utils/index.ts +22 -0
- package/src/shared/utils/inference.ts +164 -0
- package/src/shared/utils/letter.ts +47 -0
- package/src/shared/utils/merge.ts +11 -0
- package/src/shared/utils/nprogress.ts +52 -0
- package/src/shared/utils/resources.ts +21 -0
- package/src/shared/utils/stack.ts +103 -0
- package/src/shared/utils/state-handler.ts +61 -0
- package/src/shared/utils/to.ts +21 -0
- package/src/shared/utils/tree.ts +159 -0
- package/src/shared/utils/unique.ts +15 -0
- package/src/shared/utils/update-css-variables.ts +47 -0
- package/src/shared/utils/util.ts +50 -0
- package/src/shared/utils/window.ts +37 -0
- package/src/stores/index.ts +3 -0
- package/src/stores/modules/access.ts +130 -0
- package/src/stores/modules/dict.ts +244 -0
- package/src/stores/modules/index.ts +5 -0
- package/src/stores/modules/tabbar.ts +797 -0
- package/src/stores/modules/timezone.ts +132 -0
- package/src/stores/modules/user.ts +42 -0
- package/src/stores/setup.ts +79 -0
- package/src/stores/shim-pinia.d.ts +11 -0
- package/src/styles/antd/index.css +78 -0
- package/src/styles/antdv-next/index.css +90 -0
- package/src/styles/ele/index.css +44 -0
- package/src/styles/global/index.scss +1 -0
- package/src/styles/index.ts +1 -0
- package/src/styles/naive/index.css +26 -0
- package/src/styles/style-exports.d.ts +3 -0
- package/src/theme.css +592 -0
- package/src/types/global.d.ts +28 -0
- package/src/types/index.ts +2 -0
- package/src/types/shims.d.ts +1 -0
- package/src/types/user.ts +20 -0
- package/src/typings/app.d.ts +128 -0
- package/src/typings/basic.d.ts +42 -0
- package/src/typings/helper.d.ts +147 -0
- package/src/typings/index.ts +16 -0
- package/src/typings/languages.ts +28 -0
- package/src/typings/menu-record.ts +82 -0
- package/src/typings/tabs.ts +8 -0
- package/src/typings/vue-router.d.ts +169 -0
- package/src/utils/helpers/find-menu-by-path.ts +37 -0
- package/src/utils/helpers/generate-menus.ts +89 -0
- package/src/utils/helpers/generate-routes-backend.ts +108 -0
- package/src/utils/helpers/generate-routes-frontend.ts +58 -0
- package/src/utils/helpers/get-popup-container.ts +10 -0
- package/src/utils/helpers/index.ts +8 -0
- package/src/utils/helpers/merge-route-modules.ts +28 -0
- package/src/utils/helpers/reset-routes.ts +30 -0
- package/src/utils/helpers/unmount-global-loading.ts +31 -0
- package/src/utils/index.ts +4 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# Cache 模块
|
|
2
|
+
|
|
3
|
+
基于**策略模式**的异步存储管理方案,支持多种存储后端(localStorage、IndexedDB、Memory),提供统一的 API 接口。
|
|
4
|
+
|
|
5
|
+
## 架构设计
|
|
6
|
+
|
|
7
|
+
```shell
|
|
8
|
+
┌───────────────────────────────────────────────┐
|
|
9
|
+
│ StorageManager │
|
|
10
|
+
│ ┌─────────────┐ ┌───────────────────────┐ │
|
|
11
|
+
│ │ Prefix 隔离 │ │ TTL 过期管理 │ │
|
|
12
|
+
│ └─────────────┘ └───────────────────────┘ │
|
|
13
|
+
├───────────────────────────────────────────────┤
|
|
14
|
+
│ IStorageDriver │
|
|
15
|
+
├──────────┬─────────────────┬──────────────────┤
|
|
16
|
+
│ Local │ IndexedDB │ Memory │
|
|
17
|
+
│ Storage │ Driver │ Driver │
|
|
18
|
+
│ Driver │ │ │
|
|
19
|
+
└──────────┴─────────────────┴──────────────────┘
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**分层职责:**
|
|
23
|
+
|
|
24
|
+
| 层级 | 职责 |
|
|
25
|
+
| ---------------- | -------------------------------------------- |
|
|
26
|
+
| `StorageManager` | 命名空间前缀隔离、TTL 过期检查、统一对外 API |
|
|
27
|
+
| `IStorageDriver` | 纯粹的 KV 存取抽象接口 |
|
|
28
|
+
| 各 Driver 实现 | 对接具体存储引擎,不感知前缀和 TTL |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 快速开始
|
|
33
|
+
|
|
34
|
+
### 基本使用(默认 localStorage)
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { StorageManager } from '@vben-core/shared/cache';
|
|
38
|
+
|
|
39
|
+
const cache = new StorageManager({ prefix: 'myapp' });
|
|
40
|
+
// 使用 IndexedDB
|
|
41
|
+
//new StorageManager({ driver: new IndexedDBDriver(), prefix: 'app' });
|
|
42
|
+
|
|
43
|
+
// 使用 sessionStorage
|
|
44
|
+
//new StorageManager({ driver: new LocalStorageDriver({ storageType: 'sessionStorage' }), prefix: 'app' });
|
|
45
|
+
|
|
46
|
+
// 测试环境
|
|
47
|
+
//new StorageManager({ driver: new MemoryStorageDriver(), prefix: 'test' });
|
|
48
|
+
|
|
49
|
+
// 存储数据
|
|
50
|
+
await cache.setItem('user', { name: '张三', age: 28 });
|
|
51
|
+
|
|
52
|
+
// 读取数据
|
|
53
|
+
const user = await cache.getItem('user');
|
|
54
|
+
// => { name: '张三', age: 28 }
|
|
55
|
+
|
|
56
|
+
// 带默认值读取
|
|
57
|
+
const settings = await cache.getItem('settings', { theme: 'light' });
|
|
58
|
+
// 如果不存在,返回 { theme: 'light' }
|
|
59
|
+
|
|
60
|
+
// 删除数据
|
|
61
|
+
await cache.removeItem('user');
|
|
62
|
+
|
|
63
|
+
// 清除当前前缀下所有数据
|
|
64
|
+
await cache.clear();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 带 TTL 过期
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const cache = new StorageManager({ prefix: 'session' });
|
|
71
|
+
|
|
72
|
+
// 设置 5 分钟后过期(TTL 单位为毫秒)
|
|
73
|
+
await cache.setItem('token', 'abc123', 5 * 60 * 1000);
|
|
74
|
+
|
|
75
|
+
// 5 分钟内可以正常读取
|
|
76
|
+
const token = await cache.getItem('token');
|
|
77
|
+
// => 'abc123'
|
|
78
|
+
|
|
79
|
+
// 5 分钟后自动返回 null(惰性删除)
|
|
80
|
+
const expiredToken = await cache.getItem('token');
|
|
81
|
+
// => null
|
|
82
|
+
|
|
83
|
+
// 主动清理所有过期项
|
|
84
|
+
await cache.clearExpiredItems();
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 存储驱动
|
|
90
|
+
|
|
91
|
+
### LocalStorageDriver(默认)
|
|
92
|
+
|
|
93
|
+
基于浏览器 `localStorage` 或 `sessionStorage`,数据持久化存储。
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { LocalStorageDriver, StorageManager } from '@vben-core/shared/cache';
|
|
97
|
+
|
|
98
|
+
// 使用 localStorage(默认)
|
|
99
|
+
const cache = new StorageManager({
|
|
100
|
+
driver: new LocalStorageDriver(),
|
|
101
|
+
prefix: 'app',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 使用 sessionStorage
|
|
105
|
+
const sessionCache = new StorageManager({
|
|
106
|
+
driver: new LocalStorageDriver({ storageType: 'sessionStorage' }),
|
|
107
|
+
prefix: 'app',
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**特点:**
|
|
112
|
+
|
|
113
|
+
- 同步 API 用 async 包装,保持接口统一
|
|
114
|
+
- 自动处理 JSON 序列化/反序列化
|
|
115
|
+
- 数据损坏时自动清除并返回 null
|
|
116
|
+
- 存储上限约 5-10MB(视浏览器而定)
|
|
117
|
+
|
|
118
|
+
**适用场景:** 用户偏好设置、小型配置数据、Token 存储
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### IndexedDBDriver
|
|
123
|
+
|
|
124
|
+
基于浏览器 IndexedDB,支持大容量结构化数据存储。
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import {IndexedDBDriver, StorageManager} from '@vben-core/shared/cache';
|
|
128
|
+
|
|
129
|
+
const cache = new StorageManager({
|
|
130
|
+
driver: new IndexedDBDriver({
|
|
131
|
+
dbName: 'my-app-db', // 数据库名称,默认 'vben-storage'
|
|
132
|
+
dbVersion: 1, // 数据库版本,默认 1
|
|
133
|
+
storeName: 'cache-store', // 对象存储名称,默认 'kv-store'
|
|
134
|
+
}),
|
|
135
|
+
prefix: 'data',
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// 存储大量数据
|
|
139
|
+
await cache.setItem('table-data', largeDataArray);
|
|
140
|
+
|
|
141
|
+
// 存储二进制友好的结构(IndexedDB 原生支持)
|
|
142
|
+
await cache.setItem('config', {
|
|
143
|
+
columns: [...],
|
|
144
|
+
filters: [...],
|
|
145
|
+
pagination: {page: 1, size: 20},
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**特点:**
|
|
150
|
+
|
|
151
|
+
- 懒初始化:首次操作时自动打开数据库,无需手动调用 `init()`
|
|
152
|
+
- 存储容量大(通常数百 MB 到 GB 级别)
|
|
153
|
+
- 支持结构化克隆(可存储 Date、RegExp、Blob 等复杂类型)
|
|
154
|
+
- 天然异步,不阻塞主线程
|
|
155
|
+
|
|
156
|
+
**适用场景:** 离线数据缓存、大型表格数据、文件/图片缓存、复杂业务数据
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
### MemoryStorageDriver
|
|
161
|
+
|
|
162
|
+
基于内存 Map,数据不持久化,页面刷新即丢失。
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { MemoryStorageDriver, StorageManager } from '@vben-core/shared/cache';
|
|
166
|
+
|
|
167
|
+
const cache = new StorageManager({
|
|
168
|
+
driver: new MemoryStorageDriver(),
|
|
169
|
+
prefix: 'test',
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**特点:**
|
|
174
|
+
|
|
175
|
+
- 读写速度最快
|
|
176
|
+
- 无浏览器 API 依赖
|
|
177
|
+
- 数据随页面生命周期销毁
|
|
178
|
+
|
|
179
|
+
**适用场景:** 单元测试、SSR 服务端渲染、临时运行时缓存
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## API 参考
|
|
184
|
+
|
|
185
|
+
### StorageManager
|
|
186
|
+
|
|
187
|
+
#### 构造函数
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
new StorageManager(options?: StorageManagerOptions)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
194
|
+
| --- | --- | --- | --- |
|
|
195
|
+
| `driver` | `IStorageDriver` | `new LocalStorageDriver()` | 存储驱动实例 |
|
|
196
|
+
| `prefix` | `string` | `''` | 键前缀,用于命名空间隔离 |
|
|
197
|
+
|
|
198
|
+
#### 方法
|
|
199
|
+
|
|
200
|
+
| 方法 | 签名 | 说明 |
|
|
201
|
+
| --- | --- | --- |
|
|
202
|
+
| `getItem` | `getItem<T>(key: string, defaultValue?: T \| null): Promise<T \| null>` | 获取存储项,过期或不存在返回默认值 |
|
|
203
|
+
| `setItem` | `setItem<T>(key: string, value: T, ttl?: number): Promise<void>` | 设置存储项,可选 TTL(毫秒) |
|
|
204
|
+
| `removeItem` | `removeItem(key: string): Promise<void>` | 删除指定存储项 |
|
|
205
|
+
| `clear` | `clear(): Promise<void>` | 清除当前前缀下所有存储项 |
|
|
206
|
+
| `clearExpiredItems` | `clearExpiredItems(): Promise<void>` | 主动清理所有过期项 |
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### IStorageDriver 接口
|
|
211
|
+
|
|
212
|
+
自定义驱动需要实现此接口:
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
interface IStorageDriver {
|
|
216
|
+
clear(): Promise<void>;
|
|
217
|
+
|
|
218
|
+
getItem<T>(key: string): Promise<null | T>;
|
|
219
|
+
|
|
220
|
+
keys(): Promise<string[]>;
|
|
221
|
+
|
|
222
|
+
removeItem(key: string): Promise<void>;
|
|
223
|
+
|
|
224
|
+
setItem<T>(key: string, value: T): Promise<void>;
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## 高级用法
|
|
231
|
+
|
|
232
|
+
### 自定义 Driver
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import type { IStorageDriver } from '@vben-core/shared/cache';
|
|
236
|
+
|
|
237
|
+
class CookieStorageDriver implements IStorageDriver {
|
|
238
|
+
async getItem<T>(key: string): Promise<null | T> {
|
|
239
|
+
const value = getCookie(key);
|
|
240
|
+
return value ? JSON.parse(value) : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async setItem<T>(key: string, value: T): Promise<void> {
|
|
244
|
+
setCookie(key, JSON.stringify(value));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async removeItem(key: string): Promise<void> {
|
|
248
|
+
deleteCookie(key);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async clear(): Promise<void> {
|
|
252
|
+
clearAllCookies();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async keys(): Promise<string[]> {
|
|
256
|
+
return getAllCookieNames();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 使用自定义 Driver
|
|
261
|
+
const cache = new StorageManager({
|
|
262
|
+
driver: new CookieStorageDriver(),
|
|
263
|
+
prefix: 'ck',
|
|
264
|
+
});
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 根据环境动态选择 Driver
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import {
|
|
271
|
+
IndexedDBDriver,
|
|
272
|
+
LocalStorageDriver,
|
|
273
|
+
MemoryStorageDriver,
|
|
274
|
+
StorageManager,
|
|
275
|
+
} from '@vben-core/shared/cache';
|
|
276
|
+
|
|
277
|
+
function createStorageManager(prefix: string) {
|
|
278
|
+
// SSR 环境使用内存驱动
|
|
279
|
+
if (typeof window === 'undefined') {
|
|
280
|
+
return new StorageManager({
|
|
281
|
+
driver: new MemoryStorageDriver(),
|
|
282
|
+
prefix,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 大数据场景使用 IndexedDB
|
|
287
|
+
if (needsLargeStorage()) {
|
|
288
|
+
return new StorageManager({
|
|
289
|
+
driver: new IndexedDBDriver({ dbName: `${prefix}-db` }),
|
|
290
|
+
prefix,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 默认使用 localStorage
|
|
295
|
+
return new StorageManager({ prefix });
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### 命名空间隔离
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// 不同模块使用不同前缀,互不干扰
|
|
303
|
+
const userCache = new StorageManager({ prefix: 'user' });
|
|
304
|
+
const configCache = new StorageManager({ prefix: 'config' });
|
|
305
|
+
|
|
306
|
+
await userCache.setItem('profile', { name: '张三' });
|
|
307
|
+
await configCache.setItem('profile', { theme: 'dark' });
|
|
308
|
+
|
|
309
|
+
// 各自独立
|
|
310
|
+
await userCache.getItem('profile'); // => { name: '张三' }
|
|
311
|
+
await configCache.getItem('profile'); // => { theme: 'dark' }
|
|
312
|
+
|
|
313
|
+
// 只清除 user 前缀的数据
|
|
314
|
+
await userCache.clear();
|
|
315
|
+
await configCache.getItem('profile'); // => { theme: 'dark' }(不受影响)
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### 定时清理过期数据
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
const cache = new StorageManager({ prefix: 'app' });
|
|
322
|
+
|
|
323
|
+
// 应用启动时清理一次
|
|
324
|
+
await cache.clearExpiredItems();
|
|
325
|
+
|
|
326
|
+
// 或者定时清理(每 10 分钟)
|
|
327
|
+
setInterval(
|
|
328
|
+
async () => {
|
|
329
|
+
await cache.clearExpiredItems();
|
|
330
|
+
},
|
|
331
|
+
10 * 60 * 1000,
|
|
332
|
+
);
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## 数据存储格式
|
|
338
|
+
|
|
339
|
+
`StorageManager` 在 Driver 层存储的数据结构为:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
interface StorageItem<T> {
|
|
343
|
+
expiry?: number; // 过期时间戳(毫秒),undefined 表示永不过期
|
|
344
|
+
value: T; // 实际业务数据
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
实际存储的 key 格式为:`{prefix}-{key}`
|
|
349
|
+
|
|
350
|
+
例如 `prefix = 'app'`,`key = 'user'`,则实际存储键为 `app-user`。
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## 过期策略
|
|
355
|
+
|
|
356
|
+
采用**惰性删除 + 主动清理**双重策略:
|
|
357
|
+
|
|
358
|
+
| 策略 | 触发时机 | 说明 |
|
|
359
|
+
| --- | --- | --- |
|
|
360
|
+
| 惰性删除 | 调用 `getItem` 时 | 读取时检查过期,过期则删除并返回默认值 |
|
|
361
|
+
| 主动清理 | 调用 `clearExpiredItems` 时 | 遍历所有带前缀的 key,删除已过期项 |
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 各 Driver 对比
|
|
366
|
+
|
|
367
|
+
| 特性 | LocalStorageDriver | IndexedDBDriver | MemoryStorageDriver |
|
|
368
|
+
| ---------- | ------------------- | ---------------- | ------------------- |
|
|
369
|
+
| 持久化 | ✅ | ✅ | ❌ |
|
|
370
|
+
| 容量 | 5-10 MB | 数百 MB+ | 受内存限制 |
|
|
371
|
+
| 速度 | 快(同步) | 中等(异步 I/O) | 最快 |
|
|
372
|
+
| 数据类型 | 仅 JSON 可序列化 | 结构化克隆 | 任意 JS 对象 |
|
|
373
|
+
| 浏览器支持 | 所有现代浏览器 | 所有现代浏览器 | 任意环境 |
|
|
374
|
+
| 阻塞主线程 | 是 | 否 | 否 |
|
|
375
|
+
| 适用场景 | 配置、Token、小数据 | 离线缓存、大数据 | 测试、SSR |
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## 在项目中的使用
|
|
380
|
+
|
|
381
|
+
本项目中 `StorageManager` 主要被 `PreferenceManager` 消费,用于持久化用户偏好设置:
|
|
382
|
+
|
|
383
|
+
```typescript
|
|
384
|
+
// packages/@core/preferences/src/preferences.ts
|
|
385
|
+
class PreferenceManager {
|
|
386
|
+
private cache: StorageManager;
|
|
387
|
+
|
|
388
|
+
constructor() {
|
|
389
|
+
this.cache = new StorageManager();
|
|
390
|
+
this.state = reactive<Preferences>({ ...defaultPreferences });
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
initPreferences = async ({ namespace }) => {
|
|
394
|
+
// 用应用命名空间重新初始化
|
|
395
|
+
this.cache = new StorageManager({ prefix: namespace });
|
|
396
|
+
|
|
397
|
+
// 从缓存加载偏好设置
|
|
398
|
+
const cached = await this.cache.getItem<Preferences>('preferences');
|
|
399
|
+
// ...
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 注意事项
|
|
407
|
+
|
|
408
|
+
1. **所有方法都是异步的** — 即使底层是同步的 localStorage,API 也返回 Promise,确保切换 Driver 时无需改动调用方。
|
|
409
|
+
|
|
410
|
+
2. **TTL 单位是毫秒** — `setItem('key', value, 60000)` 表示 60 秒后过期。
|
|
411
|
+
|
|
412
|
+
3. **IndexedDB 懒初始化** — 不需要手动调用 `init()` 或 `open()`,首次操作时自动打开数据库连接并复用。
|
|
413
|
+
|
|
414
|
+
4. **前缀隔离是逻辑隔离** — `clear()` 只清除当前前缀下的数据,不影响其他前缀或无前缀的数据。
|
|
415
|
+
|
|
416
|
+
5. **错误处理** — LocalStorageDriver 在 JSON 解析失败时自动清除损坏数据; `PreferenceManager.saveToCache` 内部 try-catch 防止未捕获异常。
|
|
417
|
+
|
|
418
|
+
6. **IndexedDB 版本升级** — 如果需要修改 objectStore 结构,需要递增 `dbVersion`。当前实现在 `upgradeneeded` 事件中自动创建 objectStore。
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { IStorageDriver } from './types';
|
|
2
|
+
|
|
3
|
+
interface IndexedDBDriverOptions {
|
|
4
|
+
/** 数据库名称 */
|
|
5
|
+
dbName?: string;
|
|
6
|
+
/** 数据库版本 */
|
|
7
|
+
dbVersion?: number;
|
|
8
|
+
/** 对象存储名称 */
|
|
9
|
+
storeName?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* IndexedDB 驱动
|
|
14
|
+
* 采用懒初始化模式,首次操作时自动打开数据库
|
|
15
|
+
*/
|
|
16
|
+
class IndexedDBDriver implements IStorageDriver {
|
|
17
|
+
private dbName: string;
|
|
18
|
+
private dbPromise: null | Promise<IDBDatabase> = null;
|
|
19
|
+
private dbVersion: number;
|
|
20
|
+
private storeName: string;
|
|
21
|
+
|
|
22
|
+
constructor({
|
|
23
|
+
dbName = 'vben-storage',
|
|
24
|
+
dbVersion = 1,
|
|
25
|
+
storeName = 'kv-store',
|
|
26
|
+
}: IndexedDBDriverOptions = {}) {
|
|
27
|
+
this.dbName = dbName;
|
|
28
|
+
this.dbVersion = dbVersion;
|
|
29
|
+
this.storeName = storeName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async clear(): Promise<void> {
|
|
33
|
+
const db = await this.getDB();
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const tx = db.transaction(this.storeName, 'readwrite');
|
|
36
|
+
const store = tx.objectStore(this.storeName);
|
|
37
|
+
store.clear();
|
|
38
|
+
|
|
39
|
+
tx.addEventListener('complete', () => resolve());
|
|
40
|
+
tx.addEventListener('error', () => reject(tx.error));
|
|
41
|
+
tx.addEventListener('abort', () =>
|
|
42
|
+
reject(tx.error ?? new Error('Transaction aborted')),
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async getItem<T>(key: string): Promise<null | T> {
|
|
48
|
+
const db = await this.getDB();
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const tx = db.transaction(this.storeName, 'readonly');
|
|
51
|
+
const store = tx.objectStore(this.storeName);
|
|
52
|
+
const request = store.get(key);
|
|
53
|
+
|
|
54
|
+
request.addEventListener('success', () =>
|
|
55
|
+
resolve(request.result ?? null),
|
|
56
|
+
);
|
|
57
|
+
request.addEventListener('error', () => reject(request.error));
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async keys(): Promise<string[]> {
|
|
62
|
+
const db = await this.getDB();
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const tx = db.transaction(this.storeName, 'readonly');
|
|
65
|
+
const store = tx.objectStore(this.storeName);
|
|
66
|
+
const request = store.getAllKeys();
|
|
67
|
+
|
|
68
|
+
request.addEventListener('success', () =>
|
|
69
|
+
resolve(request.result.map(String)),
|
|
70
|
+
);
|
|
71
|
+
request.addEventListener('error', () => reject(request.error));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async removeItem(key: string): Promise<void> {
|
|
76
|
+
const db = await this.getDB();
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const tx = db.transaction(this.storeName, 'readwrite');
|
|
79
|
+
const store = tx.objectStore(this.storeName);
|
|
80
|
+
store.delete(key);
|
|
81
|
+
|
|
82
|
+
tx.addEventListener('complete', () => resolve());
|
|
83
|
+
tx.addEventListener('error', () => reject(tx.error));
|
|
84
|
+
tx.addEventListener('abort', () =>
|
|
85
|
+
reject(tx.error ?? new Error('Transaction aborted')),
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async setItem(key: string, value: unknown): Promise<void> {
|
|
91
|
+
const db = await this.getDB();
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const tx = db.transaction(this.storeName, 'readwrite');
|
|
94
|
+
const store = tx.objectStore(this.storeName);
|
|
95
|
+
store.put(value, key);
|
|
96
|
+
|
|
97
|
+
tx.addEventListener('complete', () => resolve());
|
|
98
|
+
tx.addEventListener('error', () => reject(tx.error));
|
|
99
|
+
tx.addEventListener('abort', () =>
|
|
100
|
+
reject(tx.error ?? new Error('Transaction aborted')),
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 懒初始化:首次调用时打开数据库,后续复用同一个 Promise
|
|
107
|
+
*/
|
|
108
|
+
private getDB(): Promise<IDBDatabase> {
|
|
109
|
+
if (!this.dbPromise) {
|
|
110
|
+
this.dbPromise = this.openDB().catch((error) => {
|
|
111
|
+
// allow retry on next call
|
|
112
|
+
this.dbPromise = null;
|
|
113
|
+
throw error;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return this.dbPromise;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private openDB(): Promise<IDBDatabase> {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const request = indexedDB.open(this.dbName, this.dbVersion);
|
|
122
|
+
|
|
123
|
+
request.addEventListener('upgradeneeded', () => {
|
|
124
|
+
const db = request.result;
|
|
125
|
+
if (!db.objectStoreNames.contains(this.storeName)) {
|
|
126
|
+
db.createObjectStore(this.storeName);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
request.addEventListener('success', () => resolve(request.result));
|
|
131
|
+
request.addEventListener('error', () => reject(request.error));
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export { IndexedDBDriver };
|
|
137
|
+
export type { IndexedDBDriverOptions };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { IStorageDriver } from './types';
|
|
2
|
+
|
|
3
|
+
type StorageType = 'localStorage' | 'sessionStorage';
|
|
4
|
+
|
|
5
|
+
interface LocalStorageDriverOptions {
|
|
6
|
+
/** 使用 localStorage 还是 sessionStorage */
|
|
7
|
+
storageType?: StorageType;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* LocalStorage / SessionStorage 驱动
|
|
12
|
+
* 用 async 包装同步 API,保持接口统一
|
|
13
|
+
*/
|
|
14
|
+
class LocalStorageDriver implements IStorageDriver {
|
|
15
|
+
private storage: Storage;
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
storageType = 'localStorage',
|
|
19
|
+
}: LocalStorageDriverOptions = {}) {
|
|
20
|
+
if (typeof window === 'undefined') {
|
|
21
|
+
throw new TypeError(
|
|
22
|
+
'LocalStorageDriver is not available in non-browser environments. Use MemoryStorageDriver instead.',
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
this.storage =
|
|
26
|
+
storageType === 'localStorage'
|
|
27
|
+
? window.localStorage
|
|
28
|
+
: window.sessionStorage;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async clear(): Promise<void> {
|
|
32
|
+
this.storage.clear();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getItem<T>(key: string): Promise<null | T> {
|
|
36
|
+
const raw = this.storage.getItem(key);
|
|
37
|
+
if (raw === null) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(raw) as T;
|
|
42
|
+
} catch {
|
|
43
|
+
// 数据损坏,清除并返回 null
|
|
44
|
+
this.storage.removeItem(key);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async keys(): Promise<string[]> {
|
|
50
|
+
const result: string[] = [];
|
|
51
|
+
for (let i = 0; i < this.storage.length; i++) {
|
|
52
|
+
const key = this.storage.key(i);
|
|
53
|
+
if (key !== null) {
|
|
54
|
+
result.push(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async removeItem(key: string): Promise<void> {
|
|
61
|
+
this.storage.removeItem(key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async setItem(key: string, value: unknown): Promise<void> {
|
|
65
|
+
this.storage.setItem(key, JSON.stringify(value));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { LocalStorageDriver };
|
|
70
|
+
export type { LocalStorageDriverOptions };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { IStorageDriver } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 内存存储驱动
|
|
5
|
+
* 适用于测试环境和 SSR 场景,数据不持久化
|
|
6
|
+
*/
|
|
7
|
+
class MemoryStorageDriver implements IStorageDriver {
|
|
8
|
+
private store = new Map<string, unknown>();
|
|
9
|
+
|
|
10
|
+
async clear(): Promise<void> {
|
|
11
|
+
this.store.clear();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async getItem<T>(key: string): Promise<null | T> {
|
|
15
|
+
const value = this.store.get(key);
|
|
16
|
+
return (value as T) ?? null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async keys(): Promise<string[]> {
|
|
20
|
+
return [...this.store.keys()];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async removeItem(key: string): Promise<void> {
|
|
24
|
+
this.store.delete(key);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async setItem(key: string, value: unknown): Promise<void> {
|
|
28
|
+
this.store.set(key, value);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { MemoryStorageDriver };
|