gameapi-client/src/views/login.vue

570 lines
17 KiB
Vue
Raw Normal View History

2025-08-14 10:33:48 +08:00
<template>
<div class="login">
<el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
<div class="" style="
left: 0;
padding-right: 10px;
position: absolute;
text-align: right;
top: 10px;
width: 100%;">
<el-select style="width: 120px;font-weight: 590;font-size: 20px;" v-model="languagesSelect"
:placeholder="t('请选择语言')" @change="languagesChange">
<el-option v-for="item in languagesList" :key="item.countryLang" :label="item.name"
:value="item.countryLang" />
</el-select>
</div>
<div class="title-container">
<img style="width: 40px;" src="@/assets/logo/logo.png" />
<h3 class="title titleONG">API后台管理系统</h3>
</div>
<el-form-item prop="username" class="el-form-item-class">
<el-input v-model="loginForm.username" type="text" size="large" style="height: 52px;" class="backgr-class"
:autocomplete="'new-password'" auto-complete="off" placeholder="账号">
<template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="password" class="el-form-item-class">
<el-input v-model="loginForm.password" :type="passwordVisible ? 'text' : 'password'" size="large"
style="height: 52px;" class="backgr-class" :autocomplete="'new-password'" auto-complete="off"
:placeholder="t('密码')" @keyup.enter="handleLogin">
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
<template #suffix>
<el-icon @click="togglePassword" style="cursor: pointer;">
<el-icon v-if="passwordVisible" :size="18">
<View />
</el-icon>
<el-icon v-else :size="18">
<Hide />
</el-icon>
</el-icon>
</template>
</el-input>
</el-form-item>
<!-- <el-form-item v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
size="large"
auto-complete="off"
placeholder="验证码"
style="width: 63%"
@keyup.enter="handleLogin"
>
<template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
</el-input>
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item> -->
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;"></el-checkbox>
<el-form-item prop="isShowEnabled" v-if="captchaEnabled">
<div class="geetest-container">
<div class="geetest-left botion_gradient_bar"></div>
<div v-if="loginForm.isShowEnabled" class="geetest-right botion_tip">
<span>验证通过</span>
</div>
<div v-if="!loginForm.isShowEnabled" @click="onShow" style="cursor: pointer;"
class="geetest-right botion_tip">
<span>点击按钮开始验证</span>
</div>
</div>
<Vcode :show="isShow" @success="onSuccess" @close="onClose" />
</el-form-item>
<el-form-item style="width:100%;">
<el-button
:loading="loading"
size="large"
type="primary"
style="width:100%;"
@click.prevent="handleLogin"
>
<span v-if="!loading"> </span>
<span v-else> ...</span>
</el-button>
<div style="float: right;" v-if="register">
<router-link class="link-type" :to="'/register'">{{ t('立即注册') }}</router-link>
</div>
<p class="password-container">{{ t('忘记密码或两步验证,请联系内部管理员,若仍无法解决,可联系') }}<span>{{ t('技术支持') }}</span></p>
</el-form-item>
</el-form>
<el-dialog v-model="goodDialogVisible" style="margin-top: 45vh !important;" :title="t('温馨提示')" width="418" :before-close="handleClose">
<div style="width: 90%;margin-bottom: 20px;">{{ t('请用手机打开客服端Google身份验证器,输入验证码') }}</div>
<div class="">
<el-input v-model="gooleCode" style="width: 90%" :placeholder="t('验证码只能为数字')" />
</div>
<template #footer>
<div class="dialog-footer">
<el-button @click="clickVisible">{{ t('') }}</el-button>
<el-button type="primary" @click="goolesubmit">
{{ t('确认') }}
</el-button>
</div>
</template>
</el-dialog>
2025-08-26 11:18:24 +08:00
<el-dialog v-model="goodDialogShow" :title="t('谷歌验证')" align-center width="500" :before-close="authenticatorClose">
2025-08-14 10:33:48 +08:00
<div style="width: 100%;margin-bottom: 20px;text-align: center;">
<img style="width: 70%;" :src="imgCods"/>
</div>
<div style="width: 100%;margin-bottom: 20px;text-align: center;">
{{ authenticatorForm.randomSecretKey }}
</div>
<el-form ref="authenticatorRef" :model="authenticatorForm" :rules="authenticatorRules">
<el-form-item label="谷歌验证码" prop="authenticatorCode">
<el-input v-model="authenticatorForm.authenticatorCode" :digit="2" style="width: 100%;height: 36px;" :placeholder="t('验证码只能为数字')" />
</el-form-item>
</el-form>
<div class="" style="margin-top: 20px;margin-bottom: 20px;">
<div>{{ t('打开手机谷歌身份验证APP扫码绑定当前账号') }}</div>
<div>{{ t('绑定成功之后在此输入谷歌身份验证APP上生效的验证码') }}</div>
<div>{{ t('提交之后,系统判断若输入的验证码正确,即可登录系统') }}</div>
</div>
<template #footer>
<div class="dialog-footer" style="width: 90%;margin-left: 5%;">
<el-button type="primary" style="width: 100%;" @click="loginGoogle">
{{ t('登录') }}
</el-button>
</div>
</template>
</el-dialog>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025</span>
</div>
</div>
</template>
<script setup>
import { getCodeImg,bindGoogleCode } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from "@/utils/jsencrypt";
import useUserStore from '@/store/modules/user';
import { i18nScope } from "@/languages"
import NumberInput from "@/components/NumberInput";
import Vcode from "vue3-puzzle-vcode";
import { setToken } from '@/utils/auth'
import {selectListLang} from '@/api/super/agent'
const userStore = useUserStore()
const route = useRoute();
const router = useRouter();
const { proxy } = getCurrentInstance();
const loginForm = ref({
username: "admin",
password: "aaa111",
rememberMe: false,
isShowEnabled: false,
code: "",
uuid: ""
});
const loginRules = {
username: [{ required: true, trigger: "blur", message: proxy.t('请输入您的账号') }],
password: [{ required: true, trigger: "blur", message: proxy.t('请输入您的密码') }],
code: [{ required: true, trigger: "change", message: proxy.t('请输入验证码') }],
isShowEnabled: [
{
validator: (rule, value, callback) => {
if (value == true) {
callback();
} else {
callback(new Error(proxy.t('请完成验证')));
}
},
trigger: 'change',
message: proxy.t('请完成验证')
}
]
};
const codeUrl = ref("");
const loading = ref(false);
// 验证码开关
const captchaEnabled = ref(false);
// 注册开关
const register = ref(false);
const redirect = ref(undefined);
const authenticatorForm = ref({
authenticatorCode: '',
randomSecretKey:'',
});
const imgCods = ref('');
const authenticatorRules = {
authenticatorCode: [
{ required: true, message: '请输入验证码', trigger: 'change' },
{ pattern: /^\d+$/, message: '验证码只能是数字', trigger: 'change' }
]
};
const isShow = ref(false)
const onShow = () => {
isShow.value = true
}
const onClose = () => {
isShow.value = false
}
const onSuccess = () => {
loginForm.value.isShowEnabled = true
onClose()
// 验证成功后的逻辑
}
watch(route, (newRoute) => {
redirect.value = newRoute.query && newRoute.query.redirect;
}, { immediate: true });
function handleLogin() {
proxy.$refs.loginRef.validate(valid => {
if (valid) {
loading.value = true;
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 });
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 });
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 });
} else {
// 否则移除
Cookies.remove("username");
Cookies.remove("password");
Cookies.remove("rememberMe");
}
// 调用action的登录方法
userStore.login(loginForm.value).then((res) => {
if (sessionStorage.getItem('TepdCode') == 500 && captchaEnabled.value == false){
captchaEnabled.value = true;
loading.value = false;
return;
}
if (res.data.code == '500'){
if (sessionStorage.getItem('TepdCode') == undefined){
captchaEnabled.value = true;
}
if (loginForm.value.isShowEnabled){
loginForm.value.isShowEnabled = false;
}
sessionStorage.setItem('TepdCode', 500);
loading.value = false;
}else{
2025-08-26 11:18:24 +08:00
if(res.data.googleCodeSwitch == 0){
const query = route.query;
if (res.data.token){
setToken(res.data.token);
sessionStorage.removeItem('TepdCode');
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
}
return acc;
}, {});
router.push({ path: redirect.value || "/", query: otherQueryParams });
}
}else{
if (res.data.bindStatus == true){
goodDialogVisible.value = true;
}else if (res.data.bindStatus == false){
goodDialogShow.value = true;
imgCods.value = res.data.bindQrCode;
authenticatorForm.value.randomSecretKey = res.data.randomSecretKey;
}
2025-08-14 10:33:48 +08:00
}
}
}).catch(() => {
loading.value = false;
// 重新获取验证码
if (captchaEnabled.value) {
// getCode();
}
});
}
});
}
// function getCode() {
// getCodeImg().then(res => {
// captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled;
// if (captchaEnabled.value) {
// codeUrl.value = "data:image/gif;base64," + res.img;
// loginForm.value.uuid = res.uuid;
// }
// });
// }
const passwordVisible = ref(false)
const togglePassword = () => {
passwordVisible.value = !passwordVisible.value
}
function getCookie() {
const username = Cookies.get("username");
const password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
loginForm.value = {
username: username === undefined ? loginForm.value.username : username,
password: password === undefined ? loginForm.value.password : decrypt(password),
rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
};
}
getCookie();
const goodDialogVisible = ref(false);
const gooleCode = ref('');// google验证码
const goodDialogShow = ref(false);
const handleClose = () => {
goodDialogVisible.value = false;
};
const clickVisible = () => {
loading.value = false;
goodDialogVisible.value = false;
}
const goolesubmit = async() => {
if (gooleCode.value != '') {
let objForm = {
...loginForm.value,
code:gooleCode.value,
}
userStore.login(objForm).then((res) => {
if (res.code == 200){
const query = route.query;
if (res.data.token){
setToken(res.data.token);
sessionStorage.removeItem('TepdCode');
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
}
return acc;
}, {});
router.push({ path: redirect.value || "/", query: otherQueryParams });
2025-08-26 11:18:24 +08:00
}
2025-08-14 10:33:48 +08:00
}
})
}else{
proxy.$message.error('请输入谷歌验证码');
}
}
const loginGoogle = () => {
proxy.$refs.authenticatorRef.validate(valid => {
if (valid) {
let objForm = {
...loginForm.value,
code:authenticatorForm.value.authenticatorCode,
randomSecretKey: authenticatorForm.value.randomSecretKey
}
bindGoogleCode(objForm).then((res) => {
if (res.data.token){
setToken(res.data.token);
sessionStorage.removeItem('TepdCode');
router.push({
path: '/',
query:null
});
}
})
}
})
}
const authenticatorClose = () => {
goodDialogShow.value = false;
loading.value = false;
}
const languagesSelect = ref('')
// 国际化
const languagesChange = async (countryLang) => {
let row = languagesList.value.find(item => item.countryLang == countryLang);
VoerkaI18n.change(row.country)
localStorage.setItem('lang', row.countryLang)
i18nScope.on("change", (newLanguage) => {
window.location.reload(); // 刷新页面
})
}
// 获取下拉语种
const languagesList = ref([])
const getLanguagesList = () => {
languagesList.value = [
{
country: "zh",
countryLang: "zh-CN",
name: "简体中文",
icon: "cn"
},
{
country: "en",
countryLang: "en-US",
name: "English",
icon: "us"
}
];
languagesSelect.value = localStorage.getItem('lang') || 'zh-CN';
}
getLanguagesList()
</script>
<style lang='scss' scoped>
.login {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
background-image: url(../assets/images/login-bg-common.png),linear-gradient(180deg,#eb5f5f,#d83c3c);
background-size: cover;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #707070;
}
.geetest-container {
margin-bottom: 22px;
width: 336px;
border: 1px solid #39c522;
display: flex;
justify-content: space-between;
background: #f3fdec;
border-radius: 5px;
}
.geetest-left{
// position: absolute;
// top: 50%;
// left: -1px;
width: 6px;
height: 50px;
border-bottom-left-radius: 4px;
border-top-left-radius: 4px;
// -webkit-transform: translateY(-50%);
// -ms-transform: translateY(-50%);
// transform: translateY(-50%);
// -webkit-transition: all .3s;
// -o-transition: all .3s;
// transition: all .3s;
}
.geetest-right{
width: 330px;
text-align: center;
line-height: 50px;
}
.botion_tip {
color: #439900;
}
.botion_gradient_bar {
background: #39c522;
}
.login-form {
background-color: #fff;
border-radius: 5px;
max-width: 100%;
padding: 65px 35px 10px;
width: 418px;
position: relative;
.el-input {
height: 40px;
input {
height: 40px;
}
}
.input-icon {
height: 39px;
width: 14px;
margin-left: 0px;
}
}
.login-tip {
font-size: 13px;
text-align: center;
color: #bfbfbf;
}
.login-code {
width: 33%;
height: 40px;
float: right;
img {
cursor: pointer;
vertical-align: middle;
}
}
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
}
.login-code-img {
height: 40px;
padding-left: 12px;
}
.el-form-item-class {
background: rgba(0,0,0,.1);
border: 1px solid hsla(0,0%,100%,.1);
border-radius: 5px;
color: #454545;
margin-bottom: 22px!important;
}
:deep(.backgr-class .el-input__wrapper){
background: none!important;
}
:deep(.el-form .el-input input) {
-webkit-appearance: none;
background: transparent;
border: 0;
border-radius: 0;
color: #666;
height: 52px;
padding: 12px 28px 12px 10px;
}
:deep(input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus) {
background-color: transparent !important;
-webkit-box-shadow:none !important; /* 强制盖住 autofill 背景 */
box-shadow: none!important;
color: #000000; /* 字体颜色可以自己调 */
}
:deep(input:-internal-autofill-selected){
appearance: menulist-button;
background-image: none !important;
background-color: transparent !important;
color: fieldtext !important;
}
.titleONG{
color: #666;
font-size: 20px;
font-weight: 700;
text-align: center;
margin:0;
}
.title-container {
align-items: center;
display: flex;
justify-content: center;
margin-bottom: 20px;
position: relative;
}
.password-container {
color: #c5c5c5;
font-size: 13px;
line-height: 18px;
text-align: center;
width: 100%;
}
.password-container>span {
color: #18f;
cursor: pointer;
text-decoration: underline;
}
:deep(.topMaing .el-dialog:not(.is-fullscreen)) {
margin-top: 44vh !important;
}
</style>