吕杰刚 6b53804282
Some checks failed
Build And Deploy v3-admin-vite / build-and-deploy (push) Has been cancelled
优化
2025-07-22 20:02:31 +08:00

592 lines
16 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.

<script lang="ts" setup>
import type { FormInstance, FormRules } from "element-plus";
import type {
LoginRequestData,
forgetRequestData,
phoneLoginRequestData,
} from "./apis/type";
import { useSettingsStore } from "@/pinia/stores/settings";
import { useUserStore } from "@/pinia/stores/user";
import ThemeSwitch from "@@/components/ThemeSwitch/index.vue";
import { Lock, User, Iphone, Promotion, Check } from "@element-plus/icons-vue";
import { loginApi } from "./apis";
import Owl from "./components/Owl.vue";
import { useFocus } from "./composables/useFocus";
import { reactive, ref, inject } from "vue";
const $md5 = inject("md5");
const router = useRouter();
const userStore = useUserStore();
const settingsStore = useSettingsStore();
const { isFocus, handleBlur, handleFocus } = useFocus();
/** 登录表单元素的引用 */
const loginFormRef = ref<FormInstance | null>(null);
/** 短信验证码登录表单元素的引用 */
const phoneFormRef = ref<FormInstance | null>(null);
/** 短信验证码登录表单元素的引用 */
const forgetFormRef = ref<FormInstance | null>(null);
/** 登录按钮 Loading */
const loading = ref(false);
//是否验证码登录
let loginMethod = ref(true);
//倒计时
let countdown = ref(60);
//是否开启倒计时
let isDisabled = ref(false);
//是否忘记密码
let isForgetPwd = ref(false);
/** 登录表单数据 */
const loginFormData: LoginRequestData = reactive({
username: "",
password: "",
});
/** 短信登录表单数据 */
const phoneFormData: phoneLoginRequestData = reactive({
phone: "",
mobileCode: "",
});
/** 忘记密码表单数据 */
const forgetFormData: forgetRequestData = reactive({
// username: "",
/** 密码 */
password: "",
//确认密码
confirmPassword: "",
//手机号
phone: "",
//手机验证码
mobileCode: "",
});
/** 登录表单校验规则 */
const loginFormRules = reactive<FormRules<LoginRequestData>>({
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" },
],
});
/** 短信验证码登录登录表单校验规则 */
const phoneFormRules = reactive<FormRules<phoneLoginRequestData>>({
phone: [
{ required: true, message: "请输入手机号", trigger: "blur" },
{ min: 11, max: 11, message: "请检查手机号是否正确!", trigger: "blur" },
],
mobileCode: [
{ required: true, message: "请输入手机验证码", trigger: "blur" },
{
min: 4,
max: 4,
message: "请检查手机验证码是否正确!",
trigger: "blur",
},
],
});
//忘记密码表单校验
const forgetFormRules = reactive<FormRules<forgetRequestData>>({
phone: [
{ required: true, message: "请输入手机号 ", trigger: "blur" },
{
validator: (rule, value, callback) => {
console.log(value.toString());
if (!value) {
return callback();
}
if (
value.toString().length != 11 ||
!/^1[3-9]\d{9}$/.test(value.toString())
) {
callback(new Error("请检查手机号是否正确!"));
} else {
callback();
}
},
trigger: "blur",
},
],
password: [
{ required: true, message: "请输入密码", trigger: "blur" },
{ min: 8, max: 16, message: "长度在 8 到 16 个字符", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (!value) {
return callback();
}
if (
!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]+$/.test(
value
)
) {
callback(new Error("密码应该包含数字、大小写字母和特殊符号!"));
} else {
callback();
}
},
trigger: "blur",
},
],
mobileCode: [{ required: true, message: "请输入验证码", trigger: "blur" }],
confirmPassword: [
{ required: true, message: "请输入确认密码", trigger: "blur" },
{
validator: (rule, value, callback) => {
if (!value) {
return callback();
}
if (value !== forgetFormData.password) {
callback(new Error("密码和确认密码请保持一致!"));
} else {
callback();
}
},
trigger: "blur",
},
],
});
/** 账号密码登录 */
async function handleUserLogin(formEl) {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
// loginFormData.password = $md5(loginFormData.password);
loginApi(loginFormData)
.then(({ data }) => {
userStore.setToken(data.token);
router.push("/");
})
.catch(() => {
loginFormData.password = "";
})
.finally(() => {
loading.value = false;
});
} else {
ElMessage.error("表单校验不通过");
return;
}
});
}
//短信验证码登录
async function handlePhoneLogin(formEl) {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
loginApi(forgetFormData)
.then(({ data }) => {
console.log(data);
userStore.setToken(data.token);
router.push("/");
})
.catch(() => {
forgetFormData.mobileCode = "";
})
.finally(() => {
loading.value = false;
});
} else {
ElMessage.error("表单校验不通过");
return;
}
});
}
//忘记密码提交按钮
async function submit(formEl) {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
loginApi()
.then(({ data }) => {
userStore.setToken(data.token);
router.push("/");
})
.catch(() => {
phoneFormData.mobileCode = "";
})
.finally(() => {
loading.value = false;
});
} else {
ElMessage.error("表单校验不通过");
return;
}
});
}
//手机验证码登录 true是账号密码登录 false是手机验证码登录
function mobileCode(type: boolean) {
loginMethod.value = type;
}
// 获取手机验证码
function getMobileCode(num: string) {
isDisabled.value = true;
}
// 更新倒计时显示
function updateCountdown() {
if (countdown.value === 0) {
isDisabled.value = false;
} else {
countdown.value = countdown.value - 1;
isDisabled.value = true;
}
}
//忘记密码
function forgetPwd() {
forgetFormData.password = ''
isForgetPwd.value = true;
}
watch(isDisabled, (newVal, oldVal) => {
if (newVal) {
setInterval(updateCountdown, 1000);
}
});
</script>
<template>
<div class="login-container">
<ThemeSwitch v-if="settingsStore.showThemeSwitch" class="theme-switch" />
<Owl :close-eyes="isFocus" />
<div class="login-card">
<div class="title">
<div class="title-left">
<img src="@@/assets/images/layouts/img.png" />
</div>
<div class="title-text">智慧矿山数据管理平台</div>
</div>
<div class="content">
<el-form
ref="loginFormRef"
:model="loginFormData"
:rules="loginFormRules"
@keyup.enter="handleUserLogin"
v-show="loginMethod && !isForgetPwd"
>
<el-form-item prop="username">
<el-input
v-model.trim="loginFormData.username"
placeholder="用户名"
type="text"
tabindex="1"
:prefix-icon="User"
size="large"
/>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="loginFormData.password"
placeholder="密码"
type="password"
tabindex="2"
:prefix-icon="Lock"
size="large"
show-password
@blur="handleBlur"
@focus="handleFocus"
/>
</el-form-item>
<el-button
v-if="loginMethod"
:loading="loading"
type="primary"
size="large"
@click.prevent="handleUserLogin(loginFormRef)"
>
</el-button>
</el-form>
<el-form
v-show="!loginMethod && !isForgetPwd"
ref="phoneFormRef"
:model="phoneFormData"
:rules="phoneFormRules"
@keyup.enter="handlePhoneLogin"
><el-form-item prop="phone">
<el-input
v-model.trim="phoneFormData.phone"
placeholder="手机号"
tabindex="3"
:prefix-icon="Iphone"
size="large"
maxlength="11"
/>
</el-form-item>
<el-form-item prop="mobileCode" v-show="!loginMethod">
<el-input
v-model.trim="phoneFormData.mobileCode"
placeholder="手机验证码"
tabindex="4"
:prefix-icon="Promotion"
size="large"
@blur="handleBlur"
@focus="handleFocus"
maxlength="4"
/>
<button
id="sendCode"
@click="getMobileCode(phoneFormData.mobileCode)"
:disabled="isDisabled"
>
<span v-if="!isDisabled">获取验证码</span>
<span v-if="isDisabled">{{ countdown + " 秒后重试" }}</span>
</button>
</el-form-item>
<el-button
:loading="loading"
type="primary"
size="large"
@click.prevent="handlePhoneLogin(phoneFormRef)"
>
</el-button>
</el-form>
<div class="login-btn" v-show="!isForgetPwd">
<el-link type="primary" @click="mobileCode(false)" v-if="loginMethod"
>手机验证码登录</el-link
>
<el-link type="primary" @click="mobileCode(true)" v-if="!loginMethod"
>用户名密码登录</el-link
>
<el-link type="primary" @click="forgetPwd()">忘记密码 </el-link>
</div>
<div class="login-thirdprty" v-if="!isForgetPwd">
<div class="css-1vli2z"></div>
<div class="css-wna3id">其他方式登录</div>
<div class="css-1vli2z"></div>
</div>
<div class="iconAll" v-if="!isForgetPwd">
<button>
<i class="iconfont icon-weixin"></i>
</button>
<button>
<i class="iconfont icon-qq"></i>
</button>
<button>
<i class="iconfont icon-weibo1"></i>
</button>
</div>
<el-form
v-if="isForgetPwd"
ref="forgetFormRef"
:model="forgetFormData"
:rules="forgetFormRules"
>
<el-form-item prop="phone">
<el-input
v-model.trim="forgetFormData.phone"
placeholder="手机号"
tabindex="1"
:prefix-icon="Iphone"
size="large"
maxlength="11"
/>
</el-form-item>
<el-form-item prop="mobileCode">
<el-input
v-model.trim="forgetFormData.mobileCode"
placeholder="手机验证码"
tabindex="2"
:prefix-icon="Promotion"
size="large"
@blur="handleBlur"
@focus="handleFocus"
maxlength="4"
/>
<button
id="sendCode"
@click="getMobileCode(forgetFormData.phone)"
:disabled="isDisabled"
>
<span v-if="!isDisabled">获取验证码</span>
<span v-if="isDisabled">{{ countdown + " 秒后重试" }}</span>
</button>
</el-form-item>
<el-form-item prop="password">
<el-input
v-model.trim="forgetFormData.password"
placeholder="密码"
type="password"
tabindex="3"
:prefix-icon="Lock"
size="large"
show-password
@blur="handleBlur"
@focus="handleFocus"
/>
</el-form-item>
<el-form-item prop="confirmPassword">
<el-input
v-model.trim="forgetFormData.confirmPassword"
placeholder="确认密码"
type="password"
tabindex="4"
:prefix-icon="Check"
size="large"
show-password
@blur="handleBlur"
@focus="handleFocus"
/>
</el-form-item>
<el-button
class="forgetLogin"
:loading="loading"
type="primary"
size="large"
@click.prevent="submit(forgetFormRef)"
>
</el-button>
</el-form>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.login-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
min-height: 100%;
// 添加背景图片样式
background-image: url("@@/assets/images/layouts/login_background.jpg"); // 修改为你的背景图路径
background-size: cover;
background-position: center;
background-repeat: no-repeat;
.theme-switch {
position: fixed;
top: 5%;
right: 5%;
cursor: pointer;
}
.login-card {
width: 480px;
max-width: 90%;
border-radius: 20px;
box-shadow: 0 0 10px #dcdfe6;
background-color: var(--el-bg-color);
overflow: hidden;
.title {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 20px 0;
img {
max-height: 40px;
width: auto;
object-fit: contain;
}
.title-text {
margin-top: 10px;
font-size: 30px;
font-weight: 800;
color: black;
text-align: center;
white-space: nowrap;
}
}
.forget-content {
padding: 20px 50px 50px 50px;
.forgetLogin {
width: 100%;
}
:deep(.el-form-item__content) {
display: flex;
flex-wrap: nowrap;
#sendCode {
width: 50%;
height: 100%;
cursor: pointer;
background-color: #fff;
width: 50%;
margin-left: 15px;
border: 1px solid #dbdde2;
border-radius: 5px;
}
}
}
.content {
padding: 20px 50px 50px 50px;
:deep(.el-input-group__append) {
padding: 0;
overflow: hidden;
.el-image {
width: 100px;
height: 40px;
border-left: 0px;
user-select: none;
cursor: pointer;
text-align: center;
}
}
.login-btn {
margin-top: 10px;
display: flex;
justify-content: space-between;
}
.el-button {
width: 100%;
margin-top: 10px;
}
:deep(.el-form-item__content) {
display: flex;
flex-wrap: nowrap;
#sendCode {
width: 50%;
height: 100%;
cursor: pointer;
background-color: #fff;
width: 50%;
margin-left: 15px;
border: 1px solid #dbdde2;
border-radius: 5px;
}
}
.login-thirdprty {
font-size: 14px;
margin-top: 20px;
display: flex;
color: #9196a1;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
.css-1vli2z {
width: 128px;
height: 0;
border-top: 1px solid #ebeced;
}
.css-wna3id {
margin: 0 20px;
}
}
.iconAll {
display: flex;
justify-content: space-evenly;
margin-top: 20px;
button {
border: none;
background-color: #fff;
cursor: pointer;
i {
font-size: 30px;
}
}
}
}
}
}
</style>