LoginDialog.vue 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. <template>
  2. <div
  3. v-if="modelValue"
  4. class="fixed inset-0 z-50 flex items-center justify-center p-4"
  5. >
  6. <!-- 背景遮罩 -->
  7. <div
  8. class="absolute inset-0 bg-black/70 backdrop-blur-sm"
  9. @click="closeDialog"
  10. ></div>
  11. <!-- 登录卡片 -->
  12. <div
  13. class="relative w-full max-w-md rounded-2xl bg-surface border border-white/10 p-6 shadow-xl"
  14. >
  15. <button
  16. @click="closeDialog"
  17. class="absolute right-4 top-4 h-8 w-8 rounded-full grid place-items-center text-white/60 hover:bg-white/10 hover:text-white transition"
  18. >
  19. <svg
  20. viewBox="0 0 24 24"
  21. width="20"
  22. height="20"
  23. fill="none"
  24. stroke="currentColor"
  25. stroke-width="2"
  26. >
  27. <path d="M18 6 6 18M6 6l12 12" />
  28. </svg>
  29. </button>
  30. <div class="text-center mb-6">
  31. <h2 class="text-xl font-bold text-white/90">
  32. {{ isRegister ? "注册账户" : "登录账户" }}
  33. </h2>
  34. <p class="text-white/60 mt-1">
  35. {{ isRegister ? "注册后即可使用全部功能" : "登录后即可使用全部功能" }}
  36. </p>
  37. </div>
  38. <div class="space-y-4">
  39. <!-- 登录时显示用户名字段 -->
  40. <div v-if="!isRegister">
  41. <label for="username" class="block text-sm text-white/70 mb-1.5"
  42. >用户名</label
  43. >
  44. <input
  45. type="text"
  46. id="username"
  47. v-model="username"
  48. 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"
  49. placeholder="请输入用户名"
  50. />
  51. </div>
  52. <!-- 注册时显示邮箱字段 -->
  53. <div v-if="isRegister">
  54. <label for="email" class="block text-sm text-white/70 mb-1.5"
  55. >邮箱</label
  56. >
  57. <input
  58. type="email"
  59. id="email"
  60. v-model="email"
  61. 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"
  62. placeholder="请输入邮箱"
  63. />
  64. </div>
  65. <div>
  66. <label for="password" class="block text-sm text-white/70 mb-1.5"
  67. >密码</label
  68. >
  69. <input
  70. type="password"
  71. id="password"
  72. v-model="password"
  73. 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"
  74. placeholder="请输入密码"
  75. />
  76. </div>
  77. <div v-if="error" class="text-red-400 text-sm py-1">
  78. {{ error }}
  79. </div>
  80. <button
  81. @click="handleButtonClick"
  82. :disabled="isLoading"
  83. class="w-full py-2.5 rounded-lg bg-brand text-slate-900 font-medium hover:bg-brand/90 transition disabled:opacity-70"
  84. >
  85. {{
  86. isLoading
  87. ? isRegister
  88. ? "注册中..."
  89. : "登录中..."
  90. : isRegister
  91. ? "注册"
  92. : "登录"
  93. }}
  94. </button>
  95. <div class="text-center text-sm text-white/60">
  96. <p v-if="!isRegister">
  97. 还没有账户?
  98. <a
  99. href="#"
  100. @click.prevent="toggleMode"
  101. class="text-brand hover:underline"
  102. >注册新账户</a
  103. >
  104. </p>
  105. <p v-else>
  106. 已有账户?
  107. <a
  108. href="#"
  109. @click.prevent="toggleMode"
  110. class="text-brand hover:underline"
  111. >立即登录</a
  112. >
  113. </p>
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </template>
  119. <script setup lang="ts">
  120. import { ref, watch } from "vue";
  121. import { useUserStore } from "@/store/user";
  122. const props = defineProps<{
  123. modelValue: boolean;
  124. }>();
  125. const emit = defineEmits<{
  126. (e: "update:modelValue", value: boolean): void;
  127. (e: "login-success"): void;
  128. }>();
  129. const username = ref("");
  130. const password = ref("");
  131. const email = ref("");
  132. const error = ref("");
  133. const isLoading = ref(false);
  134. const isRegister = ref(false);
  135. const userStore = useUserStore();
  136. const closeDialog = () => {
  137. emit("update:modelValue", false);
  138. };
  139. const toggleMode = () => {
  140. isRegister.value = !isRegister.value;
  141. error.value = "";
  142. };
  143. const handleButtonClick = () => {
  144. if (isRegister.value) {
  145. handleRegister();
  146. } else {
  147. handleLogin();
  148. }
  149. };
  150. const handleLogin = async () => {
  151. // 简单的表单验证
  152. if (!username.value || !password.value) {
  153. error.value = "用户名和密码不能为空";
  154. return;
  155. }
  156. try {
  157. error.value = "";
  158. isLoading.value = true;
  159. await userStore.login(username.value, password.value);
  160. emit("login-success");
  161. closeDialog();
  162. } catch (err: any) {
  163. error.value = err.message || "登录失败,请检查用户名和密码";
  164. } finally {
  165. isLoading.value = false;
  166. }
  167. };
  168. const handleRegister = async () => {
  169. // 注册表单验证
  170. if (!email.value || !password.value) {
  171. error.value = "邮箱和密码不能为空";
  172. return;
  173. }
  174. // 使用邮箱作为用户名
  175. const finalUsername = email.value;
  176. try {
  177. error.value = "";
  178. isLoading.value = true;
  179. await userStore.register(
  180. finalUsername,
  181. password.value,
  182. email.value
  183. );
  184. emit("login-success");
  185. closeDialog();
  186. } catch (err: any) {
  187. error.value = err.message || "注册失败,请检查输入信息";
  188. } finally {
  189. isLoading.value = false;
  190. }
  191. };
  192. // 当弹窗关闭时重置表单
  193. watch(
  194. () => props.modelValue,
  195. (newVal) => {
  196. if (!newVal) {
  197. username.value = "";
  198. password.value = "";
  199. email.value = "";
  200. error.value = "";
  201. isLoading.value = false;
  202. isRegister.value = false;
  203. }
  204. }
  205. );
  206. </script>