Register.vue 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <template>
  2. <div class="register-container">
  3. <el-card class="register-card">
  4. <template #header>
  5. <div class="card-header">
  6. <img src="/logo.png" alt="Logo" class="logo-img" />
  7. <h2>用户注册</h2>
  8. <p>注册后即可登录统一认证平台</p>
  9. </div>
  10. </template>
  11. <!-- Steps -->
  12. <el-steps :active="activeStep" finish-status="success" simple style="margin-bottom: 20px">
  13. <el-step title="验证手机" />
  14. <el-step title="设置密码" />
  15. </el-steps>
  16. <!-- Step 1: Verify Mobile -->
  17. <el-form v-if="activeStep === 0" :model="form" :rules="step1Rules" ref="step1FormRef" label-width="0">
  18. <el-form-item prop="mobile">
  19. <el-input v-model="form.mobile" placeholder="手机号" prefix-icon="Iphone" />
  20. </el-form-item>
  21. <el-form-item prop="captcha_code" class="captcha-item">
  22. <el-input v-model="form.captcha_code" placeholder="图形验证码" style="width: 60%" prefix-icon="Picture" />
  23. <div class="captcha-img" @click="fetchCaptcha" v-if="captchaImage">
  24. <img :src="captchaImage" alt="captcha" />
  25. </div>
  26. </el-form-item>
  27. <el-form-item>
  28. <el-button type="primary" style="width: 100%" @click="handleSendSms" :loading="loading">
  29. 发送验证码
  30. </el-button>
  31. </el-form-item>
  32. <div class="login-link">
  33. <router-link to="/login">已有账号?去登录</router-link>
  34. </div>
  35. </el-form>
  36. <!-- Step 2: Set Password -->
  37. <el-form v-if="activeStep === 1" :model="form" :rules="step2Rules" ref="step2FormRef" label-width="0">
  38. <el-form-item>
  39. <el-input v-model="form.mobile" disabled prefix-icon="Iphone" />
  40. </el-form-item>
  41. <el-form-item prop="sms_code">
  42. <el-input v-model="form.sms_code" placeholder="短信验证码" prefix-icon="Message" />
  43. </el-form-item>
  44. <el-form-item prop="name">
  45. <el-input v-model="form.name" placeholder="真实姓名" prefix-icon="User" />
  46. </el-form-item>
  47. <el-form-item prop="password">
  48. <el-input
  49. v-model="form.password"
  50. type="password"
  51. placeholder="设置密码"
  52. show-password
  53. prefix-icon="Lock"
  54. />
  55. </el-form-item>
  56. <el-form-item prop="confirmPassword">
  57. <el-input
  58. v-model="form.confirmPassword"
  59. type="password"
  60. placeholder="确认密码"
  61. show-password
  62. prefix-icon="Lock"
  63. />
  64. </el-form-item>
  65. <el-form-item>
  66. <el-button type="primary" @click="handleSubmit" :loading="loading" style="width: 100%">
  67. 注册
  68. </el-button>
  69. </el-form-item>
  70. <div class="links">
  71. <el-button link @click="activeStep = 0">上一步</el-button>
  72. </div>
  73. </el-form>
  74. </el-card>
  75. </div>
  76. </template>
  77. <script setup lang="ts">
  78. import { ref, reactive, onMounted } from 'vue'
  79. import { useRouter } from 'vue-router'
  80. import { ElMessage } from 'element-plus'
  81. import type { FormInstance, FormRules } from 'element-plus'
  82. import { registerDeveloper, getCaptcha, sendSms } from '../api/public'
  83. import { Iphone, Lock, Picture, Message, User } from '@element-plus/icons-vue'
  84. const router = useRouter()
  85. const activeStep = ref(0)
  86. const loading = ref(false)
  87. const captchaImage = ref('')
  88. const step1FormRef = ref<FormInstance>()
  89. const step2FormRef = ref<FormInstance>()
  90. const form = reactive({
  91. mobile: '',
  92. captcha_id: '',
  93. captcha_code: '',
  94. sms_code: '',
  95. name: '',
  96. password: '',
  97. confirmPassword: ''
  98. })
  99. const fetchCaptcha = async () => {
  100. try {
  101. const res = await getCaptcha()
  102. captchaImage.value = res.data.image
  103. form.captcha_id = res.data.captcha_id
  104. form.captcha_code = ''
  105. } catch (e) {
  106. console.error(e)
  107. }
  108. }
  109. const validatePass2 = (rule: any, value: any, callback: any) => {
  110. if (value === '') {
  111. callback(new Error('请再次输入密码'))
  112. } else if (value !== form.password) {
  113. callback(new Error('两次输入密码不一致!'))
  114. } else {
  115. callback()
  116. }
  117. }
  118. const step1Rules = reactive<FormRules>({
  119. mobile: [
  120. { required: true, message: '请输入手机号', trigger: 'blur' },
  121. { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' }
  122. ],
  123. captcha_code: [
  124. { required: true, message: '请输入图形验证码', trigger: 'blur' }
  125. ]
  126. })
  127. const step2Rules = reactive<FormRules>({
  128. sms_code: [
  129. { required: true, message: '请输入短信验证码', trigger: 'blur' }
  130. ],
  131. name: [
  132. { required: true, message: '请输入真实姓名', trigger: 'blur' }
  133. ],
  134. password: [
  135. { required: true, message: '请输入密码', trigger: 'blur' },
  136. { min: 6, message: '密码长度不能少于6位', trigger: 'blur' },
  137. { pattern: /^(?=.*[a-zA-Z])(?=.*\d).+$/, message: '密码必须包含字母和数字', trigger: 'blur' }
  138. ],
  139. confirmPassword: [
  140. { validator: validatePass2, trigger: 'blur' }
  141. ]
  142. })
  143. const handleSendSms = async () => {
  144. if (!step1FormRef.value) return
  145. await step1FormRef.value.validate(async (valid) => {
  146. if (valid) {
  147. loading.value = true
  148. try {
  149. await sendSms({
  150. mobile: form.mobile,
  151. captcha_id: form.captcha_id,
  152. captcha_code: form.captcha_code
  153. })
  154. ElMessage.success('验证码已发送 (模拟环境: 请检查后端日志)')
  155. activeStep.value = 1
  156. } catch (e) {
  157. fetchCaptcha()
  158. } finally {
  159. loading.value = false
  160. }
  161. }
  162. })
  163. }
  164. const handleSubmit = async () => {
  165. if (!step2FormRef.value) return
  166. await step2FormRef.value.validate(async (valid) => {
  167. if (valid) {
  168. loading.value = true
  169. try {
  170. await registerDeveloper({
  171. mobile: form.mobile,
  172. password: form.password,
  173. sms_code: form.sms_code,
  174. name: form.name
  175. })
  176. ElMessage.success('注册成功,请登录')
  177. router.push('/login')
  178. } catch (error) {
  179. // error
  180. } finally {
  181. loading.value = false
  182. }
  183. }
  184. })
  185. }
  186. onMounted(() => {
  187. fetchCaptcha()
  188. })
  189. </script>
  190. <style scoped>
  191. .register-container {
  192. display: flex;
  193. justify-content: center;
  194. align-items: center;
  195. height: 100vh;
  196. background-color: #f0f2f5;
  197. }
  198. .register-card {
  199. width: 450px;
  200. }
  201. .card-header {
  202. text-align: center;
  203. display: flex;
  204. flex-direction: column;
  205. align-items: center;
  206. gap: 10px;
  207. }
  208. .logo-img {
  209. height: 40px;
  210. object-fit: contain;
  211. }
  212. .card-header h2 {
  213. margin: 0;
  214. font-size: 20px;
  215. }
  216. .card-header p {
  217. margin: 0;
  218. color: #909399;
  219. font-size: 14px;
  220. }
  221. .login-link, .links {
  222. text-align: center;
  223. margin-top: 10px;
  224. }
  225. .captcha-item {
  226. display: flex;
  227. align-items: center;
  228. gap: 10px;
  229. }
  230. .captcha-img {
  231. cursor: pointer;
  232. height: 32px;
  233. display: flex;
  234. align-items: center;
  235. border-radius: 4px;
  236. overflow: hidden;
  237. border: 1px solid #dcdfe6;
  238. }
  239. .captcha-img img {
  240. height: 100%;
  241. }
  242. </style>