refactor(projects): new storage system [新的本地数据存储系统]

This commit is contained in:
Soybean 2022-11-17 01:47:06 +08:00
parent 7a58035514
commit 971915948b
23 changed files with 166 additions and 191 deletions

View File

@ -16,9 +16,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { EnumStorageKey } from '@/enum';
import { useAppInfo } from '@/composables'; import { useAppInfo } from '@/composables';
import { getLocal } from '@/utils'; import { localStg } from '@/utils';
const { title } = useAppInfo(); const { title } = useAppInfo();
@ -31,7 +30,7 @@ const lodingClasses = [
function addThemeColorCssVars() { function addThemeColorCssVars() {
const defaultColor = '#1890ff'; const defaultColor = '#1890ff';
const themeColor = getLocal(EnumStorageKey['theme-color']) || defaultColor; const themeColor = localStg.get('themeColor') || defaultColor;
const cssVars = `--primary-color: ${themeColor}`; const cssVars = `--primary-color: ${themeColor}`;
document.documentElement.style.cssText = cssVars; document.documentElement.style.cssText = cssVars;
} }

View File

@ -5,22 +5,6 @@ export enum EnumContentType {
formData = 'multipart/form-data' formData = 'multipart/form-data'
} }
/** 缓存的key */
export enum EnumStorageKey {
/** 主题颜色 */
'theme-color' = '__THEME_COLOR__',
/** 用户token */
'token' = '__TOKEN__',
/** 用户刷新token */
'refresh-token' = '__REFRESH_TOKEN__',
/** 用户信息 */
'user-info' = '__USER_INFO__',
/** 主题配置 */
'theme-settings' = '__THEME_SETTINGS__',
/** 多页签路由信息 */
'multi-tab-routes' = '__MULTI_TAB_ROUTES__'
}
/** 数据类型 */ /** 数据类型 */
export enum EnumDataType { export enum EnumDataType {
number = '[object Number]', number = '[object Number]',

View File

@ -1,7 +1,7 @@
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import { routeName } from '@/router'; import { routeName } from '@/router';
import { useRouteStore } from '@/store'; import { useRouteStore } from '@/store';
import { getToken } from '@/utils'; import { localStg } from '@/utils';
/** /**
* *
@ -12,7 +12,7 @@ export async function createDynamicRouteGuard(
next: NavigationGuardNext next: NavigationGuardNext
) { ) {
const route = useRouteStore(); const route = useRouteStore();
const isLogin = Boolean(getToken()); const isLogin = Boolean(localStg.get('token'));
// 初始化权限路由 // 初始化权限路由
if (!route.isInitAuthRoute) { if (!route.isInitAuthRoute) {

View File

@ -1,7 +1,7 @@
import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'; import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
import { routeName } from '@/router'; import { routeName } from '@/router';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
import { exeStrategyActions, getToken } from '@/utils'; import { exeStrategyActions, localStg } from '@/utils';
import { createDynamicRouteGuard } from './dynamic'; import { createDynamicRouteGuard } from './dynamic';
/** 处理路由页面的权限 */ /** 处理路由页面的权限 */
@ -22,7 +22,7 @@ export async function createPermissionGuard(
} }
const auth = useAuthStore(); const auth = useAuthStore();
const isLogin = Boolean(getToken()); const isLogin = Boolean(localStg.get('token'));
const permissions = to.meta.permissions || []; const permissions = to.meta.permissions || [];
const needLogin = Boolean(to.meta?.requiresAuth) || Boolean(permissions.length); const needLogin = Boolean(to.meta?.requiresAuth) || Boolean(permissions.length);
const hasPermission = !permissions.length || permissions.includes(auth.userInfo.userRole); const hasPermission = !permissions.length || permissions.includes(auth.userInfo.userRole);

View File

@ -1,6 +1,6 @@
import type { AxiosRequestConfig } from 'axios'; import type { AxiosRequestConfig } from 'axios';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
import { getRefreshToken, setRefreshToken, setToken } from '@/utils'; import { localStg } from '@/utils';
import { fetchUpdateToken } from '../api'; import { fetchUpdateToken } from '../api';
/** /**
@ -9,11 +9,12 @@ import { fetchUpdateToken } from '../api';
*/ */
export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) { export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
const { resetAuthStore } = useAuthStore(); const { resetAuthStore } = useAuthStore();
const refreshToken = getRefreshToken(); const refreshToken = localStg.get('refreshToken') || '';
const { data } = await fetchUpdateToken(refreshToken); const { data } = await fetchUpdateToken(refreshToken);
if (data) { if (data) {
setToken(data.token); localStg.set('token', data.token);
setRefreshToken(data.refreshToken); localStg.set('refreshToken', data.refreshToken);
const config = { ...axiosConfig }; const config = { ...axiosConfig };
if (config.headers) { if (config.headers) {
config.headers.Authorization = data.token; config.headers.Authorization = data.token;

View File

@ -2,7 +2,7 @@ import axios from 'axios';
import type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'; import type { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios';
import { REFRESH_TOKEN_CODE } from '@/config'; import { REFRESH_TOKEN_CODE } from '@/config';
import { import {
getToken, localStg,
handleAxiosError, handleAxiosError,
handleBackendError, handleBackendError,
handleResponseError, handleResponseError,
@ -49,7 +49,7 @@ export default class CustomAxiosInstance {
const contentType = handleConfig.headers['Content-Type'] as string; const contentType = handleConfig.headers['Content-Type'] as string;
handleConfig.data = await transformRequestData(handleConfig.data, contentType); handleConfig.data = await transformRequestData(handleConfig.data, contentType);
// 设置token // 设置token
handleConfig.headers.Authorization = getToken(); handleConfig.headers.Authorization = localStg.get('token') || '';
} }
return handleConfig; return handleConfig;
}, },

View File

@ -0,0 +1,25 @@
import { localStg } from '@/utils';
/** 获取token */
export function getToken() {
return localStg.get('token') || '';
}
/** 获取用户信息 */
export function getUserInfo() {
const emptyInfo: Auth.UserInfo = {
userId: '',
userName: '',
userRole: 'user'
};
const userInfo: Auth.UserInfo = localStg.get('userInfo') || emptyInfo;
return userInfo;
}
/** 去除用户相关缓存 */
export function clearAuthStorage() {
localStg.remove('token');
localStg.remove('refreshToken');
localStg.remove('userInfo');
}

View File

@ -3,9 +3,10 @@ import { defineStore } from 'pinia';
import { router } from '@/router'; import { router } from '@/router';
import { fetchLogin, fetchUserInfo } from '@/service'; import { fetchLogin, fetchUserInfo } from '@/service';
import { useRouterPush } from '@/composables'; import { useRouterPush } from '@/composables';
import { clearAuthStorage, getToken, getUserInfo, setRefreshToken, setToken, setUserInfo } from '@/utils'; import { localStg } from '@/utils';
import { useTabStore } from '../tab'; import { useTabStore } from '../tab';
import { useRouteStore } from '../route'; import { useRouteStore } from '../route';
import { getToken, getUserInfo, clearAuthStorage } from './helpers';
interface AuthState { interface AuthState {
/** 用户信息 */ /** 用户信息 */
@ -81,14 +82,14 @@ export const useAuthStore = defineStore('auth-store', {
// 先把token存储到缓存中(后面接口的请求头需要token) // 先把token存储到缓存中(后面接口的请求头需要token)
const { token, refreshToken } = backendToken; const { token, refreshToken } = backendToken;
setToken(token); localStg.set('token', token);
setRefreshToken(refreshToken); localStg.set('refreshToken', refreshToken);
// 获取用户信息 // 获取用户信息
const { data } = await fetchUserInfo(); const { data } = await fetchUserInfo();
if (data) { if (data) {
// 成功后把用户信息存储到缓存中 // 成功后把用户信息存储到缓存中
setUserInfo(data); localStg.set('userInfo', data);
// 更新状态 // 更新状态
this.userInfo = data; this.userInfo = data;

View File

@ -2,10 +2,10 @@ import { defineStore } from 'pinia';
import { ROOT_ROUTE, constantRoutes, router, routes as staticRoutes } from '@/router'; import { ROOT_ROUTE, constantRoutes, router, routes as staticRoutes } from '@/router';
import { fetchUserRoutes } from '@/service'; import { fetchUserRoutes } from '@/service';
import { import {
localStg,
filterAuthRoutesByUserPermission, filterAuthRoutesByUserPermission,
getCacheRoutes, getCacheRoutes,
getConstantRouteNames, getConstantRouteNames,
getUserInfo,
transformAuthRouteToVueRoutes, transformAuthRouteToVueRoutes,
transformAuthRouteToVueRoute, transformAuthRouteToVueRoute,
transformAuthRouteToMenu, transformAuthRouteToMenu,
@ -106,7 +106,12 @@ export const useRouteStore = defineStore('route-store', {
}, },
/** 初始化动态路由 */ /** 初始化动态路由 */
async initDynamicRoute() { async initDynamicRoute() {
const { userId } = getUserInfo(); const { userId } = localStg.get('userInfo') || {};
if (!userId) {
throw new Error('userId 不能为空!');
}
const { data } = await fetchUserRoutes(userId); const { data } = await fetchUserRoutes(userId);
if (data) { if (data) {
this.routeHomeName = data.home; this.routeHomeName = data.home;
@ -123,9 +128,6 @@ export const useRouteStore = defineStore('route-store', {
/** 初始化权限路由 */ /** 初始化权限路由 */
async initAuthRoute() { async initAuthRoute() {
const { initHomeTab } = useTabStore(); const { initHomeTab } = useTabStore();
const { userId } = getUserInfo();
if (!userId) return;
const isDynamicRoute = this.authRouteMode === 'dynamic'; const isDynamicRoute = this.authRouteMode === 'dynamic';
if (isDynamicRoute) { if (isDynamicRoute) {

View File

@ -1,6 +1,5 @@
import type { RouteLocationNormalizedLoaded, RouteRecordNormalized } from 'vue-router'; import type { RouteLocationNormalizedLoaded, RouteRecordNormalized } from 'vue-router';
import { EnumStorageKey } from '@/enum'; import { localStg } from '@/utils';
import { getLocal, setLocal } from '@/utils';
/** /**
* vue路由获取tab路由 * vue路由获取tab路由
@ -58,15 +57,10 @@ function hasFullPath(
return Boolean((route as RouteLocationNormalizedLoaded).fullPath); return Boolean((route as RouteLocationNormalizedLoaded).fullPath);
} }
/** 缓存多页签数据 */
export function setTabRoutes(data: App.GlobalTabRoute[]) {
setLocal(EnumStorageKey['multi-tab-routes'], data);
}
/** 获取缓存的多页签数据 */ /** 获取缓存的多页签数据 */
export function getTabRoutes() { export function getTabRoutes() {
const routes: App.GlobalTabRoute[] = []; const routes: App.GlobalTabRoute[] = [];
const data = getLocal<App.GlobalTabRoute[]>(EnumStorageKey['multi-tab-routes']); const data = localStg.get('multiTabRoutes');
if (data) { if (data) {
const defaultTabRoutes = data.map(item => ({ const defaultTabRoutes = data.map(item => ({
...item, ...item,
@ -82,5 +76,5 @@ export function getTabRoutes() {
/** 清空多页签数据 */ /** 清空多页签数据 */
export function clearTabRoutes() { export function clearTabRoutes() {
setTabRoutes([]); localStg.set('multiTabRoutes', []);
} }

View File

@ -1,6 +1,7 @@
import type { RouteLocationNormalizedLoaded, Router } from 'vue-router'; import type { RouteLocationNormalizedLoaded, Router } from 'vue-router';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { useRouterPush } from '@/composables'; import { useRouterPush } from '@/composables';
import { localStg } from '@/utils';
import { useThemeStore } from '../theme'; import { useThemeStore } from '../theme';
import { import {
clearTabRoutes, clearTabRoutes,
@ -8,8 +9,7 @@ import {
getIndexInTabRoutesByRouteName, getIndexInTabRoutesByRouteName,
getTabRouteByVueRoute, getTabRouteByVueRoute,
getTabRoutes, getTabRoutes,
isInTabRoutes, isInTabRoutes
setTabRoutes
} from './helpers'; } from './helpers';
interface TabState { interface TabState {
@ -52,7 +52,7 @@ export const useTabStore = defineStore('tab-store', {
}, },
/** 缓存页签路由数据 */ /** 缓存页签路由数据 */
cacheTabRoutes() { cacheTabRoutes() {
setTabRoutes(this.tabs); localStg.set('multiTabRoutes', this.tabs);
}, },
/** /**
* *

View File

@ -1,19 +1,18 @@
import type { GlobalThemeOverrides } from 'naive-ui'; import type { GlobalThemeOverrides } from 'naive-ui';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { themeSetting } from '@/settings'; import { themeSetting } from '@/settings';
import { EnumStorageKey } from '@/enum'; import { localStg, addColorAlpha, getColorPalette } from '@/utils';
import { addColorAlpha, getColorPalette, getLocal, getThemeColor, removeLocal, setLocal } from '@/utils';
/** 初始化主题配置 */ /** 初始化主题配置 */
export function initThemeSettings() { export function initThemeSettings() {
const isProd = import.meta.env.PROD; const isProd = import.meta.env.PROD;
// 生产环境才缓存主题配置本地开发实时调整配置更改配置的json // 生产环境才缓存主题配置本地开发实时调整配置更改配置的json
const storageSettings = getThemeSettings(); const storageSettings = localStg.get('themeSettings');
if (isProd && storageSettings) { if (isProd && storageSettings) {
return storageSettings; return storageSettings;
} }
const themeColor = getThemeColor() || themeSetting.themeColor; const themeColor = localStg.get('themeColor') || themeSetting.themeColor;
const info = themeSetting.isCustomizeInfoColor ? themeSetting.otherColor.info : getColorPalette(themeColor, 7); const info = themeSetting.isCustomizeInfoColor ? themeSetting.otherColor.info : getColorPalette(themeColor, 7);
const otherColor = { ...themeSetting.otherColor, info }; const otherColor = { ...themeSetting.otherColor, info };
const setting = cloneDeep({ ...themeSetting, themeColor, otherColor }); const setting = cloneDeep({ ...themeSetting, themeColor, otherColor });
@ -78,18 +77,3 @@ export function getNaiveThemeOverrides(colors: Record<ColorType, string>): Globa
} }
}; };
} }
/** 获取缓存中的主题配置 */
function getThemeSettings() {
return getLocal<Theme.Setting>(EnumStorageKey['theme-settings']);
}
/** 获取缓存中的主题配置 */
export function setThemeSettings(settings: Theme.Setting) {
return setLocal(EnumStorageKey['theme-settings'], settings);
}
/** 清除缓存配置 */
export function clearThemeSettings() {
removeLocal(EnumStorageKey['theme-settings']);
}

View File

@ -1,6 +1,7 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { darkTheme } from 'naive-ui'; import { darkTheme } from 'naive-ui';
import { clearThemeSettings, getNaiveThemeOverrides, initThemeSettings, setThemeSettings } from './helpers'; import { localStg } from '@/utils';
import { getNaiveThemeOverrides, initThemeSettings } from './helpers';
type ThemeState = Theme.Setting; type ThemeState = Theme.Setting;
@ -24,14 +25,14 @@ export const useThemeStore = defineStore('theme-store', {
actions: { actions: {
/** 重置theme状态 */ /** 重置theme状态 */
resetThemeStore() { resetThemeStore() {
clearThemeSettings(); localStg.remove('themeSettings');
this.$reset(); this.$reset();
}, },
/** 缓存主题配置 */ /** 缓存主题配置 */
cacheThemeSettings() { cacheThemeSettings() {
const isProd = import.meta.env.PROD; const isProd = import.meta.env.PROD;
if (isProd) { if (isProd) {
setThemeSettings(this.$state); localStg.set('themeSettings', this.$state);
} }
}, },
/** 设置暗黑模式 */ /** 设置暗黑模式 */

View File

@ -3,7 +3,7 @@ import { useOsTheme } from 'naive-ui';
import type { GlobalThemeOverrides } from 'naive-ui'; import type { GlobalThemeOverrides } from 'naive-ui';
import { useElementSize } from '@vueuse/core'; import { useElementSize } from '@vueuse/core';
import { kebabCase } from 'lodash-es'; import { kebabCase } from 'lodash-es';
import { setThemeColor } from '@/utils'; import { localStg } from '@/utils';
import { useThemeStore } from '../modules'; import { useThemeStore } from '../modules';
/** 订阅theme store */ /** 订阅theme store */
@ -17,7 +17,7 @@ export default function subscribeThemeStore() {
const stopThemeColor = watch( const stopThemeColor = watch(
() => theme.themeColor, () => theme.themeColor,
newValue => { newValue => {
setThemeColor(newValue); localStg.set('themeColor', newValue);
}, },
{ immediate: true } { immediate: true }
); );

22
src/typings/storage.d.ts vendored Normal file
View File

@ -0,0 +1,22 @@
declare namespace StorageInterface {
/** localStorage的存储数据的类型 */
interface Session {
demoKey: string;
}
/** localStorage的存储数据的类型 */
interface Local {
/** 主题颜色 */
themeColor: string;
/** 用户token */
token: string;
/** 用户刷新token */
refreshToken: string;
/** 用户信息 */
userInfo: Auth.UserInfo;
/** 主题配置 */
themeSettings: Theme.Setting;
/** 多页签路由信息 */
multiTabRoutes: App.GlobalTabRoute[];
}
}

View File

@ -1 +0,0 @@
export * from './user';

View File

@ -1,60 +0,0 @@
import { EnumStorageKey } from '@/enum';
import { getLocal, removeLocal, setLocal } from '../storage';
/** 设置token */
export function setToken(token: string) {
setLocal(EnumStorageKey.token, token);
}
/** 获取token */
export function getToken() {
return getLocal<string>(EnumStorageKey.token) || '';
}
/** 去除token */
export function removeToken() {
removeLocal(EnumStorageKey.token);
}
/** 获取refresh token */
export function getRefreshToken() {
return getLocal<string>(EnumStorageKey['refresh-token']) || '';
}
/** 设置refresh token */
export function setRefreshToken(token: string) {
setLocal(EnumStorageKey['refresh-token'], token);
}
/** 去除refresh token */
export function removeRefreshToken() {
removeLocal(EnumStorageKey['refresh-token']);
}
/** 获取用户信息 */
export function getUserInfo() {
const emptyInfo: Auth.UserInfo = {
userId: '',
userName: '',
userRole: 'user'
};
const userInfo: Auth.UserInfo = getLocal<Auth.UserInfo>(EnumStorageKey['user-info']) || emptyInfo;
return userInfo;
}
/** 设置用户信息 */
export function setUserInfo(userInfo: Auth.UserInfo) {
setLocal(EnumStorageKey['user-info'], userInfo);
}
/** 去除用户信息 */
export function removeUserInfo() {
removeLocal(EnumStorageKey['user-info']);
}
/** 去除用户相关缓存 */
export function clearAuthStorage() {
removeToken();
removeRefreshToken();
removeUserInfo();
}

View File

@ -1,6 +1,4 @@
export * from './typeof'; export * from './typeof';
export * from './color'; export * from './color';
export * from './number'; export * from './number';
export * from './object';
export * from './pattern'; export * from './pattern';
export * from './theme';

View File

@ -1,4 +0,0 @@
/** 设置对象数据 */
export function objectAssign<T extends Record<string, any>>(target: T, source: Partial<T>) {
Object.assign(target, source);
}

View File

@ -1,16 +0,0 @@
import { EnumStorageKey } from '@/enum';
/**
*
* @param color
*/
export function setThemeColor(color: string) {
window.localStorage.setItem(EnumStorageKey['theme-color'], color);
}
/**
*
*/
export function getThemeColor() {
return window.localStorage.getItem(EnumStorageKey['theme-color']);
}

View File

@ -1,6 +1,5 @@
export * from './common'; export * from './common';
export * from './storage'; export * from './storage';
export * from './service'; export * from './service';
export * from './auth';
export * from './router'; export * from './router';
export * from './form'; export * from './form';

View File

@ -1,23 +1,26 @@
import { decrypto, encrypto } from '../crypto'; import { decrypto, encrypto } from '../crypto';
interface StorageData<T> {
interface StorageData { value: T;
value: unknown;
expire: number | null; expire: number | null;
} }
function createLocalStorage<T extends StorageInterface.Local = StorageInterface.Local>() {
/** 默认缓存期限为7天 */ /** 默认缓存期限为7天 */
const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7;
export function setLocal(key: string, value: unknown, expire: number | null = DEFAULT_CACHE_TIME) { function set<K extends keyof T>(key: K, value: T[K], expire: number | null = DEFAULT_CACHE_TIME) {
const storageData: StorageData = { value, expire: expire !== null ? new Date().getTime() + expire * 1000 : null }; const storageData: StorageData<T[K]> = {
value,
expire: expire !== null ? new Date().getTime() + expire * 1000 : null
};
const json = encrypto(storageData); const json = encrypto(storageData);
window.localStorage.setItem(key, json); window.localStorage.setItem(key as string, json);
} }
export function getLocal<T>(key: string) { function get<K extends keyof T>(key: K) {
const json = window.localStorage.getItem(key); const json = window.localStorage.getItem(key as string);
if (json) { if (json) {
let storageData: StorageData | null = null; let storageData: StorageData<T[K]> | null = null;
try { try {
storageData = decrypto(json); storageData = decrypto(json);
} catch { } catch {
@ -27,19 +30,28 @@ export function getLocal<T>(key: string) {
const { value, expire } = storageData; const { value, expire } = storageData;
// 在有效期内直接返回 // 在有效期内直接返回
if (expire === null || expire >= Date.now()) { if (expire === null || expire >= Date.now()) {
return value as T; return value as T[K];
} }
} }
removeLocal(key); remove(key);
return null; return null;
} }
return null; return null;
} }
export function removeLocal(key: string) { function remove(key: keyof T) {
window.localStorage.removeItem(key); window.localStorage.removeItem(key as string);
} }
function clear() {
export function clearLocal() {
window.localStorage.clear(); window.localStorage.clear();
} }
return {
set,
get,
remove,
clear
};
}
export const localStg = createLocalStorage();

View File

@ -25,3 +25,37 @@ export function removeSession(key: string) {
export function clearSession() { export function clearSession() {
window.sessionStorage.clear(); window.sessionStorage.clear();
} }
function createSessionStorage<T extends StorageInterface.Session = StorageInterface.Session>() {
function set<K extends keyof T>(key: K, value: T[K]) {
const json = encrypto(value);
sessionStorage.setItem(key as string, json);
}
function get<K extends keyof T>(key: K) {
const json = sessionStorage.getItem(key as string);
let data: T[K] | null = null;
if (json) {
try {
data = decrypto(json);
} catch {
// 防止解析失败
}
}
return data;
}
function remove(key: keyof T) {
window.sessionStorage.removeItem(key as string);
}
function clear() {
window.sessionStorage.clear();
}
return {
set,
get,
remove,
clear
};
}
export const sessionStg = createSessionStorage();