fix: fix lint && typecheck
parent
5bbdcffb97
commit
0c1b737325
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
"@vben/layouts": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: update primary color when toggling dark/light mode with custom theme
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
"@vben/common-ui": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: skip fixed footer height in auto-content-height calculation
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
"@vben/icons": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: guard svg icon loading during docs SSR
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
"@vben-core/shadcn-ui": patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix: preserve tree default value when treeData starts empty
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# Cache 模块
|
# Cache 模块
|
||||||
|
|
||||||
基于**策略模式**的异步存储管理方案,支持多种存储后端(localStorage、IndexedDB、Memory),提供统一的 API
|
基于**策略模式**的异步存储管理方案,支持多种存储后端(localStorage、IndexedDB、Memory),提供统一的 API 接口。
|
||||||
接口。
|
|
||||||
|
|
||||||
## 架构设计
|
## 架构设计
|
||||||
|
|
||||||
|
|
@ -23,7 +22,7 @@
|
||||||
**分层职责:**
|
**分层职责:**
|
||||||
|
|
||||||
| 层级 | 职责 |
|
| 层级 | 职责 |
|
||||||
|------------------|----------------------------|
|
| ---------------- | -------------------------------------------- |
|
||||||
| `StorageManager` | 命名空间前缀隔离、TTL 过期检查、统一对外 API |
|
| `StorageManager` | 命名空间前缀隔离、TTL 过期检查、统一对外 API |
|
||||||
| `IStorageDriver` | 纯粹的 KV 存取抽象接口 |
|
| `IStorageDriver` | 纯粹的 KV 存取抽象接口 |
|
||||||
| 各 Driver 实现 | 对接具体存储引擎,不感知前缀和 TTL |
|
| 各 Driver 实现 | 对接具体存储引擎,不感知前缀和 TTL |
|
||||||
|
|
@ -192,14 +191,14 @@ new StorageManager(options?: StorageManagerOptions)
|
||||||
```
|
```
|
||||||
|
|
||||||
| 参数 | 类型 | 默认值 | 说明 |
|
| 参数 | 类型 | 默认值 | 说明 |
|
||||||
|----------|------------------|----------------------------|--------------|
|
| --- | --- | --- | --- |
|
||||||
| `driver` | `IStorageDriver` | `new LocalStorageDriver()` | 存储驱动实例 |
|
| `driver` | `IStorageDriver` | `new LocalStorageDriver()` | 存储驱动实例 |
|
||||||
| `prefix` | `string` | `''` | 键前缀,用于命名空间隔离 |
|
| `prefix` | `string` | `''` | 键前缀,用于命名空间隔离 |
|
||||||
|
|
||||||
#### 方法
|
#### 方法
|
||||||
|
|
||||||
| 方法 | 签名 | 说明 |
|
| 方法 | 签名 | 说明 |
|
||||||
|---------------------|-------------------------------------------------------------------------|-------------------|
|
| --- | --- | --- |
|
||||||
| `getItem` | `getItem<T>(key: string, defaultValue?: T \| null): Promise<T \| null>` | 获取存储项,过期或不存在返回默认值 |
|
| `getItem` | `getItem<T>(key: string, defaultValue?: T \| null): Promise<T \| null>` | 获取存储项,过期或不存在返回默认值 |
|
||||||
| `setItem` | `setItem<T>(key: string, value: T, ttl?: number): Promise<void>` | 设置存储项,可选 TTL(毫秒) |
|
| `setItem` | `setItem<T>(key: string, value: T, ttl?: number): Promise<void>` | 设置存储项,可选 TTL(毫秒) |
|
||||||
| `removeItem` | `removeItem(key: string): Promise<void>` | 删除指定存储项 |
|
| `removeItem` | `removeItem(key: string): Promise<void>` | 删除指定存储项 |
|
||||||
|
|
@ -357,7 +356,7 @@ interface StorageItem<T> {
|
||||||
采用**惰性删除 + 主动清理**双重策略:
|
采用**惰性删除 + 主动清理**双重策略:
|
||||||
|
|
||||||
| 策略 | 触发时机 | 说明 |
|
| 策略 | 触发时机 | 说明 |
|
||||||
|------|--------------------------|---------------------|
|
| --- | --- | --- |
|
||||||
| 惰性删除 | 调用 `getItem` 时 | 读取时检查过期,过期则删除并返回默认值 |
|
| 惰性删除 | 调用 `getItem` 时 | 读取时检查过期,过期则删除并返回默认值 |
|
||||||
| 主动清理 | 调用 `clearExpiredItems` 时 | 遍历所有带前缀的 key,删除已过期项 |
|
| 主动清理 | 调用 `clearExpiredItems` 时 | 遍历所有带前缀的 key,删除已过期项 |
|
||||||
|
|
||||||
|
|
@ -366,7 +365,7 @@ interface StorageItem<T> {
|
||||||
## 各 Driver 对比
|
## 各 Driver 对比
|
||||||
|
|
||||||
| 特性 | LocalStorageDriver | IndexedDBDriver | MemoryStorageDriver |
|
| 特性 | LocalStorageDriver | IndexedDBDriver | MemoryStorageDriver |
|
||||||
|-------|--------------------|-----------------|---------------------|
|
| ---------- | ------------------- | ---------------- | ------------------- |
|
||||||
| 持久化 | ✅ | ✅ | ❌ |
|
| 持久化 | ✅ | ✅ | ❌ |
|
||||||
| 容量 | 5-10 MB | 数百 MB+ | 受内存限制 |
|
| 容量 | 5-10 MB | 数百 MB+ | 受内存限制 |
|
||||||
| 速度 | 快(同步) | 中等(异步 I/O) | 最快 |
|
| 速度 | 快(同步) | 中等(异步 I/O) | 最快 |
|
||||||
|
|
@ -406,8 +405,7 @@ class PreferenceManager {
|
||||||
|
|
||||||
## 注意事项
|
## 注意事项
|
||||||
|
|
||||||
1. **所有方法都是异步的** — 即使底层是同步的 localStorage,API 也返回 Promise,确保切换 Driver
|
1. **所有方法都是异步的** — 即使底层是同步的 localStorage,API 也返回 Promise,确保切换 Driver 时无需改动调用方。
|
||||||
时无需改动调用方。
|
|
||||||
|
|
||||||
2. **TTL 单位是毫秒** — `setItem('key', value, 60000)` 表示 60 秒后过期。
|
2. **TTL 单位是毫秒** — `setItem('key', value, 60000)` 表示 60 秒后过期。
|
||||||
|
|
||||||
|
|
@ -415,8 +413,6 @@ class PreferenceManager {
|
||||||
|
|
||||||
4. **前缀隔离是逻辑隔离** — `clear()` 只清除当前前缀下的数据,不影响其他前缀或无前缀的数据。
|
4. **前缀隔离是逻辑隔离** — `clear()` 只清除当前前缀下的数据,不影响其他前缀或无前缀的数据。
|
||||||
|
|
||||||
5. **错误处理** — LocalStorageDriver 在 JSON 解析失败时自动清除损坏数据;
|
5. **错误处理** — LocalStorageDriver 在 JSON 解析失败时自动清除损坏数据; `PreferenceManager.saveToCache` 内部 try-catch 防止未捕获异常。
|
||||||
`PreferenceManager.saveToCache` 内部 try-catch 防止未捕获异常。
|
|
||||||
|
|
||||||
6. **IndexedDB 版本升级** — 如果需要修改 objectStore 结构,需要递增 `dbVersion`。当前实现在
|
6. **IndexedDB 版本升级** — 如果需要修改 objectStore 结构,需要递增 `dbVersion`。当前实现在 `upgradeneeded` 事件中自动创建 objectStore。
|
||||||
`upgradeneeded` 事件中自动创建 objectStore。
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ class IndexedDBDriver implements IStorageDriver {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async setItem<T>(key: string, value: T): Promise<void> {
|
async setItem(key: string, value: unknown): Promise<void> {
|
||||||
const db = await this.getDB();
|
const db = await this.getDB();
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const tx = db.transaction(this.storeName, 'readwrite');
|
const tx = db.transaction(this.storeName, 'readwrite');
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ class LocalStorageDriver implements IStorageDriver {
|
||||||
this.storage.removeItem(key);
|
this.storage.removeItem(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setItem<T>(key: string, value: T): Promise<void> {
|
async setItem(key: string, value: unknown): Promise<void> {
|
||||||
this.storage.setItem(key, JSON.stringify(value));
|
this.storage.setItem(key, JSON.stringify(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class MemoryStorageDriver implements IStorageDriver {
|
||||||
this.store.delete(key);
|
this.store.delete(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
async setItem<T>(key: string, value: T): Promise<void> {
|
async setItem(key: string, value: unknown): Promise<void> {
|
||||||
this.store.set(key, value);
|
this.store.set(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,10 +106,10 @@ class StorageManager {
|
||||||
* @param value 值
|
* @param value 值
|
||||||
* @param ttl 存活时间(毫秒)
|
* @param ttl 存活时间(毫秒)
|
||||||
*/
|
*/
|
||||||
async setItem<T>(key: string, value: T, ttl?: number): Promise<void> {
|
async setItem(key: string, value: unknown, ttl?: number): Promise<void> {
|
||||||
const fullKey = this.getFullKey(key);
|
const fullKey = this.getFullKey(key);
|
||||||
const expiry = ttl ? Date.now() + ttl : undefined;
|
const expiry = ttl ? Date.now() + ttl : undefined;
|
||||||
const item: StorageItem<T> = { expiry, value };
|
const item: StorageItem<unknown> = { expiry, value };
|
||||||
await this.driver.setItem(fullKey, item);
|
await this.driver.setItem(fullKey, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface IStorageDriver {
|
||||||
removeItem(key: string): Promise<void>;
|
removeItem(key: string): Promise<void>;
|
||||||
|
|
||||||
/** 设置存储项 */
|
/** 设置存储项 */
|
||||||
setItem<T>(key: string, value: T): Promise<void>;
|
setItem(key: string, value: unknown): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,9 @@ describe('stateHandler', () => {
|
||||||
}, 10);
|
}, 10);
|
||||||
|
|
||||||
// 等待过程中,期望 Promise 被 reject
|
// 等待过程中,期望 Promise 被 reject
|
||||||
await expect(handler.waitForCondition()).rejects.toThrow();
|
await expect(handler.waitForCondition()).rejects.toThrow(
|
||||||
|
'Condition was set to false',
|
||||||
|
);
|
||||||
expect(handler.isConditionTrue()).toBe(false);
|
expect(handler.isConditionTrue()).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -138,8 +138,10 @@ describe('getNestedValue', () => {
|
||||||
expect(result).toBe(2);
|
expect(result).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the entire object if path is empty', () => {
|
it('should throw if path is empty', () => {
|
||||||
expect(() => getNestedValue(data, '')()).toThrow();
|
expect(() => getNestedValue(data, '')).toThrow(
|
||||||
|
'Path must be a non-empty string',
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle paths with array indexes', () => {
|
it('should handle paths with array indexes', () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export class StateHandler {
|
export class StateHandler {
|
||||||
private condition: boolean = false;
|
private condition: boolean = false;
|
||||||
private rejectCondition: (() => void) | null = null;
|
private rejectCondition: ((reason?: Error) => void) | null = null;
|
||||||
private resolveCondition: (() => void) | null = null;
|
private resolveCondition: (() => void) | null = null;
|
||||||
|
|
||||||
isConditionTrue(): boolean {
|
isConditionTrue(): boolean {
|
||||||
|
|
@ -16,7 +16,7 @@ export class StateHandler {
|
||||||
setConditionFalse() {
|
setConditionFalse() {
|
||||||
this.condition = false;
|
this.condition = false;
|
||||||
if (this.rejectCondition) {
|
if (this.rejectCondition) {
|
||||||
this.rejectCondition();
|
this.rejectCondition(new Error('Condition was set to false'));
|
||||||
this.clearPromises();
|
this.clearPromises();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -180,11 +180,7 @@ class PreferenceManager {
|
||||||
* 更新扩展偏好设置
|
* 更新扩展偏好设置
|
||||||
* @param updates - 要更新的扩展偏好设置
|
* @param updates - 要更新的扩展偏好设置
|
||||||
*/
|
*/
|
||||||
updateCustomPreferences = <
|
updateCustomPreferences = (updates: DeepPartial<object>) => {
|
||||||
TCustomPreferences extends object = CustomPreferencesRecord,
|
|
||||||
>(
|
|
||||||
updates: DeepPartial<TCustomPreferences>,
|
|
||||||
) => {
|
|
||||||
if (!this.customPreferencesExtension) {
|
if (!this.customPreferencesExtension) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,11 @@ defineOptions({
|
||||||
name: 'Page',
|
name: 'Page',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { autoContentHeight = false, heightOffset = 0, footerFixed = false } =
|
const {
|
||||||
defineProps<PageProps>();
|
autoContentHeight = false,
|
||||||
|
heightOffset = 0,
|
||||||
|
footerFixed = false,
|
||||||
|
} = defineProps<PageProps>();
|
||||||
|
|
||||||
const headerHeight = ref(0);
|
const headerHeight = ref(0);
|
||||||
const footerHeight = ref(0);
|
const footerHeight = ref(0);
|
||||||
|
|
@ -40,7 +43,7 @@ async function calcContentHeight() {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
headerHeight.value = headerRef.value?.offsetHeight || 0;
|
headerHeight.value = headerRef.value?.offsetHeight || 0;
|
||||||
|
|
||||||
footerHeight.value = footerFixed ? 0 : (footerRef.value?.offsetHeight || 0);
|
footerHeight.value = footerFixed ? 0 : footerRef.value?.offsetHeight || 0;
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
shouldAutoHeight.value = true;
|
shouldAutoHeight.value = true;
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,7 @@ function findPlaceholderPos(doc: ProseMirrorNode, blobUrl: string): number {
|
||||||
found = offset;
|
found = offset;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* eslint-disable unicorn/no-nested-ternary */
|
||||||
import type { VxeGridProps as VxeTableGridProps } from 'vxe-table';
|
import type { VxeGridProps as VxeTableGridProps } from 'vxe-table';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
@ -183,9 +184,9 @@ export function useViewedRow<T = any>(
|
||||||
) {
|
) {
|
||||||
// ========== 解析持久化配置 ==========
|
// ========== 解析持久化配置 ==========
|
||||||
const persistOpts: null | ViewedRowPersistOptions = options.persist
|
const persistOpts: null | ViewedRowPersistOptions = options.persist
|
||||||
? (typeof options.persist === 'string'
|
? typeof options.persist === 'string'
|
||||||
? { key: options.persist, type: 'localStorage' }
|
? { key: options.persist, type: 'localStorage' }
|
||||||
: options.persist)
|
: options.persist
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
const adapter = createStorageAdapter(options.persist);
|
const adapter = createStorageAdapter(options.persist);
|
||||||
|
|
@ -521,9 +522,9 @@ export function applyViewedRowOptions(
|
||||||
// 拦截 CellOperation columns
|
// 拦截 CellOperation columns
|
||||||
const actionCodes =
|
const actionCodes =
|
||||||
!isBoolean(viewedRowConfig) && viewedRowConfig.actionCodes
|
!isBoolean(viewedRowConfig) && viewedRowConfig.actionCodes
|
||||||
? (Array.isArray(viewedRowConfig.actionCodes)
|
? Array.isArray(viewedRowConfig.actionCodes)
|
||||||
? viewedRowConfig.actionCodes
|
? viewedRowConfig.actionCodes
|
||||||
: [viewedRowConfig.actionCodes])
|
: [viewedRowConfig.actionCodes]
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
if (actionCodes.length > 0 && Array.isArray(mergedOptions.columns)) {
|
if (actionCodes.length > 0 && Array.isArray(mergedOptions.columns)) {
|
||||||
|
|
|
||||||
|
|
@ -50,24 +50,18 @@ describe('requestClient', () => {
|
||||||
|
|
||||||
it('should handle network errors', async () => {
|
it('should handle network errors', async () => {
|
||||||
mock.onGet('/test/error').networkError();
|
mock.onGet('/test/error').networkError();
|
||||||
try {
|
await expect(requestClient.get('/test/error')).rejects.toMatchObject({
|
||||||
await requestClient.get('/test/error');
|
isAxiosError: true,
|
||||||
expect(true).toBe(false);
|
message: 'Network Error',
|
||||||
} catch (error: any) {
|
});
|
||||||
expect(error.isAxiosError).toBe(true);
|
|
||||||
expect(error.message).toBe('Network Error');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle timeout', async () => {
|
it('should handle timeout', async () => {
|
||||||
mock.onGet('/test/timeout').timeout();
|
mock.onGet('/test/timeout').timeout();
|
||||||
try {
|
await expect(requestClient.get('/test/timeout')).rejects.toMatchObject({
|
||||||
await requestClient.get('/test/timeout');
|
isAxiosError: true,
|
||||||
expect(true).toBe(false);
|
code: 'ECONNABORTED',
|
||||||
} catch (error: any) {
|
});
|
||||||
expect(error.isAxiosError).toBe(true);
|
|
||||||
expect(error.code).toBe('ECONNABORTED');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should successfully upload a file', async () => {
|
it('should successfully upload a file', async () => {
|
||||||
|
|
@ -92,7 +86,7 @@ describe('requestClient', () => {
|
||||||
|
|
||||||
mock.onGet('/test/download').reply(200, mockFileContent);
|
mock.onGet('/test/download').reply(200, mockFileContent);
|
||||||
|
|
||||||
const res = await requestClient.download('/test/download');
|
const res = await requestClient.download<any>('/test/download');
|
||||||
|
|
||||||
expect(res.data).toBeInstanceOf(Blob);
|
expect(res.data).toBeInstanceOf(Blob);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ function applyPreset(type: 'compact' | 'focus' | 'review') {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
updateCustomPreferences<PlaygroundPreferencesExtension>(presetMap[type]);
|
updateCustomPreferences(presetMap[type]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPriorityColor(priority: DemoTaskItem['priority']) {
|
function getPriorityColor(priority: DemoTaskItem['priority']) {
|
||||||
|
|
@ -136,7 +136,7 @@ function getPriorityColor(priority: DemoTaskItem['priority']) {
|
||||||
:title="$t('demos.preferencesExtensionDemo.currentConfig')"
|
:title="$t('demos.preferencesExtensionDemo.currentConfig')"
|
||||||
>
|
>
|
||||||
<Alert :type="toneConfig.alertType" show-icon>
|
<Alert :type="toneConfig.alertType" show-icon>
|
||||||
<template #message>
|
<template #title>
|
||||||
{{
|
{{
|
||||||
$t('demos.preferencesExtensionDemo.currentTitle', {
|
$t('demos.preferencesExtensionDemo.currentTitle', {
|
||||||
title: playgroundPreferences.reportTitle,
|
title: playgroundPreferences.reportTitle,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue