m2pool_web_frontend/mining-pool/src/views/login/login.vue

755 lines
20 KiB
Vue
Raw Normal View History

<template>
<div class="loginPage">
<section class="mobileMain" v-if="$isMobile">
<header class="headerBox">
<img
@click="handelJump(`/`)"
src="../../assets/mobile/login/LOGO.svg"
alt="logo"
loading="lazy"
/>
<span class="title">{{ $t(`home.MLogin`) }}</span>
<span></span>
</header>
<div class="imgTop">
<img
src="../../assets/mobile/login/logointop.svg"
alt="Login for mining"
loading="lazy"
/>
</div>
<section class="formInput">
<el-form
:model="loginForm"
status-icon
:rules="loginRules"
ref="ruleForm"
class="demo-ruleForm"
>
<el-form-item prop="userName">
<el-input
prefix-icon="el-icon-user"
v-model="loginForm.userName"
autocomplete="off"
:placeholder="$t('user.Account')"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="loginForm.password"
prefix-icon="el-icon-unlock"
autocomplete="off"
showPassword
:placeholder="$t('user.password')"
></el-input>
</el-form-item>
<el-form-item prop="code">
<div class="verificationCode">
<el-input
type="text"
prefix-icon="el-icon-chat-line-square"
v-model="loginForm.code"
autocomplete="off"
:placeholder="$t(`user.verificationCode`)"
></el-input>
<el-button
class="codeBtn"
:disabled="btnDisabled"
@click="handelCode"
><span v-if="countDownTime < 60 && countDownTime > 0">{{
countDownTime
}}</span>
{{ $t(bthText) }}</el-button
>
</div>
</el-form-item>
<div class="registerBox">
<span class="noAccount">{{ $t("user.noAccount") }}</span>
<span style="color: #661fff" @click="handelJump(`register`)">{{
$t("user.register")
}}</span>
<span
style="color: #661fff"
class="forget"
@click="handelJump(`resetPassword`)"
>{{ $t("user.forgotPassword") }}</span
>
</div>
<el-form-item>
<el-button
style="
width: 100%;
background: #661fff;
color: aliceblue;
margin-top: 6%;
"
:loading="loginLoading"
@click="submitForm('ruleForm')"
>{{ $t("user.login") }}</el-button
>
<div style="text-align: left">
<el-radio @input="handelRadio" v-model="radio" label="zh"
>简体中文</el-radio
>
<el-radio @input="handelRadio" v-model="radio" label="en"
>English</el-radio
>
</div>
</el-form-item>
</el-form>
</section>
</section>
<section class="loginModular" v-else>
<div class="leftBox">
<img
class="logo"
src="../../assets/img/WILOGO.png"
alt="logo"
@click="handleClick"
/>
<img src="../../assets/img/logicon.png" alt="Login for mining" />
</div>
<div class="loginBox">
<div class="closeBox" @click="handleClick">
<i class="iconfont icon-guanbi1 close"></i>
</div>
<el-form
:model="loginForm"
status-icon
:rules="loginRules"
ref="ruleForm"
class="demo-ruleForm"
>
<el-form-item>
<p class="loginTitle">{{ $t(`user.login`) }}</p>
</el-form-item>
<el-form-item prop="userName">
<el-input
prefix-icon="el-icon-user"
v-model="loginForm.userName"
autocomplete="off"
:placeholder="$t('user.Account')"
type="email"
></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input
type="password"
v-model="loginForm.password"
prefix-icon="el-icon-unlock"
autocomplete="off"
showPassword
:placeholder="$t('user.password')"
></el-input>
</el-form-item>
<el-form-item prop="code">
<div class="verificationCode">
<el-input
type="text"
prefix-icon="el-icon-chat-line-square"
v-model="loginForm.code"
autocomplete="off"
:placeholder="$t(`user.verificationCode`)"
></el-input>
<el-button
class="codeBtn"
:disabled="btnDisabled"
@click="handelCode"
><span v-if="countDownTime < 60 && countDownTime > 0">{{
countDownTime
}}</span>
{{ $t(bthText) }}</el-button
>
</div>
</el-form-item>
<div class="registerBox">
<span class="noAccount">{{ $t("user.noAccount") }}</span>
<span style="cursor: pointer" @click="handelJump(`/register`)">{{
$t("user.register")
}}</span>
<span class="forget" @click="handelJump(`/resetPassword`)">{{
$t("user.forgotPassword")
}}</span>
</div>
<el-form-item>
<el-button
style="
width: 100%;
background: #661fff;
color: aliceblue;
margin-top: 6%;
"
:loading="loginLoading"
@click="submitForm('ruleForm')"
>{{ $t("user.login") }}</el-button
>
<div style="text-align: left">
<el-radio @input="handelRadio" v-model="radio" label="zh"
>简体中文</el-radio
>
<el-radio @input="handelRadio" v-model="radio" label="en"
>English</el-radio
>
</div>
</el-form-item>
</el-form>
</div>
</section>
</div>
</template>
<script>
import {
getLogin,
getLoginCode,
getAccountGradeList,
getUserProfile,
} from "../../api/login";
import { encryption } from "../../utils/fun";
import { getAccountList } from "../../api/personalCenter";
2025-06-13 06:58:47 +00:00
export default {
data() {
return {
loginForm: {
userName: "",
password: "",
code: "",
},
loginRules: {
userName: [
{
required: true,
trigger: "blur",
message: this.$t(`user.inputEmail`),
// type: "email",
},
],
password: [
{
required: true,
trigger: "blur",
message: this.$t(`user.inputPassword`),
},
],
code: [
{
required: true,
trigger: "change",
message: this.$t(`user.inputCode`),
},
],
},
radio: `en`,
btnDisabled: false,
bthText: "user.obtainVerificationCode",
time: "",
loginLoading: false,
accountList: [],
loginCodeTime: "",
countDownTime: 60,
timer: null,
lang: "en",
};
},
computed: {
countDown() {
const minutes = Math.floor(this.countDownTime / 60);
const seconds = this.countDownTime % 60;
const m = minutes < 10 ? "0" + minutes : minutes;
const s = seconds < 10 ? "0" + seconds : seconds;
return `${s}`;
},
},
watch: {
"$i18n.locale": function () {
this.translate();
},
},
created() {
if (window.sessionStorage.getItem("exam_time")) {
this.countDownTime = Number(window.sessionStorage.getItem("exam_time"));
this.startCountDown();
this.btnDisabled = true;
this.bthText = `user.again`;
}
// if (window.sessionStorage.getItem("exam_time") == null){
// this.startCountDown()
// }else{
// this.countDownTime = Number(window.sessionStorage.getItem("exam_time"));
// this.startCountDown()
// }
},
mounted() {
this.lang = this.$i18n.locale; // 初始化语言值
this.radio = localStorage.getItem("lang")
? localStorage.getItem("lang")
: "en";
},
methods: {
translate() {
this.loginRules = {
// type: "email",
userName: [
{
required: true,
trigger: "blur",
message: this.$t(`user.inputEmail`),
},
],
password: [
{
required: true,
trigger: "blur",
message: this.$t(`user.inputPassword`),
},
],
code: [
{
required: true,
trigger: "change",
message: this.$t(`user.inputCode`),
},
],
};
},
//账户权限
async fetchJurisdiction() {
const data = await getUserProfile();
this.$addStorageEvent(1, "jurisdiction", JSON.stringify(data.data.role));
this.$addStorageEvent(1, "userEmail", JSON.stringify(data.data.email));
if (data && data.code == 200) {
this.$message({
message: this.$t(`user.loginSuccess`),
type: "success",
showClose: true,
});
this.$router.push(`/${this.lang}`);
}
},
async fetchAccountGradeList() {
const data = await getAccountGradeList();
if (data && data.code == 200) {
this.$addStorageEvent(
1,
`miningAccountList`,
JSON.stringify(data.data)
);
}
},
async fetchAccountList(params) {
const data = await getAccountList(params);
if (data && data.code == 200) {
this.accountList = data.data;
this.$addStorageEvent(
1,
`accountList`,
JSON.stringify(this.accountList)
);
}
},
async fetchLogin(params) {
this.loginLoading = true;
const data = await getLogin(params);
if (data && data.code === 200) {
this.$addStorageEvent(1, "userName", data.data.userName);
this.$addStorageEvent(
1,
"token",
JSON.stringify(data.data.access_token)
);
2025-06-13 06:58:47 +00:00
// 等待一小段时间确保写入完成
await new Promise(resolve => setTimeout(resolve, 50));
// 登录成功后
this.$bus.$emit('user-logged-in'); // 触发登录成功全局事件
this.fetchAccountList();
this.fetchAccountGradeList();
this.fetchJurisdiction();
}
this.loginLoading = false;
},
async fetchCOde(params) {
const data = await getLoginCode(params);
if (data && data.code == 200) {
this.$message({
message: this.$t(`user.codeSuccess`),
type: "success",
showClose: true,
});
}
},
handelCode() {
if (!this.loginForm.userName) {
this.$message({
message: this.$t(`user.inputAccount`),
type: "error",
customClass: "messageClass",
showClose: true,
});
return;
}
//邮箱格式验证 /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
let isMailbox = emailRegex.test(this.loginForm.userName);
if (!this.loginForm.userName || !isMailbox) {
this.$message({
message: this.$t(`user.emailVerification`),
type: "error",
showClose: true,
});
return;
}
this.fetchCOde({ account: this.loginForm.userName });
if (window.sessionStorage.getItem("exam_time") == null) {
this.startCountDown();
} else {
this.countDownTime = Number(window.sessionStorage.getItem("exam_time"));
this.startCountDown();
}
// this.time = 60;
// let timer = setInterval(()=>{
// if (this.time) {
// this.time--
// this.btnDisabled = true;
// // this.bthText=this.time+`s后重新获取`
// this.bthText=`user.again`
// }else {
// this.btnDisabled = false;
// this.bthText=`user.obtainVerificationCode`
// this.time = "";
// clearTimeout(timer)
// }
// },1000)
},
startCountDown() {
this.timer = setInterval(() => {
if (this.countDownTime <= 1) {
//当监测到countDownTime为0时,清除计数器并且移除sessionStorage,然后执行提交试卷逻辑
clearInterval(this.timer);
sessionStorage.removeItem("exam_time");
this.countDownTime = 60;
this.btnDisabled = false;
this.bthText = `user.obtainVerificationCode`;
} else if (this.countDownTime > 0) {
//每秒让countDownTime -1秒,并设置到sessionStorage中
this.countDownTime--;
this.btnDisabled = true;
this.bthText = `user.again`;
window.sessionStorage.setItem("exam_time", this.countDownTime);
}
}, 1000);
},
handelJump(url) {
// 移除开头的斜杠
const cleanPath = url.startsWith("/") ? url.slice(1) : url;
this.$router.push(`/${this.lang}/${cleanPath}`);
},
handelRadio(val) {
// 保存旧的语言值用于路径替换
const oldLang = this.lang;
// 更新语言设置
this.lang = val;
this.$i18n.locale = val;
localStorage.setItem("lang", val);
// 更新当前路由的语言部分
const currentPath = this.$route.path;
const newPath = currentPath.replace(`/${oldLang}`, `/${val}`);
// 保持查询参数
this.$router
.push({
path: newPath,
query: this.$route.query,
})
.catch((err) => {
if (err.name !== "NavigationDuplicated") {
console.error("Navigation failed:", err);
}
});
},
submitForm() {
this.$refs.ruleForm.validate((valid) => {
if (valid) {
//去空格
this.loginForm.userName = this.loginForm.userName.trim();
this.loginForm.password = this.loginForm.password.trim();
//邮箱格式验证
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/;
let isMailbox = emailRegex.test(this.loginForm.userName);
if (!isMailbox) {
this.$message({
message: this.$t(`user.emailVerification`),
type: "error",
customClass: "messageClass",
showClose: true,
});
return;
// 用户名规则1.长度限制3<=用户名<=16; 字符限制:仅允许使用字母、数字、下划线 用户名必须以字母开头
// const regex = /^[a-zA-Z][a-zA-Z0-9_]{2,15}$/; // 正则表达式
// const isValid = regex.test(this.loginForm.userName);
// if (!isValid) {
// // 如果输入不符合要求,可以根据具体需求给出错误提示或进行其他处理
// this.$message({
// message: this.$t(`user.accountReminder`),
// type: "error",
// customClass: "messageClass",
// showClose: true
// });
// return;
// }
}
// 密码验证 8<=密码<=32 包含大小写字母、数字和特殊字符(!@#¥%……&.*
const regexPassword =
/^(?!.*[\u4e00-\u9fa5])(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\W_]+$)(?![a-z0-9]+$)(?![a-z\W_]+$)(?![0-9\W_]+$)[a-zA-Z0-9\W_]{8,32}$/; // 正则表达式
const PasswordIsValid = regexPassword.test(this.loginForm.password);
if (!PasswordIsValid) {
// 如果输入不符合要求,可以根据具体需求给出错误提示或进行其他处理
this.$message({
message: this.$t(`user.PasswordReminder`),
type: "error",
showClose: true,
});
return;
}
//加密
const form = { ...this.loginForm };
form.password = encryption(this.loginForm.password);
this.fetchLogin(form);
}
});
},
handleClick() {
this.$router.push(`/${this.lang}`);
},
},
};
</script>
<style scoped lang="scss">
.loginPage {
width: 100%;
height: 100%;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
padding: 100px 0px;
background-image: url(../../assets/img/logBg.png);
background-size: cover;
background-repeat: no-repeat;
.loginModular {
width: 50%;
height: 85%;
display: flex;
border-radius: 10px;
overflow: hidden;
box-shadow: 0px 0px 20px 18px #d6d2ea;
// box-shadow: 0px 0px 20PX 30PX #000;
}
.leftBox {
width: 47%;
height: 100%;
background-image: linear-gradient(150deg, #18b7e6 -20%, #651fff 60%);
position: relative;
.logo {
position: absolute;
left: 30px;
top: 18px;
width: 22%;
}
img {
width: 100%;
position: absolute;
right: -16%;
bottom: 0;
z-index: 99;
}
}
}
.el-form {
width: 90%;
padding: 0px 20px 50px 20px;
}
.el-form-item {
width: 100%;
}
.loginBox {
width: 53%;
// box-shadow: 0px 0px 5PX 1PX #ccc;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
position: relative;
flex-direction: column;
overflow: hidden;
background: #fff;
position: relative;
padding: 0px 35px;
.demo-ruleForm {
height: 100%;
padding-top: 5%;
}
img {
width: 18%;
position: absolute;
top: 20px;
left: 30px;
cursor: pointer;
}
.closeBox {
position: absolute;
top: 18px;
right: 30px;
cursor: pointer;
.close {
font-size: 1.3em;
}
}
.closeBox:hover {
color: #661fff;
}
}
.loginTitle {
font-size: 20px;
font-weight: 600;
margin-bottom: 30px;
text-align: center;
}
.loginColor {
width: 100%;
height: 15px;
background: #661fff;
}
.langBox {
display: flex;
justify-content: space-between;
align-content: center;
}
.registerBox {
display: flex;
justify-content: start;
margin: 0;
font-size: 12px;
height: 20px;
span {
padding: 5px 0px;
display: inline-block;
height: 100%;
z-index: 99;
}
span:hover {
color: #661fff;
}
.noAccount:hover {
color: #000;
}
.forget {
margin-left: 8px;
cursor: pointer;
}
}
.forget {
margin-left: 10px;
}
.forgotPassword {
display: inline-block;
width: 20%;
}
.verificationCode {
display: flex;
.codeBtn {
font-size: 13px;
margin-left: 2px;
}
}
// 手机端适配
@media screen and (min-width: 220px) and (max-width: 1279px) {
.mobileMain {
width: 100vw;
min-height: 100vh;
background: #fff;
background-image: url("../../assets/mobile/login/bgtop.svg");
background-repeat: no-repeat;
background-size: 115%;
background-position: 49% 47%;
}
.headerBox {
width: 100%;
height: 60px;
// background: palegoldenrod;
// border-bottom: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0px 20px;
box-shadow: 0px 0px 2px 1px #ccc;
img {
width: 30px;
}
.title {
font-weight: 600;
}
}
.imgTop {
width: 100%;
display: flex;
justify-content: center;
margin-top: 2%;
img {
height: 159px;
}
}
.formInput {
width: 100%;
display: flex;
justify-content: center;
}
.footer {
width: 100%;
height: 100px;
background: #651fff;
}
}
</style>