32 changed files with 585 additions and 550 deletions
@ -0,0 +1,3 @@ |
|||
declare module '@tailwindcss/nesting' { |
|||
export default any; |
|||
} |
|||
@ -1,22 +0,0 @@ |
|||
import type { Config } from 'tailwindcss'; |
|||
|
|||
import plugin from 'tailwindcss/plugin'; |
|||
|
|||
const flexCenterStyles = { |
|||
'align-items': 'center', |
|||
display: 'flex', |
|||
'justify-content': 'center', |
|||
}; |
|||
|
|||
const plugins = [ |
|||
plugin(({ addUtilities }) => { |
|||
addUtilities({ |
|||
'.flex-center': flexCenterStyles, |
|||
'.flex-col-center': { |
|||
...flexCenterStyles, |
|||
}, |
|||
}); |
|||
}), |
|||
] as unknown as Config['plugins'][]; |
|||
|
|||
export { plugins }; |
|||
@ -0,0 +1,7 @@ |
|||
import { defineBuildConfig } from 'unbuild'; |
|||
|
|||
export default defineBuildConfig({ |
|||
clean: true, |
|||
declaration: true, |
|||
entries: ['src/index'], |
|||
}); |
|||
@ -0,0 +1,43 @@ |
|||
{ |
|||
"name": "@vben-core/cache", |
|||
"version": "1.0.0", |
|||
"type": "module", |
|||
"license": "MIT", |
|||
"homepage": "https://github.com/vbenjs/vue-vben-admin", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git", |
|||
"directory": "packages/@vben-core/shared/toolkit" |
|||
}, |
|||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues", |
|||
"scripts": { |
|||
"build": "pnpm unbuild", |
|||
"stub": "pnpm unbuild --stub" |
|||
}, |
|||
"files": [ |
|||
"dist" |
|||
], |
|||
"sideEffects": false, |
|||
"main": "./dist/index.mjs", |
|||
"module": "./dist/index.mjs", |
|||
"imports": { |
|||
"#*": "./src/*" |
|||
}, |
|||
"exports": { |
|||
".": { |
|||
"types": "./src/index.ts", |
|||
"development": "./src/index.ts", |
|||
"default": "./dist/index.mjs" |
|||
} |
|||
}, |
|||
"publishConfig": { |
|||
"exports": { |
|||
".": { |
|||
"types": "./dist/index.d.ts", |
|||
"default": "./dist/index.mjs" |
|||
} |
|||
} |
|||
}, |
|||
"dependencies": {}, |
|||
"devDependencies": {} |
|||
} |
|||
@ -0,0 +1 @@ |
|||
export * from './storage-cache'; |
|||
@ -0,0 +1,104 @@ |
|||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; |
|||
|
|||
import { StorageCache } from './storage-cache'; |
|||
|
|||
describe('storageCache', () => { |
|||
let localStorageCache: StorageCache; |
|||
let sessionStorageCache: StorageCache; |
|||
|
|||
beforeEach(() => { |
|||
localStorageCache = new StorageCache('prefix_', 'localStorage'); |
|||
sessionStorageCache = new StorageCache('prefix_', 'sessionStorage'); |
|||
localStorage.clear(); |
|||
sessionStorage.clear(); |
|||
vi.useFakeTimers(); |
|||
}); |
|||
|
|||
afterEach(() => { |
|||
vi.useRealTimers(); |
|||
}); |
|||
|
|||
it('should set and get an item with prefix in localStorage', () => { |
|||
localStorageCache.setItem('testKey', 'testValue'); |
|||
const value = localStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBe('testValue'); |
|||
expect(localStorage.getItem('prefix_testKey')).not.toBeNull(); |
|||
}); |
|||
|
|||
it('should set and get an item with prefix in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey', 'testValue'); |
|||
const value = sessionStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBe('testValue'); |
|||
expect(sessionStorage.getItem('prefix_testKey')).not.toBeNull(); |
|||
}); |
|||
|
|||
it('should return null for expired item in localStorage', () => { |
|||
localStorageCache.setItem('testKey', 'testValue', 1 / 60); // 1 second expiry
|
|||
vi.advanceTimersByTime(2000); // Fast-forward 2 seconds
|
|||
const value = localStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBeNull(); |
|||
}); |
|||
|
|||
it('should return null for expired item in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey', 'testValue', 1 / 60); // 1 second expiry
|
|||
vi.advanceTimersByTime(2000); // Fast-forward 2 seconds
|
|||
const value = sessionStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBeNull(); |
|||
}); |
|||
|
|||
it('should remove an item with prefix in localStorage', () => { |
|||
localStorageCache.setItem('testKey', 'testValue'); |
|||
localStorageCache.removeItem('testKey'); |
|||
const value = localStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBeNull(); |
|||
expect(localStorage.getItem('prefix_testKey')).toBeNull(); |
|||
}); |
|||
|
|||
it('should remove an item with prefix in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey', 'testValue'); |
|||
sessionStorageCache.removeItem('testKey'); |
|||
const value = sessionStorageCache.getItem<string>('testKey'); |
|||
expect(value).toBeNull(); |
|||
expect(sessionStorage.getItem('prefix_testKey')).toBeNull(); |
|||
}); |
|||
|
|||
it('should clear all items in localStorage', () => { |
|||
localStorageCache.setItem('testKey1', 'testValue1'); |
|||
localStorageCache.setItem('testKey2', 'testValue2'); |
|||
localStorageCache.clear(); |
|||
expect(localStorageCache.length()).toBe(0); |
|||
}); |
|||
|
|||
it('should clear all items in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey1', 'testValue1'); |
|||
sessionStorageCache.setItem('testKey2', 'testValue2'); |
|||
sessionStorageCache.clear(); |
|||
expect(sessionStorageCache.length()).toBe(0); |
|||
}); |
|||
|
|||
it('should return correct length in localStorage', () => { |
|||
localStorageCache.setItem('testKey1', 'testValue1'); |
|||
localStorageCache.setItem('testKey2', 'testValue2'); |
|||
expect(localStorageCache.length()).toBe(2); |
|||
}); |
|||
|
|||
it('should return correct length in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey1', 'testValue1'); |
|||
sessionStorageCache.setItem('testKey2', 'testValue2'); |
|||
expect(sessionStorageCache.length()).toBe(2); |
|||
}); |
|||
|
|||
it('should return correct key by index in localStorage', () => { |
|||
localStorageCache.setItem('testKey1', 'testValue1'); |
|||
localStorageCache.setItem('testKey2', 'testValue2'); |
|||
expect(localStorageCache.key(0)).toBe('prefix_testKey1'); |
|||
expect(localStorageCache.key(1)).toBe('prefix_testKey2'); |
|||
}); |
|||
|
|||
it('should return correct key by index in sessionStorage', () => { |
|||
sessionStorageCache.setItem('testKey1', 'testValue1'); |
|||
sessionStorageCache.setItem('testKey2', 'testValue2'); |
|||
expect(sessionStorageCache.key(0)).toBe('prefix_testKey1'); |
|||
expect(sessionStorageCache.key(1)).toBe('prefix_testKey2'); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,145 @@ |
|||
import type { IStorageCache, StorageType, StorageValue } from './types'; |
|||
|
|||
class StorageCache implements IStorageCache { |
|||
protected prefix: string; |
|||
protected storage: Storage; |
|||
|
|||
constructor(prefix: string = '', storageType: StorageType = 'localStorage') { |
|||
this.prefix = prefix; |
|||
this.storage = |
|||
storageType === 'localStorage' ? localStorage : sessionStorage; |
|||
} |
|||
|
|||
// 获取带前缀的键名
|
|||
private getFullKey(key: string): string { |
|||
return this.prefix + key; |
|||
} |
|||
|
|||
// 获取项之后的钩子方法
|
|||
protected afterGetItem<T>(_key: string, _value: T | null): void {} |
|||
|
|||
// 设置项之后的钩子方法
|
|||
protected afterSetItem<T>( |
|||
_key: string, |
|||
_value: T, |
|||
_expiryInMinutes?: number, |
|||
): void {} |
|||
|
|||
// 获取项之前的钩子方法
|
|||
protected beforeGetItem(_key: string): void {} |
|||
|
|||
// 设置项之前的钩子方法
|
|||
protected beforeSetItem<T>( |
|||
_key: string, |
|||
_value: T, |
|||
_expiryInMinutes?: number, |
|||
): void {} |
|||
|
|||
/** |
|||
* 清空存储 |
|||
*/ |
|||
clear(): void { |
|||
try { |
|||
this.storage.clear(); |
|||
} catch (error) { |
|||
console.error('Error clearing storage', error); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取存储项 |
|||
* @param key 存储键 |
|||
* @returns 存储值或 null |
|||
*/ |
|||
getItem<T>(key: string): T | null { |
|||
const fullKey = this.getFullKey(key); |
|||
this.beforeGetItem(fullKey); |
|||
|
|||
let value: T | null = null; |
|||
try { |
|||
const item = this.storage.getItem(fullKey); |
|||
if (item) { |
|||
const storageValue: StorageValue<T> = JSON.parse(item); |
|||
if (storageValue.expiry && storageValue.expiry < Date.now()) { |
|||
this.storage.removeItem(fullKey); |
|||
} else { |
|||
value = storageValue.data; |
|||
} |
|||
} |
|||
} catch (error) { |
|||
console.error('Error getting item from storage', error); |
|||
} |
|||
|
|||
this.afterGetItem(fullKey, value); |
|||
return value; |
|||
} |
|||
|
|||
/** |
|||
* 获取存储中的键 |
|||
* @param index 键的索引 |
|||
* @returns 存储键或 null |
|||
*/ |
|||
key(index: number): null | string { |
|||
try { |
|||
return this.storage.key(index); |
|||
} catch (error) { |
|||
console.error('Error getting key from storage', error); |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取存储项的数量 |
|||
* @returns 存储项的数量 |
|||
*/ |
|||
length(): number { |
|||
try { |
|||
return this.storage.length; |
|||
} catch (error) { |
|||
console.error('Error getting storage length', error); |
|||
return 0; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 删除存储项 |
|||
* @param key 存储键 |
|||
*/ |
|||
removeItem(key: string): void { |
|||
const fullKey = this.getFullKey(key); |
|||
try { |
|||
this.storage.removeItem(fullKey); |
|||
} catch (error) { |
|||
console.error('Error removing item from storage', error); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 设置存储项 |
|||
* @param key 存储键 |
|||
* @param value 存储值 |
|||
* @param expiryInMinutes 过期时间(分钟) |
|||
*/ |
|||
setItem<T>(key: string, value: T, expiryInMinutes?: number): void { |
|||
const fullKey = this.getFullKey(key); |
|||
this.beforeSetItem(fullKey, value, expiryInMinutes); |
|||
|
|||
const now = Date.now(); |
|||
const expiry = expiryInMinutes ? now + expiryInMinutes * 60_000 : null; |
|||
|
|||
const storageValue: StorageValue<T> = { |
|||
data: value, |
|||
expiry, |
|||
}; |
|||
|
|||
try { |
|||
this.storage.setItem(fullKey, JSON.stringify(storageValue)); |
|||
} catch (error) { |
|||
console.error('Error setting item in storage', error); |
|||
} |
|||
|
|||
this.afterSetItem(fullKey, value, expiryInMinutes); |
|||
} |
|||
} |
|||
|
|||
export { StorageCache }; |
|||
@ -0,0 +1,17 @@ |
|||
type StorageType = 'localStorage' | 'sessionStorage'; |
|||
|
|||
interface StorageValue<T> { |
|||
data: T; |
|||
expiry: null | number; |
|||
} |
|||
|
|||
interface IStorageCache { |
|||
clear(): void; |
|||
getItem<T>(key: string): T | null; |
|||
key(index: number): null | string; |
|||
length(): number; |
|||
removeItem(key: string): void; |
|||
setItem<T>(key: string, value: T, expiryInMinutes?: number): void; |
|||
} |
|||
|
|||
export type { IStorageCache, StorageType, StorageValue }; |
|||
@ -0,0 +1,5 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/library.json", |
|||
"include": ["src"] |
|||
} |
|||
@ -1,6 +1,4 @@ |
|||
:root.dark { |
|||
/* authentication */ |
|||
--color-authentication: hsl(240deg 11% 2%); |
|||
|
|||
color-scheme: dark; |
|||
} |
|||
|
|||
File diff suppressed because it is too large
Loading…
Reference in new issue