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">
<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 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 |
| :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
| last 2 versions | last 2 versions | last 2 versions | last 2 versions |
| >=88 | >=78 | >=87 | >=14 |
## 🙌 学习交流
Ench-Admin 是完全开源免费的项目旨在帮助开发者更方便地进行中大型管理系统开发有使用问题欢迎在QQ交流群内提问。
@ -68,4 +68,4 @@ Ench-Admin 是完全开源免费的项目,旨在帮助开发者更方便地进
## 🧾License
该项目采用MIT许可证详见[LICENSE](LICENSE)文件。
[MIT](LICENSE)

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<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" />
<title>%VITE_APP_TITLE%</title>
</head>

View File

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

View File

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

View File

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

View File

@ -11,12 +11,12 @@ interface Itoken {
export function fetchLogin(params: Ilogin) {
return mockRequest.post<any>('/login', params);
}
export function fetchUpdateToken(params: string) {
export function fetchUpdateToken(params: any) {
return mockRequest.post<Itoken>('/updateToken', params);
}
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);
}

View File

@ -10,7 +10,7 @@ import {
} from '@/config';
import { useAuthStore } from '@/store';
import { fetchUpdateToken } from '@/service';
import { setToken, setRefreshToken, getRefreshToken } from '@/utils';
import { local } from '@/utils';
import { showError } from './utils';
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) {
const { resetAuthStore } = useAuthStore();
const refreshToken = getRefreshToken();
const refreshToken = local.get('refreshToken');
const { data } = await fetchUpdateToken(refreshToken);
if (data) {
setRefreshToken(data.token);
setToken(data.refreshToken);
local.set('refreshToken', data.token, )
local.set('token', data.refreshToken)
// 设置token
if (config.headers) {

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@ declare namespace Auth {
type RoleType = 'super' | 'admin' | 'manage' | 'user';
interface UserInfo {
/** 用户id */
userId: string;
userId: number;
/** 用户名 */
userName: string;
/* 用户称呼 */
@ -25,8 +25,7 @@ declare namespace Auth {
avatar: string;
/** 用户角色类型 */
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 { 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 }) });
}

View File

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

View File

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

View File

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