diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 0000000..433a548 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,43 @@ +import { useUserStoreHook } from "@/store/modules/user"; +import { formatToken, getToken } from "@/utils/auth"; +import axios from "axios"; +export const api = axios.create({ + // 公共配置 + baseURL: "", + timeout: 15000 +}); + +api.interceptors.request.use( + // 在发送请求之前做什么 + config => { + const data = getToken(); + if (data) { + const now = new Date().getTime(); + const expired = parseInt(data.expires) - now <= 0; + if (expired) { + useUserStoreHook().logOut(); + } else { + config.headers["Authorization"] = formatToken(data.accessToken); + } + } + return config; + }, + // 对请求错误做点什么 + error => Promise.reject(error) +); + +api.interceptors.response.use( + // 对响应数据做点什么 + response => { + return response; + }, +); + +function errorHandle(status: number) { + switch (status) { + case 401: + break; + default: + break; + } +} diff --git a/src/layout/components/setting/index.vue b/src/layout/components/setting/index.vue index 6543f65..ebe991e 100644 --- a/src/layout/components/setting/index.vue +++ b/src/layout/components/setting/index.vue @@ -12,8 +12,7 @@ import { useDark, debounce, useGlobal, - storageLocal, - storageSession + storageLocal } from "@pureadmin/utils"; import { getConfig } from "@/config"; import { useRouter } from "vue-router"; @@ -133,7 +132,6 @@ const multiTagsCacheChange = () => { function onReset() { removeToken(); storageLocal().clear(); - storageSession().clear(); const { Grey, Weak, MultiTagsCache, EpThemeColor, Layout } = getConfig(); useAppStoreHook().setLayout(Layout); setEpThemeColor(EpThemeColor); diff --git a/src/router/index.ts b/src/router/index.ts index ef66c29..12aca96 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -21,8 +21,8 @@ import { formatTwoStageRoutes, formatFlatteningRoutes } from "./utils"; -import { buildHierarchyTree } from "@/utils/tree"; -import { isUrl, openLink, storageSession, isAllEmpty } from "@pureadmin/utils"; +import {buildHierarchyTree} from "@/utils/tree"; +import {isUrl, openLink, isAllEmpty, storageLocal} from "@pureadmin/utils"; import remainingRouter from "./modules/remaining"; @@ -108,8 +108,7 @@ router.beforeEach((to: ToRouteType, _from, next) => { handleAliveRoute(to); } } - const userInfo = storageSession().getItem>(sessionKey); - NProgress.start(); + const userInfo = storageLocal().getItem>(sessionKey); const externalLink = isUrl(to?.name as string); if (!externalLink) { to.matched.some(item => { diff --git a/src/router/utils.ts b/src/router/utils.ts index 2436e2b..ba3a9ba 100644 --- a/src/router/utils.ts +++ b/src/router/utils.ts @@ -13,8 +13,7 @@ import { cloneDeep, isAllEmpty, intersection, - storageSession, - isIncludeAllChildren + isIncludeAllChildren, storageLocal } from "@pureadmin/utils"; import { getConfig } from "@/config"; import { menuType } from "@/layout/types"; @@ -73,7 +72,7 @@ function filterChildrenTree(data: RouteComponent[]) { } /** 判断两个数组彼此是否存在相同值 */ -function isOneOfArray(a: Array, b: Array) { +function isOneOfArray(a: Array, b: Array) { return Array.isArray(a) && Array.isArray(b) ? intersection(a, b).length > 0 ? true @@ -84,7 +83,7 @@ function isOneOfArray(a: Array, b: Array) { /** 从sessionStorage里取出当前登陆用户的角色roles,过滤无权限的菜单 */ function filterNoPermissionTree(data: RouteComponent[]) { const currentRoles = - storageSession().getItem>(sessionKey)?.roles ?? []; + storageLocal().getItem>(sessionKey)?.roles ?? []; const newTree = cloneDeep(data).filter((v: any) => isOneOfArray(v.meta?.roles, currentRoles) ); @@ -150,7 +149,7 @@ function addPathMatch() { } /** 处理动态路由(后端返回的路由) */ -function handleAsyncRoutes(routeList) { +export function handleAsyncRoutes(routeList) { if (routeList.length === 0) { usePermissionStoreHook().handleWholeMenus(routeList); } else { @@ -186,7 +185,7 @@ function initRouter() { if (getConfig()?.CachingAsyncRoutes) { // 开启动态路由缓存本地sessionStorage const key = "async-routes"; - const asyncRouteList = storageSession().getItem(key) as any; + const asyncRouteList = storageLocal().getItem(key) as any; if (asyncRouteList && asyncRouteList?.length > 0) { return new Promise(resolve => { handleAsyncRoutes(asyncRouteList); @@ -196,7 +195,7 @@ function initRouter() { return new Promise(resolve => { getAsyncRoutes().then(({ data }) => { handleAsyncRoutes(cloneDeep(data)); - storageSession().setItem(key, data); + storageLocal().setItem(key, data); resolve(router); }); }); diff --git a/src/store/modules/user.ts b/src/store/modules/user.ts index 5d6b337..3b630e3 100644 --- a/src/store/modules/user.ts +++ b/src/store/modules/user.ts @@ -1,22 +1,23 @@ -import { defineStore } from "pinia"; -import { store } from "@/store"; -import { userType } from "./types"; -import { routerArrays } from "@/layout/types"; -import { router, resetRouter } from "@/router"; -import { storageSession } from "@pureadmin/utils"; -import { getLogin, refreshTokenApi } from "@/api/user"; -import { UserResult, RefreshTokenResult } from "@/api/user"; -import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; -import { type DataInfo, setToken, removeToken, sessionKey } from "@/utils/auth"; +import {defineStore} from "pinia"; +import {store} from "@/store"; +import {userType} from "./types"; +import {routerArrays} from "@/layout/types"; +import {router, resetRouter} from "@/router"; +import {storageLocal} from "@pureadmin/utils"; +import {getLogin, refreshTokenApi} from "@/api/user"; +import {UserResult, RefreshTokenResult} from "@/api/user"; +import {useMultiTagsStoreHook} from "@/store/modules/multiTags"; +import {type DataInfo, setToken, removeToken, sessionKey} from "@/utils/auth"; +import {http} from "@/utils/http"; export const useUserStore = defineStore({ id: "pure-user", state: (): userType => ({ // 用户名 username: - storageSession().getItem>(sessionKey)?.username ?? "", + storageLocal().getItem>(sessionKey)?.username ?? "", // 页面级别权限 - roles: storageSession().getItem>(sessionKey)?.roles ?? [] + roles: storageLocal().getItem>(sessionKey)?.roles ?? [] }), actions: { /** 存储用户名 */ @@ -42,6 +43,48 @@ export const useUserStore = defineStore({ }); }); }, + async loginByCode(data) { + return new Promise((resolve, reject) => { + http + .post("/wide_data/user/login_by_verify_code", {data}) + .then(res => { + const userInfo = res.user_info; + const dataInfo: DataInfo = { + username: userInfo.name, + accessToken: res.token, + expires: new Date(new Date().getTime() + 60 * 60 * 24 * 3 * 1000), + refreshToken: res.token, + roles: userInfo.auth_data || [] + }; + setToken(dataInfo); + resolve(res); + }) + .catch(error => { + reject(error); + }); + }); + }, + async refreshToken() { + return new Promise((resolve, reject) => { + http + .post("/wide_data/user/refresh_token", {}) + .then(res => { + const userInfo = res.user_info; + const dataInfo: DataInfo = { + username: userInfo.name, + accessToken: res.token, + expires: new Date(new Date().getTime() + 60 * 60 * 24 * 3 * 1000), + refreshToken: res.token, + roles: userInfo.auth_data || [] + }; + setToken(dataInfo); + resolve(res); + }) + .catch(error => { + reject(error); + }); + }); + }, /** 前端登出(不调用接口) */ logOut() { this.username = ""; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index a673803..0a1ff13 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,6 +1,6 @@ import Cookies from "js-cookie"; -import { storageSession } from "@pureadmin/utils"; import { useUserStoreHook } from "@/store/modules/user"; +import {storageLocal} from "@pureadmin/utils"; export interface DataInfo { /** token */ @@ -23,7 +23,7 @@ export function getToken(): DataInfo { // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错 return Cookies.get(TokenKey) ? JSON.parse(Cookies.get(TokenKey)) - : storageSession().getItem(sessionKey); + : storageLocal().getItem(sessionKey); } /** @@ -47,7 +47,7 @@ export function setToken(data: DataInfo) { function setSessionKey(username: string, roles: Array) { useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_ROLES(roles); - storageSession().setItem(sessionKey, { + storageLocal().setItem(sessionKey, { refreshToken, expires, username, @@ -60,9 +60,9 @@ export function setToken(data: DataInfo) { setSessionKey(username, roles); } else { const username = - storageSession().getItem>(sessionKey)?.username ?? ""; + storageLocal().getItem>(sessionKey)?.username ?? ""; const roles = - storageSession().getItem>(sessionKey)?.roles ?? []; + storageLocal().getItem>(sessionKey)?.roles ?? []; setSessionKey(username, roles); } } @@ -70,7 +70,7 @@ export function setToken(data: DataInfo) { /** 删除`token`以及key值为`user-info`的session信息 */ export function removeToken() { Cookies.remove(TokenKey); - sessionStorage.clear(); + localStorage.removeItem("user-info"); } /** 格式化token(jwt格式) */ diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 1f0220d..ca8c554 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -17,8 +17,7 @@ import dayIcon from "@/assets/svg/day.svg?component"; import darkIcon from "@/assets/svg/dark.svg?component"; import Lock from "@iconify-icons/ri/lock-fill"; import User from "@iconify-icons/ri/user-3-fill"; -import { tr } from "element-plus/es/locale"; - +import {api} from "@/api/api"; defineOptions({ name: "Login" }); @@ -34,47 +33,64 @@ dataThemeChange(); const { title } = useNav(); const ruleForm = reactive({ - username: "admin", - password: "admin123", - checked: true + account: "", + code: "" }); - -const onLogin = async (formEl: FormInstance | undefined) => { - loading.value = true; - if (!formEl) return; - await formEl.validate((valid, fields) => { - if (valid) { - useUserStoreHook() - .loginByUsername({ username: ruleForm.username, password: "admin123" }) - .then(res => { - if (res.success) { - // 获取后端路由 - initRouter().then(() => { - router.push(getTopMenu(true).path); - message("登录成功", { type: "success" }); - }); - } - }); - } else { - loading.value = false; - return fields; +function getLoginVerifyCode() { + if (!ruleForm.account) { + message("请输入正确的手机号或邮箱", {type: "warning"}); + return; } - }); + api + .post("/wide_data/user/get_login_verify_code", { + account: ruleForm.account + }) + .then(res => { + message("验证码已发送", {type: "success"}); + }) + .catch(e => { + console.log(e); + message(e.response.data.detail, {type: "warning"}); + }); +} +const onLogin = async (formEl: FormInstance | undefined) => { + loading.value = true; + if (!formEl) return; + await formEl.validate((valid, fields) => { + if (valid) { + useUserStoreHook() + .loginByCode({account: ruleForm.account, code: ruleForm.code}) + .then(res => { + // 获取后端路由 + initRouter().then(() => { + router.push(getTopMenu(true).path); + message("登录成功", {type: "success"}); + }); + }).catch(e => { + console.log(e); + loading.value = false; + message(e.response.data.detail, {type: "warning"}); + }); + } else { + loading.value = false; + return fields; + } + }); }; /** 使用公共函数,避免`removeEventListener`失效 */ -function onkeypress({ code }: KeyboardEvent) { - if (code === "Enter") { - onLogin(ruleFormRef.value); - } +function onkeypress({code}: KeyboardEvent) { + if (code === "Enter") { + onLogin(ruleFormRef.value); + } } onMounted(() => { - window.document.addEventListener("keypress", onkeypress); + window.document.addEventListener("keypress", onkeypress); }); onBeforeUnmount(() => { - window.document.removeEventListener("keypress", onkeypress); + window.document.removeEventListener("keypress", onkeypress); }); @@ -113,14 +129,14 @@ onBeforeUnmount(() => { { required: true, message: '请输入账号', - trigger: 'blur' + trigger: 'change' } ]" - prop="username" + prop="account" > @@ -128,31 +144,45 @@ onBeforeUnmount(() => { - + + > + + -
- 自动登录 - 忘记密码 -
- - - 登录 - - + + + 登录 + + diff --git a/src/views/permission/page/index.vue b/src/views/permission/page/index.vue index f02ad5a..e52d98f 100644 --- a/src/views/permission/page/index.vue +++ b/src/views/permission/page/index.vue @@ -1,6 +1,6 @@