feat: 首次提交NBA前端项目

This commit is contained in:
2026-05-14 22:16:25 +08:00
parent 5f48a8a55e
commit 3533904ced
26 changed files with 8668 additions and 434 deletions

View 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>