diff --git a/README.md b/README.md index 7f0f71f..53f54a9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-

Ench Admin

+

Ench Admin

@@ -54,7 +54,7 @@ pnpm commit | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
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) diff --git a/index.html b/index.html index 5e73499..aadbeb4 100644 --- a/index.html +++ b/index.html @@ -2,7 +2,7 @@ - + %VITE_APP_TITLE% diff --git a/mock/module/user.ts b/mock/module/user.ts index 829350f..3948711 100644 --- a/mock/module/user.ts +++ b/mock/module/user.ts @@ -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: [ { diff --git a/src/config/system.ts b/src/config/system.ts index 3666b4d..e1b8ada 100644 --- a/src/config/system.ts +++ b/src/config/system.ts @@ -1,15 +1,3 @@ -/** 本地存储信息字段 */ -export const storageKey = { - /* 用户信息 */ - userInfo: '__USER_INFO__', - /* token */ - token: '__TOKEN__', - /* refreshToken */ - refreshToken: '__REFRESH_TOKEN__', - /* 标签栏信息 */ - tabsRoutes: '__TABS_ROUTES__', -}; - /** 本地存储前缀 */ export const STORAGE_PREFIX = ''; diff --git a/src/layouts/components/sider/Menu.vue b/src/layouts/components/sider/Menu.vue index 965457f..129df42 100644 --- a/src/layouts/components/sider/Menu.vue +++ b/src/layouts/components/sider/Menu.vue @@ -6,23 +6,16 @@ accordion :options="routesStore.menus" :value="routesStore.activeMenu" - @update:value="handleClickMenu" /> diff --git a/src/router/guard/permission.ts b/src/router/guard/permission.ts index 4c8cc59..20d29cd 100644 --- a/src/router/guard/permission.ts +++ b/src/router/guard/permission.ts @@ -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() diff --git a/src/service/api/login.ts b/src/service/api/login.ts index 8f8f09e..11db346 100644 --- a/src/service/api/login.ts +++ b/src/service/api/login.ts @@ -11,12 +11,12 @@ interface Itoken { export function fetchLogin(params: Ilogin) { return mockRequest.post('/login', params); } -export function fetchUpdateToken(params: string) { +export function fetchUpdateToken(params: any) { return mockRequest.post('/updateToken', params); } export function fetchUserInfo() { - return mockRequest.get('/getUserInfo'); + return mockRequest.get('/getUserInfo'); } -export function fetchUserRoutes(params: string) { +export function fetchUserRoutes(params: { userId: number }) { return mockRequest.post('/getUserRoutes', params); } diff --git a/src/service/http/handle.ts b/src/service/http/handle.ts index a379f0a..e6cf7ec 100644 --- a/src/service/http/handle.ts +++ b/src/service/http/handle.ts @@ -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(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) { diff --git a/src/service/http/instance.ts b/src/service/http/instance.ts index c720c9f..7ad58fd 100644 --- a/src/service/http/instance.ts +++ b/src/service/http/instance.ts @@ -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; }, diff --git a/src/store/modules/auth.ts b/src/store/modules/auth.ts index 88477c6..c9b9637 100644 --- a/src/store/modules/auth.ts +++ b/src/store/modules/auth.ts @@ -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; diff --git a/src/store/modules/route.ts b/src/store/modules/route.ts index baaafa8..53554f1 100644 --- a/src/store/modules/route.ts +++ b/src/store/modules/route.ts @@ -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); // 生成侧边菜单 diff --git a/src/typings/business.d.ts b/src/typings/business.d.ts index a284142..2ef1f2c 100644 --- a/src/typings/business.d.ts +++ b/src/typings/business.d.ts @@ -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; + } } /* 系统消息 */ diff --git a/src/typings/storage.d.ts b/src/typings/storage.d.ts new file mode 100644 index 0000000..6b6286f --- /dev/null +++ b/src/typings/storage.d.ts @@ -0,0 +1,13 @@ +declare namespace Storage { + + interface Session { + demoKey: string + } + + interface Local { + userInfo: Auth.UserInfo + token: string + refreshToken: string + tabsRoutes: string + } +} \ No newline at end of file diff --git a/src/utils/auth.ts b/src/utils/auth.ts deleted file mode 100644 index 226851b..0000000 --- a/src/utils/auth.ts +++ /dev/null @@ -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(); -} diff --git a/src/utils/icon.ts b/src/utils/icon.ts index eae480b..b4be110 100644 --- a/src/utils/icon.ts +++ b/src/utils/icon.ts @@ -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 }) }); } diff --git a/src/utils/index.ts b/src/utils/index.ts index fabc1eb..037190e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,3 @@ -export * from './auth'; export * from './icon'; export * from './is'; export * from './storage'; diff --git a/src/utils/storage.ts b/src/utils/storage.ts index 4ddd302..34f64f3 100644 --- a/src/utils/storage.ts +++ b/src/utils/storage.ts @@ -2,30 +2,30 @@ import { encrypto, decrypto } from './crypto'; // 读取缓存前缀 import { STORAGE_PREFIX, STORAGE_DEFAULT_CACHE_TIME } from '@/config'; -interface StorageData { - value: any; +interface StorageData { + value: T; expire: number | null; } /** * LocalStorage部分操作 */ -function createLocalStorage() { +function createLocalStorage() { // 默认缓存期限为7天 - function set(key: string, value: any, expire: number = STORAGE_DEFAULT_CACHE_TIME) { - const storageData: StorageData = { + function set(key: K, value: T[K], expire: number = STORAGE_DEFAULT_CACHE_TIME) { + const storageData: StorageData = { 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(key: K) { + const json = window.localStorage.getItem(`${STORAGE_PREFIX}${String(key)}`); if (!json) return null; - let storageData: StorageData | null = null; + let storageData: StorageData | 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() { + function set(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(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(); diff --git a/src/views/login/index.vue b/src/views/login/index.vue index e366877..e1d720c 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -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; }