Просмотр исходного кода

新增注册功能,优化登录对话框逻辑,支持邮箱、手机号和邀请码输入,提升用户体验

wuyi 3 месяцев назад
Родитель
Сommit
2ccdc49132

+ 123 - 6
src/components/LoginDialog.vue

@@ -30,8 +30,12 @@
       </button>
 
       <div class="text-center mb-6">
-        <h2 class="text-xl font-bold text-white/90">登录账户</h2>
-        <p class="text-white/60 mt-1">登录后即可使用全部功能</p>
+        <h2 class="text-xl font-bold text-white/90">
+          {{ isRegister ? "注册账户" : "登录账户" }}
+        </h2>
+        <p class="text-white/60 mt-1">
+          {{ isRegister ? "注册后即可使用全部功能" : "登录后即可使用全部功能" }}
+        </p>
       </div>
 
       <div class="space-y-4">
@@ -61,22 +65,86 @@
           />
         </div>
 
+        <!-- 注册表单额外字段 -->
+        <div v-if="isRegister" class="space-y-4">
+          <div>
+            <label for="email" class="block text-sm text-white/70 mb-1.5"
+              >邮箱(选填)</label
+            >
+            <input
+              type="email"
+              id="email"
+              v-model="email"
+              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
+              placeholder="请输入邮箱"
+            />
+          </div>
+
+          <div>
+            <label for="phone" class="block text-sm text-white/70 mb-1.5"
+              >手机号(选填)</label
+            >
+            <input
+              type="tel"
+              id="phone"
+              v-model="phone"
+              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
+              placeholder="请输入手机号"
+            />
+          </div>
+
+          <div>
+            <label for="code" class="block text-sm text-white/70 mb-1.5"
+              >邀请码(选填)</label
+            >
+            <input
+              type="text"
+              id="code"
+              v-model="code"
+              class="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white/90 focus:outline-none focus:ring-2 focus:ring-brand/50"
+              placeholder="请输入邀请码"
+            />
+          </div>
+        </div>
+
         <div v-if="error" class="text-red-400 text-sm py-1">
           {{ error }}
         </div>
 
         <button
-          @click="handleLogin"
+          @click="handleButtonClick"
           :disabled="isLoading"
           class="w-full py-2.5 rounded-lg bg-brand text-slate-900 font-medium hover:bg-brand/90 transition disabled:opacity-70"
         >
-          {{ isLoading ? "登录中..." : "登录" }}
+          {{
+            isLoading
+              ? isRegister
+                ? "注册中..."
+                : "登录中..."
+              : isRegister
+              ? "注册"
+              : "登录"
+          }}
         </button>
 
         <div class="text-center text-sm text-white/60">
-          <p>
+          <p v-if="!isRegister">
             还没有账户?
-            <a href="#" class="text-brand hover:underline">注册新账户</a>
+            <a
+              href="#"
+              @click.prevent="toggleMode"
+              class="text-brand hover:underline"
+              >注册新账户</a
+            >
+          </p>
+          <p v-else>
+            已有账户?
+            <a
+              href="#"
+              @click.prevent="toggleMode"
+              class="text-brand hover:underline"
+              >立即登录</a
+            >
           </p>
         </div>
       </div>
@@ -99,14 +167,31 @@ const emit = defineEmits<{
 
 const username = ref("");
 const password = ref("");
+const email = ref("");
+const phone = ref("");
+const code = ref("");
 const error = ref("");
 const isLoading = ref(false);
+const isRegister = ref(false);
 const userStore = useUserStore();
 
 const closeDialog = () => {
   emit("update:modelValue", false);
 };
 
+const toggleMode = () => {
+  isRegister.value = !isRegister.value;
+  error.value = "";
+};
+
+const handleButtonClick = () => {
+  if (isRegister.value) {
+    handleRegister();
+  } else {
+    handleLogin();
+  }
+};
+
 const handleLogin = async () => {
   // 简单的表单验证
   if (!username.value || !password.value) {
@@ -127,6 +212,34 @@ const handleLogin = async () => {
   }
 };
 
+const handleRegister = async () => {
+  // 简单的表单验证
+  if (!username.value || !password.value) {
+    error.value = "用户名和密码不能为空";
+    return;
+  }
+
+  try {
+    error.value = "";
+    isLoading.value = true;
+
+    await userStore.register(
+      username.value,
+      password.value,
+      email.value || undefined,
+      phone.value || undefined,
+      code.value || undefined
+    );
+
+    emit("login-success");
+    closeDialog();
+  } catch (err: any) {
+    error.value = err.message || "注册失败,请检查输入信息";
+  } finally {
+    isLoading.value = false;
+  }
+};
+
 // 当弹窗关闭时重置表单
 watch(
   () => props.modelValue,
@@ -134,8 +247,12 @@ watch(
     if (!newVal) {
       username.value = "";
       password.value = "";
+      email.value = "";
+      phone.value = "";
+      code.value = "";
       error.value = "";
       isLoading.value = false;
+      isRegister.value = false;
     }
   }
 );

+ 6 - 3
src/components/layout/MainLayout.vue

@@ -98,9 +98,12 @@ function switchTab(key: TabKey) {
   }
 }
 
-function handleLoginSuccess() {
-  if (active.value === "account") {
-    userStore.sync();
+async function handleLoginSuccess() {
+  // 登录/注册成功后,确保用户信息已同步
+  try {
+    await userStore.sync();
+  } catch (error) {
+    console.error("登录成功后同步用户信息失败:", error);
   }
 }
 

+ 16 - 4
src/services/api.ts

@@ -97,13 +97,25 @@ export const login = async (name: string, password: string): Promise<any> => {
   return res.data;
 };
 
-export const profile = async (): Promise<any> => {
-  const res = await api.get("/member/profile");
+export const register = async (
+  name: string,
+  password: string,
+  email?: string,
+  phone?: string,
+  code?: string
+): Promise<any> => {
+  const res = await api.post("/member/register", {
+    name,
+    password,
+    email: email || null,
+    phone: phone || null,
+    code: code || null,
+  });
   return res.data;
 };
 
-export const resetPassword = async (password: string): Promise<any> => {
-  const res = await api.post("/users/reset-password", { password });
+export const profile = async (): Promise<any> => {
+  const res = await api.get("/member/profile");
   return res.data;
 };
 

+ 21 - 1
src/store/user.ts

@@ -1,6 +1,11 @@
 import { defineStore } from "pinia";
 import { ref } from "vue";
-import { login as apiLogin, profile, newGuest } from "@/services/api";
+import {
+  login as apiLogin,
+  register as apiRegister,
+  profile,
+  newGuest,
+} from "@/services/api";
 import { useStorage } from "@vueuse/core";
 import { VipLevel } from "@/types/vip";
 
@@ -30,6 +35,20 @@ export const useUserStore = defineStore("user", () => {
     return response;
   };
 
+  const register = async (
+    name: string,
+    password: string,
+    email?: string,
+    phone?: string,
+    code?: string
+  ) => {
+    const response = await apiRegister(name, password, email, phone, code);
+    setToken(response.token);
+    setUserInfo(response.user);
+    userManuallyLoggedOut.value = false;
+    return response;
+  };
+
   const sync = async () => {
     const response = await profile();
     setUserInfo(response);
@@ -61,6 +80,7 @@ export const useUserStore = defineStore("user", () => {
     setUserInfo,
     getVipLevel,
     login,
+    register,
     logout,
     sync,
     createGuest,

+ 38 - 0
src/types/user.ts

@@ -0,0 +1,38 @@
+// 用户信息接口
+export interface User {
+  id: number;
+  name: string;
+  role: string;
+  vipLevel: string;
+  email?: string;
+  phone?: string;
+}
+
+// 登录请求参数
+export interface LoginRequest {
+  name: string;
+  password: string;
+}
+
+// 注册请求参数
+export interface RegisterRequest {
+  name: string;
+  password: string;
+  email?: string;
+  phone?: string;
+  code?: string;
+}
+
+// 登录响应
+export interface LoginResponse {
+  message: string;
+  user: User;
+  token: string;
+}
+
+// 注册响应
+export interface RegisterResponse {
+  message: string;
+  user: User;
+  token: string;
+}

+ 25 - 5
src/views/Account.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 import { useUserStore } from "@/store/user";
 import { usePriceStore } from "@/store/price";
-import { computed, ref, onMounted } from "vue";
+import { computed, ref, onMounted, watch } from "vue";
 import { upgradeGuest, purchaseMember, userQueryOrder } from "@/services/api";
 import { vipLevelToText, VipLevel } from "@/types/vip";
 
@@ -216,12 +216,32 @@ const handleQueryOrder = async () => {
   }
 };
 
+// 加载价格配置的函数
+const loadPriceConfig = async () => {
+  if (isLoggedIn.value && !priceStore.isPriceConfigLoaded) {
+    try {
+      await priceStore.fetchPriceConfig();
+    } catch (error) {
+      console.error("价格配置加载失败:", error);
+    }
+  }
+};
+
+// 监听登录状态变化
+watch(
+  isLoggedIn,
+  (newValue) => {
+    if (newValue) {
+      loadPriceConfig();
+    }
+  },
+  { immediate: true }
+);
+
 onMounted(async () => {
   checkPaymentSuccess();
-  // 加载价格配置
-  if (!priceStore.isPriceConfigLoaded) {
-    await priceStore.fetchPriceConfig();
-  }
+  // 初始加载价格配置
+  await loadPriceConfig();
 });
 </script>