feat: 首次提交NBA前端项目
This commit is contained in:
365
src/components/AuthModal.vue
Normal file
365
src/components/AuthModal.vue
Normal file
@@ -0,0 +1,365 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user