feat: 🚀 用户登录未完成
This commit is contained in:
parent
25cd96e6d9
commit
33fc048aab
2
.env
2
.env
|
@ -1,5 +1,5 @@
|
|||
# 平台本地运行端口号
|
||||
VITE_PORT=8201
|
||||
VITE_PORT=7000
|
||||
|
||||
# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||
VITE_ROUTER_HISTORY="hash"
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
# 平台本地运行端口号
|
||||
VITE_PORT=8201
|
||||
VITE_PORT=7000
|
||||
|
||||
# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||
VITE_ROUTER_HISTORY="hash"
|
||||
|
||||
# 基础请求路径
|
||||
VITE_BASE_API=/api
|
||||
VITE_BASE_API=/admin
|
||||
|
||||
# 跨域代理地址
|
||||
VITE_APP_URL=http://localhost:8801
|
||||
VITE_APP_URL=http://localhost:7070
|
||||
|
||||
# mock地址
|
||||
VITE_MOCK_BASE_API=/mock
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# 平台本地运行端口号
|
||||
VITE_PORT=8201
|
||||
VITE_PORT=7000
|
||||
|
||||
# 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数")
|
||||
VITE_ROUTER_HISTORY="hash"
|
||||
|
@ -8,7 +8,7 @@ VITE_ROUTER_HISTORY="hash"
|
|||
VITE_BASE_API=/api
|
||||
|
||||
# 跨域代理地址
|
||||
VITE_APP_URL=http://localhost:8801
|
||||
VITE_APP_URL=http://localhost:7070
|
||||
|
||||
# mock地址
|
||||
VITE_MOCK_BASE_API=/mock
|
||||
|
|
|
@ -1,9 +1,39 @@
|
|||
// @ts-check
|
||||
// @see: https://www.prettier.cn
|
||||
|
||||
/** @type {import("prettier").Config} */
|
||||
export default {
|
||||
// 超过最大值换行
|
||||
printWidth: 200,
|
||||
// 缩进字节数
|
||||
tabWidth: 1,
|
||||
// 使用制表符而不是空格缩进行
|
||||
useTabs: true,
|
||||
// 结尾不用分号(true有,false没有)
|
||||
semi: true,
|
||||
// 使用单引号(true单引号,false双引号)
|
||||
singleQuote: true,
|
||||
// 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
|
||||
quoteProps: "as-needed",
|
||||
// 在对象,数组括号与文字之间加空格 "{ foo: bar }"
|
||||
bracketSpacing: true,
|
||||
singleQuote: false,
|
||||
// 多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>",默认none
|
||||
trailingComma: "all",
|
||||
// 在JSX中使用单引号而不是双引号
|
||||
jsxSingleQuote: true,
|
||||
// (x) => {} 箭头函数参数只有一个时是否要有小括号。avoid:省略括号 ,always:不省略括号
|
||||
arrowParens: "avoid",
|
||||
trailingComma: "none"
|
||||
// 如果文件顶部已经有一个 doclock,这个选项将新建一行注释,并打上@format标记。
|
||||
insertPragma: false,
|
||||
// 指定要使用的解析器,不需要写文件开头的 @prettier
|
||||
requirePragma: false,
|
||||
// 默认值。因为使用了一些折行敏感型的渲染器(如GitHub comment)而按照markdown文本样式进行折行
|
||||
proseWrap: "preserve",
|
||||
// 在html中空格是否是敏感的 "css" - 遵守CSS显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的
|
||||
htmlWhitespaceSensitivity: "css",
|
||||
// 换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr>"
|
||||
endOfLine: "auto",
|
||||
// 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
|
||||
rangeStart: 0,
|
||||
rangeEnd: Infinity,
|
||||
|
||||
vueIndentScriptAndStyle: false // Vue文件脚本和样式标签缩进
|
||||
};
|
||||
|
|
|
@ -10,10 +10,10 @@ export const serverOptions = (mode: string) => {
|
|||
open: true,
|
||||
cors: true,
|
||||
proxy: {
|
||||
"/api": {
|
||||
"/admin": {
|
||||
target: VITE_APP_URL,
|
||||
changeOrigin: true,
|
||||
rewrite: (path: string) => path.replace(/^\/api/, "/api")
|
||||
rewrite: (path: string) => path.replace(/^\/admin/, "/admin")
|
||||
},
|
||||
"/mock": {
|
||||
target: VITE_APP_URL,
|
||||
|
|
|
@ -10,9 +10,10 @@ import type {
|
|||
RequestMethods
|
||||
} from "./types";
|
||||
import { stringify } from "qs";
|
||||
import NProgress from "../../utils/progress";
|
||||
import NProgress from "@/utils/progress";
|
||||
import { formatToken, getToken } from "@/utils/auth";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { message } from "@/utils/message";
|
||||
|
||||
// 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
|
||||
const defaultConfig: AxiosRequestConfig = {
|
||||
|
@ -55,7 +56,7 @@ class PureHttp {
|
|||
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
||||
return new Promise(resolve => {
|
||||
PureHttp.requests.push((token: string) => {
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
config.headers["token"] = formatToken(token);
|
||||
resolve(config);
|
||||
});
|
||||
});
|
||||
|
@ -136,7 +137,7 @@ class PureHttp {
|
|||
// token过期刷新
|
||||
useUserStoreHook()
|
||||
.handRefreshToken({ refreshToken: data.refreshToken })
|
||||
.then(res => {
|
||||
.then((res: any) => {
|
||||
const token = res.data.accessToken;
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
PureHttp.requests.forEach(cb => cb(token));
|
||||
|
@ -188,8 +189,9 @@ class PureHttp {
|
|||
$error.isCancelRequest = Axios.isCancel($error);
|
||||
// 关闭进度条动画
|
||||
NProgress.done();
|
||||
message(error.message, { type: "error" });
|
||||
// 所有的响应异常 区分来源为取消请求/非取消请求
|
||||
return Promise.reject($error);
|
||||
return $error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class PureHttp {
|
|||
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
||||
return new Promise(resolve => {
|
||||
PureHttp.requests.push((token: string) => {
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
config.headers["token"] = formatToken(token);
|
||||
resolve(config);
|
||||
});
|
||||
});
|
||||
|
@ -129,7 +129,7 @@ class PureHttp {
|
|||
// token过期刷新
|
||||
useUserStoreHook()
|
||||
.handRefreshToken({ refreshToken: data.refreshToken })
|
||||
.then(res => {
|
||||
.then((res: any) => {
|
||||
const token = res.data.accessToken;
|
||||
config.headers["Authorization"] = formatToken(token);
|
||||
PureHttp.requests.forEach(cb => cb(token));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { http } from "@/utils/http";
|
||||
import { http } from "@/api/service";
|
||||
|
||||
type Result = {
|
||||
success: boolean;
|
||||
|
|
|
@ -1,45 +1,57 @@
|
|||
import { http } from "@/api/service/mockRequest";
|
||||
import { http } from "@/api/service";
|
||||
import type { BaseResult } from "@/types/common/BaseResult";
|
||||
|
||||
export type UserResult = {
|
||||
success: boolean;
|
||||
data: {
|
||||
/** 头像 */
|
||||
avatar: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 昵称 */
|
||||
nickname: string;
|
||||
/** 当前登录用户的角色 */
|
||||
roles: Array<string>;
|
||||
/** 按钮级别权限 */
|
||||
permissions: Array<string>;
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
};
|
||||
};
|
||||
export interface UserResult {
|
||||
/** 头像 */
|
||||
avatar: string;
|
||||
/** 用户名 */
|
||||
username: string;
|
||||
/** 昵称 */
|
||||
nickname: string;
|
||||
/** 当前登录用户的角色 */
|
||||
roles: Array<string>;
|
||||
/** 按钮级别权限 */
|
||||
permissions: Array<string>;
|
||||
/** `token` */
|
||||
accessToken: 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 interface RefreshTokenResult {
|
||||
/** `token` */
|
||||
accessToken: string;
|
||||
/** 用于调用刷新`accessToken`的接口时所需的`token` */
|
||||
refreshToken: string;
|
||||
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx') */
|
||||
expires: Date;
|
||||
}
|
||||
|
||||
/** 登录 */
|
||||
export const getLogin = (data?: object) => {
|
||||
return http.request<UserResult>("post", "/login", { data });
|
||||
export const fetchLogin = (data?: object) => {
|
||||
return http.request<BaseResult<UserResult>>("post", "/login", { data });
|
||||
};
|
||||
|
||||
/**
|
||||
* * 发送邮件
|
||||
* @param data
|
||||
*/
|
||||
export const fetchPostEmailCode = (data: any) => {
|
||||
return http.request<BaseResult<any>>(
|
||||
"post",
|
||||
"/user/noAuth/sendLoginEmail",
|
||||
{ data },
|
||||
{ headers: { "Content-Type": "multipart/form-data" } }
|
||||
);
|
||||
};
|
||||
|
||||
/** 刷新`token` */
|
||||
export const refreshTokenApi = (data?: object) => {
|
||||
return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
|
||||
return http.request<BaseResult<RefreshTokenResult>>(
|
||||
"post",
|
||||
"/refresh-token",
|
||||
{ data }
|
||||
);
|
||||
};
|
||||
|
|
|
@ -361,10 +361,9 @@ function hasAuth(value: string | Array<string>): boolean {
|
|||
/** 从当前路由的`meta`字段里获取按钮级别的所有自定义`code`值 */
|
||||
const metaAuths = getAuths();
|
||||
if (!metaAuths) return false;
|
||||
const isAuths = isString(value)
|
||||
return isString(value)
|
||||
? metaAuths.includes(value)
|
||||
: isIncludeAllChildren(value, metaAuths);
|
||||
return isAuths ? true : false;
|
||||
}
|
||||
|
||||
function handleTopMenu(route) {
|
||||
|
|
|
@ -8,16 +8,17 @@ import {
|
|||
type userType
|
||||
} from "../utils";
|
||||
import {
|
||||
getLogin,
|
||||
fetchLogin,
|
||||
fetchPostEmailCode,
|
||||
refreshTokenApi,
|
||||
type RefreshTokenResult,
|
||||
type UserResult
|
||||
type RefreshTokenResult
|
||||
} from "@/api/v1/user";
|
||||
import { useMultiTagsStoreHook } from "./multiTags";
|
||||
import { type DataInfo, removeToken, setToken, userKey } from "@/utils/auth";
|
||||
import { message } from "@/utils/message";
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: "pure-user",
|
||||
id: "system-user",
|
||||
state: (): userType => ({
|
||||
// 头像
|
||||
avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
|
||||
|
@ -65,33 +66,53 @@ export const useUserStore = defineStore({
|
|||
this.loginDay = Number(value);
|
||||
},
|
||||
/** 登入 */
|
||||
async loginByUsername(data) {
|
||||
return new Promise<UserResult>((resolve, reject) => {
|
||||
getLogin(data)
|
||||
.then(data => {
|
||||
if (data?.success) setToken(data.data);
|
||||
resolve(data);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
async loginByUsername(data: any) {
|
||||
const result = await fetchLogin(data);
|
||||
|
||||
if (result.code === 200) {
|
||||
setToken(data.data);
|
||||
return true;
|
||||
}
|
||||
|
||||
message(result.message, { type: "error" });
|
||||
return false;
|
||||
},
|
||||
/** 前端登出(不调用接口) */
|
||||
logOut() {
|
||||
|
||||
/**
|
||||
* * 发送邮箱验证码
|
||||
* @param email
|
||||
*/
|
||||
async postEmailCode(email: string) {
|
||||
const response = await fetchPostEmailCode({ email });
|
||||
if (response.code === 200) {
|
||||
message(response.message, { type: "success" });
|
||||
return true;
|
||||
}
|
||||
message(response.message, { type: "error" });
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* 前端登出(不调用接口)
|
||||
*/
|
||||
async logOut() {
|
||||
this.username = "";
|
||||
this.roles = [];
|
||||
this.permissions = [];
|
||||
removeToken();
|
||||
useMultiTagsStoreHook().handleTags("equal", [...routerArrays]);
|
||||
resetRouter();
|
||||
router.push("/login");
|
||||
await router.push("/login");
|
||||
},
|
||||
/** 刷新`token` */
|
||||
|
||||
/**
|
||||
* 刷新`token`
|
||||
*/
|
||||
async handRefreshToken(data) {
|
||||
return new Promise<RefreshTokenResult>((resolve, reject) => {
|
||||
refreshTokenApi(data)
|
||||
.then(data => {
|
||||
.then((data: any) => {
|
||||
if (data) {
|
||||
setToken(data.data);
|
||||
resolve(data);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
// 基础后端返回内容
|
||||
export interface BaseResult<T> {
|
||||
code: number;
|
||||
data: T;
|
||||
message: string;
|
||||
}
|
|
@ -8,8 +8,7 @@ import { useNav } from "@/layout/hooks/useNav";
|
|||
import type { FormInstance } from "element-plus";
|
||||
import { $t } from "@/plugins/i18n";
|
||||
import { useLayout } from "@/layout/hooks/useLayout";
|
||||
import { useUserStoreHook } from "@/store/modules/user";
|
||||
import { getTopMenu, initRouter } from "@/router/utils";
|
||||
import { useUserStore } from "@/store/modules/user";
|
||||
import { avatar, bg, illustration } from "./utils/static";
|
||||
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
|
||||
import { onBeforeUnmount, onMounted, reactive, ref, toRaw } from "vue";
|
||||
|
@ -22,6 +21,7 @@ import globalization from "@/assets/svg/globalization.svg?component";
|
|||
import Lock from "@iconify-icons/ri/lock-fill";
|
||||
import Check from "@iconify-icons/ep/check";
|
||||
import User from "@iconify-icons/ri/user-3-fill";
|
||||
import { getTopMenu, initRouter } from "@/router/utils";
|
||||
|
||||
defineOptions({
|
||||
name: "Login"
|
||||
|
@ -29,6 +29,8 @@ defineOptions({
|
|||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const ruleFormRef = ref<FormInstance>();
|
||||
const sendSecond = ref(60);
|
||||
const timer = ref(null);
|
||||
|
||||
const { initStorage } = useLayout();
|
||||
initStorage();
|
||||
|
@ -38,32 +40,71 @@ const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
|
|||
dataThemeChange(overallStyle.value);
|
||||
const { title, getDropdownItemStyle, getDropdownItemClass } = useNav();
|
||||
const { locale, translationCh, translationEn } = useTranslationLang();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const ruleForm = reactive({
|
||||
username: "admin",
|
||||
password: "admin123"
|
||||
username: "1319900154@qq.com",
|
||||
password: "admin123",
|
||||
emailCode: ""
|
||||
});
|
||||
|
||||
/**
|
||||
* * 发送邮箱验证码
|
||||
*/
|
||||
const onSendEmailCode = async () => {
|
||||
// 判断是否填写邮箱,如果没有填写邮箱不给发送验证码
|
||||
if (ruleForm.username === "" || ruleForm.username === void 0) {
|
||||
message("请先填写邮箱地址", { type: "warning" });
|
||||
return false;
|
||||
}
|
||||
const result = await userStore.postEmailCode(ruleForm.username);
|
||||
if (result) {
|
||||
// 开始倒计时,之后发送邮箱验证码
|
||||
onSendEmailTimer();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* * 发送邮箱倒计时点击
|
||||
*/
|
||||
const onSendEmailTimer = () => {
|
||||
// 开始倒计时
|
||||
timer.value = setInterval(() => {
|
||||
// 当定时小于0时清除定时器
|
||||
if (sendSecond.value <= 0) {
|
||||
clearInterval(timer.value);
|
||||
timer.value = null;
|
||||
sendSecond.value = 60;
|
||||
return;
|
||||
}
|
||||
|
||||
// 之后每秒减去时间
|
||||
sendSecond.value--;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* 登录
|
||||
* @param formEl
|
||||
*/
|
||||
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return;
|
||||
await formEl.validate((valid, fields) => {
|
||||
await formEl.validate(async valid => {
|
||||
if (valid) {
|
||||
loading.value = true;
|
||||
useUserStoreHook()
|
||||
.loginByUsername({ username: ruleForm.username, password: "admin123" })
|
||||
.then(res => {
|
||||
if (res.success) {
|
||||
// 获取后端路由
|
||||
return initRouter().then(() => {
|
||||
router.push(getTopMenu(true).path).then(() => {
|
||||
message(t("login.loginSuccess"), { type: "success" });
|
||||
});
|
||||
});
|
||||
} else {
|
||||
message(t("login.loginFail"), { type: "error" });
|
||||
}
|
||||
})
|
||||
.finally(() => (loading.value = false));
|
||||
const result = await userStore.loginByUsername(ruleForm);
|
||||
|
||||
if (result) {
|
||||
// 获取后端路由
|
||||
await initRouter();
|
||||
router.push(getTopMenu(true).path).then(() => {
|
||||
message(t("login.loginSuccess"), { type: "success" });
|
||||
});
|
||||
} else {
|
||||
message(t("login.loginFail"), { type: "error" });
|
||||
}
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -178,6 +219,39 @@ onBeforeUnmount(() => {
|
|||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="150">
|
||||
<el-form-item prop="emailCode">
|
||||
<el-input
|
||||
v-model="ruleForm.emailCode"
|
||||
:placeholder="t('login.emailCode')"
|
||||
:prefix-icon="useRenderIcon('ic:outline-email')"
|
||||
clearable
|
||||
@keydown.enter="onLogin(ruleFormRef)"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<el-link
|
||||
v-if="sendSecond === 60"
|
||||
:underline="false"
|
||||
class="px-2"
|
||||
type="primary"
|
||||
@click="onSendEmailCode"
|
||||
>
|
||||
{{ t("login.getEmailCode") }}
|
||||
</el-link>
|
||||
<el-link
|
||||
v-else
|
||||
:underline="false"
|
||||
class="px-2"
|
||||
type="primary"
|
||||
>
|
||||
{{ sendSecond }}
|
||||
{{ t("login.getCodeInfo") }}
|
||||
</el-link>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</Motion>
|
||||
|
||||
<Motion :delay="250">
|
||||
<el-button
|
||||
:loading="loading"
|
||||
|
|
|
@ -21,7 +21,8 @@ const loginRules = reactive(<FormRules>{
|
|||
},
|
||||
trigger: "blur"
|
||||
}
|
||||
]
|
||||
],
|
||||
emailCode: [{ required: true, trigger: "blur", type: "string" }]
|
||||
});
|
||||
|
||||
export { loginRules };
|
||||
|
|
Loading…
Reference in New Issue