570 lines
17 KiB
Vue
570 lines
17 KiB
Vue
<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>
|