refactor(utils): 完善本地存储类型,移除用户获取工具类

This commit is contained in:
Coffee-crocodile 2023-03-24 18:10:28 +08:00
parent 4d0dcf1695
commit 0181de4670
18 changed files with 95 additions and 131 deletions

View File

@ -1,5 +1,5 @@
<div align="center"> <div align="center">
<h1> <img src="./public/logo.svg" style="width:30px"/> Ench Admin</h1> <h1> <img src="./public/favicon.svg" style="width:30px"/> Ench Admin</h1>
</div> </div>
<div align="center"> <div align="center">
@ -54,7 +54,7 @@ pnpm commit
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Safari | | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)<br/>Safari |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| last 2 versions | last 2 versions | last 2 versions | last 2 versions | | >=88 | >=78 | >=87 | >=14 |
## 🙌 学习交流 ## 🙌 学习交流
Ench-Admin 是完全开源免费的项目旨在帮助开发者更方便地进行中大型管理系统开发有使用问题欢迎在QQ交流群内提问。 Ench-Admin 是完全开源免费的项目旨在帮助开发者更方便地进行中大型管理系统开发有使用问题欢迎在QQ交流群内提问。
@ -68,4 +68,4 @@ Ench-Admin 是完全开源免费的项目,旨在帮助开发者更方便地进
## 🧾License ## 🧾License
该项目采用MIT许可证详见[LICENSE](LICENSE)文件。 [MIT](LICENSE)

View File

@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_APP_TITLE%</title> <title>%VITE_APP_TITLE%</title>
</head> </head>

View File

@ -6,7 +6,7 @@ const Random = Mock.Random;
const token = () => Random.string('upper', 32, 32); const token = () => Random.string('upper', 32, 32);
const userInfo = { const userInfo = {
userId: '1', userId: 1,
userName: 'iamsee', userName: 'iamsee',
realName: '管理员大人', realName: '管理员大人',
avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg', avatar: 'https://z3.ax1x.com/2021/10/29/5jnWgf.jpg',
@ -308,7 +308,7 @@ const userRoutes = [
meta: { meta: {
title: '超管super可见', title: '超管super可见',
requiresAuth: true, requiresAuth: true,
roles:['super'], roles: ['super'],
icon: 'icon-park-outline:wrong-user', icon: 'icon-park-outline:wrong-user',
}, },
}, },
@ -322,7 +322,7 @@ const userRoutes = [
title: '异常页', title: '异常页',
requiresAuth: true, requiresAuth: true,
icon: 'icon-park-outline:error-computer', icon: 'icon-park-outline:error-computer',
}, },
children: [ children: [
{ {

View File

@ -1,15 +1,3 @@
/** 本地存储信息字段 */
export const storageKey = {
/* 用户信息 */
userInfo: '__USER_INFO__',
/* token */
token: '__TOKEN__',
/* refreshToken */
refreshToken: '__REFRESH_TOKEN__',
/* 标签栏信息 */
tabsRoutes: '__TABS_ROUTES__',
};
/** 本地存储前缀 */ /** 本地存储前缀 */
export const STORAGE_PREFIX = ''; export const STORAGE_PREFIX = '';

View File

@ -6,23 +6,16 @@
accordion accordion
:options="routesStore.menus" :options="routesStore.menus"
:value="routesStore.activeMenu" :value="routesStore.activeMenu"
@update:value="handleClickMenu"
/> />
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useAppStore } from '@/store'; import { useAppStore } from '@/store';
import { useAppRouter } from '@/hooks';
import { useRouteStore } from '~/src/store/modules/route'; import { useRouteStore } from '~/src/store/modules/route';
import type { MenuOption } from 'naive-ui';
const { routerPush } = useAppRouter();
const appStore = useAppStore(); const appStore = useAppStore();
const routesStore = useRouteStore(); const routesStore = useRouteStore();
const handleClickMenu = (key: string, item: MenuOption) => {
routerPush(key);
};
</script> </script>
<style scoped></style> <style scoped></style>

View File

@ -1,5 +1,5 @@
import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router'; import { RouteLocationNormalized, NavigationGuardNext } from 'vue-router';
import { getToken } from '@/utils/auth'; import { local } from '@/utils';
import { useRouteStore } from '@/store'; import { useRouteStore } from '@/store';
export async function createPermissionGuard( export async function createPermissionGuard(
@ -10,7 +10,7 @@ export async function createPermissionGuard(
const routeStore = useRouteStore(); const routeStore = useRouteStore();
// 判断有无TOKEN,登录鉴权 // 判断有无TOKEN,登录鉴权
const isLogin = Boolean(getToken()); const isLogin = Boolean(local.get('token'));
if (!isLogin) { if (!isLogin) {
if (to.name == 'login') { if (to.name == 'login') {
next() next()

View File

@ -11,12 +11,12 @@ interface Itoken {
export function fetchLogin(params: Ilogin) { export function fetchLogin(params: Ilogin) {
return mockRequest.post<any>('/login', params); return mockRequest.post<any>('/login', params);
} }
export function fetchUpdateToken(params: string) { export function fetchUpdateToken(params: any) {
return mockRequest.post<Itoken>('/updateToken', params); return mockRequest.post<Itoken>('/updateToken', params);
} }
export function fetchUserInfo() { export function fetchUserInfo() {
return mockRequest.get('/getUserInfo'); return mockRequest.get<Auth.UserInfo>('/getUserInfo');
} }
export function fetchUserRoutes(params: string) { export function fetchUserRoutes(params: { userId: number }) {
return mockRequest.post<any>('/getUserRoutes', params); return mockRequest.post<any>('/getUserRoutes', params);
} }

View File

@ -10,7 +10,7 @@ import {
} from '@/config'; } from '@/config';
import { useAuthStore } from '@/store'; import { useAuthStore } from '@/store';
import { fetchUpdateToken } from '@/service'; import { fetchUpdateToken } from '@/service';
import { setToken, setRefreshToken, getRefreshToken } from '@/utils'; import { local } from '@/utils';
import { showError } from './utils'; import { showError } from './utils';
type ErrorStatus = keyof typeof ERROR_STATUS; type ErrorStatus = keyof typeof ERROR_STATUS;
@ -123,11 +123,11 @@ export async function handleServiceResult<T = any>(data: any, error: Service.Req
*/ */
export async function handleRefreshToken(config: AxiosRequestConfig) { export async function handleRefreshToken(config: AxiosRequestConfig) {
const { resetAuthStore } = useAuthStore(); const { resetAuthStore } = useAuthStore();
const refreshToken = getRefreshToken(); const refreshToken = local.get('refreshToken');
const { data } = await fetchUpdateToken(refreshToken); const { data } = await fetchUpdateToken(refreshToken);
if (data) { if (data) {
setRefreshToken(data.token); local.set('refreshToken', data.token, )
setToken(data.refreshToken); local.set('token', data.refreshToken)
// 设置token // 设置token
if (config.headers) { if (config.headers) {

View File

@ -1,6 +1,6 @@
import axios from 'axios'; import axios from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios'; import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
import { getToken } from '@/utils'; import { local } from '@/utils';
import { REFRESH_TOKEN_CODE } from '@/config'; import { REFRESH_TOKEN_CODE } from '@/config';
import { import {
handleAxiosError, handleAxiosError,
@ -45,7 +45,7 @@ export default class createAxiosInstance {
// 设置token // 设置token
typeof handleConfig.headers.set === 'function' && typeof handleConfig.headers.set === 'function' &&
handleConfig.headers.set('Authorization', `Bearer ${getToken() || ''}`); handleConfig.headers.set('Authorization', `Bearer ${local.get('token') || ''}`);
} }
return handleConfig; return handleConfig;
}, },

View File

@ -1,16 +1,16 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { fetchLogin, fetchUserInfo } from '@/service'; import { fetchLogin, fetchUserInfo } from '@/service';
import { setUserInfo, getUserInfo, getToken, setToken, clearAuthStorage, setRefreshToken } from '@/utils/auth';
import { router } from '@/router'; import { router } from '@/router';
import { useAppRouter } from '@/hooks'; import { useAppRouter } from '@/hooks';
import { unref } from 'vue'; import { unref } from 'vue';
import { useRouteStore } from './route'; import { useRouteStore } from './route';
import { local } from '@/utils';
export const useAuthStore = defineStore('auth-store', { export const useAuthStore = defineStore('auth-store', {
state: () => { state: () => {
return { return {
userInfo: getUserInfo(), userInfo: local.get('userInfo'),
token: getToken(), token: local.get('token'),
loginLoading: false, loginLoading: false,
}; };
}, },
@ -27,7 +27,7 @@ export const useAuthStore = defineStore('auth-store', {
const { toLogin } = useAppRouter(false); const { toLogin } = useAppRouter(false);
const { resetRouteStore } = useRouteStore(); const { resetRouteStore } = useRouteStore();
// 清除本地缓存 // 清除本地缓存
clearAuthStorage(); this.clearAuthStorage();
// 清空路由、菜单等数据 // 清空路由、菜单等数据
resetRouteStore(); resetRouteStore();
this.$reset(); this.$reset();
@ -35,6 +35,11 @@ export const useAuthStore = defineStore('auth-store', {
toLogin(); toLogin();
} }
}, },
clearAuthStorage() {
local.remove('token');
local.remove('refreshToken');
local.remove('userInfo');
},
/* 用户登录 */ /* 用户登录 */
async login(userName: string, password: string) { async login(userName: string, password: string) {
@ -64,7 +69,7 @@ export const useAuthStore = defineStore('auth-store', {
// 触发用户提示 // 触发用户提示
window.$notification?.success({ window.$notification?.success({
title: '登录成功!', title: '登录成功!',
content: `欢迎回来😊,${this.userInfo.realName}!`, content: `欢迎回来😊,${this.userInfo?.realName}!`,
duration: 3000, duration: 3000,
}); });
return; return;
@ -84,12 +89,14 @@ export const useAuthStore = defineStore('auth-store', {
let catchSuccess = false; let catchSuccess = false;
// 先存储token // 先存储token
const { token, refreshToken } = userToken; const { token, refreshToken } = userToken;
setToken(token); local.set('token', token);
setRefreshToken(refreshToken); local.set('refreshToken', refreshToken,)
// 请求/存储用户信息 // 请求/存储用户信息
const { data } = await fetchUserInfo(); const { data } = await fetchUserInfo();
setUserInfo(data); if (data) {
local.set('userInfo', data);
}
// 再将token和userInfo初始化 // 再将token和userInfo初始化
this.userInfo = data; this.userInfo = data;
this.token = token; this.token = token;

View File

@ -1,12 +1,13 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { renderIcon, getUserInfo} from '@/utils'; import { renderIcon,local } from '@/utils';
import { MenuOption } from 'naive-ui'; import { MenuOption } from 'naive-ui';
import { createDynamicRoutes } from '@/router/guard/dynamic'; import { createDynamicRoutes } from '@/router/guard/dynamic';
import { router } from '@/router'; import { router } from '@/router';
import { fetchUserRoutes } from '@/service'; import { fetchUserRoutes } from '@/service';
import { staticRoutes } from '@/router/modules'; import { staticRoutes } from '@/router/modules';
import { useAuthStore } from '@/store'; import { RouterLink } from 'vue-router'
import { usePermission } from '@/hooks' import { usePermission } from '@/hooks'
import { h } from 'vue'
interface RoutesStatus { interface RoutesStatus {
isInitAuthRoute: boolean; isInitAuthRoute: boolean;
@ -80,8 +81,7 @@ export const useRouteStore = defineStore('route-store', {
}, },
//* 将返回的路由表渲染成侧边栏 */ //* 将返回的路由表渲染成侧边栏 */
transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] { transformAuthRoutesToMenus(userRoutes: AppRoute.Route[]): MenuOption[] {
const authStore = useAuthStore()
const { role } = authStore.userInfo
return userRoutes return userRoutes
/** 隐藏不需要显示的菜单 */ /** 隐藏不需要显示的菜单 */
.filter((item) => { .filter((item) => {
@ -94,13 +94,19 @@ export const useRouteStore = defineStore('route-store', {
/** 转换为侧边菜单数据结构 */ /** 转换为侧边菜单数据结构 */
.map((item) => { .map((item) => {
const target: MenuOption = { const target: MenuOption = {
label: item.meta.title, label: () =>
h(
RouterLink,
{
to: {
path: item.path
}
},
{ default: () => item.meta.title }
),
key: item.path, key: item.path,
icon: renderIcon(item.meta.icon)
}; };
/** 判断有无图标 */
if (item.meta.icon) {
target.icon = renderIcon(item.meta.icon);
}
/** 判断子元素 */ /** 判断子元素 */
if (item.children) { if (item.children) {
const children = this.transformAuthRoutesToMenus(item.children); const children = this.transformAuthRoutesToMenus(item.children);
@ -115,8 +121,13 @@ export const useRouteStore = defineStore('route-store', {
/* 初始化动态路由 */ /* 初始化动态路由 */
async initDynamicRoute() { async initDynamicRoute() {
// 根据用户id来获取用户的路由 // 根据用户id来获取用户的路由
const { userId } = getUserInfo() const userInfo = local.get('userInfo')
const { data: routes } = await fetchUserRoutes({ userId });
if (!userInfo||!userInfo.userId) {
return
}
const { data: routes } = await fetchUserRoutes({ userId: userInfo.userId});
// 根据用户返回的路由表来生成真实路由 // 根据用户返回的路由表来生成真实路由
const appRoutes = await createDynamicRoutes(routes); const appRoutes = await createDynamicRoutes(routes);
// 生成侧边菜单 // 生成侧边菜单

View File

@ -16,7 +16,7 @@ declare namespace Auth {
type RoleType = 'super' | 'admin' | 'manage' | 'user'; type RoleType = 'super' | 'admin' | 'manage' | 'user';
interface UserInfo { interface UserInfo {
/** 用户id */ /** 用户id */
userId: string; userId: number;
/** 用户名 */ /** 用户名 */
userName: string; userName: string;
/* 用户称呼 */ /* 用户称呼 */
@ -25,8 +25,7 @@ declare namespace Auth {
avatar: string; avatar: string;
/** 用户角色类型 */ /** 用户角色类型 */
role: RoleType; role: RoleType;
/* 密码 */
password: string;
} }
} }
/* 系统消息 */ /* 系统消息 */

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

@ -0,0 +1,13 @@
declare namespace Storage {
interface Session {
demoKey: string
}
interface Local {
userInfo: Auth.UserInfo
token: string
refreshToken: string
tabsRoutes: string
}
}

View File

@ -1,49 +0,0 @@
import { local } from './storage';
import { storageKey } from '@/config';
const DURATION = 6 * 60 * 60;
/* 获取当前token */
export function getToken() {
return local.get(storageKey.token);
}
/* 设置token */
export function setToken(data: string) {
local.set(storageKey.token, data, DURATION);
}
/* 移除token */
export function removeToken() {
local.remove(storageKey.token);
}
/* 获取当前refreshToken */
export function getRefreshToken() {
return local.get(storageKey.refreshToken);
}
/* 设置refreshToken */
export function setRefreshToken(data: string) {
local.set(storageKey.refreshToken, data, DURATION);
}
/* 移除refreshToken */
export function removeRefreshToken() {
local.remove(storageKey.refreshToken);
}
/* 获取用户详情 */
export function getUserInfo() {
return local.get(storageKey.userInfo);
}
/* 设置用户详情 */
export function setUserInfo(data: any) {
local.set(storageKey.userInfo, data);
}
/* 移除用户详情 */
export function removeUserInfo() {
local.remove(storageKey.userInfo);
}
/** 去除用户相关缓存 */
export function clearAuthStorage() {
removeToken();
removeRefreshToken();
removeUserInfo();
}

View File

@ -2,6 +2,9 @@ import { h } from 'vue';
import { Icon } from '@iconify/vue'; import { Icon } from '@iconify/vue';
import { NIcon } from 'naive-ui'; import { NIcon } from 'naive-ui';
export function renderIcon(icon: string) { export function renderIcon(icon?: string) {
if (!icon) {
return undefined
}
return () => h(NIcon, null, { default: () => h(Icon, { icon }) }); return () => h(NIcon, null, { default: () => h(Icon, { icon }) });
} }

View File

@ -1,4 +1,3 @@
export * from './auth';
export * from './icon'; export * from './icon';
export * from './is'; export * from './is';
export * from './storage'; export * from './storage';

View File

@ -2,30 +2,30 @@ import { encrypto, decrypto } from './crypto';
// 读取缓存前缀 // 读取缓存前缀
import { STORAGE_PREFIX, STORAGE_DEFAULT_CACHE_TIME } from '@/config'; import { STORAGE_PREFIX, STORAGE_DEFAULT_CACHE_TIME } from '@/config';
interface StorageData { interface StorageData<T> {
value: any; value: T;
expire: number | null; expire: number | null;
} }
/** /**
* LocalStorage部分操作 * LocalStorage部分操作
*/ */
function createLocalStorage() { function createLocalStorage<T extends Storage.Local>() {
// 默认缓存期限为7天 // 默认缓存期限为7天
function set(key: string, value: any, expire: number = STORAGE_DEFAULT_CACHE_TIME) { function set<K extends keyof T>(key: K, value: T[K], expire: number = STORAGE_DEFAULT_CACHE_TIME) {
const storageData: StorageData = { const storageData: StorageData<T[K]> = {
value, value,
expire: new Date().getTime() + expire * 1000, expire: new Date().getTime() + expire * 1000,
}; };
const json = encrypto(storageData); const json = encrypto(storageData);
window.localStorage.setItem(STORAGE_PREFIX + key, json); window.localStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json);
} }
function get(key: string) { function get<K extends keyof T>(key: K) {
const json = window.localStorage.getItem(STORAGE_PREFIX + key); const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`);
if (!json) return null; if (!json) return null;
let storageData: StorageData | null = null; let storageData: StorageData<T[K]> | null = null;
try { try {
storageData = decrypto(json); storageData = decrypto(json);
} catch { } catch {
@ -42,8 +42,8 @@ function createLocalStorage() {
return null; return null;
} }
function remove(key: string) { function remove(key: keyof T) {
window.localStorage.removeItem(STORAGE_PREFIX + key); window.localStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`);
} }
function clear() { function clear() {
@ -60,16 +60,16 @@ function createLocalStorage() {
* sessionStorage部分操作 * sessionStorage部分操作
*/ */
function createSessionStorage() { function createSessionStorage<T extends Storage.Session>() {
function set(key: string, value: any) { function set<K extends keyof T>(key: K, value: T[K]) {
const json = encrypto(value); const json = encrypto(value);
window.sessionStorage.setItem(STORAGE_PREFIX + key, json); window.sessionStorage.setItem(`${STORAGE_PREFIX}${String(key)}`, json);
} }
function get(key: string) { function get<K extends keyof T>(key: K) {
const json = sessionStorage.getItem(STORAGE_PREFIX + key); const json = sessionStorage.getItem(`${STORAGE_PREFIX}${String(key)}`);
if (!json) return null; if (!json) return null;
let storageData; let storageData: T[K] | null = null;
try { try {
storageData = decrypto(json); storageData = decrypto(json);
} catch { } catch {
@ -81,8 +81,8 @@ function createSessionStorage() {
} }
return null; return null;
} }
function remove(key: string) { function remove(key: keyof T) {
window.sessionStorage.removeItem(STORAGE_PREFIX + key); window.sessionStorage.removeItem(`${STORAGE_PREFIX}${String(key)}`);
} }
function clear() { function clear() {
window.sessionStorage.clear(); window.sessionStorage.clear();

View File

@ -63,8 +63,8 @@ const formComponets = {
&::before { &::before {
position: absolute; position: absolute;
content: ''; content: '';
width: 800px; width: 30vw;
height: 400px; height: 15vw;
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
} }