366 lines
7.0 KiB
Vue
366 lines
7.0 KiB
Vue
<template>
|
||
<div class="auth-overlay" @click.self="close">
|
||
<div class="auth-modal">
|
||
<header class="auth-header">
|
||
<div class="tabs">
|
||
<button
|
||
:class="{ active: mode === 'login' }"
|
||
@click="switchMode('login')"
|
||
>
|
||
登录
|
||
</button>
|
||
<button
|
||
:class="{ active: mode === 'register' }"
|
||
@click="switchMode('register')"
|
||
>
|
||
注册
|
||
</button>
|
||
<button
|
||
:class="{ active: mode === 'reset' }"
|
||
@click="switchMode('reset')"
|
||
>
|
||
修改密码
|
||
</button>
|
||
</div>
|
||
<button class="close-btn" @click="close">×</button>
|
||
</header>
|
||
|
||
<form @submit.prevent="handleSubmit" class="auth-form">
|
||
<div class="form-group">
|
||
<label>邮箱</label>
|
||
<div class="email-row">
|
||
<input
|
||
v-model="form.email"
|
||
type="email"
|
||
required
|
||
placeholder="请输入邮箱"
|
||
/>
|
||
<button
|
||
v-if="mode !== 'login'"
|
||
type="button"
|
||
class="code-btn"
|
||
:disabled="countdown > 0"
|
||
@click="handleSendCode"
|
||
>
|
||
{{ countdown > 0 ? countdown + ' 秒后重发' : '发送验证码' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group" v-if="mode !== 'login'">
|
||
<label>邮箱验证码</label>
|
||
<input
|
||
v-model="form.code"
|
||
type="text"
|
||
required
|
||
placeholder="请输入邮箱验证码"
|
||
/>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>{{ mode === 'reset' ? '新密码' : '密码' }}</label>
|
||
<input
|
||
v-model="form.password"
|
||
type="password"
|
||
required
|
||
placeholder="请输入密码"
|
||
/>
|
||
</div>
|
||
|
||
<div class="form-group" v-if="mode !== 'login'">
|
||
<label>确认密码</label>
|
||
<input
|
||
v-model="form.confirm"
|
||
type="password"
|
||
required
|
||
placeholder="请再次输入密码"
|
||
/>
|
||
</div>
|
||
|
||
<div class="actions">
|
||
<button type="submit" class="primary-btn">
|
||
{{
|
||
mode === 'login'
|
||
? '登录'
|
||
: mode === 'register'
|
||
? '注册'
|
||
: '修改密码'
|
||
}}
|
||
</button>
|
||
<button type="button" class="ghost-btn" @click="close">
|
||
取消
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { reactive, ref, watch, onUnmounted } from "vue";
|
||
|
||
const props = defineProps({
|
||
modelValue: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
defaultMode: {
|
||
type: String,
|
||
default: "login",
|
||
},
|
||
});
|
||
|
||
const emit = defineEmits(["update:modelValue", "submit", "send-code"]);
|
||
|
||
const countdown = ref(0);
|
||
let timer = null;
|
||
|
||
const form = reactive({
|
||
email: "",
|
||
password: "",
|
||
confirm: "",
|
||
code: "",
|
||
});
|
||
|
||
const mode = ref(props.defaultMode || "login");
|
||
|
||
watch(
|
||
() => props.defaultMode,
|
||
() => {
|
||
resetForm();
|
||
mode.value = props.defaultMode || "login";
|
||
}
|
||
);
|
||
|
||
const resetForm = () => {
|
||
form.email = "";
|
||
form.password = "";
|
||
form.confirm = "";
|
||
form.code = "";
|
||
countdown.value = 0;
|
||
if (timer) {
|
||
clearInterval(timer);
|
||
timer = null;
|
||
}
|
||
};
|
||
|
||
const switchMode = (target) => {
|
||
if (target !== mode.value) {
|
||
mode.value = target;
|
||
resetForm();
|
||
}
|
||
};
|
||
|
||
const close = () => {
|
||
resetForm();
|
||
mode.value = props.defaultMode || "login";
|
||
emit("update:modelValue", false);
|
||
};
|
||
|
||
const isEmailValid = (email) => {
|
||
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
if (!isEmailValid(form.email)) {
|
||
alert("邮箱格式不正确");
|
||
return;
|
||
}
|
||
if (mode.value !== "login" && form.password !== form.confirm) {
|
||
alert("两次输入的密码不一致");
|
||
return;
|
||
}
|
||
if (mode.value !== "login" && !form.code) {
|
||
alert("请输入邮箱验证码");
|
||
return;
|
||
}
|
||
emit("submit", {
|
||
mode: mode.value,
|
||
email: form.email,
|
||
password: form.password,
|
||
code: form.code,
|
||
});
|
||
resetForm();
|
||
close();
|
||
};
|
||
|
||
const handleSendCode = () => {
|
||
if (countdown.value > 0) {
|
||
// 在倒计时中,直接忽略点击
|
||
return;
|
||
}
|
||
|
||
if (!form.email) {
|
||
alert("请先输入邮箱");
|
||
return;
|
||
}
|
||
if (!isEmailValid(form.email)) {
|
||
alert("邮箱格式不正确");
|
||
return;
|
||
}
|
||
|
||
// 通知父组件发送验证码
|
||
emit("send-code", { email: form.email, mode: mode.value });
|
||
|
||
// 这里默认认为发送成功,如果你要等后端返回再开始倒计时,
|
||
// 可以把下面这段逻辑放到父组件里,再通过 props 传回当前组件。
|
||
// alert("验证码发送成功");
|
||
countdown.value = 60;
|
||
timer = setInterval(() => {
|
||
countdown.value--;
|
||
if (countdown.value <= 0) {
|
||
countdown.value = 0;
|
||
clearInterval(timer);
|
||
timer = null;
|
||
}
|
||
}, 1000);
|
||
};
|
||
|
||
onUnmounted(() => {
|
||
if (timer) {
|
||
clearInterval(timer);
|
||
timer = null;
|
||
}
|
||
});
|
||
|
||
// const handleSendCode = () => {
|
||
// if (!form.email) {
|
||
// alert("请先输入邮箱");
|
||
// return;
|
||
// }
|
||
// if (!isEmailValid(form.email)) {
|
||
//
|
||
("邮箱格式不正确");
|
||
// return;
|
||
// }
|
||
// emit("send-code", { email: form.email });
|
||
// };
|
||
</script>
|
||
|
||
<style scoped>
|
||
.code-btn[disabled] {
|
||
opacity: 0.6;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.auth-overlay {
|
||
position: fixed;
|
||
inset: 0;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2000;
|
||
}
|
||
|
||
.auth-modal {
|
||
width: 360px;
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.18);
|
||
padding: 20px;
|
||
}
|
||
|
||
.auth-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.tabs {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.tabs button {
|
||
padding: 8px 14px;
|
||
border: none;
|
||
border-radius: 6px;
|
||
background: #f2f4f7;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.tabs button.active {
|
||
background: #1d428a;
|
||
color: #fff;
|
||
}
|
||
|
||
.close-btn {
|
||
border: none;
|
||
background: transparent;
|
||
font-size: 20px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.auth-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.form-group {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
.email-row {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
|
||
.email-row input {
|
||
flex: 1;
|
||
}
|
||
|
||
.code-btn {
|
||
white-space: nowrap;
|
||
padding: 0 12px;
|
||
border: 1px solid #1d428a;
|
||
background: #1d428a;
|
||
color: #fff;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.form-group label {
|
||
font-size: 14px;
|
||
color: #444;
|
||
}
|
||
|
||
.form-group input {
|
||
padding: 10px 12px;
|
||
border-radius: 6px;
|
||
border: 1px solid #d9d9d9;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.actions {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.primary-btn,
|
||
.ghost-btn {
|
||
flex: 1;
|
||
padding: 10px 12px;
|
||
border-radius: 6px;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.primary-btn {
|
||
background: #1d428a;
|
||
color: #fff;
|
||
}
|
||
|
||
.ghost-btn {
|
||
background: #f2f4f7;
|
||
color: #1d428a;
|
||
}
|
||
</style>
|