WholeProcessPlatform/frontend/src/views/login/index.vue
2026-04-23 18:33:39 +08:00

727 lines
19 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>
<div class="login-container">
<h1>
<div>
<img :src="loginImg" alt="Logo" class="logo-img" />
<a class="system-title">{{ $t("login.title") }}</a>
</div>
</h1>
<div class="login-wrapper">
<!-- 左侧背景图区域 -->
<div class="left-section">
<div class="slogan">
<p>绿水青山就是金山银山</p>
</div>
</div>
<!-- 右侧登录表单区域 -->
<div class="right-section">
<!-- 忘记密码页面 -->
<div v-if="showForgotPassword" class="forgot-password-container">
<div class="forgot-password-header">
<a-button type="link" @click="backToLogin" class="back-button">
<span>← 返回</span>
</a-button>
<h3 class="forgot-password-title">忘记密码</h3>
</div>
<a-form
:model="forgotPasswordForm"
:rules="forgotPasswordRules"
layout="vertical"
class="form-container"
>
<a-form-item label="" name="phone">
<a-input placeholder="请输入手机号" size="large">
<template #prefix>
<MobileOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-input
v-model="forgotPasswordForm.captcha"
placeholder="请输入验证码"
size="large"
class="captcha-input"
>
<template #prefix>
<LockOutlined />
</template>
<template #suffix>
<a-button
type="text"
size="small"
@click="sendForgotPasswordSms"
:disabled="smsButtonDisabled"
:loading="loading"
>
{{
smsCountdown > 0 ? `${smsCountdown}秒后重新获取` : "获取验证码"
}}
</a-button>
</template>
</a-input>
</div>
</a-form-item>
<a-form-item label="" name="newPassword">
<a-input-password
v-model="forgotPasswordForm.newPassword"
placeholder="请输入新密码"
size="large"
>
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
<a-button
type="primary"
size="large"
block
@click="handleResetPassword"
:loading="loading"
>
<span>提交</span>
</a-button>
</a-form>
</div>
<!-- 登录页面 -->
<div v-else>
<!-- Tabs 切换:账号登录 / 短信登录 -->
<a-tabs v-model:activeKey="activeTab" class="login-tabs">
<a-tab-pane key="account" tab="账号登录">
<a-form
:model="loginData"
:rules="loginRules"
layout="vertical"
class="form-container"
@finish="onFinish"
>
<!-- 用户名/账号/手机号输入框 -->
<a-form-item label="" name="username">
<a-input
ref="username"
v-model:value="loginData.username"
clearable
type="text"
placeholder="请输入用户账号/身份证号/手机号"
size="large"
>
<template #prefix>
<UserOutlined />
</template>
</a-input>
</a-form-item>
<!-- 密码输入框 -->
<a-form-item label="" name="password">
<a-input-password
type="password"
v-model:value="loginData.password"
placeholder="请输入密码"
size="large"
>
<template #prefix>
<LockOutlined />
</template>
</a-input-password>
</a-form-item>
<!-- 验证码区域 -->
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-row :gutter="24" align="middle">
<a-col :span="10"
><a-input
v-model:value="loginData.code"
placeholder="请输入验证码"
size="large"
class="captcha-input"
/></a-col>
<a-col :span="10"
><img :src="codeUrl" alt="验证码" class="captcha-img"
/></a-col>
<a-col :span="4"> <a @click="getCode">换一张</a></a-col>
</a-row>
</div>
</a-form-item>
<a-button
type="primary"
size="large"
block
htmlType="submit"
:loading="loading"
>
<span>登录</span>
</a-button>
<!-- <a-button
type="link"
size="mini"
block
@click="showForgotPasswordPage"
:style="{ marginTop: '10px', border: 'none' }"
>
忘记密码
</a-button> -->
<!-- 忘记密码 -->
</a-form>
</a-tab-pane>
<!-- 短信登录 Tab (占位) -->
<!-- <a-tab-pane key="sms" tab="短信登录">
<a-form
:model="loginData"
:rules="loginRules"
layout="vertical"
class="form-container"
>
<a-form-item label="" name="username">
<a-input
v-model:value="loginData.username"
placeholder="请输入手机号"
size="large"
>
<template #prefix>
<MobileOutlined />
</template>
</a-input>
</a-form-item>
<a-form-item label="" name="captcha">
<div class="captcha-row">
<a-input
v-model="loginData.code"
placeholder="请输入验证码"
size="large"
class="captcha-input"
>
<template #prefix>
<LockOutlined />
</template>
<template #suffix>
<a-button
type="text"
size="small"
@click="sendSms"
:disabled="smsButtonDisabled"
:loading="loading"
>
{{
smsCountdown > 0
? `${smsCountdown}秒后重新获取`
: "获取验证码"
}}
</a-button>
</template>
</a-input>
</div>
</a-form-item>
<a-button
type="primary"
size="large"
block
htmlType="submit"
:loading="loading"
>
<span>登录</span>
</a-button>
</a-form>
</a-tab-pane> -->
</a-tabs>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { setPath } from '@/utils/auth';
import { onMounted, reactive, ref, toRefs, watch } from "vue";
import loginImg from "@/assets/images/logo.png";
import { UserOutlined, LockOutlined, MobileOutlined } from "@ant-design/icons-vue";
import { getCaptcha } from "@/api/auth";
import { message } from "ant-design-vue";
// 组件依赖
import router from "@/router";
import Cookies from "js-cookie";
// API依赖
import { useRoute } from "vue-router";
import { LoginData } from "@/api/auth/types";
//密码加密
import { encrypt, decrypt } from "@/utils/rsaEncrypt";
// 状态管理依赖
import { useUserStore } from "@/store/modules/user";
// 国际化
import { useI18n } from "vue-i18n";
const { t } = useI18n();
const userStore = useUserStore();
const route = useRoute();
// 图片验证码
const codeUrl = ref("");
const smsCountdown = ref(0); // 短信验证码倒计时
const smsButtonDisabled = ref(false); // 短信按钮禁用状态
// 记住密码
let remember = ref(false);
// 忘记密码表单数据
const forgotPasswordForm = ref({
phone: "",
captcha: "",
newPassword: "",
});
// 忘记密码
const showForgotPassword = ref(false);
// 登录方式
const activeTab = ref("account");
const state = reactive({
redirect: "",
loginData: {
uuid: "",
username: "admin",
password: "123456",
code: "",
} as LoginData,
loginRules: {
username: [{ required: true, trigger: "blur", message: t("login.rulesUsername") }],
password: [{ required: true, trigger: "blur", message: t("login.rulesPassword") }],
code: [{ required: true, trigger: "blur", message: "请输入验证码" }],
},
loginImg: loginImg[0],
loading: false,
passwordType: "password",
// 大写提示禁用
capslockTooltipDisabled: true,
otherQuery: {},
clientHeight: document.documentElement.clientHeight,
showDialog: false,
cookiePass: "",
});
// 忘记密码表单校验规则
const forgotPasswordRules = ref({
phone: [
{ required: true, message: "手机号不能为空", trigger: "blur" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号", trigger: "blur" },
],
captcha: [{ required: true, message: "验证码不能为空", trigger: "blur" }],
newPassword: [
{ required: true, message: "新密码不能为空", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" },
],
});
const { loginData, loginRules, loading,
// passwordType, capslockTooltipDisabled
} = toRefs(
state
);
// function checkCapslock(e: any) {
// const { key } = e;
// state.capslockTooltipDisabled = key && key.length === 1 && key >= "A" && key <= "Z";
// }
// function showPwd() {
// if (passwordType.value === "password") {
// passwordType.value = "";
// } else {
// passwordType.value = "password";
// }
// nextTick(() => {
// passwordRef.value.focus();
// });
// }
/**
* 登录
*/
function onFinish() {
state.loading = true;
const user = {
username: state.loginData.username,
password: state.loginData.password,
// rememberMe: state.loginData.rememberMe,
code: state.loginData.code,
uuid: state.loginData.uuid,
};
if (user.password !== state.cookiePass) {
user.password = encrypt(user.password);
}
console.log(user);
userStore
.login(user)
.then(() => {
Cookies.set("username", user.username);
router.push({ path: "/" });
setPath('/login')
state.loading = false;
})
.catch(() => {
getCode();
state.loading = false;
});
}
watch(
route,
() => {
const query = route.query;
if (query) {
state.redirect = query.redirect as string;
state.otherQuery = getOtherQuery(query);
}
},
{
immediate: true,
}
);
function getOtherQuery(query: any) {
return Object.keys(query).reduce((acc: any, cur: any) => {
if (cur !== "redirect") {
acc[cur] = query[cur];
}
return acc;
}, {});
}
function getCookie() {
const username = Cookies.get("username");
let password = Cookies.get("password");
const rememberMe = Cookies.get("rememberMe");
rememberMe == "true" ? (remember.value = Boolean(rememberMe)) : false;
// 保存cookie里面的加密后的密码
state.cookiePass = password === undefined ? "" : password;
password = password === undefined ? state.loginData.password : password;
state.loginData = {
username: username === undefined ? state.loginData.username : username,
password: decrypt(password),
code: "",
uuid: "",
};
remember.value = rememberMe === undefined ? false : Boolean(rememberMe);
}
function getCode() {
getCaptcha().then((result: any) => {
codeUrl.value = result.data.img;
state.loginData.uuid = result.data.uuid;
});
}
// 开始倒计时
const startCountdown = () => {
smsCountdown.value = 60;
const timer = setInterval(() => {
smsCountdown.value--;
if (smsCountdown.value <= 0) {
clearInterval(timer);
smsButtonDisabled.value = false;
}
}, 1000);
};
// 显示忘记密码页面
const showForgotPasswordPage = () => {
showForgotPassword.value = true;
};
// 返回登录页面
const backToLogin = () => {
showForgotPassword.value = false;
// 重置忘记密码表单
forgotPasswordForm.value = {
phone: "",
captcha: "",
newPassword: "",
};
};
// 发送短信验证码
const sendSms = async () => {
// 检查手机号是否为空
if (!loginData.value.username) {
message.error("请输入手机号");
return;
}
// 检查手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(loginData.value.username)) {
message.error("请输入正确的手机号");
return;
}
// 如果正在倒计时,不允许重复发送
if (smsCountdown.value > 0) {
return;
}
loading.value = true;
smsButtonDisabled.value = true;
try {
// 模拟发送短信验证码接口
// await axios.post('/sms/send', { phone: loginForm.value.username })
// 模拟发送成功
message.success("验证码发送成功");
// 开始倒计时
startCountdown();
} catch (error) {
console.error("发送验证码失败", error);
message.error("验证码发送失败,请重试");
smsButtonDisabled.value = false;
} finally {
loading.value = false;
}
};
// 发送忘记密码短信验证码
const sendForgotPasswordSms = async () => {
// 检查手机号是否为空
if (!forgotPasswordForm.value.phone) {
message.error("请输入手机号");
return;
}
// 检查手机号格式
const phoneRegex = /^1[3-9]\d{9}$/;
if (!phoneRegex.test(forgotPasswordForm.value.phone)) {
message.error("请输入正确的手机号");
return;
}
// 如果正在倒计时,不允许重复发送
if (smsCountdown.value > 0) {
return;
}
loading.value = true;
smsButtonDisabled.value = true;
try {
// 模拟发送短信验证码接口
// await axios.post('/sms/forgot-password', { phone: forgotPasswordForm.value.phone })
// 模拟发送成功
message.success("验证码发送成功");
// 开始倒计时
startCountdown();
} catch (error) {
console.error("发送验证码失败", error);
message.error("验证码发送失败,请重试");
smsButtonDisabled.value = false;
} finally {
loading.value = false;
}
};
// 处理密码重置
const handleResetPassword = async () => {
loading.value = true;
try {
// 2. 模拟接口请求 (实际替换为真实重置密码接口)
// await axios.post('/user/reset-password', {
// phone: forgotPasswordForm.value.phone,
// captcha: forgotPasswordForm.value.captcha,
// newPassword: forgotPasswordForm.value.newPassword
// })
// 3. 模拟重置成功
message.success("密码重置成功");
// 4. 返回登录页面
backToLogin();
} catch (error) {
console.error("密码重置失败", error);
message.error("密码重置失败,请重试");
} finally {
loading.value = false;
}
};
onMounted(() => {
getCookie();
getCode();
});
</script>
<style scoped lang="scss">
.login-container {
margin: 0px auto;
position: relative;
width: 100%;
height: 100%;
min-width: 1500px;
background-color: rgb(255, 255, 255);
h1 {
position: relative;
padding: 66px 0px 2px 80px;
margin-bottom: 0px;
height: 155px;
div {
position: relative;
line-height: 71px;
}
.logo-img {
position: absolute;
left: 0px;
top: 10px;
width: 55px;
}
.system-title {
margin-left: 71px;
font-weight: 700;
font-size: 35px;
color: rgb(47, 107, 152);
}
}
:deep(.ant-tabs-tab) {
padding: 8px 0;
}
:deep(.ant-input-prefix) {
display: flex;
width: 26px;
svg {
width: 18px;
height: 18px;
margin-right: 4px;
}
}
.login-wrapper {
position: relative;
width: 100%;
height: 72%;
min-height: 600px;
background: url("@/assets/images/login-bg.jpg");
background-repeat: no-repeat;
background-size: 100% 100%;
}
// 左侧背景区域
.left-section {
.slogan {
position: absolute;
top: 28%;
left: 18%;
width: 440px;
height: 112px;
color: rgb(255, 255, 255);
font-size: 40px;
}
}
// 右侧登录卡片区域
.right-section {
position: absolute;
left: 74%;
top: 20%;
width: 20%;
max-height: 402px;
max-width: 400px;
min-height: 362px;
border-radius: 3px;
padding: 15px 24px 24px;
background-color: rgb(255, 255, 255);
.ant-tabs-nav {
height: 38px;
}
}
/* 验证码布局 */
.captcha-row {
display: flex;
.ant-row {
display: flex;
align-items: start;
}
/* gap: 12px;
align-items: center; */
.captcha-img {
width: 100%;
height: 40px;
line-height: 40px;
background-repeat: no-repeat;
background-size: contain;
background-position: center center;
}
a {
line-height: 50px;
float: right;
color: #1890ff;
text-decoration: none;
background-color: transparent;
outline: none;
cursor: pointer;
transition: color 0.3s;
white-space: nowrap;
}
}
// 忘记密码样式
.forgot-link {
float: right;
color: #1677ff;
font-size: 14px;
}
// 短信登录占位提示
.sms-login-tip {
padding: 40px 20px;
text-align: center;
color: #999;
}
// 忘记密码页面样式
.forgot-password-container {
.forgot-password-header {
display: flex;
align-items: center;
margin-bottom: 24px;
.back-button {
padding: 0;
margin-right: 12px;
color: #1677ff;
font-size: 14px;
&:hover {
color: #4096ff;
}
}
.forgot-password-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #333;
}
}
}
}
</style>