refactor(login): 适配后端登录接口

This commit is contained in:
snape 2024-03-26 13:47:24 +08:00
parent fda66ee37c
commit ee77d5c917
17 changed files with 64 additions and 127 deletions

2
.env
View File

@ -1,5 +1,5 @@
# 平台本地运行端口号 # 平台本地运行端口号
VITE_PORT = 8848 VITE_PORT = 10086
# 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置 # 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置
VITE_HIDE_HOME = false VITE_HIDE_HOME = false

View File

@ -1,5 +1,5 @@
# 平台本地运行端口号 # 平台本地运行端口号
VITE_PORT = 8848 VITE_PORT = 10086
# 开发环境读取配置文件路径 # 开发环境读取配置文件路径
VITE_PUBLIC_PATH = / VITE_PUBLIC_PATH = /

View File

@ -10,7 +10,7 @@ import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console"; import removeConsole from "vite-plugin-remove-console";
import { themePreprocessorPlugin } from "@pureadmin/theme"; import { themePreprocessorPlugin } from "@pureadmin/theme";
import { genScssMultipleScopeVars } from "../src/layout/theme"; import { genScssMultipleScopeVars } from "../src/layout/theme";
import { vitePluginFakeServer } from "vite-plugin-fake-server"; // import { vitePluginFakeServer } from "vite-plugin-fake-server";
export function getPluginsList( export function getPluginsList(
VITE_CDN: boolean, VITE_CDN: boolean,
@ -29,12 +29,12 @@ export function getPluginsList(
*/ */
removeNoMatch(), removeNoMatch(),
// mock支持 // mock支持
vitePluginFakeServer({ // vitePluginFakeServer({
logger: false, // logger: false,
include: "mock", // include: "mock",
infixName: false, // infixName: false,
enableProd: true // enableProd: true
}), // }),
// 自定义主题 // 自定义主题
themePreprocessorPlugin({ themePreprocessorPlugin({
scss: { scss: {

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="zh">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />

View File

@ -1,6 +1,6 @@
{ {
"Version": "5.2.0", "Version": "1.0.1",
"Title": "PureAdmin", "Title": "电力配餐后台管理系统",
"FixedHeader": true, "FixedHeader": true,
"HiddenSideBar": false, "HiddenSideBar": false,
"MultiTagsCache": false, "MultiTagsCache": false,

View File

@ -1,4 +1,5 @@
import { http } from "@/utils/http"; import { http } from "@/utils/http";
import { baseUrlApi } from "@/api/utils";
type Result = { type Result = {
success: boolean; success: boolean;
@ -6,5 +7,8 @@ type Result = {
}; };
export const getAsyncRoutes = () => { export const getAsyncRoutes = () => {
return http.request<Result>("get", "/get-async-routes"); return http.request<Result>(
"get",
baseUrlApi("api/v1/user/manage/getRoutes")
);
}; };

View File

@ -1,4 +1,5 @@
import { http } from "@/utils/http"; import { http } from "@/utils/http";
import { baseUrlApi } from "@/api/utils";
export type UserResult = { export type UserResult = {
success: boolean; success: boolean;
@ -8,32 +9,13 @@ export type UserResult = {
/** 当前登陆用户的角色 */ /** 当前登陆用户的角色 */
roles: Array<string>; roles: Array<string>;
/** `token` */ /** `token` */
accessToken: string; access_token: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
};
};
export type RefreshTokenResult = {
success: boolean;
data: {
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
}; };
}; };
/** 登录 */ /** 登录 */
export const getLogin = (data?: object) => { export const getLogin = (data?: object) => {
return http.request<UserResult>("post", "/login", { data }); return http.request<UserResult>("post", baseUrlApi("api/v1/auth/login"), {
}; data
});
/** 刷新token */
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
}; };

1
src/api/utils.ts Normal file
View File

@ -0,0 +1 @@
export const baseUrlApi = (url: string) => `/dev-api/${url}`;

View File

@ -29,7 +29,7 @@ const { title, getLogo } = useNav();
class="sidebar-logo-link" class="sidebar-logo-link"
:to="getTopMenu()?.path ?? '/'" :to="getTopMenu()?.path ?? '/'"
> >
<img :src="getLogo()" alt="logo" /> <!-- <img :src="getLogo()" alt="logo" /> -->
<span class="sidebar-title">{{ title }}</span> <span class="sidebar-title">{{ title }}</span>
</router-link> </router-link>
</transition> </transition>

View File

@ -113,7 +113,7 @@ router.beforeEach((to: ToRouteType, _from, next) => {
handleAliveRoute(to); handleAliveRoute(to);
} }
} }
const userInfo = storageLocal().getItem<DataInfo<number>>(userKey); const userInfo = storageLocal().getItem<DataInfo>(userKey);
NProgress.start(); NProgress.start();
const externalLink = isUrl(to?.name as string); const externalLink = isUrl(to?.name as string);
if (!externalLink) { if (!externalLink) {

View File

@ -3,7 +3,7 @@ export default {
redirect: "/error/403", redirect: "/error/403",
meta: { meta: {
icon: "ri:information-line", icon: "ri:information-line",
// showLink: false, showLink: false,
title: "异常页面", title: "异常页面",
rank: 9 rank: 9
}, },

View File

@ -83,8 +83,7 @@ function isOneOfArray(a: Array<string>, b: Array<string>) {
/** 从localStorage里取出当前登陆用户的角色roles过滤无权限的菜单 */ /** 从localStorage里取出当前登陆用户的角色roles过滤无权限的菜单 */
function filterNoPermissionTree(data: RouteComponent[]) { function filterNoPermissionTree(data: RouteComponent[]) {
const currentRoles = const currentRoles = storageLocal().getItem<DataInfo>(userKey)?.roles ?? [];
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
const newTree = cloneDeep(data).filter((v: any) => const newTree = cloneDeep(data).filter((v: any) =>
isOneOfArray(v.meta?.roles, currentRoles) isOneOfArray(v.meta?.roles, currentRoles)
); );

View File

@ -4,8 +4,8 @@ import type { userType } from "./types";
import { routerArrays } from "@/layout/types"; import { routerArrays } from "@/layout/types";
import { router, resetRouter } from "@/router"; import { router, resetRouter } from "@/router";
import { storageLocal } from "@pureadmin/utils"; import { storageLocal } from "@pureadmin/utils";
import { getLogin, refreshTokenApi } from "@/api/user"; import { getLogin } from "@/api/user";
import type { UserResult, RefreshTokenResult } from "@/api/user"; import type { UserResult } from "@/api/user";
import { useMultiTagsStoreHook } from "@/store/modules/multiTags"; import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth"; import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
@ -13,9 +13,9 @@ export const useUserStore = defineStore({
id: "pure-user", id: "pure-user",
state: (): userType => ({ state: (): userType => ({
// 用户名 // 用户名
username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "", username: storageLocal().getItem<DataInfo>(userKey)?.username ?? "",
// 页面级别权限 // 页面级别权限
roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [], roles: storageLocal().getItem<DataInfo>(userKey)?.roles ?? [],
// 是否勾选了登录页的免登录 // 是否勾选了登录页的免登录
isRemembered: false, isRemembered: false,
// 登录页的免登录存储几天默认7天 // 登录页的免登录存储几天默认7天
@ -39,7 +39,7 @@ export const useUserStore = defineStore({
this.loginDay = Number(value); this.loginDay = Number(value);
}, },
/** 登入 */ /** 登入 */
async loginByUsername(data) { async loginByUsername(data: object) {
return new Promise<UserResult>((resolve, reject) => { return new Promise<UserResult>((resolve, reject) => {
getLogin(data) getLogin(data)
.then(data => { .then(data => {
@ -61,21 +61,6 @@ export const useUserStore = defineStore({
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]); useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
resetRouter(); resetRouter();
router.push("/login"); router.push("/login");
},
/** 刷新`token` */
async handRefreshToken(data) {
return new Promise<RefreshTokenResult>((resolve, reject) => {
refreshTokenApi(data)
.then(data => {
if (data) {
setToken(data.data);
resolve(data);
}
})
.catch(error => {
reject(error);
});
});
} }
} }
}); });

View File

@ -2,13 +2,9 @@ import Cookies from "js-cookie";
import { storageLocal } from "@pureadmin/utils"; import { storageLocal } from "@pureadmin/utils";
import { useUserStoreHook } from "@/store/modules/user"; import { useUserStoreHook } from "@/store/modules/user";
export interface DataInfo<T> { export interface DataInfo {
/** token */ /** token */
accessToken: string; access_token: string;
/** `accessToken`的过期时间(时间戳) */
expires: T;
/** 用于调用刷新accessToken的接口时所需的token */
refreshToken: string;
/** 用户名 */ /** 用户名 */
username?: string; username?: string;
/** 当前登陆用户的角色 */ /** 当前登陆用户的角色 */
@ -16,7 +12,7 @@ export interface DataInfo<T> {
} }
export const userKey = "user-info"; export const userKey = "user-info";
export const TokenKey = "authorized-token"; export const TokenKey = "Authorization";
/** /**
* `multiple-tabs``cookie` * `multiple-tabs``cookie`
* *
@ -26,7 +22,7 @@ export const TokenKey = "authorized-token";
export const multipleTabsKey = "multiple-tabs"; export const multipleTabsKey = "multiple-tabs";
/** 获取`token` */ /** 获取`token` */
export function getToken(): DataInfo<number> { export function getToken(): DataInfo {
// 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错 // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
return Cookies.get(TokenKey) return Cookies.get(TokenKey)
? JSON.parse(Cookies.get(TokenKey)) ? JSON.parse(Cookies.get(TokenKey))
@ -39,35 +35,17 @@ export function getToken(): DataInfo<number> {
* `accessToken``expires`key值为authorized-token的cookie里 * `accessToken``expires`key值为authorized-token的cookie里
* `username``roles``refreshToken``expires`key值为`user-info`localStorage里`multipleTabsKey` * `username``roles``refreshToken``expires`key值为`user-info`localStorage里`multipleTabsKey`
*/ */
export function setToken(data: DataInfo<Date>) { export function setToken(data: DataInfo) {
let expires = 0; const { access_token } = data;
const { accessToken, refreshToken } = data; const cookieString = JSON.stringify({ access_token });
const { isRemembered, loginDay } = useUserStoreHook(); Cookies.set(TokenKey, cookieString);
expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳将此处代码改为expires = data.expires然后把上面的DataInfo<Date>改成DataInfo<number>即可
const cookieString = JSON.stringify({ accessToken, expires });
expires > 0 Cookies.set(multipleTabsKey, "true");
? Cookies.set(TokenKey, cookieString, {
expires: (expires - Date.now()) / 86400000
})
: Cookies.set(TokenKey, cookieString);
Cookies.set(
multipleTabsKey,
"true",
isRemembered
? {
expires: loginDay
}
: {}
);
function setUserKey(username: string, roles: Array<string>) { function setUserKey(username: string, roles: Array<string>) {
useUserStoreHook().SET_USERNAME(username); useUserStoreHook().SET_USERNAME(username);
useUserStoreHook().SET_ROLES(roles); useUserStoreHook().SET_ROLES(roles);
storageLocal().setItem(userKey, { storageLocal().setItem(userKey, {
refreshToken,
expires,
username, username,
roles roles
}); });
@ -77,10 +55,8 @@ export function setToken(data: DataInfo<Date>) {
const { username, roles } = data; const { username, roles } = data;
setUserKey(username, roles); setUserKey(username, roles);
} else { } else {
const username = const username = storageLocal().getItem<DataInfo>(userKey)?.username ?? "";
storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? ""; const roles = storageLocal().getItem<DataInfo>(userKey)?.roles ?? [];
const roles =
storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
setUserKey(username, roles); setUserKey(username, roles);
} }
} }

View File

@ -12,7 +12,6 @@ import type {
import { stringify } from "qs"; import { stringify } from "qs";
import NProgress from "../progress"; import NProgress from "../progress";
import { getToken, formatToken } from "@/utils/auth"; import { getToken, formatToken } from "@/utils/auth";
import { useUserStoreHook } from "@/store/modules/user";
// 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1 // 相关配置请参考www.axios-js.com/zh-cn/docs/#axios-request-config-1
const defaultConfig: AxiosRequestConfig = { const defaultConfig: AxiosRequestConfig = {
@ -79,31 +78,10 @@ class PureHttp {
: new Promise(resolve => { : new Promise(resolve => {
const data = getToken(); const data = getToken();
if (data) { if (data) {
const now = new Date().getTime(); config.headers["Authorization"] = formatToken(
const expired = parseInt(data.expires) - now <= 0; data.access_token
if (expired) { );
if (!PureHttp.isRefreshing) { resolve(config);
PureHttp.isRefreshing = true;
// token过期刷新
useUserStoreHook()
.handRefreshToken({ refreshToken: data.refreshToken })
.then(res => {
const token = res.data.accessToken;
config.headers["Authorization"] = formatToken(token);
PureHttp.requests.forEach(cb => cb(token));
PureHttp.requests = [];
})
.finally(() => {
PureHttp.isRefreshing = false;
});
}
resolve(PureHttp.retryOriginalRequest(config));
} else {
config.headers["Authorization"] = formatToken(
data.accessToken
);
resolve(config);
}
} else { } else {
resolve(config); resolve(config);
} }

View File

@ -33,8 +33,9 @@ dataThemeChange();
const { title } = useNav(); const { title } = useNav();
const ruleForm = reactive({ const ruleForm = reactive({
username: "admin", grantType: "WEB",
password: "admin123" username: "",
password: ""
}); });
const onLogin = async (formEl: FormInstance | undefined) => { const onLogin = async (formEl: FormInstance | undefined) => {
@ -43,7 +44,11 @@ const onLogin = async (formEl: FormInstance | undefined) => {
await formEl.validate((valid, fields) => { await formEl.validate((valid, fields) => {
if (valid) { if (valid) {
useUserStoreHook() useUserStoreHook()
.loginByUsername({ username: ruleForm.username, password: "admin123" }) .loginByUsername({
grantType: ruleForm.grantType,
username: ruleForm.username,
password: ruleForm.password
})
.then(res => { .then(res => {
if (res.success) { if (res.success) {
// //

View File

@ -24,7 +24,14 @@ export default ({ mode }: ConfigEnv): UserConfigExport => {
port: VITE_PORT, port: VITE_PORT,
host: "0.0.0.0", host: "0.0.0.0",
// 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy // 本地跨域代理 https://cn.vitejs.dev/config/server-options.html#server-proxy
proxy: {}, proxy: {
"/dev-api": {
// 这里填写后端地址
target: "http://192.168.1.33:8000",
changeOrigin: true,
rewrite: path => path.replace(/^\/dev-api/, "")
}
},
// 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布 // 预热文件以提前转换和缓存结果,降低启动期间的初始页面加载时长并防止转换瀑布
warmup: { warmup: {
clientFiles: ["./index.html", "./src/{views,components}/*"] clientFiles: ["./index.html", "./src/{views,components}/*"]