Skip to content

Commit

Permalink
feature: 封装storage
Browse files Browse the repository at this point in the history
  • Loading branch information
jsxiaosi committed Jul 30, 2022
1 parent dd1d9f7 commit 1a357b4
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 36 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@commitlint/config-conventional": "^17.0.3",
"@ehutch79/vite-eslint": "^0.0.1",
"@types/core-js": "^2.5.5",
"@types/crypto-js": "^4.1.1",
"@types/intro.js": "^5.1.0",
"@types/lodash-es": "^4.17.6",
"@types/marked": "^4.0.3",
Expand All @@ -70,6 +71,7 @@
"autoprefixer": "^10.4.7",
"babel-eslint": "^10.1.0",
"commitizen": "^4.2.5",
"crypto-js": "^4.1.1",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.9.1",
"eslint": "^8.20.0",
Expand Down
8 changes: 7 additions & 1 deletion public/serverConfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
"collapseMenu": false,
"sidebarMode": "vertical",
"themeMode": "day",
"locale": "zh-ch"
"locale": "zh-ch",
"StorageConfig":{
"type": "localStorage",
"prefix": "xiaosiAdmin",
"expire": 0,
"isEncrypt": false
}
}
2 changes: 2 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getConfigInfo } from '@/server/config';
import { appConfig } from '@/store/types';
import { setStorageConfig } from '@/utils/storage';
import { App } from 'vue';

let config: appConfig = {} as appConfig;
Expand All @@ -22,6 +23,7 @@ export async function getServerConfig(app: App): Promise<appConfig> {
throw `\npublic文件夹下无法查找到serverConfig配置文件\nUnable to find serverconfig configuration file under public folder`;
}
}
setStorageConfig(config.StorageConfig);
app.config.globalProperties.$config = getConfig();
return config;
}
4 changes: 1 addition & 3 deletions src/locales/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ interface localesType {
locale: string;
}

const config = import.meta.globEager('./**/index.ts');
const config: Recordable = import.meta.globEager('./**/index.ts');

const messages: any = {};
const localesList: localesType[] = [];
Expand All @@ -22,8 +22,6 @@ if (locStoAPP) {
appConfigMode = JSON.parse(locStoAPP);
}

// console.log(getConfig());

const i18n = createI18n({
legacy: false,
locale: appConfigMode.locale || 'zh-ch',
Expand Down
4 changes: 2 additions & 2 deletions src/store/modules/app.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { defineStore } from 'pinia';
import { getConfig } from '@/config';
import { store } from '@/store';
import type { AppState, appConfig } from '../types';
import { getConfig } from '@/config';

const localAppConfig: appConfig = getConfig();
export const localAppConfig: appConfig = getConfig();

const useAppStore = defineStore({
id: 'app',
Expand Down
9 changes: 5 additions & 4 deletions src/store/modules/permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { RouteRecordName } from 'vue-router';
import type { MultiTabsType, PermissionState } from '../types';
import { AppRouteRecordRaw } from '#/route';
import { isEqual } from 'lodash';
import { getlocalStorage, setlocalStorage } from '@/utils/storage';
import { getStorage, removeStorage, setStorage } from '@/utils/storage';

// console.log(getStorage('multiTabsList'));
const usePermissionStore = defineStore({
id: 'permission',
state: (): PermissionState => ({
Expand All @@ -14,7 +15,7 @@ const usePermissionStore = defineStore({
// 缓存页面keepAlive
cachePageList: [],
// 标签页(路由记录)
multiTabs: getlocalStorage('multiTabsList') || [],
multiTabs: getStorage<MultiTabsType[]>('multiTabsList') || [],
}),
actions: {
setWholeMenus(routeList: AppRouteRecordRaw[]) {
Expand Down Expand Up @@ -53,10 +54,10 @@ const usePermissionStore = defineStore({
default:
break;
}
setlocalStorage('multiTabsList', this.multiTabs);
setStorage('multiTabsList', this.multiTabs);
},
handleRemoveMultiTabs() {
setlocalStorage('multiTabsList');
removeStorage('multiTabsList');
this.multiTabs = [];
this.clearAllCachePage();
},
Expand Down
2 changes: 2 additions & 0 deletions src/store/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { StorageConfig } from '#/global';
import { AppRouteRecordRaw } from '#/route';
import { RouteRecordName, _RouteLocationBase } from 'vue-router';

Expand All @@ -13,6 +14,7 @@ export interface appConfig {
sidebarMode: SidebarMode;
themeMode: string;
locale: string;
StorageConfig: StorageConfig;
}

export type MultiTabsType = Omit<
Expand Down
13 changes: 0 additions & 13 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,6 @@ export const configMainGlobalProperties = (app: App<Element>): void => {
*/
};

// // 延迟进入vue,显示loding页
// export const getServerConfig = (): Promise<string> => {
// const appConfigMode = localStorage.getItem('appConfigMode');
// if (appConfigMode) {
// setWindowAppConfig(JSON.parse(appConfigMode));
// }
// return new Promise((resolve) => {
// resolve('');

// setTimeout(() => {}, 0);
// });
// };

export const withInstall = <T>(component: T, alias?: string) => {
const comp = component as any;
comp.install = (app: App) => {
Expand Down
190 changes: 178 additions & 12 deletions src/utils/storage.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,182 @@
export function setlocalStorage(key: string, value?: Recordable) {
if (value) {
localStorage.setItem(key, JSON.stringify(value));
} else {
localStorage.removeItem(key);
import { StorageConfig } from '#/global';

import CryptoJS from 'crypto-js';

type StorageValue<T> = T | null | undefined;

// 十六位十六进制数作为密钥
const SECRET_KEY = CryptoJS.enc.Utf8.parse('3333e6e143439161');
// 十六位十六进制数作为密钥偏移量
const SECRET_IV = CryptoJS.enc.Utf8.parse('e3bbe7e3ba84431a');

// 类型 window.localStorage,window.sessionStorage,
let config: StorageConfig = {
type: 'localStorage', // 本地存储类型 sessionStorage
prefix: 'xiaosiAdmin', // 名称前缀 建议:项目名 + 项目版本
expire: 0, //过期时间 单位:秒
isEncrypt: false, // 默认加密 为了调试方便, 开发过程中可以不加密
};

// 根据请求配置替换默认config
export const setStorageConfig = (info: StorageConfig) => {
config = { ...config, ...info };
};

// 判断是否支持 Storage
export const isSupportStorage = () => {
return typeof Storage !== 'undefined' ? true : false;
};

// 设置 setStorage
export const setStorage = <T>(key: string, value: StorageValue<T>, expire = 0) => {
if (value === null || value === undefined) {
value = null;
}
}

export function getlocalStorage(key: string) {
const value = localStorage.getItem(key);
if (value) {
return JSON.parse(value);
} else {
if (isNaN(expire) || expire < 0) throw new Error('Expire 必须是数字');

if (config.expire > 0 || expire > 0) expire = (expire ? expire : config.expire) * 1000;
const data = {
value: value, // 存储值
time: Date.now(), //存值时间戳
expire: expire, // 过期时间
};

const encryptString = config.isEncrypt ? encrypt(JSON.stringify(data)) : JSON.stringify(data);

window[config.type].setItem(autoAddPrefix(key), encryptString);
};

// 获取 getStorage
export const getStorage = <T>(key: string): StorageValue<T> => {
key = autoAddPrefix(key);
// key 不存在判断
if (
!window[config.type].getItem(key) ||
JSON.stringify(window[config.type].getItem(key)) === 'null'
) {
return null;
}
}

// 优化 持续使用中续期
const storage = config.isEncrypt
? JSON.parse(decrypt(window[config.type].getItem(key) || ''))
: JSON.parse(window[config.type].getItem(key) || '');

const nowTime = Date.now();

// 过期删除
if (storage.expire && config.expire * 6000 < nowTime - storage.time) {
removeStorage(key);
return null;
} else {
// 未过期期间被调用 则自动续期 进行保活
setStorage(autoRemovePrefix(key), storage.value);
return storage.value;
}
};

// 是否存在 hasStorage
export const hasStorage = (key: string): boolean => {
key = autoAddPrefix(key);
const arr = getStorageAll().filter((item) => {
return item.key === key;
});
return arr.length ? true : false;
};

// 获取所有key
export const getStorageKeys = (): (string | null)[] => {
const items = getStorageAll();
const keys = [];
for (let index = 0; index < items.length; index++) {
keys.push(items[index].key);
}
return keys;
};

// 根据索引获取key
export const getStorageForIndex = (index: number) => {
return window[config.type].key(index);
};

// 获取localStorage长度
export const getStorageLength = () => {
return window[config.type].length;
};

// 获取全部 getAllStorage
export const getStorageAll = () => {
const len = window[config.type].length; // 获取长度
const arr = []; // 定义数据集
for (let i = 0; i < len; i++) {
// 获取key 索引从0开始
const getKey = window[config.type].key(i) || '';
// 获取key对应的值
const getVal = window[config.type].getItem(getKey);
// 放进数组
arr[i] = { key: getKey, val: getVal };
}
return arr;
};

// 删除 removeStorage
export const removeStorage = (key: string) => {
window[config.type].removeItem(autoAddPrefix(key));
};

// 清空 clearStorage
export const clearStorage = () => {
window[config.type].clear();
};

// 名称前自动添加前缀
const autoAddPrefix = (key: string): string => {
const prefix = config.prefix ? config.prefix + '_' : '';
return prefix + key;
};

// 移除已添加的前缀
const autoRemovePrefix = (key: string) => {
const len = config.prefix ? config.prefix.length + 1 : 0;
return key.substr(len);
};

/**
* 加密方法
* @param data
* @returns {string}
*/
const encrypt = (data: string): string => {
if (typeof data === 'object') {
try {
data = JSON.stringify(data);
} catch (error) {
console.error('encrypt error:', error);
}
}
const dataHex = CryptoJS.enc.Utf8.parse(data);
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
return encrypted.ciphertext.toString();
};

/**
* 解密方法
* @param data
* @returns {string}
*/
const decrypt = (data: string): string => {
const encryptedHexStr = CryptoJS.enc.Hex.parse(data);
const str = CryptoJS.enc.Base64.stringify(encryptedHexStr);
const decrypt = CryptoJS.AES.decrypt(str, SECRET_KEY, {
iv: SECRET_IV,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
});
const decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
};
7 changes: 6 additions & 1 deletion types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@ declare module '*.vue' {
export default Component;
}

declare type Recordable<T = any> = Record<string, T>;
export interface StorageConfig {
type: 'localStorage' | 'sessionStorage';
prefix: string;
expire: number;
isEncrypt: boolean;
}
2 changes: 2 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
declare type RefType<T> = T | null;

declare type Recordable<T = any> = Record<string, T>;

declare interface Fn<T = any, R = T> {
(...arg: T[]): R;
}
Expand Down

0 comments on commit 1a357b4

Please sign in to comment.