Register.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <template>
  2. <div>
  3. <h3 text-center m-0 mb-20px>{{ t("login.reg") }}</h3>
  4. <el-form ref="formRef" :model="model" :rules="rules" size="large" label-suffix=":">
  5. <!-- 邀请码 -->
  6. <!-- <el-form-item prop="invite_code">
  7. <el-input v-model.trim="model.invite_code" placeholder="请输入邀请码" clearable>
  8. <template #prefix>
  9. <el-icon><SetUp /></el-icon>
  10. </template>
  11. </el-input>
  12. </el-form-item> -->
  13. <el-form-item prop="mobile">
  14. <el-input v-model.trim="model.mobile" placeholder="请输入手机号" clearable>
  15. <template #prefix>
  16. <el-icon><Iphone /></el-icon>
  17. </template>
  18. </el-input>
  19. </el-form-item>
  20. <el-form-item prop="sms_code">
  21. <div class="flex gap-10px">
  22. <el-input v-model.trim="model.sms_code" placeholder="请输入验证码" clearable class="flex-1"
  23. @keyup.enter="">
  24. <template #prefix>
  25. <el-icon>
  26. <Message />
  27. </el-icon>
  28. </template>
  29. </el-input>
  30. <el-button :disabled="smsCountdown > 0 || !model.mobile" :loading="smsCodeLoading" class="w-32"
  31. @click="getSmsCode">
  32. {{ smsCountdown > 0 ? `${smsCountdown}s` : '获取验证码' }}
  33. </el-button>
  34. </div>
  35. </el-form-item>
  36. <!-- 账号 -->
  37. <!-- <el-form-item prop="username">
  38. <el-input v-model.trim="model.username" :placeholder="t('login.username')" clearable>
  39. <template #prefix>
  40. <el-icon><User /></el-icon>
  41. </template>
  42. </el-input>
  43. </el-form-item> -->
  44. <!-- 密码 -->
  45. <el-tooltip :visible="isCapsLock" :content="t('login.capsLock')" placement="right">
  46. <el-form-item prop="password">
  47. <el-input v-model.trim="model.password" :placeholder="t('login.password')" type="password" show-password
  48. clearable @keyup="checkCapsLock" @keyup.enter="submit">
  49. <template #prefix>
  50. <el-icon>
  51. <Lock />
  52. </el-icon>
  53. </template>
  54. </el-input>
  55. </el-form-item>
  56. </el-tooltip>
  57. <!-- 确认密码 -->
  58. <el-tooltip :visible="isCapsLock" :content="t('login.capsLock')" placement="right">
  59. <el-form-item prop="confirmPassword">
  60. <el-input v-model.trim="model.confirmPassword" :placeholder="t('login.message.password.confirm')"
  61. type="password" show-password clearable @keyup="checkCapsLock" @keyup.enter="submit">
  62. <template #prefix>
  63. <el-icon>
  64. <Lock />
  65. </el-icon>
  66. </template>
  67. </el-input>
  68. </el-form-item>
  69. </el-tooltip>
  70. <el-form-item>
  71. <div class="flex-y-center w-full gap-10px">
  72. <el-checkbox v-model="isRead">{{ t("login.agree") }}</el-checkbox>
  73. <el-link type="primary" underline="never" :href="configStore.configData.sys_web_clause.config_value"
  74. target="_blank">
  75. {{ t("login.userAgreement") }}
  76. </el-link>
  77. </div>
  78. </el-form-item>
  79. <!-- 注册按钮 -->
  80. <el-form-item>
  81. <el-button :loading="loading" type="primary" class="w-full" @click="submit">
  82. {{ t("login.register") }}
  83. </el-button>
  84. </el-form-item>
  85. </el-form>
  86. <div flex-center gap-10px>
  87. <el-text size="default">{{ t("login.haveAccount") }}</el-text>
  88. <el-link type="primary" underline="never" @click="toLogin">{{ t("login.login") }}</el-link>
  89. </div>
  90. </div>
  91. </template>
  92. <script setup lang="ts">
  93. import type { FormInstance } from "element-plus";
  94. import { Lock } from "@element-plus/icons-vue";
  95. import UserAPI, { type RegisterForm } from "@/api/module_system/user";
  96. import { useConfigStore } from "@/store";
  97. import { useI18n } from "vue-i18n";
  98. import AuthAPI, {
  99. } from "@/api/module_system/auth";
  100. import { Auth } from "@/utils/auth";
  101. const { t } = useI18n();
  102. // 使用 defineModel 简化具名 v-model 绑定
  103. const modelValue = defineModel<string>();
  104. const presetUsername = defineModel<string>("presetUsername");
  105. const presetPassword = defineModel<string>("presetPassword");
  106. const toLogin = () => {
  107. modelValue.value = "login";
  108. };
  109. const configStore = useConfigStore();
  110. const formRef = ref<FormInstance>();
  111. const loading = ref(false); // 按钮 loading 状态
  112. const isCapsLock = ref(false); // 是否大写锁定
  113. const isRead = ref(false);
  114. const smsLoading = ref(false);
  115. const smsCodeLoading = ref(false);
  116. const smsCountdown = ref(0);
  117. const model = ref<RegisterForm>({
  118. mobile: "",
  119. sms_code: "",
  120. username: "",
  121. password: "",
  122. confirmPassword: "",
  123. });
  124. const rules = computed(() => {
  125. return {
  126. // invite_code: [
  127. // {
  128. // required: true,
  129. // trigger: "blur",
  130. // message: "请输入邀请码",
  131. // },
  132. // ],
  133. // 验证国内手机号格式
  134. mobile: [
  135. {
  136. required: true,
  137. trigger: "blur",
  138. message: "请输入手机号",
  139. },
  140. {
  141. pattern: /^1[3456789]\d{9}$/,
  142. trigger: "blur",
  143. message: "请输入正确的手机号格式",
  144. },
  145. ],
  146. sms_code: [
  147. {
  148. required: true,
  149. trigger: "blur",
  150. message: "请输入验证码",
  151. },
  152. ],
  153. username: [
  154. {
  155. required: true,
  156. trigger: "blur",
  157. message: t("login.message.username.required"),
  158. },
  159. ],
  160. password: [
  161. {
  162. required: true,
  163. trigger: "blur",
  164. message: t("login.message.password.required"),
  165. },
  166. {
  167. min: 6,
  168. message: t("login.message.password.min"),
  169. trigger: "blur",
  170. },
  171. ],
  172. confirmPassword: [
  173. {
  174. required: true,
  175. trigger: "blur",
  176. message: t("login.message.password.required"),
  177. },
  178. {
  179. min: 6,
  180. message: t("login.message.password.min"),
  181. trigger: "blur",
  182. },
  183. {
  184. validator: (_: any, value: string) => {
  185. return value === model.value.password;
  186. },
  187. trigger: "blur",
  188. message: t("login.message.password.inconformity"),
  189. },
  190. ],
  191. };
  192. });
  193. // 检查输入大小写
  194. function checkCapsLock(event: KeyboardEvent) {
  195. // 防止浏览器密码自动填充时报错
  196. if (event instanceof KeyboardEvent) {
  197. isCapsLock.value = event.getModifierState("CapsLock");
  198. }
  199. }
  200. const submit = async () => {
  201. try {
  202. // 0. 检查是否已勾选协议
  203. if (!isRead.value) {
  204. ElMessage.warning(t("login.message.agree.required"));
  205. return;
  206. }
  207. // 1. 表单验证
  208. const valid = await formRef.value?.validate();
  209. if (!valid) return;
  210. loading.value = true;
  211. model.value.username = model.value.mobile;
  212. await UserAPI.registerUser(model.value);
  213. // 注册成功后,双向绑定回写父容器的用户名和密码,并切回登录
  214. presetUsername.value = model.value.username;
  215. presetPassword.value = model.value.password;
  216. toLogin();
  217. } catch (error) {
  218. console.error(error);
  219. } finally {
  220. loading.value = false;
  221. }
  222. };
  223. // 获取短信验证码
  224. async function getSmsCode() {
  225. try {
  226. const valid = await formRef.value?.validateField("mobile");
  227. if (!valid) return;
  228. smsCodeLoading.value = true;
  229. // 调用后端API获取短信验证码
  230. await AuthAPI.getSmsCode({ mobile: model.value.mobile, template_name: "verify" });
  231. // 开始倒计时
  232. smsCountdown.value = 60;
  233. const timer = setInterval(() => {
  234. smsCountdown.value--;
  235. if (smsCountdown.value <= 0) {
  236. clearInterval(timer);
  237. }
  238. }, 1000);
  239. // ElMessage.success("验证码已发送");
  240. } catch (error: any) {
  241. // ElMessage.error(error?.response?.data?.msg || "获取验证码失败");
  242. } finally {
  243. smsCodeLoading.value = false;
  244. }
  245. }
  246. onMounted(() => {});
  247. </script>