UNIVPLMDataIntegration/Web/src/views/login/component/account.vue

395 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<el-tooltip :visible="state.capsLockVisible" effect="light" content="大写锁定已打开" placement="top">
<el-form ref="ruleFormRef" :model="state.ruleForm" size="large" :rules="state.rules" class="login-content-form">
<el-form-item class="login-animation1" prop="account">
<el-input ref="accountRef" text placeholder="请输入账号" v-model="state.ruleForm.account" clearable autocomplete="off" @keyup.enter.native="handleSignIn">
<template #prefix>
<el-icon>
<ele-User />
</el-icon>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation2" prop="password">
<el-input ref="passwordRef" :type="state.isShowPassword ? 'text' : 'password'" placeholder="请输入密码" v-model="state.ruleForm.password" autocomplete="off" @keyup.enter.native="handleSignIn">
<template #prefix>
<el-icon>
<ele-Unlock />
</el-icon>
</template>
<template #suffix>
<i class="iconfont el-input__icon login-content-password" :class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'" @click="state.isShowPassword = !state.isShowPassword">
</i>
</template>
</el-input>
</el-form-item>
<el-form-item class="login-animation3" prop="captcha" v-if="state.captchaEnabled">
<el-col :span="15">
<el-input
ref="codeRef"
text
maxlength="4"
:placeholder="$t('message.account.accountPlaceholder3')"
v-model="state.ruleForm.code"
clearable
autocomplete="off"
@keyup.enter.native="handleSignIn"
>
<template #prefix>
<el-icon>
<ele-Position />
</el-icon>
</template>
</el-input>
</el-col>
<el-col :span="1"></el-col>
<el-col :span="8">
<div :class="[state.expirySeconds > 0 ? 'login-content-code' : 'login-content-code-expired']" @click="getCaptcha">
<img class="login-content-code-img" width="130px" height="38px" :src="state.captchaImage" style="cursor: pointer" />
</div>
</el-col>
</el-form-item>
<el-form-item class="login-animation4">
<el-button type="primary" class="login-content-submit" round v-waves @click="handleSignIn" :loading="state.loading.signIn">
<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">
<DragVerifyImgRotate
ref="dragRef"
:imgsrc="state.rotateVerifyImg"
v-model:isPassing="state.isPassRotate"
text="请按住滑块拖动"
successText="验证通过"
handlerIcon="fa fa-angle-double-right"
successIcon="fa fa-hand-peace-o"
@passcallback="passRotateVerify"
/>
</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';
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>();
const dragRef: any = ref(null);
const state = reactive({
isShowPassword: false,
ruleForm: {
account: '',
password: '',
code: '',
codeId: 0,
},
rules: {
account: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
// code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
},
loading: {
signIn: false,
},
captchaImage: '',
rotateVerifyVisible: false,
// rotateVerifyImg: verifyImg,
rotateVerifyImg: themeConfig.value.logoUrl,
secondVerEnabled: false,
captchaEnabled: false,
isPassRotate: false,
capsLockVisible: false,
expirySeconds: 60, // 验证码过期时间
});
// 验证码过期计时器
let timer: any = null;
// 页面初始化
onMounted(async () => {
// 若URL带有Token参数第三方登录
var accessToken = route.query.token;
if (accessToken != null && accessToken != undefined) {
await saveTokenAndInitRoutes(accessToken);
}
// 获取登录配置
state.secondVerEnabled = themeConfig.value.secondVer ?? false;
state.captchaEnabled = themeConfig.value.captcha ?? true;
// 获取验证码
getCaptcha();
// 注册验证码过期计时器
if (state.captchaEnabled) {
timer = setInterval(() => {
if (state.expirySeconds > 0) state.expirySeconds -= 1;
}, 1000);
}
// 检测大小写按键/CapsLK
document.addEventListener('keyup', handleKeyPress);
});
// 页面卸载
onUnmounted(() => {
// 销毁验证码过期计时器
clearInterval(timer);
timer = null;
document.removeEventListener('keyup', handleKeyPress);
});
// 检测大小写按键
const handleKeyPress = (e: KeyboardEvent) => {
const isCapsLockOn = e.getModifierState('CapsLock');
state.capsLockVisible = isCapsLockOn;
};
// 获取验证码
const getCaptcha = async () => {
if (!state.captchaEnabled) return;
state.ruleForm.code = '';
var res = await getAPI(SysAuthApi).apiSysAuthCaptchaGet();
state.captchaImage = 'data:text/html;base64,' + res.data.result?.img;
state.ruleForm.codeId = res.data.result?.id;
state.expirySeconds = res.data.result?.expirySeconds;
};
// 获取时间
const currentTime = computed(() => {
return formatAxis(new Date());
});
// 登录
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);
const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthLoginPost({ ...state.ruleForm, password: password }));
if (err) {
getCaptcha(); // 重新获取验证码
return;
}
if (res.data.result?.accessToken == undefined) {
getCaptcha(); // 重新获取验证码
ElMessage.error('登录失败,请检查账号!');
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) {
ElMessage.warning('抱歉,您没有登录权限');
clearTokens(); // 清空Token缓存
} else {
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
if (route.query?.redirect) {
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('/');
}
// 登录成功提示
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) {
width: unset !important;
.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;
position: relative;
.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;
}
}
}
.login-content-code-expired {
@extend .login-content-code;
&::before {
content: '验证码已过期';
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;
}
}
.login-content-submit {
width: 100%;
letter-spacing: 2px;
font-weight: 300;
margin-top: 15px;
}
.login-msg {
color: var(--el-text-color-placeholder);
}
}
</style>