Register.vue 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <template>
  2. <div class="register-page">
  3. <div class="register-container">
  4. <!-- Logo 区域 -->
  5. <div class="register-header">
  6. <div class="logo">🚀 灵越智报</div>
  7. <p class="subtitle">智能报告生成平台</p>
  8. </div>
  9. <!-- 注册表单 -->
  10. <el-card class="register-card">
  11. <template #header>
  12. <div class="card-header">
  13. <span>用户注册</span>
  14. <router-link to="/login" class="switch-link">已有账号?立即登录</router-link>
  15. </div>
  16. </template>
  17. <el-form
  18. ref="formRef"
  19. :model="form"
  20. :rules="rules"
  21. label-width="0"
  22. size="large"
  23. @submit.prevent="handleRegister"
  24. >
  25. <el-form-item prop="username">
  26. <el-input
  27. v-model="form.username"
  28. placeholder="用户名(3-20个字符,字母、数字、下划线)"
  29. :prefix-icon="User"
  30. clearable
  31. />
  32. </el-form-item>
  33. <el-form-item prop="email">
  34. <el-input
  35. v-model="form.email"
  36. placeholder="邮箱地址"
  37. :prefix-icon="Message"
  38. clearable
  39. />
  40. </el-form-item>
  41. <el-form-item prop="password">
  42. <el-input
  43. v-model="form.password"
  44. type="password"
  45. placeholder="密码(8-32位,包含字母和数字)"
  46. :prefix-icon="Lock"
  47. show-password
  48. />
  49. </el-form-item>
  50. <el-form-item prop="confirmPassword">
  51. <el-input
  52. v-model="form.confirmPassword"
  53. type="password"
  54. placeholder="确认密码"
  55. :prefix-icon="Lock"
  56. show-password
  57. @keyup.enter="handleRegister"
  58. />
  59. </el-form-item>
  60. <el-form-item prop="agreement">
  61. <el-checkbox v-model="form.agreement">
  62. 我已阅读并同意
  63. <a href="#" class="link">《用户协议》</a>
  64. <a href="#" class="link">《隐私政策》</a>
  65. </el-checkbox>
  66. </el-form-item>
  67. <el-form-item>
  68. <el-button
  69. type="primary"
  70. class="register-btn"
  71. :loading="loading"
  72. @click="handleRegister"
  73. >
  74. {{ loading ? '注册中...' : '立即注册' }}
  75. </el-button>
  76. </el-form-item>
  77. </el-form>
  78. </el-card>
  79. <!-- 底部信息 -->
  80. <div class="register-footer">
  81. <p>© 2026 灵越智报 - 企业级智能报告生成平台</p>
  82. </div>
  83. </div>
  84. </div>
  85. </template>
  86. <script setup>
  87. import { ref, reactive } from 'vue'
  88. import { useRouter } from 'vue-router'
  89. import { User, Lock, Message } from '@element-plus/icons-vue'
  90. import { ElMessage } from 'element-plus'
  91. import { authApi } from '@/api'
  92. const router = useRouter()
  93. const formRef = ref(null)
  94. const loading = ref(false)
  95. const form = reactive({
  96. username: '',
  97. email: '',
  98. password: '',
  99. confirmPassword: '',
  100. agreement: false
  101. })
  102. // 确认密码验证
  103. const validateConfirmPassword = (rule, value, callback) => {
  104. if (value !== form.password) {
  105. callback(new Error('两次输入的密码不一致'))
  106. } else {
  107. callback()
  108. }
  109. }
  110. // 用户协议验证
  111. const validateAgreement = (rule, value, callback) => {
  112. if (!value) {
  113. callback(new Error('请阅读并同意用户协议'))
  114. } else {
  115. callback()
  116. }
  117. }
  118. const rules = {
  119. username: [
  120. { required: true, message: '请输入用户名', trigger: 'blur' },
  121. { min: 3, max: 20, message: '用户名长度必须在3-20个字符之间', trigger: 'blur' },
  122. { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线', trigger: 'blur' }
  123. ],
  124. email: [
  125. { required: true, message: '请输入邮箱地址', trigger: 'blur' },
  126. { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
  127. ],
  128. password: [
  129. { required: true, message: '请输入密码', trigger: 'blur' },
  130. { min: 8, max: 32, message: '密码长度必须在8-32个字符之间', trigger: 'blur' },
  131. { pattern: /^(?=.*[a-zA-Z])(?=.*\d).+$/, message: '密码必须包含字母和数字', trigger: 'blur' }
  132. ],
  133. confirmPassword: [
  134. { required: true, message: '请再次输入密码', trigger: 'blur' },
  135. { validator: validateConfirmPassword, trigger: 'blur' }
  136. ],
  137. agreement: [
  138. { validator: validateAgreement, trigger: 'change' }
  139. ]
  140. }
  141. async function handleRegister() {
  142. if (!formRef.value) return
  143. await formRef.value.validate(async (valid) => {
  144. if (!valid) return
  145. loading.value = true
  146. try {
  147. const response = await authApi.register({
  148. username: form.username,
  149. email: form.email,
  150. password: form.password,
  151. confirmPassword: form.confirmPassword
  152. })
  153. // 注册成功后自动登录
  154. localStorage.setItem('accessToken', response.accessToken)
  155. localStorage.setItem('refreshToken', response.refreshToken)
  156. localStorage.setItem('userId', response.userId)
  157. localStorage.setItem('username', response.username)
  158. ElMessage.success('注册成功')
  159. router.push('/')
  160. } catch (error) {
  161. console.error('注册失败:', error)
  162. ElMessage.error(error.message || '注册失败,请稍后重试')
  163. } finally {
  164. loading.value = false
  165. }
  166. })
  167. }
  168. </script>
  169. <style lang="scss" scoped>
  170. .register-page {
  171. min-height: 100vh;
  172. display: flex;
  173. align-items: center;
  174. justify-content: center;
  175. background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  176. padding: 20px;
  177. }
  178. .register-container {
  179. width: 100%;
  180. max-width: 420px;
  181. }
  182. .register-header {
  183. text-align: center;
  184. margin-bottom: 30px;
  185. color: #fff;
  186. .logo {
  187. font-size: 32px;
  188. font-weight: 700;
  189. margin-bottom: 8px;
  190. }
  191. .subtitle {
  192. font-size: 14px;
  193. opacity: 0.9;
  194. }
  195. }
  196. .register-card {
  197. border-radius: 12px;
  198. box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  199. .card-header {
  200. display: flex;
  201. justify-content: space-between;
  202. align-items: center;
  203. span {
  204. font-size: 18px;
  205. font-weight: 600;
  206. }
  207. .switch-link {
  208. font-size: 13px;
  209. color: var(--el-color-primary);
  210. text-decoration: none;
  211. &:hover {
  212. text-decoration: underline;
  213. }
  214. }
  215. }
  216. }
  217. .link {
  218. color: var(--el-color-primary);
  219. text-decoration: none;
  220. &:hover {
  221. text-decoration: underline;
  222. }
  223. }
  224. .register-btn {
  225. width: 100%;
  226. height: 44px;
  227. font-size: 16px;
  228. }
  229. .register-footer {
  230. text-align: center;
  231. margin-top: 24px;
  232. color: rgba(255, 255, 255, 0.7);
  233. font-size: 12px;
  234. }
  235. </style>