feat(app): migrate auth pages to Vue SPA (stage 2)

This commit is contained in:
2025-12-13 23:30:51 +08:00
parent 34f44eed3e
commit 324e0d614a
35 changed files with 1175 additions and 85 deletions

View File

@@ -1,11 +1,140 @@
<script setup>
import { computed, onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { fetchEmailVerifyStatus, generateCaptcha, register } from '../api/auth'
const router = useRouter()
const form = reactive({
username: '',
password: '',
confirm_password: '',
email: '',
captcha: '',
})
const emailVerifyEnabled = ref(false)
const captchaImage = ref('')
const captchaSession = ref('')
const loading = ref(false)
const errorText = ref('')
const successTitle = ref('')
const successDesc = ref('')
const emailLabel = computed(() => (emailVerifyEnabled.value ? '邮箱 *' : '邮箱(可选)'))
const emailHint = computed(() => (emailVerifyEnabled.value ? '必填,用于账号验证' : '选填,用于接收审核通知'))
async function refreshCaptcha() {
try {
const data = await generateCaptcha()
captchaSession.value = data?.session_id || ''
captchaImage.value = data?.captcha_image || ''
form.captcha = ''
} catch {
captchaSession.value = ''
captchaImage.value = ''
}
}
async function loadEmailVerifyStatus() {
try {
const data = await fetchEmailVerifyStatus()
emailVerifyEnabled.value = Boolean(data?.register_verify_enabled)
} catch {
emailVerifyEnabled.value = false
}
}
function clearAlerts() {
errorText.value = ''
successTitle.value = ''
successDesc.value = ''
}
async function onSubmit() {
clearAlerts()
const username = form.username.trim()
const password = form.password
const confirmPassword = form.confirm_password
const email = form.email.trim()
const captcha = form.captcha.trim()
if (username.length < 3) {
errorText.value = '用户名至少3个字符'
ElMessage.error(errorText.value)
return
}
if (password.length < 6) {
errorText.value = '密码至少6个字符'
ElMessage.error(errorText.value)
return
}
if (password !== confirmPassword) {
errorText.value = '两次输入的密码不一致'
ElMessage.error(errorText.value)
return
}
if (emailVerifyEnabled.value && !email) {
errorText.value = '请填写邮箱地址用于账号验证'
ElMessage.error(errorText.value)
return
}
if (email && !email.includes('@')) {
errorText.value = '邮箱格式不正确'
ElMessage.error(errorText.value)
return
}
if (!captcha) {
errorText.value = '请输入验证码'
ElMessage.error(errorText.value)
return
}
loading.value = true
try {
const res = await register({
username,
password,
email,
captcha_session: captchaSession.value,
captcha,
})
successTitle.value = res?.message || '注册成功'
successDesc.value = res?.need_verify ? '请检查您的邮箱(包括垃圾邮件文件夹)' : ''
ElMessage.success('注册成功')
form.username = ''
form.password = ''
form.confirm_password = ''
form.email = ''
form.captcha = ''
setTimeout(() => {
window.location.href = '/login'
}, 3000)
} catch (e) {
const data = e?.response?.data
errorText.value = data?.error || '注册失败'
ElMessage.error(errorText.value)
await refreshCaptcha()
} finally {
loading.value = false
}
}
function goLogin() {
router.push('/login')
}
onMounted(async () => {
await refreshCaptcha()
await loadEmailVerifyStatus()
})
</script>
<template>
@@ -16,15 +145,67 @@ function goLogin() {
<div class="brand-sub app-muted">用户注册</div>
</div>
<el-alert v-if="errorText" type="error" :closable="false" :title="errorText" show-icon class="alert" />
<el-alert
type="info"
v-if="successTitle"
type="success"
:closable="false"
title="阶段1仅完成前台工程与布局搭建。注册/邮箱验证等功能将在后续阶段迁移。"
:title="successTitle"
:description="successDesc"
show-icon
class="alert"
/>
<el-form label-position="top">
<el-form-item label="用户名 *">
<el-input v-model="form.username" placeholder="至少3个字符" autocomplete="username" />
<div class="hint app-muted">至少3个字符</div>
</el-form-item>
<el-form-item label="密码 *">
<el-input
v-model="form.password"
type="password"
show-password
placeholder="至少6个字符"
autocomplete="new-password"
/>
<div class="hint app-muted">至少6个字符</div>
</el-form-item>
<el-form-item label="确认密码 *">
<el-input
v-model="form.confirm_password"
type="password"
show-password
placeholder="请再次输入密码"
autocomplete="new-password"
@keyup.enter="onSubmit"
/>
</el-form-item>
<el-form-item :label="emailLabel">
<el-input v-model="form.email" placeholder="name@example.com" autocomplete="email" />
<div class="hint app-muted">{{ emailHint }}</div>
</el-form-item>
<el-form-item label="验证码 *">
<div class="captcha-row">
<el-input v-model="form.captcha" placeholder="请输入验证码" @keyup.enter="onSubmit" />
<img
v-if="captchaImage"
class="captcha-img"
:src="captchaImage"
alt="验证码"
title="点击刷新"
@click="refreshCaptcha"
/>
<el-button @click="refreshCaptcha">刷新</el-button>
</div>
</el-form-item>
</el-form>
<el-button type="primary" class="submit-btn" :loading="loading" @click="onSubmit">注册</el-button>
<div class="actions">
<el-button @click="goLogin">返回登录</el-button>
<span class="app-muted">已有账号</span>
<el-button link type="primary" @click="goLogin">立即登录</el-button>
</div>
</el-card>
</div>
@@ -61,8 +242,40 @@ function goLogin() {
font-size: 12px;
}
.alert {
margin-bottom: 12px;
}
.hint {
margin-top: 6px;
font-size: 12px;
}
.captcha-row {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
}
.captcha-img {
height: 40px;
border: 1px solid var(--app-border);
border-radius: 8px;
cursor: pointer;
user-select: none;
}
.submit-btn {
width: 100%;
margin-top: 4px;
}
.actions {
margin-top: 16px;
margin-top: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
</style>