Files
zsglpt/app-frontend/src/pages/RegisterPage.vue
T

347 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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'
import { validateStrongPassword } from '../utils/password'
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 socialPendingKeys = {
token: 'zsglpt_social_pending_bind_token',
provider: 'zsglpt_social_pending_bind_provider',
nickname: 'zsglpt_social_pending_bind_nickname',
avatar: 'zsglpt_social_pending_bind_avatar_url',
}
const providerLabels = {
qq: 'QQ',
wx: '微信',
alipay: '支付宝',
}
const pendingSocial = reactive({
token: '',
provider: '',
nickname: '',
avatar_url: '',
})
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 = ''
}
function loadPendingSocialBind() {
try {
pendingSocial.token = window.sessionStorage.getItem(socialPendingKeys.token) || ''
pendingSocial.provider = window.sessionStorage.getItem(socialPendingKeys.provider) || ''
pendingSocial.nickname = window.sessionStorage.getItem(socialPendingKeys.nickname) || ''
pendingSocial.avatar_url = window.sessionStorage.getItem(socialPendingKeys.avatar) || ''
if (pendingSocial.nickname && !form.username) {
form.username = pendingSocial.nickname.replace(/[^\w\u4e00-\u9fa5]/g, '').slice(0, 20)
}
} catch {
pendingSocial.token = ''
pendingSocial.provider = ''
pendingSocial.nickname = ''
pendingSocial.avatar_url = ''
}
}
function clearPendingSocialBind() {
try {
Object.values(socialPendingKeys).forEach((key) => window.sessionStorage.removeItem(key))
} catch {
// ignore storage failures
}
pendingSocial.token = ''
pendingSocial.provider = ''
pendingSocial.nickname = ''
pendingSocial.avatar_url = ''
}
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
}
const passwordCheck = validateStrongPassword(password)
if (!passwordCheck.ok) {
errorText.value = passwordCheck.message || '密码格式不正确'
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,
social_bind_token: pendingSocial.token || undefined,
})
successTitle.value = res?.message || '注册成功'
successDesc.value = res?.need_verify ? '请检查您的邮箱(包括垃圾邮件文件夹)' : ''
ElMessage.success('注册成功')
if (pendingSocial.token) {
clearPendingSocialBind()
}
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 () => {
loadPendingSocialBind()
await refreshCaptcha()
await loadEmailVerifyStatus()
})
</script>
<template>
<div class="auth-wrap">
<el-card shadow="never" class="auth-card" :body-style="{ padding: '22px' }">
<div class="brand">
<div class="brand-title">知识管理平台</div>
<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
v-if="successTitle"
type="success"
:closable="false"
:title="successTitle"
:description="successDesc"
show-icon
class="alert"
/>
<el-alert
v-if="pendingSocial.token"
type="info"
:closable="false"
show-icon
class="alert"
:title="`注册后绑定${providerLabels[pendingSocial.provider] || '快捷登录'}`"
/>
<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="至少8位且包含字母和数字"
autocomplete="new-password"
/>
<div class="hint app-muted">至少8位且包含字母和数字</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">
<span class="app-muted">已有账号</span>
<el-button link type="primary" @click="goLogin">立即登录</el-button>
</div>
</el-card>
</div>
</template>
<style scoped>
.auth-wrap {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.auth-card {
width: 100%;
max-width: 420px;
border-radius: var(--app-radius);
border: 1px solid var(--app-border);
box-shadow: var(--app-shadow);
}
.brand {
margin-bottom: 14px;
}
.brand-title {
font-size: 18px;
font-weight: 900;
}
.brand-sub {
margin-top: 4px;
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: 14px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
}
</style>