2024-06-15 13:02:35 +08:00
|
|
|
|
<template>
|
2025-02-27 10:48:50 +08:00
|
|
|
|
<el-tooltip :visible="state.capsLockVisible" effect="light" :content="t('message.account.lockTag')" placement="top">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
<el-form ref="ruleFormRef" :model="state.ruleForm" size="large" :rules="state.rules" class="login-content-form">
|
|
|
|
|
|
<el-form-item class="login-animation1" prop="account">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<el-input ref="accountRef" text :placeholder="t('message.account.accountneed')"
|
|
|
|
|
|
v-model="state.ruleForm.account" clearable autocomplete="off" @keyup.enter.native="handleSignIn">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
<template #prefix>
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<ele-User />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item class="login-animation2" prop="password">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<el-input ref="passwordRef" :type="state.isShowPassword ? 'text' : 'password'"
|
|
|
|
|
|
:placeholder="t('message.account.passwordneed')" v-model="state.ruleForm.password"
|
|
|
|
|
|
autocomplete="off" @keyup.enter.native="handleSignIn">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
<template #prefix>
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<ele-Unlock />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template #suffix>
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<i class="iconfont el-input__icon login-content-password"
|
|
|
|
|
|
:class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
|
|
|
|
|
|
@click="state.isShowPassword = !state.isShowPassword">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
</i>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item class="login-animation3" prop="captcha" v-if="state.captchaEnabled">
|
|
|
|
|
|
<el-col :span="15">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<el-input ref="codeRef" text maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')"
|
|
|
|
|
|
v-model="state.ruleForm.code" clearable autocomplete="off" @keyup.enter.native="handleSignIn">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
<template #prefix>
|
|
|
|
|
|
<el-icon>
|
|
|
|
|
|
<ele-Position />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
</el-input>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
<el-col :span="1"></el-col>
|
|
|
|
|
|
<el-col :span="8">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<div :class="[state.expirySeconds > 0 ? 'login-content-code' : 'login-content-code-expired']"
|
|
|
|
|
|
@click="getCaptcha">
|
|
|
|
|
|
<div style="width: 130px; height: 38px; text-align: center; cursor: pointer"
|
|
|
|
|
|
v-if="state.captchaImage == ''">
|
2024-12-10 08:37:09 +08:00
|
|
|
|
<el-icon class="is-loading">
|
|
|
|
|
|
<ele-Loading />
|
|
|
|
|
|
</el-icon>
|
|
|
|
|
|
</div>
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<img class="login-content-code-img" width="130px" height="38px" :src="state.captchaImage"
|
|
|
|
|
|
style="cursor: pointer" v-else />
|
2024-06-15 13:02:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</el-col>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<el-form-item class="login-animation4">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<el-button type="primary" icon="ele-Promotion" class="login-content-submit" round v-waves
|
|
|
|
|
|
@click="handleSignIn" :loading="state.loading.signIn">
|
2024-06-15 13:02:35 +08:00
|
|
|
|
<span>{{ $t('message.account.accountBtnText') }}</span>
|
|
|
|
|
|
</el-button>
|
|
|
|
|
|
</el-form-item>
|
|
|
|
|
|
<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
|
|
|
|
|
|
<!-- <el-button type="primary" round v-waves @click="weixinSignIn" :loading="state.loading.signIn"></el-button> -->
|
|
|
|
|
|
</el-form>
|
|
|
|
|
|
</el-tooltip>
|
|
|
|
|
|
<div class="dialog-header">
|
|
|
|
|
|
<el-dialog v-model="state.rotateVerifyVisible" :show-close="false">
|
2025-02-27 15:01:29 +08:00
|
|
|
|
<DragVerifyImgRotate ref="dragRef" :imgsrc="state.rotateVerifyImg" v-model:isPassing="state.isPassRotate"
|
|
|
|
|
|
:text="t('message.account.splitslive')" :successText="t('message.account.success')"
|
|
|
|
|
|
handlerIcon="fa fa-angle-double-right" successIcon="fa fa-hand-peace-o"
|
|
|
|
|
|
@passcallback="passRotateVerify" />
|
2024-06-15 13:02:35 +08:00
|
|
|
|
</el-dialog>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script lang="ts" setup name="loginAccount">
|
|
|
|
|
|
import { reactive, computed, ref, onMounted, defineAsyncComponent, onUnmounted } from 'vue';
|
|
|
|
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
|
|
import { ElMessage, InputInstance } from 'element-plus';
|
|
|
|
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
|
|
import { initBackEndControlRoutes } from '/@/router/backEnd';
|
|
|
|
|
|
import { Local, Session } from '/@/utils/storage';
|
|
|
|
|
|
import { formatAxis } from '/@/utils/formatTime';
|
|
|
|
|
|
import { NextLoading } from '/@/utils/loading';
|
|
|
|
|
|
import { sm2 } from 'sm-crypto-v2';
|
2024-09-30 15:34:05 +08:00
|
|
|
|
import { useRoutesList } from '/@/stores/routesList';
|
2024-06-15 13:02:35 +08:00
|
|
|
|
import { useThemeConfig } from '/@/stores/themeConfig';
|
|
|
|
|
|
import { storeToRefs } from 'pinia';
|
|
|
|
|
|
|
|
|
|
|
|
import { accessTokenKey, clearTokens, feature, getAPI } from '/@/utils/axios-utils';
|
|
|
|
|
|
import { SysAuthApi } from '/@/api-services/api';
|
|
|
|
|
|
|
|
|
|
|
|
// 旋转图片滑块组件
|
|
|
|
|
|
// import verifyImg from '/@/assets/logo-mini.svg';
|
|
|
|
|
|
const DragVerifyImgRotate = defineAsyncComponent(() => import('/@/components/dragVerify/dragVerifyImgRotate.vue'));
|
|
|
|
|
|
|
|
|
|
|
|
const storesThemeConfig = useThemeConfig();
|
|
|
|
|
|
const { themeConfig } = storeToRefs(storesThemeConfig);
|
|
|
|
|
|
|
|
|
|
|
|
const { t } = useI18n();
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
|
|
const router = useRouter();
|
|
|
|
|
|
|
|
|
|
|
|
const ruleFormRef = ref();
|
|
|
|
|
|
const accountRef = ref<InputInstance>();
|
|
|
|
|
|
const passwordRef = ref<InputInstance>();
|
|
|
|
|
|
const codeRef = ref<InputInstance>();
|
2025-02-27 10:48:50 +08:00
|
|
|
|
const accountNeed = t('message.account.accountneed');
|
|
|
|
|
|
const passwordNeed = t('message.account.passwordneed');
|
|
|
|
|
|
const codeNeed = t('message.account.codeNeed');
|
|
|
|
|
|
const loginFail = t('message.account.loginfail');
|
|
|
|
|
|
const notPrivilege = t('message.account.notprivilege');
|
2024-06-15 13:02:35 +08:00
|
|
|
|
|
|
|
|
|
|
const dragRef: any = ref(null);
|
|
|
|
|
|
const state = reactive({
|
|
|
|
|
|
isShowPassword: false,
|
|
|
|
|
|
ruleForm: {
|
2024-08-10 03:54:29 +08:00
|
|
|
|
account: window.__env__.VITE_DEFAULT_USER,
|
|
|
|
|
|
password: window.__env__.VITE_DEFAULT_USER_PASSWORD,
|
2024-06-15 13:02:35 +08:00
|
|
|
|
code: '',
|
2024-12-10 08:37:09 +08:00
|
|
|
|
codeId: 0 as any,
|
2024-06-15 13:02:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
rules: {
|
2025-02-27 10:48:50 +08:00
|
|
|
|
account: [{ required: true, message: accountNeed, trigger: 'blur' }],
|
|
|
|
|
|
password: [{ required: true, message: passwordNeed, trigger: 'blur' }],
|
|
|
|
|
|
code: [{ required: true, message: codeNeed, trigger: 'blur' }],
|
2024-06-15 13:02:35 +08:00
|
|
|
|
},
|
|
|
|
|
|
loading: {
|
|
|
|
|
|
signIn: false,
|
|
|
|
|
|
},
|
|
|
|
|
|
captchaImage: '',
|
|
|
|
|
|
rotateVerifyVisible: false,
|
|
|
|
|
|
// rotateVerifyImg: verifyImg,
|
|
|
|
|
|
rotateVerifyImg: themeConfig.value.logoUrl,
|
|
|
|
|
|
secondVerEnabled: false,
|
|
|
|
|
|
captchaEnabled: false,
|
|
|
|
|
|
isPassRotate: false,
|
|
|
|
|
|
capsLockVisible: false,
|
2024-12-10 08:37:09 +08:00
|
|
|
|
expirySeconds: 60 as any, // 验证码过期时间
|
2024-06-15 13:02:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2024-08-04 01:49:41 +08:00
|
|
|
|
// 验证码过期计时器
|
|
|
|
|
|
let timer: any = null;
|
|
|
|
|
|
|
2024-08-03 10:58:44 +08:00
|
|
|
|
// 页面初始化
|
2024-06-15 13:02:35 +08:00
|
|
|
|
onMounted(async () => {
|
|
|
|
|
|
// 若URL带有Token参数(第三方登录)
|
2024-11-25 16:27:14 +08:00
|
|
|
|
const accessToken = route.query.token;
|
|
|
|
|
|
if (accessToken) await saveTokenAndInitRoutes(accessToken);
|
2024-06-15 13:02:35 +08:00
|
|
|
|
|
2025-01-26 01:25:31 +08:00
|
|
|
|
// // 若公钥为空则刷新页面
|
|
|
|
|
|
// if (window.__env__.VITE_SM_PUBLIC_KEY == '' || window.__env__.VITE_SM_PUBLIC_KEY == undefined) {
|
|
|
|
|
|
// window.location.reload();
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
2024-06-15 13:02:35 +08:00
|
|
|
|
// 获取登录配置
|
2024-08-06 03:48:48 +08:00
|
|
|
|
state.secondVerEnabled = themeConfig.value.secondVer ?? false;
|
2024-08-03 10:58:44 +08:00
|
|
|
|
state.captchaEnabled = themeConfig.value.captcha ?? true;
|
2024-06-15 13:02:35 +08:00
|
|
|
|
|
|
|
|
|
|
// 获取验证码
|
|
|
|
|
|
getCaptcha();
|
|
|
|
|
|
|
2024-08-04 01:49:41 +08:00
|
|
|
|
// 注册验证码过期计时器
|
|
|
|
|
|
if (state.captchaEnabled) {
|
|
|
|
|
|
timer = setInterval(() => {
|
|
|
|
|
|
if (state.expirySeconds > 0) state.expirySeconds -= 1;
|
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-15 13:02:35 +08:00
|
|
|
|
// 检测大小写按键/CapsLK
|
|
|
|
|
|
document.addEventListener('keyup', handleKeyPress);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-08-03 10:58:44 +08:00
|
|
|
|
// 页面卸载
|
2024-06-15 13:02:35 +08:00
|
|
|
|
onUnmounted(() => {
|
2024-08-04 14:41:56 +08:00
|
|
|
|
// 销毁验证码过期计时器
|
2024-08-04 01:49:41 +08:00
|
|
|
|
clearInterval(timer);
|
|
|
|
|
|
timer = null;
|
|
|
|
|
|
|
2024-06-15 13:02:35 +08:00
|
|
|
|
document.removeEventListener('keyup', handleKeyPress);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2024-07-26 12:51:05 +08:00
|
|
|
|
// 检测大小写按键
|
|
|
|
|
|
const handleKeyPress = (e: KeyboardEvent) => {
|
2025-01-11 15:25:15 +08:00
|
|
|
|
state.capsLockVisible = e.key === 'CapsLock';
|
2024-06-15 13:02:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取验证码
|
|
|
|
|
|
const getCaptcha = async () => {
|
|
|
|
|
|
if (!state.captchaEnabled) return;
|
|
|
|
|
|
|
|
|
|
|
|
state.ruleForm.code = '';
|
2024-11-25 16:27:14 +08:00
|
|
|
|
const res = await getAPI(SysAuthApi)
|
|
|
|
|
|
.apiSysAuthCaptchaGet()
|
|
|
|
|
|
.then((res) => res.data.result);
|
|
|
|
|
|
state.captchaImage = 'data:text/html;base64,' + res?.img;
|
|
|
|
|
|
state.expirySeconds = res?.expirySeconds;
|
|
|
|
|
|
state.ruleForm.codeId = res?.id;
|
2024-06-15 13:02:35 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 获取时间
|
|
|
|
|
|
const currentTime = computed(() => {
|
2025-02-28 01:41:52 +08:00
|
|
|
|
return formatAxis(new Date(), t);
|
2024-06-15 13:02:35 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 登录
|
|
|
|
|
|
const onSignIn = async () => {
|
|
|
|
|
|
ruleFormRef.value.validate(async (valid: boolean) => {
|
|
|
|
|
|
if (!valid) return false;
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
state.loading.signIn = true;
|
|
|
|
|
|
|
|
|
|
|
|
// SM2加密密码
|
|
|
|
|
|
// const keys = SM2.generateKeyPair();
|
|
|
|
|
|
const publicKey = window.__env__.VITE_SM_PUBLIC_KEY;
|
|
|
|
|
|
const password = sm2.doEncrypt(state.ruleForm.password, publicKey, 1);
|
|
|
|
|
|
|
2025-01-14 14:47:34 +08:00
|
|
|
|
const tenantid = route.query.tenantid ?? 0;
|
|
|
|
|
|
const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthLoginPost({ ...state.ruleForm, password: password, tenantId: Number(tenantid) }));
|
2024-06-15 13:02:35 +08:00
|
|
|
|
if (err) {
|
|
|
|
|
|
getCaptcha(); // 重新获取验证码
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (res.data.result?.accessToken == undefined) {
|
|
|
|
|
|
getCaptcha(); // 重新获取验证码
|
2025-02-27 10:48:50 +08:00
|
|
|
|
ElMessage.error(loginFail);
|
2024-06-15 13:02:35 +08:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
await saveTokenAndInitRoutes(res.data.result?.accessToken);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
state.loading.signIn = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 保持Token并初始化路由
|
|
|
|
|
|
const saveTokenAndInitRoutes = async (accessToken: string | any) => {
|
|
|
|
|
|
// 缓存token
|
|
|
|
|
|
Local.set(accessTokenKey, accessToken);
|
|
|
|
|
|
// Local.set(refreshAccessTokenKey, refreshAccessToken);
|
|
|
|
|
|
Session.set('token', accessToken);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加完动态路由再进行router跳转,否则可能报错 No match found for location with path "/"
|
|
|
|
|
|
const isNoPower = await initBackEndControlRoutes();
|
|
|
|
|
|
signInSuccess(isNoPower); // 再执行 signInSuccess
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 登录成功后的跳转
|
|
|
|
|
|
const signInSuccess = (isNoPower: boolean | undefined) => {
|
|
|
|
|
|
if (isNoPower) {
|
2025-02-27 10:48:50 +08:00
|
|
|
|
ElMessage.warning(notPrivilege);
|
2024-06-15 13:02:35 +08:00
|
|
|
|
clearTokens(); // 清空Token缓存
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 初始化登录成功时间问候语
|
|
|
|
|
|
let currentTimeInfo = currentTime.value;
|
|
|
|
|
|
// 登录成功,跳到转首页 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
|
|
|
|
|
|
if (route.query?.redirect) {
|
2024-09-30 15:34:05 +08:00
|
|
|
|
const stores = useRoutesList();
|
|
|
|
|
|
const { routesList } = storeToRefs(stores);
|
|
|
|
|
|
const recursion = (routeList: any[], url: string): boolean | undefined => {
|
|
|
|
|
|
if (routeList && routeList.length > 0) {
|
|
|
|
|
|
for (let i = 0; i < routeList.length; i++) {
|
|
|
|
|
|
if (routeList[i].path === url) return true;
|
|
|
|
|
|
if (routeList[i]?.children.length > 0) {
|
|
|
|
|
|
let result = recursion(routeList[i]?.children, url);
|
|
|
|
|
|
if (result) return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
let exist = recursion(routesList.value, route.query?.redirect as string);
|
|
|
|
|
|
if (exist) {
|
|
|
|
|
|
router.push({
|
|
|
|
|
|
path: <string>route.query?.redirect,
|
|
|
|
|
|
query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
|
|
|
|
|
|
});
|
|
|
|
|
|
} else router.push('/');
|
2024-06-15 13:02:35 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
router.push('/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 登录成功提示
|
|
|
|
|
|
const signInText = t('message.signInText');
|
|
|
|
|
|
ElMessage.success(`${currentTimeInfo},${signInText}`);
|
|
|
|
|
|
// 添加 loading,防止第一次进入界面时出现短暂空白
|
|
|
|
|
|
NextLoading.start();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 打开旋转验证
|
|
|
|
|
|
const openRotateVerify = () => {
|
|
|
|
|
|
state.rotateVerifyVisible = true;
|
|
|
|
|
|
state.isPassRotate = false;
|
|
|
|
|
|
dragRef.value?.reset();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 通过旋转验证
|
|
|
|
|
|
const passRotateVerify = () => {
|
|
|
|
|
|
state.rotateVerifyVisible = false;
|
|
|
|
|
|
state.isPassRotate = true;
|
|
|
|
|
|
onSignIn();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 登录处理
|
|
|
|
|
|
const handleSignIn = () => {
|
|
|
|
|
|
if (!state.ruleForm.account) {
|
|
|
|
|
|
accountRef.value?.focus();
|
|
|
|
|
|
} else if (!state.ruleForm.password) {
|
|
|
|
|
|
passwordRef.value?.focus();
|
|
|
|
|
|
} else if (state.captchaEnabled && !state.ruleForm.code) {
|
|
|
|
|
|
codeRef.value?.focus();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
state.secondVerEnabled ? openRotateVerify() : onSignIn();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// // 微信登录
|
|
|
|
|
|
// const weixinSignIn = () => {
|
|
|
|
|
|
// window.open('http://localhost:5005/api/sysoauth/signin?provider=Gitee&redirectUrl=http://localhost:8888');
|
|
|
|
|
|
// };
|
|
|
|
|
|
|
|
|
|
|
|
// 导出对象
|
|
|
|
|
|
defineExpose({ saveTokenAndInitRoutes });
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.dialog-header {
|
|
|
|
|
|
:deep(.el-dialog) {
|
2024-07-10 22:55:19 +08:00
|
|
|
|
width: unset !important;
|
|
|
|
|
|
|
2024-06-15 13:02:35 +08:00
|
|
|
|
.el-dialog__header {
|
|
|
|
|
|
display: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.el-dialog__wrapper {
|
|
|
|
|
|
position: absolute !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.v-modal {
|
|
|
|
|
|
position: absolute !important;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.login-content-form {
|
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
@for $i from 0 through 4 {
|
|
|
|
|
|
.login-animation#{$i} {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
animation-name: error-num;
|
|
|
|
|
|
animation-duration: 0.5s;
|
|
|
|
|
|
animation-fill-mode: forwards;
|
|
|
|
|
|
animation-delay: calc($i/10) + s;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.login-content-password {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
width: 20px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.login-content-code {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-around;
|
2024-08-05 09:18:54 +08:00
|
|
|
|
position: relative;
|
2024-06-15 13:02:35 +08:00
|
|
|
|
|
|
|
|
|
|
.login-content-code-img {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
line-height: 40px;
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border: 1px solid rgb(220, 223, 230);
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
transition: all ease 0.2s;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
|
|
border-color: #c0c4cc;
|
|
|
|
|
|
transition: all ease 0.2s;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-08-04 01:49:41 +08:00
|
|
|
|
.login-content-code-expired {
|
|
|
|
|
|
@extend .login-content-code;
|
2025-02-27 15:01:29 +08:00
|
|
|
|
|
2024-08-04 01:49:41 +08:00
|
|
|
|
&::before {
|
2025-02-27 10:48:50 +08:00
|
|
|
|
content: t('message.account.captchaExpired');
|
2024-08-04 01:49:41 +08:00
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
bottom: 0;
|
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
|
|
|
color: #ffffff;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-06-15 13:02:35 +08:00
|
|
|
|
.login-content-submit {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
letter-spacing: 2px;
|
|
|
|
|
|
font-weight: 300;
|
|
|
|
|
|
margin-top: 15px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.login-msg {
|
|
|
|
|
|
color: var(--el-text-color-placeholder);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|