LoginView.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  1. <template>
  2. <div class="auth-view">
  3. <div class="auth-container">
  4. <div class="auth-decoration">
  5. <div class="decoration-circle circle-1"></div>
  6. <div class="decoration-circle circle-2"></div>
  7. <div class="decoration-circle circle-3"></div>
  8. </div>
  9. <el-card class="auth-card" shadow="never">
  10. <el-button class="back-btn" text @click="router.push('/')">
  11. <el-icon><ArrowLeft /></el-icon>
  12. 返回主页
  13. </el-button>
  14. <div class="auth-header">
  15. <h1 class="auth-title">欢迎</h1>
  16. <p class="auth-subtitle">请登录或注册您的账号</p>
  17. </div>
  18. <el-tabs v-model="activeTab" class="auth-tabs" stretch>
  19. <el-tab-pane label="登录" name="login">
  20. <el-form
  21. ref="loginFormRef"
  22. :model="loginForm"
  23. :rules="loginRules"
  24. class="auth-form"
  25. @submit.prevent="handleLogin"
  26. >
  27. <el-form-item prop="account">
  28. <el-input
  29. v-model="loginForm.account"
  30. placeholder="请输入账号"
  31. :prefix-icon="User"
  32. size="large"
  33. />
  34. </el-form-item>
  35. <el-form-item prop="password">
  36. <el-input
  37. v-model="loginForm.password"
  38. type="password"
  39. placeholder="请输入密码"
  40. :prefix-icon="Lock"
  41. show-password
  42. size="large"
  43. />
  44. </el-form-item>
  45. <el-form-item>
  46. <el-button
  47. type="primary"
  48. :loading="loginLoading"
  49. size="large"
  50. class="auth-submit"
  51. @click="handleLogin"
  52. >
  53. 登录
  54. </el-button>
  55. </el-form-item>
  56. </el-form>
  57. </el-tab-pane>
  58. <el-tab-pane label="注册" name="register">
  59. <el-form
  60. ref="registerFormRef"
  61. :model="registerForm"
  62. :rules="registerRules"
  63. class="auth-form"
  64. @submit.prevent="handleRegister"
  65. >
  66. <el-form-item prop="account">
  67. <el-input
  68. v-model="registerForm.account"
  69. placeholder="请输入账号"
  70. :prefix-icon="User"
  71. size="large"
  72. />
  73. </el-form-item>
  74. <el-form-item prop="username">
  75. <el-input
  76. v-model="registerForm.username"
  77. placeholder="请输入用户名"
  78. :prefix-icon="User"
  79. size="large"
  80. />
  81. </el-form-item>
  82. <el-form-item prop="password">
  83. <el-input
  84. v-model="registerForm.password"
  85. type="password"
  86. placeholder="请输入密码"
  87. :prefix-icon="Lock"
  88. show-password
  89. size="large"
  90. />
  91. </el-form-item>
  92. <el-form-item prop="confirmPassword">
  93. <el-input
  94. v-model="registerForm.confirmPassword"
  95. type="password"
  96. placeholder="请再次输入密码"
  97. :prefix-icon="Lock"
  98. show-password
  99. size="large"
  100. />
  101. </el-form-item>
  102. <el-form-item>
  103. <el-button
  104. type="primary"
  105. :loading="registerLoading"
  106. size="large"
  107. class="auth-submit"
  108. @click="handleRegister"
  109. >
  110. 注册
  111. </el-button>
  112. </el-form-item>
  113. </el-form>
  114. </el-tab-pane>
  115. </el-tabs>
  116. </el-card>
  117. </div>
  118. </div>
  119. </template>
  120. <script setup lang="ts">
  121. import { reactive, ref } from 'vue'
  122. import { useRoute, useRouter } from 'vue-router'
  123. import { ElMessage } from 'element-plus'
  124. import { User, Lock, ArrowLeft } from '@element-plus/icons-vue'
  125. import type { FormInstance, FormRules } from 'element-plus'
  126. import { getAuthController } from '../api/auth-controller'
  127. import type { LoginDto, RegisterDto } from '../api/models'
  128. import { useLoginUserStore } from '../store'
  129. import type { User as UserType } from '../type'
  130. const route = useRoute()
  131. const router = useRouter()
  132. const loginUserStore = useLoginUserStore()
  133. const activeTab = ref('login')
  134. const loginFormRef = ref<FormInstance>()
  135. const registerFormRef = ref<FormInstance>()
  136. const loginLoading = ref(false)
  137. const registerLoading = ref(false)
  138. const loginForm = reactive({
  139. account: '',
  140. password: ''
  141. })
  142. const registerForm = reactive({
  143. account: '',
  144. username: '',
  145. password: '',
  146. confirmPassword: ''
  147. })
  148. const validateConfirmPassword = (_rule: any, value: string, callback: any) => {
  149. if (value !== registerForm.password) {
  150. callback(new Error('两次输入密码不一致'))
  151. } else {
  152. callback()
  153. }
  154. }
  155. const loginRules = reactive<FormRules>({
  156. account: [{ required: true, message: '请输入账号', trigger: 'blur' }],
  157. password: [
  158. { required: true, message: '请输入密码', trigger: 'blur' },
  159. { min: 4, message: '密码长度至少4位', trigger: 'blur' }
  160. ]
  161. })
  162. const registerRules = reactive<FormRules>({
  163. account: [
  164. { required: true, message: '请输入账号', trigger: 'blur' },
  165. { min: 4, max: 20, message: '账号长度4-20个字符', trigger: 'blur' }
  166. ],
  167. username: [
  168. { required: true, message: '请输入用户名', trigger: 'blur' },
  169. { min: 2, max: 32, message: '用户名长度2-32个字符', trigger: 'blur' }
  170. ],
  171. password: [
  172. { required: true, message: '请输入密码', trigger: 'blur' },
  173. { min: 4, max: 20, message: '密码长度4-20个字符', trigger: 'blur' }
  174. ],
  175. confirmPassword: [
  176. { required: true, message: '请再次输入密码', trigger: 'blur' },
  177. { validator: validateConfirmPassword, trigger: 'blur' }
  178. ]
  179. })
  180. const handleLogin = async () => {
  181. const valid = await loginFormRef.value?.validate().catch(() => false)
  182. if (!valid) return
  183. loginLoading.value = true
  184. try {
  185. const authApi = getAuthController()
  186. const res = await authApi.login({
  187. account: loginForm.account,
  188. password: loginForm.password
  189. } as LoginDto)
  190. const tokenData = res.data
  191. if (res.code === 200 && tokenData?.token) {
  192. localStorage.setItem('token', tokenData.token)
  193. loginUserStore.loginSuccess({
  194. id: String(tokenData.userId || ''),
  195. name: tokenData.username || tokenData.account || '用户',
  196. role: (tokenData.role as UserType['role']) || 'user'
  197. })
  198. ElMessage.success('登录成功')
  199. router.push((route.query.redirect as string) || '/')
  200. } else {
  201. ElMessage.error(res.message || '登录失败')
  202. }
  203. } catch (err: any) {
  204. ElMessage.error(err.message || '登录失败,请检查网络')
  205. } finally {
  206. loginLoading.value = false
  207. }
  208. }
  209. const handleRegister = async () => {
  210. const valid = await registerFormRef.value?.validate().catch(() => false)
  211. if (!valid) return
  212. registerLoading.value = true
  213. try {
  214. const authApi = getAuthController()
  215. const res = await authApi.register({
  216. account: registerForm.account,
  217. password: registerForm.password,
  218. username: registerForm.username
  219. } as RegisterDto)
  220. if (res.code === 200) {
  221. ElMessage.success('注册成功,请登录')
  222. activeTab.value = 'login'
  223. registerForm.account = ''
  224. registerForm.username = ''
  225. registerForm.password = ''
  226. registerForm.confirmPassword = ''
  227. } else {
  228. ElMessage.error(res.message || '注册失败')
  229. }
  230. } catch (err: any) {
  231. ElMessage.error(err.message || '注册失败,请检查网络')
  232. } finally {
  233. registerLoading.value = false
  234. }
  235. }
  236. </script>
  237. <style scoped>
  238. .auth-view {
  239. display: flex;
  240. justify-content: center;
  241. align-items: center;
  242. min-height: 100vh;
  243. background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 50%, #fbcfe8 100%);
  244. position: relative;
  245. overflow: hidden;
  246. }
  247. .auth-decoration {
  248. position: absolute;
  249. inset: 0;
  250. pointer-events: none;
  251. }
  252. .decoration-circle {
  253. position: absolute;
  254. border-radius: 50%;
  255. opacity: 0.15;
  256. }
  257. .circle-1 {
  258. width: 400px;
  259. height: 400px;
  260. background: #db2777;
  261. top: -100px;
  262. right: -100px;
  263. animation: float 20s ease-in-out infinite;
  264. }
  265. .circle-2 {
  266. width: 300px;
  267. height: 300px;
  268. background: #f472b6;
  269. bottom: -80px;
  270. left: -80px;
  271. animation: float 15s ease-in-out infinite reverse;
  272. }
  273. .circle-3 {
  274. width: 200px;
  275. height: 200px;
  276. background: #ca8a04;
  277. top: 50%;
  278. left: 60%;
  279. animation: float 18s ease-in-out infinite;
  280. }
  281. @keyframes float {
  282. 0%,
  283. 100% {
  284. transform: translate(0, 0) scale(1);
  285. }
  286. 25% {
  287. transform: translate(20px, -30px) scale(1.05);
  288. }
  289. 50% {
  290. transform: translate(-10px, 20px) scale(0.95);
  291. }
  292. 75% {
  293. transform: translate(15px, 10px) scale(1.02);
  294. }
  295. }
  296. .auth-container {
  297. position: relative;
  298. z-index: 1;
  299. width: 100%;
  300. max-width: 420px;
  301. padding: 20px;
  302. }
  303. .auth-card {
  304. border-radius: 16px;
  305. border: 1px solid rgba(219, 39, 119, 0.1);
  306. background: rgba(255, 255, 255, 0.9);
  307. backdrop-filter: blur(10px);
  308. padding: 8px;
  309. }
  310. .back-btn {
  311. position: absolute;
  312. top: 16px;
  313. left: 16px;
  314. color: #be185d;
  315. font-size: 14px;
  316. cursor: pointer;
  317. transition: color 0.2s;
  318. }
  319. .back-btn:hover {
  320. color: #db2777;
  321. }
  322. .back-btn .el-icon {
  323. margin-right: 4px;
  324. }
  325. .auth-header {
  326. text-align: center;
  327. padding: 24px 0 16px;
  328. }
  329. .auth-title {
  330. margin: 0 0 8px;
  331. font-size: 28px;
  332. font-weight: 600;
  333. color: #831843;
  334. letter-spacing: 2px;
  335. }
  336. .auth-subtitle {
  337. margin: 0;
  338. font-size: 14px;
  339. color: #be185d;
  340. }
  341. .auth-tabs {
  342. padding: 0 8px 8px;
  343. }
  344. :deep(.auth-tabs .el-tabs__header) {
  345. margin-bottom: 24px;
  346. }
  347. :deep(.auth-tabs .el-tabs__item) {
  348. font-size: 16px;
  349. font-weight: 500;
  350. color: #831843;
  351. transition: color 0.2s;
  352. }
  353. :deep(.auth-tabs .el-tabs__item.is-active) {
  354. color: #db2777;
  355. }
  356. :deep(.auth-tabs .el-tabs__active-bar) {
  357. background-color: #db2777;
  358. height: 3px;
  359. border-radius: 3px;
  360. }
  361. :deep(.auth-tabs .el-tabs__nav-wrap::after) {
  362. background-color: rgba(219, 39, 119, 0.15);
  363. height: 1px;
  364. }
  365. .auth-form {
  366. padding: 0 8px;
  367. }
  368. :deep(.el-input__wrapper) {
  369. border-radius: 10px;
  370. padding: 8px 12px;
  371. box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.15) inset;
  372. transition: box-shadow 0.2s;
  373. }
  374. :deep(.el-input__wrapper:hover) {
  375. box-shadow: 0 0 0 1px rgba(219, 39, 119, 0.3) inset;
  376. }
  377. :deep(.el-input__wrapper.is-focus) {
  378. box-shadow: 0 0 0 1px #db2777 inset;
  379. }
  380. :deep(.el-input__prefix) {
  381. color: #db2777;
  382. margin-right: 8px;
  383. }
  384. .auth-submit {
  385. width: 100%;
  386. border-radius: 10px;
  387. font-size: 16px;
  388. font-weight: 500;
  389. letter-spacing: 4px;
  390. background: linear-gradient(135deg, #db2777 0%, #be185d 100%);
  391. border: none;
  392. transition: all 0.2s;
  393. }
  394. .auth-submit:hover {
  395. background: linear-gradient(135deg, #be185d 0%, #9d174d 100%);
  396. transform: translateY(-1px);
  397. box-shadow: 0 4px 12px rgba(219, 39, 119, 0.3);
  398. }
  399. .auth-submit:active {
  400. transform: translateY(0);
  401. }
  402. :deep(.el-form-item) {
  403. margin-bottom: 20px;
  404. }
  405. :deep(.el-form-item__error) {
  406. padding-left: 4px;
  407. }
  408. @media (max-width: 480px) {
  409. .auth-container {
  410. padding: 16px;
  411. }
  412. .auth-title {
  413. font-size: 24px;
  414. }
  415. .circle-1 {
  416. width: 250px;
  417. height: 250px;
  418. }
  419. .circle-2 {
  420. width: 200px;
  421. height: 200px;
  422. }
  423. .circle-3 {
  424. width: 150px;
  425. height: 150px;
  426. }
  427. }
  428. </style>