gameapi-client/src/views/login.vue

570 lines
17 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>
<el-dialog v-model="goodDialogShow" :title="t('谷歌验证')" align-center width="500" :before-close="authenticatorClose">
<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{
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;
}
}
}
}).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 });
}
}
})
}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>