更新
This commit is contained in:
+183
-25
@@ -20,6 +20,10 @@
|
||||
<div class="login-panel">
|
||||
<h2 class="login-title">欢迎登录</h2>
|
||||
<div class="login-desc">请填写您的账号信息</div>
|
||||
<div class="mode-switch">
|
||||
<button class="mode-btn" :class="{ active: loginMode === 'password' }" @click="switchMode('password')">账号密码登录</button>
|
||||
<button class="mode-btn" :class="{ active: loginMode === 'sms' }" @click="switchMode('sms')">手机号验证码登录</button>
|
||||
</div>
|
||||
<div class="form-group icon-input-group">
|
||||
<span class="input-icon">
|
||||
<i class="fa-solid fa-building-columns"></i>
|
||||
@@ -27,13 +31,13 @@
|
||||
<input v-model="tenant_name" type="text" placeholder="租户名称" autocomplete="tenant_name"
|
||||
class="input input-with-icon" />
|
||||
</div>
|
||||
<div class="form-group icon-input-group">
|
||||
<div v-if="loginMode === 'password'" class="form-group icon-input-group">
|
||||
<span class="input-icon">
|
||||
<i class="fa-solid fa-user"></i>
|
||||
</span>
|
||||
<input v-model="account" type="text" placeholder="用户名" autocomplete="account" class="input input-with-icon" />
|
||||
</div>
|
||||
<div class="form-group icon-input-group">
|
||||
<div v-if="loginMode === 'password'" class="form-group icon-input-group">
|
||||
<span class="input-icon">
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
</span>
|
||||
@@ -45,16 +49,29 @@
|
||||
<i v-else class="fa-solid fa-eye-slash"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="loginMode === 'sms'" class="form-group icon-input-group">
|
||||
<span class="input-icon">
|
||||
<i class="fa-solid fa-mobile-screen-button"></i>
|
||||
</span>
|
||||
<input v-model="phone" type="text" placeholder="手机号" autocomplete="tel" class="input input-with-icon" />
|
||||
</div>
|
||||
<div v-if="loginMode === 'sms'" class="form-group code-row">
|
||||
<input v-model="smsCode" type="text" placeholder="短信验证码" class="input code-input" />
|
||||
<button class="code-btn" :disabled="codeLoading || countdown > 0" @click="handleSendLoginCode">
|
||||
{{ countdown > 0 ? `${countdown}s` : (codeLoading ? "发送中..." : "发送验证码") }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 极验验证码容器 -->
|
||||
<div style="display: none;" v-if="showCaptchaContainer" class="geetest-container" ref="captchaContainer"></div>
|
||||
|
||||
<div class="remember-me-row">
|
||||
<label class="remember-me-label">
|
||||
<label v-if="loginMode === 'password'" class="remember-me-label">
|
||||
<input type="checkbox" v-model="rememberMe" class="remember-me-checkbox" @change="handleRememberMeChange" />
|
||||
<span>记住我</span>
|
||||
</label>
|
||||
<span v-else></span>
|
||||
<div class="action-links">
|
||||
<a class="register-link" @click.prevent="goRegister">注册账号</a>
|
||||
<span class="divider">|</span>
|
||||
@@ -76,10 +93,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, nextTick } from "vue";
|
||||
import { ref, onMounted, nextTick, onUnmounted } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { login, getOpenVerify, getGeetest4Infos } from "@/api/login";
|
||||
import { login, getOpenVerify, getGeetest4Infos, sendLoginCode, loginBySms } from "@/api/login";
|
||||
import "@/assets/js/gt4.js";
|
||||
import { ElMessageBox, ElMessage } from "element-plus";
|
||||
|
||||
@@ -88,11 +105,17 @@ const authStore = useAuthStore();
|
||||
|
||||
// --- 表单数据 ---
|
||||
const tenant_name = ref("");
|
||||
const loginMode = ref<"password" | "sms">("password");
|
||||
const account = ref("");
|
||||
const password = ref("");
|
||||
const phone = ref("");
|
||||
const smsCode = ref("");
|
||||
const passwordVisible = ref(false);
|
||||
const rememberMe = ref(false);
|
||||
const loading = ref(false);
|
||||
const codeLoading = ref(false);
|
||||
const countdown = ref(0);
|
||||
let countdownTimer: ReturnType<typeof setInterval> | null = null;
|
||||
const errorMsg = ref("");
|
||||
|
||||
// --- 极验相关变量 ---
|
||||
@@ -127,6 +150,25 @@ const cleanCaptchaInstance = () => {
|
||||
|
||||
// --- 执行登录请求 ---
|
||||
const performLoginRequest = async () => {
|
||||
if (loginMode.value === "sms") {
|
||||
const res = await loginBySms({
|
||||
tenant_name: tenant_name.value,
|
||||
phone: phone.value,
|
||||
sms_code: smsCode.value,
|
||||
});
|
||||
if (res && res.code === 200) {
|
||||
authStore.setLoginInfo(res.data);
|
||||
const { useTabsStore } = await import("@/stores");
|
||||
const tabsStore = useTabsStore();
|
||||
tabsStore.resetTabs();
|
||||
router.push({ path: "/home" });
|
||||
ElMessage.success("登录成功!");
|
||||
return true;
|
||||
}
|
||||
errorMsg.value = res.msg || "登录失败";
|
||||
return false;
|
||||
}
|
||||
|
||||
const res = await login({
|
||||
tenant_name: tenant_name.value,
|
||||
account: account.value,
|
||||
@@ -163,6 +205,53 @@ const performLoginRequest = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const switchMode = (mode: "password" | "sms") => {
|
||||
loginMode.value = mode;
|
||||
errorMsg.value = "";
|
||||
};
|
||||
|
||||
const startCountdown = () => {
|
||||
countdown.value = 60;
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
countdownTimer = setInterval(() => {
|
||||
countdown.value -= 1;
|
||||
if (countdown.value <= 0) {
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
countdownTimer = null;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleSendLoginCode = async () => {
|
||||
if (codeLoading.value || countdown.value > 0) return;
|
||||
if (!tenant_name.value.trim()) {
|
||||
errorMsg.value = "请输入租户名称";
|
||||
return;
|
||||
}
|
||||
if (!phone.value.trim()) {
|
||||
errorMsg.value = "请输入手机号";
|
||||
return;
|
||||
}
|
||||
codeLoading.value = true;
|
||||
errorMsg.value = "";
|
||||
try {
|
||||
const res = await sendLoginCode({
|
||||
tenant_name: tenant_name.value,
|
||||
phone: phone.value,
|
||||
});
|
||||
if (res && res.code === 200) {
|
||||
ElMessage.success("验证码已发送");
|
||||
startCountdown();
|
||||
} else {
|
||||
errorMsg.value = res?.msg || "发送验证码失败";
|
||||
}
|
||||
} catch (err: any) {
|
||||
errorMsg.value = err?.response?.data?.msg || err?.message || "发送验证码失败";
|
||||
} finally {
|
||||
codeLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// --- 初始化极验4.0 ---
|
||||
const startGeetest4 = async () => {
|
||||
showCaptchaContainer.value = true;
|
||||
@@ -277,34 +366,50 @@ const handleLogin = async () => {
|
||||
errorMsg.value = "请输入租户名称";
|
||||
return;
|
||||
}
|
||||
if (!account.value.trim()) {
|
||||
errorMsg.value = "请输入用户名";
|
||||
return;
|
||||
}
|
||||
if (!password.value.trim()) {
|
||||
errorMsg.value = "请输入密码";
|
||||
return;
|
||||
if (loginMode.value === "password") {
|
||||
if (!account.value.trim()) {
|
||||
errorMsg.value = "请输入用户名";
|
||||
return;
|
||||
}
|
||||
if (!password.value.trim()) {
|
||||
errorMsg.value = "请输入密码";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!phone.value.trim()) {
|
||||
errorMsg.value = "请输入手机号";
|
||||
return;
|
||||
}
|
||||
if (!smsCode.value.trim()) {
|
||||
errorMsg.value = "请输入短信验证码";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (loading.value) return;
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
// 先判断是否开启验证
|
||||
const verifyRes = await getOpenVerify();
|
||||
let openVerify = "0";
|
||||
if (loginMode.value === "password") {
|
||||
// 先判断是否开启验证
|
||||
const verifyRes = await getOpenVerify();
|
||||
let openVerify = "0";
|
||||
|
||||
if (verifyRes && verifyRes.code === 200 && Array.isArray(verifyRes.data)) {
|
||||
verifyRes.data.forEach((item: any) => {
|
||||
if (item.label === "openVerify") {
|
||||
openVerify = item.value || "0";
|
||||
}
|
||||
});
|
||||
}
|
||||
if (verifyRes && verifyRes.code === 200 && Array.isArray(verifyRes.data)) {
|
||||
verifyRes.data.forEach((item: any) => {
|
||||
if (item.label === "openVerify") {
|
||||
openVerify = item.value || "0";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 如果开启了验证,使用极验4.0
|
||||
if (openVerify === "1") {
|
||||
await startGeetest4();
|
||||
// 如果开启了验证,使用极验4.0
|
||||
if (openVerify === "1") {
|
||||
await startGeetest4();
|
||||
} else {
|
||||
// 直接登录
|
||||
await performLoginRequest();
|
||||
}
|
||||
} else {
|
||||
// 直接登录
|
||||
await performLoginRequest();
|
||||
@@ -348,6 +453,10 @@ onMounted(() => {
|
||||
}
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (countdownTimer) clearInterval(countdownTimer);
|
||||
});
|
||||
|
||||
const goRegister = () => router.push("/register");
|
||||
const goForget = () => router.push("/forget");
|
||||
</script>
|
||||
@@ -447,6 +556,29 @@ const goForget = () => router.push("/forget");
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.mode-switch {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.mode-btn {
|
||||
flex: 1;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #d6e6fa;
|
||||
border-radius: 7px;
|
||||
background: #f7fbfe;
|
||||
color: #407ad6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mode-btn.active {
|
||||
background: #e8f3ff;
|
||||
border-color: #7cb8ff;
|
||||
color: #1d63c2;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
@@ -520,6 +652,32 @@ const goForget = () => router.push("/forget");
|
||||
box-shadow: 0 0 0 2px #e3f2ffb1;
|
||||
}
|
||||
|
||||
.code-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.code-input {
|
||||
margin-bottom: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.code-btn {
|
||||
width: 120px;
|
||||
height: 44px;
|
||||
border: 1.3px solid #d6e6fa;
|
||||
border-radius: 7px;
|
||||
background: #fff;
|
||||
color: #3a78ca;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-btn:disabled {
|
||||
opacity: 0.65;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 极验验证码容器样式 */
|
||||
.geetest-container {
|
||||
margin: 10px 0;
|
||||
|
||||
Reference in New Issue
Block a user