feat: 添加注册界面

This commit is contained in:
SikongJueluo 2025-07-11 19:26:27 +08:00
parent fae07d9eae
commit 3f2c772eeb
No known key found for this signature in database
10 changed files with 294 additions and 179 deletions

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1741694797806" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2622" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" p-id="2623"></path><path d="M192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" p-id="2624"></path><path d="M192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0Z" p-id="2625"></path><path d="M864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z" p-id="2626"></path></svg>

Before

Width:  |  Height:  |  Size: 870 B

View File

@ -1 +0,0 @@
<svg t="1741522876251" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3628" width="200" height="200"><path d="M327.04 85.333333h369.92C841.472 85.333333 938.666667 186.794667 938.666667 337.749333v348.501334C938.666667 837.205333 841.472 938.666667 696.874667 938.666667h-229.546667a32 32 0 0 1 0-64h229.546667c107.989333 0 177.792-73.941333 177.792-188.416V337.749333c0-114.474667-69.802667-188.416-177.749334-188.416H327.04C219.093333 149.333333 149.333333 223.274667 149.333333 337.749333v348.501334c0 114.474667 69.76 188.416 177.706667 188.416a32 32 0 0 1 0 64C182.442667 938.666667 85.333333 837.205333 85.333333 686.250667V337.749333C85.333333 186.794667 182.442667 85.333333 327.04 85.333333z m-51.114667 381.098667a31.914667 31.914667 0 0 1 42.325334-16.042667 31.914667 31.914667 0 0 1 16.042666 42.282667A47.061333 47.061333 0 1 0 424.192 512c0-25.898667-21.077333-46.933333-47.018667-46.933333a32 32 0 0 1 0-64c50.048 0 91.904 33.408 105.728 78.933333h242.858667a32 32 0 0 1 32 32v79.018667a32 32 0 0 1-64 0V544h-56.704v47.018667a32 32 0 0 1-64 0V544h-90.154667a110.72 110.72 0 0 1-105.728 79.018667 111.104 111.104 0 0 1-101.248-156.544z" fill="#200E32" p-id="3629"></path></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg t="1741522263287" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2626" width="200" height="200"><path d="M511.913993 941.605241c-255.612968 0-385.311608-57.452713-385.311608-170.810012 0-80.846632 133.654964-133.998992 266.621871-151.88846L393.224257 602.049387c-79.986561-55.904586-118.86175-153.436587-118.86175-297.240383 0-139.33143 87.211154-222.586259 233.423148-222.586259l7.912649 0c146.211994 0 233.423148 83.254829 233.423148 222.586259 0 54.184445 0 214.67361-117.829666 297.412397l-0.344028 16.685369c132.966907 18.061482 266.105829 71.041828 266.105829 151.716445C897.225601 884.152528 767.526961 941.605241 511.913993 941.605241zM507.957668 141.567613c-79.470519 0-174.250294 28.382328-174.250294 163.241391 0 129.698639 34.230808 213.469511 104.584579 255.784982 8.944734 5.332437 14.277171 14.965228 14.277171 25.286074l0 59.344868c0 15.309256-11.524945 28.0383-26.662187 29.414413-144.319839 14.449185-239.959684 67.429531-239.959684 95.983874 0 92.199563 177.346548 111.637158 325.966739 111.637158 148.792206 0 325.966739-19.26558 325.966739-111.637158 0-28.726356-95.639845-81.534688-239.959684-95.983874-15.48127-1.548127-27.006215-14.621199-26.662187-30.102469l1.376113-59.344868c0.172014-10.148833 5.676466-19.437594 14.277171-24.770032 70.525785-42.487485 103.208466-123.678145 103.208466-255.784982 0-135.031077-94.779775-163.241391-174.250294-163.241391L507.957668 141.567613 507.957668 141.567613z" fill="#575B66" p-id="2627"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1,145 +0,0 @@
<template>
<div class="card card-dash h-80 w-100 shadow-xl bg-base-100">
<div class="card-body">
<h1 class="card-title place-self-center my-3 text-2xl">User Login</h1>
<div class="flex flex-col w-full h-full">
<label class="input w-full my-3">
<img
class="h-[1em] opacity-50"
src="@/assets/user.svg"
alt="User img"
/>
<input
type="text"
class="grow"
placeholder="用户名"
v-model="username"
@keyup.enter="handleLogin"
/>
</label>
<label class="input w-full my-3">
<img
class="h-[1em] opacity-50"
src="@/assets/pwd.svg"
alt="User img"
/>
<input
type="password"
class="grow"
placeholder="密码"
v-model="password"
@keyup.enter="handleLogin"
/>
</label>
</div>
<div class="flex justify-end mx-3">
<RouterLink to="/">忘记密码?</RouterLink>
</div>
<div class="card-actions flex items-end my-3">
<button class="btn flex-1" @click="handleRegister">注册</button>
<button
class="btn btn-primary flex-3"
@click="handleLogin"
:disabled="isLoading"
>
{{ isLoading ? "登录中..." : "登录" }}
</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { AuthManager } from "@/utils/AuthManager";
import { useAlertStore } from "@/components/Alert";
const router = useRouter();
// Alert store
const alertStore = useAlertStore();
//
const username = ref("");
const password = ref("");
const isLoading = ref(false);
//
const handleLogin = async () => {
//
if (!username.value.trim()) {
alertStore?.show("请输入用户名", "error");
return;
}
if (!password.value.trim()) {
alertStore?.show("请输入密码", "error");
return;
}
isLoading.value = true;
try {
// AuthManager
await AuthManager.login(username.value.trim(), password.value.trim());
//
alertStore?.show("登录成功", "success", 1000);
// project
setTimeout(async () => {
await router.push("/project");
}, 1000);
} catch (error: any) {
console.error("Login error:", error);
//
let errorMessage = "登录失败,请检查网络连接";
if (error.status === 400) {
errorMessage = "用户名或密码错误";
} else if (error.status === 401) {
errorMessage = "用户名或密码错误";
} else if (error.status === 500) {
errorMessage = "服务器错误,请稍后重试";
} else if (error.message) {
errorMessage = error.message;
}
alertStore?.show(errorMessage, "error");
} finally {
isLoading.value = false;
}
};
//
const handleRegister = () => {
//
console.log("Navigate to register page");
// router.push('/register')
};
// token
const checkExistingToken = async () => {
try {
const isValid = await AuthManager.verifyToken();
if (isValid) {
// tokenproject
await router.push("/project");
}
} catch (error) {
// token
console.log("Token verification failed, showing login page");
}
};
// token
onMounted(() => {
checkExistingToken();
});
</script>
<style scoped>
@import "@/assets/main.css";
</style>

View File

@ -1,22 +1,18 @@
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import LoginView from "../views/LoginView.vue";
import LabView from "../views/LabView.vue";
import AuthView from "../views/AuthView.vue";
import ProjectView from "../views/Project/Index.vue";
import TestView from "../views/TestView.vue";
import UserView from "@/views/User/Index.vue";
import AdminView from "../views/AdminView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: "/", name: "home", component: HomeView },
{ path: "/login", name: "login", component: LoginView },
{ path: "/lab/:id", name: "lab", component: LabView },
{ path: "/login", name: "login", component: AuthView },
{ path: "/project", name: "project", component: ProjectView },
{ path: "/test", name: "test", component: TestView },
{ path: "/user", name: "user", component: UserView },
{ path: "/admin", name: "admin", component: AdminView },
],
});

288
src/views/AuthView.vue Normal file
View File

@ -0,0 +1,288 @@
<template>
<div class="flex items-center justify-center min-h-screen bg-base-200">
<div class="relative w-full max-w-md">
<!-- Login Card -->
<div v-if="!showSignUp" class="card card-dash h-80 w-100 shadow-xl bg-base-100">
<div class="card-body">
<h1 class="card-title place-self-center my-3 text-2xl">用户登录</h1>
<div class="flex flex-col w-full h-full">
<label class="input w-full my-3">
<User class="h-[1em] opacity-50" />
<input
type="text"
class="grow"
placeholder="用户名"
v-model="username"
@keyup.enter="handleLogin"
/>
</label>
<label class="input w-full my-3">
<Lock class="h-[1em] opacity-50" />
<input
type="password"
class="grow"
placeholder="密码"
v-model="password"
@keyup.enter="handleLogin"
/>
</label>
</div>
<div class="flex justify-end mx-3">
<RouterLink to="/">忘记密码?</RouterLink>
</div>
<div class="card-actions flex items-end my-3">
<button class="btn flex-1" @click="handleRegister">注册</button>
<button
class="btn btn-primary flex-3"
@click="handleLogin"
:disabled="isLoading"
>
{{ isLoading ? "登录中..." : "登录" }}
</button>
</div>
</div>
</div>
<!-- Sign Up Card -->
<div v-if="showSignUp" class="card card-dash h-96 w-100 shadow-xl bg-base-100">
<div class="card-body">
<h1 class="card-title place-self-center my-3 text-2xl">用户注册</h1>
<div class="flex flex-col w-full h-full">
<label class="input w-full my-2">
<User class="h-[1em] opacity-50" />
<input
type="text"
class="grow"
placeholder="用户名"
v-model="signUpData.username"
@keyup.enter="handleSignUp"
/>
</label>
<label class="input w-full my-2">
<Mail class="h-[1em] opacity-50" />
<input
type="email"
class="grow"
placeholder="邮箱"
v-model="signUpData.email"
@keyup.enter="handleSignUp"
/>
</label>
<label class="input w-full my-2">
<Lock class="h-[1em] opacity-50" />
<input
type="password"
class="grow"
placeholder="密码"
v-model="signUpData.password"
@keyup.enter="handleSignUp"
/>
</label>
</div>
<div class="card-actions flex items-end my-3">
<button class="btn flex-1" @click="backToLogin">返回登录</button>
<button
class="btn btn-primary flex-3"
@click="handleSignUp"
:disabled="isSignUpLoading"
>
{{ isSignUpLoading ? "注册中..." : "注册" }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import { AuthManager } from "@/utils/AuthManager";
import { useAlertStore } from "@/components/Alert";
import { User, Lock, Mail } from "lucide-vue-next";
import { DataClient } from "@/APIClient";
const router = useRouter();
// Alert store
const alertStore = useAlertStore();
// API
const dataClient = new DataClient();
//
const username = ref("");
const password = ref("");
const isLoading = ref(false);
//
const showSignUp = ref(false);
const isSignUpLoading = ref(false);
const signUpData = ref({
username: "",
email: "",
password: ""
});
//
const handleLogin = async () => {
//
if (!username.value.trim()) {
alertStore?.show("请输入用户名", "error");
return;
}
if (!password.value.trim()) {
alertStore?.show("请输入密码", "error");
return;
}
isLoading.value = true;
try {
// AuthManager
await AuthManager.login(username.value.trim(), password.value.trim());
//
alertStore?.show("登录成功", "success", 1000);
// project
setTimeout(async () => {
await router.push("/project");
}, 1000);
} catch (error: any) {
console.error("Login error:", error);
//
let errorMessage = "登录失败,请检查网络连接";
if (error.status === 400) {
errorMessage = "用户名或密码错误";
} else if (error.status === 401) {
errorMessage = "用户名或密码错误";
} else if (error.status === 500) {
errorMessage = "服务器错误,请稍后重试";
} else if (error.message) {
errorMessage = error.message;
}
alertStore?.show(errorMessage, "error");
} finally {
isLoading.value = false;
}
};
//
const handleRegister = () => {
showSignUp.value = true;
//
signUpData.value = {
username: "",
email: "",
password: ""
};
};
//
const backToLogin = () => {
showSignUp.value = false;
};
//
const handleSignUp = async () => {
//
if (!signUpData.value.username.trim()) {
alertStore?.show("请输入用户名", "error");
return;
}
if (!signUpData.value.email.trim()) {
alertStore?.show("请输入邮箱", "error");
return;
}
//
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(signUpData.value.email.trim())) {
alertStore?.show("请输入有效的邮箱地址", "error");
return;
}
if (!signUpData.value.password.trim()) {
alertStore?.show("请输入密码", "error");
return;
}
//
if (signUpData.value.password.length < 6) {
alertStore?.show("密码长度至少6位", "error");
return;
}
isSignUpLoading.value = true;
try {
// API
const result = await dataClient.signUpUser(
signUpData.value.username.trim(),
signUpData.value.email.trim(),
signUpData.value.password.trim()
);
if (result) {
//
alertStore?.show("注册成功!请登录", "success", 2000);
//
setTimeout(() => {
backToLogin();
}, 2000);
} else {
alertStore?.show("注册失败,请重试", "error");
}
} catch (error: any) {
console.error("Sign up error:", error);
let errorMessage = "注册失败,请检查网络连接";
if (error.status === 400) {
//
if (error.result && error.result.detail) {
errorMessage = error.result.detail;
} else {
errorMessage = "注册信息无效,请检查输入";
}
} else if (error.status === 500) {
errorMessage = "服务器错误,请稍后重试";
} else if (error.message) {
errorMessage = error.message;
}
alertStore?.show(errorMessage, "error");
} finally {
isSignUpLoading.value = false;
}
};
// token
const checkExistingToken = async () => {
try {
const isValid = await AuthManager.verifyToken();
if (isValid) {
// tokenproject
await router.push("/project");
}
} catch (error) {
// token
console.log("Token verification failed, showing login page");
}
};
// token
onMounted(() => {
checkExistingToken();
});
</script>
<style scoped></style>

View File

@ -1,11 +0,0 @@
<template>
<div>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped lang="postcss"></style>

View File

@ -1,13 +0,0 @@
<template>
<div class="flex items-center justify-center min-h-screen bg-base-200">
<div class="relative w-full max-w-md">
<LoginCard />
</div>
</div>
</template>
<script setup lang="ts">
import LoginCard from "@/components/LoginCard.vue";
</script>
<style scoped></style>

View File

@ -249,5 +249,5 @@ async function refreshData() {
</script>
<style scoped lang="postcss">
@import "../assets/main.css";
@import "@/assets/main.css";
</style>

View File

@ -7,6 +7,9 @@
<li id="2" @click="setActivePage">
<a :class="{ 'menu-active': activePage === 2 }">Item 2</a>
</li>
<li id="" @click="setActivePage">
<a :class="{ 'menu-active': activePage === 2 }">Item 2</a>
</li>
</ul>
<div class="divider divider-horizontal h-full"></div>
<div class="card bg-base-200 w-300"></div>