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

427 lines
12 KiB
Vue
Raw Normal View History

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 10:48:50 +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 10:48:50 +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>
<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">
2024-08-04 01:49:41 +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 == ''">
<el-icon class="is-loading">
<ele-Loading />
</el-icon>
</div>
<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">
<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">
<DragVerifyImgRotate
ref="dragRef"
:imgsrc="state.rotateVerifyImg"
v-model:isPassing="state.isPassRotate"
2025-02-27 10:48:50 +08:00
:text="t('message.account.splitslive')"
:successText="t('message.account.success')"
2024-06-15 13:02:35 +08:00
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 { 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: '',
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,
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-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
// // 若公钥为空则刷新页面
// 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
// 获取登录配置
state.secondVerEnabled = themeConfig.value.secondVer ?? false;
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-06-15 13:02:35 +08:00
onUnmounted(() => {
// 销毁验证码过期计时器
2024-08-04 01:49:41 +08:00
clearInterval(timer);
timer = null;
2024-06-15 13:02:35 +08:00
document.removeEventListener('keyup', handleKeyPress);
});
// 检测大小写按键
const handleKeyPress = (e: KeyboardEvent) => {
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(() => {
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 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) {
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) {
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;
&::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>