Login.vue 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <template>
  2. <div class="login-container">
  3. <a-card class="login-card" :title="isRegister ? '注册' : '登录'">
  4. <template #extra>
  5. <a-button type="link" @click="isRegister = !isRegister">
  6. {{ isRegister ? '已有账号?去登录' : '没有账号?去注册' }}
  7. </a-button>
  8. </template>
  9. <a-form :model="form" :rules="rules" layout="vertical" @finish="handleSubmit">
  10. <a-form-item label="用户名" name="username">
  11. <a-input v-model:value="form.username" placeholder="请输入用户名" size="large" />
  12. </a-form-item>
  13. <a-form-item label="密码" name="password">
  14. <a-input-password v-model:value="form.password" placeholder="请输入密码" size="large" />
  15. </a-form-item>
  16. <a-form-item v-if="isRegister" label="确认密码" name="confirmPassword">
  17. <a-input-password v-model:value="form.confirmPassword" placeholder="请再次输入密码" size="large" />
  18. </a-form-item>
  19. <a-form-item>
  20. <a-button type="primary" html-type="submit" :loading="loading" block size="large">
  21. {{ isRegister ? '注册' : '登录' }}
  22. </a-button>
  23. </a-form-item>
  24. </a-form>
  25. <div v-if="error" class="error-msg">{{ error }}</div>
  26. </a-card>
  27. </div>
  28. </template>
  29. <script setup lang="ts">
  30. import { ref, reactive, onMounted } from 'vue'
  31. import { useRouter } from 'vue-router'
  32. import { message } from 'ant-design-vue'
  33. import { rsaEncrypt, fetchPublicKey } from '@/services/crypto'
  34. const router = useRouter()
  35. const isRegister = ref(false)
  36. const loading = ref(false)
  37. const error = ref('')
  38. // 提前获取 RSA 公钥,提升用户体验
  39. let publicKeyPem: string | undefined
  40. onMounted(async () => {
  41. try {
  42. publicKeyPem = await fetchPublicKey()
  43. } catch {
  44. // 公钥获取失败不影响后续操作(encrypt 时会再试)
  45. }
  46. })
  47. const form = reactive({
  48. username: '',
  49. password: '',
  50. confirmPassword: '',
  51. })
  52. const rules = {
  53. username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
  54. password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
  55. confirmPassword: [
  56. { required: true, message: '请确认密码', trigger: 'blur' },
  57. {
  58. validator: (_: any, value: string) => {
  59. if (isRegister.value && value !== form.password) {
  60. return Promise.reject('两次密码不一致')
  61. }
  62. return Promise.resolve()
  63. },
  64. trigger: 'blur'
  65. }
  66. ],
  67. }
  68. async function handleSubmit() {
  69. if (isRegister.value && form.password !== form.confirmPassword) {
  70. error.value = '两次密码不一致'
  71. return
  72. }
  73. loading.value = true
  74. error.value = ''
  75. try {
  76. // RSA 加密密码
  77. const encryptedPassword = await rsaEncrypt(form.password, publicKeyPem)
  78. const endpoint = isRegister.value ? 'register' : 'login'
  79. const baseUrl = import.meta.env.VITE_API_BASE_URL || 'https://localhost:8000'
  80. const res = await fetch(`${baseUrl}/api/auth/${endpoint}`, {
  81. method: 'POST',
  82. headers: { 'Content-Type': 'application/json' },
  83. credentials: 'include', // 让 cookie 可被设置
  84. body: JSON.stringify({
  85. username: form.username,
  86. encrypted_password: encryptedPassword,
  87. }),
  88. })
  89. const data = await res.json()
  90. if (!res.ok) {
  91. error.value = data.detail || '操作失败'
  92. return
  93. }
  94. // 保存登录用户名(前端显示用,不影响认证)
  95. localStorage.setItem('auth_username', data.username)
  96. message.success(isRegister.value ? '注册成功' : '登录成功')
  97. // 跳转到首页(带上登录标记)
  98. router.push('/')
  99. } catch (e: any) {
  100. error.value = '网络错误,请检查后端是否启动'
  101. } finally {
  102. loading.value = false
  103. }
  104. }
  105. </script>
  106. <style scoped>
  107. .login-container {
  108. display: flex;
  109. justify-content: center;
  110. align-items: center;
  111. min-height: 60vh;
  112. }
  113. .login-card {
  114. width: 420px;
  115. border-radius: 12px;
  116. box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
  117. }
  118. .error-msg {
  119. color: #ff4d4f;
  120. text-align: center;
  121. margin-top: 8px;
  122. }
  123. </style>