From 1a357b4b80ae134695f27ee7463eba10dd7ec050 Mon Sep 17 00:00:00 2001 From: SuperCuteXiaoSi <1531733886@qq.com> Date: Sat, 30 Jul 2022 11:59:38 +0800 Subject: [PATCH] =?UTF-8?q?feature:=20=E5=B0=81=E8=A3=85storage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 + public/serverConfig.json | 8 +- src/config/index.ts | 2 + src/locales/index.ts | 4 +- src/store/modules/app.ts | 4 +- src/store/modules/permission.ts | 9 +- src/store/types.ts | 2 + src/utils/index.ts | 13 --- src/utils/storage.ts | 190 ++++++++++++++++++++++++++++++-- types/global.d.ts | 7 +- types/index.d.ts | 2 + 11 files changed, 207 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index de1fe258..269315df 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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", diff --git a/public/serverConfig.json b/public/serverConfig.json index 84fabf43..7232b813 100644 --- a/public/serverConfig.json +++ b/public/serverConfig.json @@ -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 + } } \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts index 15168c7e..4d035138 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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; @@ -22,6 +23,7 @@ export async function getServerConfig(app: App): Promise { throw `\npublic文件夹下无法查找到serverConfig配置文件\nUnable to find serverconfig configuration file under public folder`; } } + setStorageConfig(config.StorageConfig); app.config.globalProperties.$config = getConfig(); return config; } diff --git a/src/locales/index.ts b/src/locales/index.ts index 276a467c..641ea0ab 100644 --- a/src/locales/index.ts +++ b/src/locales/index.ts @@ -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[] = []; @@ -22,8 +22,6 @@ if (locStoAPP) { appConfigMode = JSON.parse(locStoAPP); } -// console.log(getConfig()); - const i18n = createI18n({ legacy: false, locale: appConfigMode.locale || 'zh-ch', diff --git a/src/store/modules/app.ts b/src/store/modules/app.ts index 27c9fafe..9435558e 100644 --- a/src/store/modules/app.ts +++ b/src/store/modules/app.ts @@ -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', diff --git a/src/store/modules/permission.ts b/src/store/modules/permission.ts index c4f87b9b..36577da8 100644 --- a/src/store/modules/permission.ts +++ b/src/store/modules/permission.ts @@ -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 => ({ @@ -14,7 +15,7 @@ const usePermissionStore = defineStore({ // 缓存页面keepAlive cachePageList: [], // 标签页(路由记录) - multiTabs: getlocalStorage('multiTabsList') || [], + multiTabs: getStorage('multiTabsList') || [], }), actions: { setWholeMenus(routeList: AppRouteRecordRaw[]) { @@ -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(); }, diff --git a/src/store/types.ts b/src/store/types.ts index 652db4a0..4f1bee2f 100644 --- a/src/store/types.ts +++ b/src/store/types.ts @@ -1,3 +1,4 @@ +import { StorageConfig } from '#/global'; import { AppRouteRecordRaw } from '#/route'; import { RouteRecordName, _RouteLocationBase } from 'vue-router'; @@ -13,6 +14,7 @@ export interface appConfig { sidebarMode: SidebarMode; themeMode: string; locale: string; + StorageConfig: StorageConfig; } export type MultiTabsType = Omit< diff --git a/src/utils/index.ts b/src/utils/index.ts index f901d423..c046bcca 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -14,19 +14,6 @@ export const configMainGlobalProperties = (app: App): void => { */ }; -// // 延迟进入vue,显示loding页 -// export const getServerConfig = (): Promise => { -// const appConfigMode = localStorage.getItem('appConfigMode'); -// if (appConfigMode) { -// setWindowAppConfig(JSON.parse(appConfigMode)); -// } -// return new Promise((resolve) => { -// resolve(''); - -// setTimeout(() => {}, 0); -// }); -// }; - export const withInstall = (component: T, alias?: string) => { const comp = component as any; comp.install = (app: App) => { diff --git a/src/utils/storage.ts b/src/utils/storage.ts index e076b2f9..08fb8aea 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -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 | 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 = (key: string, value: StorageValue, 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 = (key: string): StorageValue => { + 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(); +}; diff --git a/types/global.d.ts b/types/global.d.ts index ff8380c2..e292ec60 100644 --- a/types/global.d.ts +++ b/types/global.d.ts @@ -4,4 +4,9 @@ declare module '*.vue' { export default Component; } -declare type Recordable = Record; +export interface StorageConfig { + type: 'localStorage' | 'sessionStorage'; + prefix: string; + expire: number; + isEncrypt: boolean; +} diff --git a/types/index.d.ts b/types/index.d.ts index 021794bb..54dc7342 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,5 +1,7 @@ declare type RefType = T | null; +declare type Recordable = Record; + declare interface Fn { (...arg: T[]): R; }