<script setup lang="ts">
|
import Motion from "./utils/motion";
|
import { useRouter } from "vue-router";
|
import { message } from "@/utils/message";
|
import { loginRules } from "./utils/rule";
|
import {
|
reactive,
|
computed,
|
ref,
|
onMounted,
|
defineAsyncComponent,
|
onUnmounted,
|
watch
|
} from "vue";
|
import { debounce } from "@pureadmin/utils";
|
import { useNav } from "@/layout/hooks/useNav";
|
import { useEventListener } from "@vueuse/core";
|
import type { FormInstance } from "element-plus";
|
import { useLayout } from "@/layout/hooks/useLayout";
|
import { useUserStoreHook } from "@/store/modules/user";
|
import { initRouter, getTopMenu } from "@/router/utils";
|
import { bg, avatar, logo1 } from "./utils/static";
|
// import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
import { useRoute } from "vue-router";
|
|
const route = useRoute();
|
|
// import dayIcon from "@/assets/svg/day.svg?component";
|
// import darkIcon from "@/assets/svg/dark.svg?component";
|
import Lock from "~icons/ri/lock-fill";
|
import User from "~icons/ri/user-3-fill";
|
import { captcha, phoneNumberCode, exRole } from "@/api/register/index.ts";
|
|
defineOptions({
|
name: "Login"
|
});
|
|
const router = useRouter();
|
const loading = ref(false);
|
const disabled = ref(false);
|
const ruleFormRef = ref<FormInstance>();
|
|
const { initStorage } = useLayout();
|
initStorage();
|
|
const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
|
dataThemeChange(overallStyle.value);
|
const { title } = useNav();
|
// 获取验证码
|
const getCaptcha = async () => {
|
// if (!state.captchaEnabled) return;
|
|
state.ruleForm.code = "";
|
const res = await captcha();
|
console.log(res);
|
|
state.captchaImage = "data:text/html;base64," + res.result?.img;
|
state.expirySeconds = res.result?.expirySeconds;
|
state.ruleForm.codeId = res.result?.id;
|
};
|
const state = reactive({
|
isShowPassword: false,
|
ruleForm: {
|
account: "",
|
nickName: "",
|
phone: "",
|
phoneVCode: "",
|
// tenantId: props.tenantInfo.id,
|
code: "",
|
codeId: 0,
|
email: "",
|
exRoleCode: ""
|
},
|
rules: {
|
code: [
|
{
|
required: true,
|
message: "请输入手机验证码",
|
trigger: "blur"
|
}
|
],
|
phone: [
|
{
|
required: true,
|
message: "请输入您的手机号码",
|
trigger: "blur"
|
}
|
],
|
exRoleCode: [
|
{
|
required: true,
|
message: "请选择角色",
|
trigger: "blur"
|
}
|
]
|
// code: [{ required: true, message: t('message.account.placeholder4'), trigger: 'blur' }],
|
},
|
loading: {
|
signIn: false
|
},
|
captchaImage: "",
|
rotateVerifyVisible: false,
|
// rotateVerifyImg: verifyImg,
|
// rotateVerifyImg: themeConfig.value.logoUrl,
|
secondVerEnabled: false,
|
// captchaEnabled: false,
|
isPassRotate: false,
|
capsLockVisible: false,
|
hideTenantForLogin: false,
|
expirySeconds: 60, // 验证码过期时间
|
phoneSeconds: 0, // 手机验证码倒计时
|
roleList: []
|
});
|
// 验证码过期计时器
|
let timer: any = null;
|
let phonetimer: any = null;
|
|
// 页面初始化
|
onMounted(async () => {
|
// 若URL带有Token参数(第三方登录)
|
const accessToken = route.query.token;
|
// if (accessToken) await saveTokenAndInitRoutes(accessToken);
|
// watch(
|
// () => themeConfig.value.isLoaded,
|
// isLoaded => {
|
// if (isLoaded) {
|
// 获取登录配置
|
// state.hideTenantForLogin = themeConfig.value.hideTenantForLogin ?? true;
|
// state.secondVerEnabled = themeConfig.value.secondVer ?? true;
|
// state.captchaEnabled = themeConfig.value.captcha ?? true;
|
|
// 获取验证码
|
getCaptcha();
|
exRole().then(res => {
|
state.roleList = res.result;
|
});
|
// 注册验证码过期计时器
|
// if (state.captchaEnabled) {
|
timer = setInterval(() => {
|
if (state.expirySeconds > 0) state.expirySeconds -= 1;
|
}, 1000);
|
|
// }
|
// }
|
// },
|
// { immediate: true }
|
// );
|
});
|
// 页面卸载
|
onUnmounted(() => {
|
// 销毁验证码过期计时器
|
clearInterval(timer);
|
timer = null;
|
clearInterval(phonetimer);
|
phonetimer = null;
|
});
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
if (!formEl) return;
|
await formEl.validate(valid => {
|
if (valid) {
|
loading.value = true;
|
useUserStoreHook()
|
.loginByUsername({
|
phone: state.ruleForm.phone,
|
code: state.ruleForm.phoneVCode,
|
exRuleCode: state.ruleForm.exRoleCode
|
})
|
.then(res => {
|
if (res.code == 200) {
|
// 获取后端路由
|
return initRouter().then(() => {
|
disabled.value = true;
|
router
|
.replace("index")
|
.then(() => {
|
message("登录成功", { type: "success" });
|
})
|
.finally(() => (disabled.value = false));
|
});
|
} else {
|
message(res?.message || "登录失败", { type: "error" });
|
}
|
})
|
.finally(() => (loading.value = false));
|
}
|
});
|
};
|
const sendValidationCode = async () => {
|
if (!state.ruleForm.phone) {
|
return message("请先输入手机号", { type: "warning" });
|
}
|
if (!state.ruleForm.code) {
|
return message("请先输入验证码", { type: "warning" });
|
}
|
const res = await phoneNumberCode(state.ruleForm);
|
if (res?.code != 200) {
|
return message(res?.message, { type: "warning" });
|
}
|
state.phoneSeconds = 60;
|
phonetimer = setInterval(() => {
|
if (state.phoneSeconds > 0) state.phoneSeconds -= 1;
|
}, 1000);
|
};
|
const immediateDebounce: any = debounce(
|
formRef => onLogin(formRef),
|
1000,
|
true
|
);
|
|
useEventListener(document, "keydown", ({ code }) => {
|
if (
|
["Enter", "NumpadEnter"].includes(code) &&
|
!disabled.value &&
|
!loading.value
|
)
|
immediateDebounce(ruleFormRef.value);
|
});
|
</script>
|
|
<template>
|
<div class="select-none">
|
<div class="wave">
|
<img width="400px" :src="logo1" class="logo1" />
|
</div>
|
<!-- <img :src="bg" class="wave" /> -->
|
<div class="flex-c absolute right-5 top-3">
|
<!-- 主题 -->
|
<el-switch
|
v-model="dataTheme"
|
inline-prompt
|
:active-icon="dayIcon"
|
:inactive-icon="darkIcon"
|
@change="dataThemeChange"
|
/>
|
</div>
|
<div class="login-container">
|
<div class="img">
|
<!-- <component :is="toRaw(illustration)" /> -->
|
</div>
|
<div class="login-box">
|
<div class="login-form">
|
<!-- <avatar class="avatar" /> -->
|
<Motion>
|
<h2 class="outline-hidden">{{ title }}</h2>
|
</Motion>
|
|
<el-form
|
ref="ruleFormRef"
|
:model="state.ruleForm"
|
:rules="loginRules"
|
size="large"
|
>
|
<Motion :delay="150">
|
<el-form-item prop="exRoleCode">
|
<el-radio-group v-model="state.ruleForm.exRoleCode">
|
<el-radio
|
v-for="item in state.roleList"
|
:key="item.id"
|
:value="item.code"
|
>{{ item.name }}</el-radio
|
>
|
</el-radio-group>
|
</el-form-item>
|
</Motion>
|
<Motion :delay="100">
|
<el-form-item
|
:rules="[
|
{
|
required: true,
|
message: '请输入手机号',
|
trigger: 'blur'
|
}
|
]"
|
prop="phone"
|
>
|
<el-input
|
v-model="state.ruleForm.phone"
|
clearable
|
placeholder="手机号"
|
/>
|
</el-form-item>
|
</Motion>
|
|
<Motion :delay="150">
|
<el-form-item class="login-animation3" prop="code">
|
<el-col :span="15">
|
<el-input
|
ref="codeRef"
|
v-model="state.ruleForm.code"
|
text
|
maxlength="4"
|
placeholder="请输入验证码"
|
clearable
|
autocomplete="off"
|
/>
|
</el-col>
|
<el-col :span="1" />
|
<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 prop="password">
|
<el-input
|
v-model="ruleForm.password"
|
clearable
|
show-password
|
placeholder="密码"
|
:prefix-icon="useRenderIcon(Lock)"
|
/>
|
</el-form-item> -->
|
</Motion>
|
<Motion :delay="150">
|
<el-form-item prop="phoneVCode">
|
<el-input
|
v-model.number="state.ruleForm.phoneVCode"
|
class="form-input"
|
placeholder="请输入验证码"
|
>
|
<template #suffix>
|
<span v-if="state.phoneSeconds == 0" id="suffix-span">
|
<span
|
id="suffix-span-2"
|
ref="spanRef"
|
@click="sendValidationCode(state.ruleForm.phone)"
|
>
|
获取验证码
|
</span>
|
</span>
|
<span v-else id="suffix-span">
|
<span id="suffix-span-2" ref="spanRef">
|
{{ state.phoneSeconds }}秒后重新获取
|
</span>
|
</span>
|
</template>
|
</el-input>
|
</el-form-item>
|
</Motion>
|
<Motion :delay="250">
|
<el-button
|
class="w-full mt-4!"
|
size="default"
|
type="primary"
|
:loading="loading"
|
:disabled="disabled"
|
@click="onLogin(ruleFormRef)"
|
>
|
登录
|
</el-button>
|
</Motion>
|
</el-form>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<style scoped>
|
@import url("@/style/login.css");
|
</style>
|
|
<style lang="scss" scoped>
|
:deep(.el-input-group__append, .el-input-group__prepend) {
|
padding: 0;
|
}
|
#suffix-span {
|
cursor: pointer;
|
}
|
</style>
|