diff --git a/offer_frontend/src/api/auth.js b/offer_frontend/src/api/auth.js index a32a438..677bdbc 100644 --- a/offer_frontend/src/api/auth.js +++ b/offer_frontend/src/api/auth.js @@ -3,6 +3,9 @@ import axios from 'axios' export const sendCode = (email) => axios.post('/api/auth/send-code/', { email }) export const loginApi = (data) => axios.post('/api/auth/login/', data) -export const register = (data) => client.post('/auth/register/', data) +export const register = (data) => axios.post('/api/auth/register/', data) export const getMe = () => client.get('/auth/me/') export const updateMe = (data) => client.patch('/auth/me/', data) +export const resetPassword = (email) => axios.post('/api/auth/reset-password/', { email }) +export const confirmResetPassword = (email, code, newPassword) => + axios.post('/api/auth/confirm-reset-password/', { email, code, new_password: newPassword }) diff --git a/offer_frontend/src/router/index.js b/offer_frontend/src/router/index.js index a5ce303..f5fa62e 100644 --- a/offer_frontend/src/router/index.js +++ b/offer_frontend/src/router/index.js @@ -18,6 +18,7 @@ const routes = [ }, { path: '/login', name: 'Login', component: () => import('@/views/auth/LoginView.vue') }, { path: '/register', name: 'Register', component: () => import('@/views/auth/RegisterView.vue') }, + { path: '/forgot-password', name: 'ResetPassword', component: () => import('@/views/auth/ResetPasswordView.vue') }, // 求职者中心 { path: '/seeker', diff --git a/offer_frontend/src/views/auth/LoginView.vue b/offer_frontend/src/views/auth/LoginView.vue index 3afad90..fdbaf2f 100644 --- a/offer_frontend/src/views/auth/LoginView.vue +++ b/offer_frontend/src/views/auth/LoginView.vue @@ -19,71 +19,112 @@ - - - - - - + +
+ +
+ + +
- - + + + + + + + + - 获取验证码 + 登录 - +
+ + 忘记密码? + +
+ - - -
+ + + + + + + + - {{ codeCountdown > 0 ? `${codeCountdown}s` : '重新获取' }} + 获取验证码 -
-
+ - -
- 还有 {{ attemptsLeft }} 次重试机会 -
-
- 验证次数过多,请10分钟后重试 -
+ + +
+ + + {{ codeCountdown > 0 ? `${codeCountdown}s` : '重新获取' }} + +
+
- - - 登录 - - + + + 登录 + + +
- + { // 发送验证码 async function handleSendCode() { - if (!seekerForm.email) { + if (!codeForm.email) { ElMessage.warning('请输入邮箱') return } sendingCode.value = true try { - await sendCode(seekerForm.email) + await sendCode(codeForm.email) ElMessage.success('验证码已发送到您的邮箱') codeSent.value = true - attemptsLeft.value = 5 - isLocked.value = false startCountdown() } catch (err) { - const errorMsg = err.response?.data?.error || - err.response?.data?.email?.[0] || - err.response?.data?.detail || - '发送失败,请检查邮箱是否正确' - console.error('SendCode Error:', err.response?.data) - ElMessage.error(errorMsg) + ElMessage.error(err.response?.data?.email?.[0] || err.response?.data?.error || '发送失败') } finally { sendingCode.value = false } @@ -182,43 +219,63 @@ async function handleSendCode() { // 重新发送验证码 async function handleResendCode() { - seekerForm.code = '' + codeForm.code = '' await handleSendCode() } -// 求职者登入 -async function handleSeekerLogin() { - if (!seekerForm.code || seekerForm.code.length !== 6) { - ElMessage.warning('请输入正确的6位验证码') +// 密码登入 +async function handlePasswordLogin() { + if (!passwordForm.username || !passwordForm.password) { + ElMessage.warning('请输入邮箱/用户名和密码') return } - seekerLoading.value = true + passwordLoading.value = true try { - await auth.login(seekerForm.email, seekerForm.code) + const { data } = await loginApi({ + username: passwordForm.username, + password: passwordForm.password, + }) + localStorage.setItem('access_token', data.access) + localStorage.setItem('refresh_token', data.refresh) + await auth.fetchMe() + ElMessage.success('登录成功') const redirect = route.query.redirect if (redirect) return router.push(redirect) router.push('/seeker/resume') } catch (err) { - const errorMsg = err.response?.data?.non_field_errors?.[0] || - err.response?.data?.code?.[0] || - err.response?.data?.email?.[0] || - err.response?.data?.error || - '登录失败' - - ElMessage.error(errorMsg) - - // 处理重试次数 - if (errorMsg.includes('错误')) { - attemptsLeft.value = Math.max(0, attemptsLeft.value - 1) - if (attemptsLeft.value === 0) { - isLocked.value = true - ElMessage.error('验证次数过多,请10分钟后重试') - } - } + ElMessage.error(err.response?.data?.detail || err.response?.data?.username?.[0] || '登录失败') } finally { - seekerLoading.value = false + passwordLoading.value = false + } +} + +// 验证码登入 +async function handleCodeLogin() { + if (!codeForm.code || codeForm.code.length !== 6) { + ElMessage.warning('请输入正确的6位验证码') + return + } + + codeLoading.value = true + try { + const { data } = await loginApi({ + email: codeForm.email, + code: codeForm.code, + }) + localStorage.setItem('access_token', data.access) + localStorage.setItem('refresh_token', data.refresh) + await auth.fetchMe() + + ElMessage.success('登录成功') + const redirect = route.query.redirect + if (redirect) return router.push(redirect) + router.push('/seeker/resume') + } catch (err) { + ElMessage.error(err.response?.data?.code?.[0] || '登录失败') + } finally { + codeLoading.value = false } } @@ -231,7 +288,10 @@ async function handleAdminLogin() { adminLoading.value = true try { - const { data } = await loginApi({ username: adminForm.username, password: adminForm.password }) + const { data } = await loginApi({ + username: adminForm.username, + password: adminForm.password, + }) localStorage.setItem('access_token', data.access) localStorage.setItem('refresh_token', data.refresh) await auth.fetchMe() @@ -241,7 +301,7 @@ async function handleAdminLogin() { if (redirect) return router.push(redirect) router.push('/admin/jobs') } catch (err) { - ElMessage.error('用户名或密码错误') + ElMessage.error(err.response?.data?.detail || '用户名或密码错误') } finally { adminLoading.value = false } @@ -298,4 +358,40 @@ h2 { font-weight: 600; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); } + +.method-tabs { + display: flex; + gap: 8px; + margin-bottom: 20px; + background: #f5f7fa; + padding: 4px; + border-radius: 4px; +} + +.method-tab { + flex: 1; + padding: 6px 12px; + border: none; + background: transparent; + cursor: pointer; + font-size: 12px; + color: #606266; + border-radius: 3px; + transition: all 0.2s; +} + +.method-tab:hover { + background: rgba(255, 255, 255, 0.5); +} + +.method-tab.active { + background: #fff; + color: #409eff; + font-weight: 600; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06); +} + +.login-container { + width: 100%; +} diff --git a/offer_frontend/src/views/auth/RegisterView.vue b/offer_frontend/src/views/auth/RegisterView.vue index 2b1c3d8..20f78c2 100644 --- a/offer_frontend/src/views/auth/RegisterView.vue +++ b/offer_frontend/src/views/auth/RegisterView.vue @@ -3,67 +3,56 @@

求职者注册

- - - - - + + - + + + + + + + + - - - - 获取验证码 - + + + - - -
- - - {{ codeCountdown > 0 ? `${codeCountdown}s` : '重新获取' }} - -
+ + + 注册 @@ -82,68 +71,43 @@ import { reactive, ref } from 'vue' import { useRouter } from 'vue-router' import { useAuthStore } from '@/stores/auth' import { ElMessage } from 'element-plus' -import { sendCode } from '@/api/auth' -const form = reactive({ username: '', email: '', phone: '', code: '' }) -const codeSent = ref(false) -const sendingCode = ref(false) -const registering = ref(false) -const codeCountdown = ref(0) +const form = reactive({ + email: '', + username: '', + phone: '', + password: '', + passwordConfirm: '', +}) +const loading = ref(false) const router = useRouter() const auth = useAuthStore() -// 倒计时逻辑 -const startCountdown = () => { - codeCountdown.value = 60 - const interval = setInterval(() => { - codeCountdown.value-- - if (codeCountdown.value <= 0) clearInterval(interval) - }, 1000) -} - -// 发送验证码 -async function handleSendCode() { - if (!form.username) { - ElMessage.warning('请输入用户名') - return - } +async function handleRegister() { + // 验证字段 if (!form.email) { ElMessage.warning('请输入邮箱') return } + if (!form.username) { + ElMessage.warning('请输入用户名') + return + } if (!form.phone) { ElMessage.warning('请输入手机号') return } - - sendingCode.value = true - try { - await sendCode(form.email) - ElMessage.success('验证码已发送到您的邮箱') - codeSent.value = true - startCountdown() - } catch (err) { - ElMessage.error(err.response?.data?.email?.[0] || err.response?.data?.error || '发送失败') - } finally { - sendingCode.value = false + if (!form.password || form.password.length < 6) { + ElMessage.warning('请输入至少6位密码') + return } -} - -// 重新发送验证码 -async function handleResendCode() { - form.code = '' - await handleSendCode() -} - -// 注册 -async function handleRegister() { - if (!form.code || form.code.length !== 6) { - ElMessage.warning('请输入正确的6位验证码') + if (form.password !== form.passwordConfirm) { + ElMessage.warning('两次输入的密码不一致') return } - registering.value = true + loading.value = true try { const response = await fetch('/api/auth/register/', { method: 'POST', @@ -151,16 +115,16 @@ async function handleRegister() { 'Content-Type': 'application/json', }, body: JSON.stringify({ - username: form.username, email: form.email, + username: form.username, phone: form.phone, - code: form.code, + password: form.password, }), }) if (!response.ok) { const data = await response.json() - const errorMsg = data.code?.[0] || data.username?.[0] || data.email?.[0] || data.phone?.[0] || '注册失败' + const errorMsg = data.username?.[0] || data.email?.[0] || data.phone?.[0] || data.password?.[0] || '注册失败' ElMessage.error(errorMsg) return } @@ -177,7 +141,7 @@ async function handleRegister() { } catch (err) { ElMessage.error('注册失败,请稍后重试') } finally { - registering.value = false + loading.value = false } } diff --git a/offer_frontend/src/views/auth/ResetPasswordView.vue b/offer_frontend/src/views/auth/ResetPasswordView.vue new file mode 100644 index 0000000..4d551e5 --- /dev/null +++ b/offer_frontend/src/views/auth/ResetPasswordView.vue @@ -0,0 +1,244 @@ + + + + +