mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-07 18:07:47 +08:00
refactor(frontend): modularize i18n architecture
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
class="h-16 w-16 mb-4"
|
||||
>
|
||||
<h2 class="text-2xl font-semibold text-foreground">
|
||||
登录到 {{ siteName }}
|
||||
{{ t('auth.login.title', { siteName }) }}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
class="rounded-lg border border-primary/20 bg-primary/5 p-3 mb-5"
|
||||
>
|
||||
<p class="text-xs font-medium text-foreground mb-2">
|
||||
演示模式
|
||||
{{ t('auth.login.demoMode') }}
|
||||
</p>
|
||||
<div class="space-y-1.5">
|
||||
<button
|
||||
@@ -66,7 +66,7 @@
|
||||
v-html="getOAuthIcon(oauthProviders[0].provider_type, oauthProviders[0].icon_url)"
|
||||
/>
|
||||
<!-- eslint-enable vue/no-v-html -->
|
||||
<span>使用 {{ oauthProviders[0].display_name }} 登录</span>
|
||||
<span>{{ t('auth.login.oauthWithProvider', { provider: oauthProviders[0].display_name }) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
v-else
|
||||
class="flex flex-col items-center gap-3"
|
||||
>
|
||||
<span class="text-xs text-muted-foreground">使用以下方式登录</span>
|
||||
<span class="text-xs text-muted-foreground">{{ t('auth.login.oauthOptions') }}</span>
|
||||
<div class="flex items-center justify-center gap-3">
|
||||
<button
|
||||
v-for="p in oauthProviders"
|
||||
@@ -102,7 +102,7 @@
|
||||
class="flex items-center gap-3 mb-5"
|
||||
>
|
||||
<div class="flex-1 h-px bg-border" />
|
||||
<span class="text-xs text-muted-foreground px-2">或使用账号密码</span>
|
||||
<span class="text-xs text-muted-foreground px-2">{{ t('auth.login.accountPassword') }}</span>
|
||||
<div class="flex-1 h-px bg-border" />
|
||||
</div>
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
:class="[authType === 'local' && 'active']"
|
||||
@click="authType = 'local'"
|
||||
>
|
||||
本地登录
|
||||
{{ t('auth.login.local') }}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -125,7 +125,7 @@
|
||||
:class="[authType === 'ldap' && 'active']"
|
||||
@click="authType = 'ldap'"
|
||||
>
|
||||
LDAP 登录
|
||||
{{ t('auth.login.ldap') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
class="text-xs text-muted-foreground/60 hover:text-muted-foreground transition-colors"
|
||||
@click="authType = 'local'"
|
||||
>
|
||||
管理员本地登录
|
||||
{{ t('auth.login.adminLocal') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="ldapExclusive && authType === 'local'"
|
||||
@@ -162,7 +162,7 @@
|
||||
class="text-xs text-muted-foreground/60 hover:text-muted-foreground transition-colors"
|
||||
@click="authType = 'ldap'"
|
||||
>
|
||||
返回 LDAP 登录
|
||||
{{ t('auth.login.backToLdap') }}
|
||||
</button>
|
||||
</div>
|
||||
<Input
|
||||
@@ -171,7 +171,7 @@
|
||||
type="text"
|
||||
name="username"
|
||||
required
|
||||
placeholder="用户名或邮箱"
|
||||
:placeholder="t('auth.login.usernamePlaceholder')"
|
||||
autocomplete="username"
|
||||
autocapitalize="none"
|
||||
spellcheck="false"
|
||||
@@ -184,7 +184,7 @@
|
||||
for="password"
|
||||
class="text-sm"
|
||||
>
|
||||
密码
|
||||
{{ t('auth.login.password') }}
|
||||
</Label>
|
||||
<Input
|
||||
id="password"
|
||||
@@ -192,7 +192,7 @@
|
||||
type="password"
|
||||
name="password"
|
||||
required
|
||||
placeholder="输入密码"
|
||||
:placeholder="t('auth.login.passwordPlaceholder')"
|
||||
autocomplete="current-password"
|
||||
:disable-autofill="false"
|
||||
/>
|
||||
@@ -204,7 +204,7 @@
|
||||
:disabled="authStore.loading"
|
||||
class="w-full h-12"
|
||||
>
|
||||
{{ authStore.loading ? '登录中...' : '登录' }}
|
||||
{{ authStore.loading ? t('auth.login.submitting') : t('auth.login.submit') }}
|
||||
</Button>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
@@ -212,7 +212,7 @@
|
||||
v-if="!isDemo && !allowRegistration"
|
||||
class="text-xs text-muted-foreground text-center"
|
||||
>
|
||||
如需开通账户,请联系管理员
|
||||
{{ t('auth.login.contactAdmin') }}
|
||||
</p>
|
||||
</form>
|
||||
|
||||
@@ -221,13 +221,13 @@
|
||||
v-if="allowRegistration"
|
||||
class="mt-5 pt-5 border-t border-border text-center text-sm text-muted-foreground"
|
||||
>
|
||||
还没有账户?
|
||||
{{ t('auth.login.noAccount') }}
|
||||
<button
|
||||
type="button"
|
||||
class="text-primary hover:text-primary/80 font-medium transition-colors"
|
||||
@click="handleSwitchToRegister"
|
||||
>
|
||||
立即注册
|
||||
{{ t('auth.login.registerNow') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -266,6 +266,7 @@ import { getClientDeviceId } from '@/utils/deviceId'
|
||||
import { getApiUrl } from '@/utils/url'
|
||||
import { getOAuthIcon } from '@/utils/oauth-icons'
|
||||
import { navigateAfterLogin } from '@/features/auth/utils/loginRedirect'
|
||||
import { useI18n } from '@/i18n'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
@@ -280,6 +281,7 @@ const route = useRoute()
|
||||
const authStore = useAuthStore()
|
||||
const { success: showSuccess, warning: showWarning, error: showError } = useToast()
|
||||
const { siteName } = useSiteInfo()
|
||||
const { t } = useI18n()
|
||||
|
||||
const isOpen = ref(props.modelValue)
|
||||
const isDemo = computed(() => isDemoMode())
|
||||
@@ -321,7 +323,7 @@ const showAuthTypeTabs = computed(() => {
|
||||
})
|
||||
|
||||
const emailLabel = computed(() => {
|
||||
return '用户名/邮箱'
|
||||
return t('auth.login.usernameEmail')
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
@@ -354,7 +356,7 @@ async function handleLogin(event?: Event) {
|
||||
const { email, password } = readCurrentLoginCredentials(event)
|
||||
|
||||
if (!email || !password) {
|
||||
showWarning('请输入邮箱和密码')
|
||||
showWarning(t('auth.login.required'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -364,12 +366,12 @@ async function handleLogin(event?: Event) {
|
||||
|
||||
await navigateAfterLogin(router, targetPath)
|
||||
|
||||
showSuccess('登录成功,正在跳转...')
|
||||
showSuccess(t('auth.login.successRedirecting'))
|
||||
|
||||
// 关闭对话框
|
||||
isOpen.value = false
|
||||
} else {
|
||||
showError(authStore.error || '登录失败,请检查邮箱和密码')
|
||||
showError(authStore.error || t('auth.login.failed'))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,7 +425,7 @@ function handleSwitchToRegister() {
|
||||
|
||||
function handleRegisterSuccess() {
|
||||
showRegisterDialog.value = false
|
||||
showSuccess('注册成功!请登录')
|
||||
showSuccess(t('auth.register.successLogin'))
|
||||
isOpen.value = true
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
>
|
||||
</div>
|
||||
<h2 class="text-2xl font-semibold text-slate-900 dark:text-white">
|
||||
注册新账户
|
||||
{{ registerUi.title }}
|
||||
</h2>
|
||||
<p class="mt-1 text-sm text-muted-foreground">
|
||||
{{ emailConfigured ? '请填写您的信息完成注册' : '请填写用户名和密码完成注册' }}
|
||||
{{ emailConfigured ? registerUi.fillEmailInfo : registerUi.fillBasicInfo }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
class="space-y-2"
|
||||
>
|
||||
<Label for="reg-email">
|
||||
邮箱
|
||||
{{ registerUi.email }}
|
||||
<span
|
||||
v-if="requireEmailVerification"
|
||||
class="text-destructive"
|
||||
@@ -42,7 +42,7 @@
|
||||
<span
|
||||
v-else
|
||||
class="text-muted-foreground text-xs"
|
||||
>(可选)</span>
|
||||
>{{ registerUi.optional }}</span>
|
||||
</Label>
|
||||
<Input
|
||||
id="reg-email"
|
||||
@@ -59,7 +59,7 @@
|
||||
v-if="turnstileRequired"
|
||||
class="space-y-2"
|
||||
>
|
||||
<Label>人机验证 <span class="text-destructive">*</span></Label>
|
||||
<Label>{{ registerUi.turnstile }} <span class="text-destructive">*</span></Label>
|
||||
<TurnstileWidget
|
||||
ref="turnstileWidgetRef"
|
||||
v-model="turnstileToken"
|
||||
@@ -76,7 +76,7 @@
|
||||
class="space-y-3"
|
||||
>
|
||||
<div class="flex items-center justify-between">
|
||||
<Label>验证码 <span class="text-destructive">*</span></Label>
|
||||
<Label>{{ registerUi.verificationCode }} <span class="text-destructive">*</span></Label>
|
||||
<Button
|
||||
type="button"
|
||||
variant="link"
|
||||
@@ -143,12 +143,12 @@
|
||||
|
||||
<!-- Username -->
|
||||
<div class="space-y-2">
|
||||
<Label for="reg-uname">用户名 <span class="text-destructive">*</span></Label>
|
||||
<Label for="reg-uname">{{ registerUi.username }} <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
id="reg-uname"
|
||||
v-model="formData.username"
|
||||
type="text"
|
||||
placeholder="请输入用户名"
|
||||
:placeholder="registerUi.usernamePlaceholder"
|
||||
required
|
||||
disable-autofill
|
||||
:disabled="isLoading"
|
||||
@@ -164,7 +164,9 @@
|
||||
|
||||
<!-- Password -->
|
||||
<div class="space-y-2">
|
||||
<Label :for="`pwd-${formNonce}`">密码 <span class="text-destructive">*</span></Label>
|
||||
<Label :for="`pwd-${formNonce}`">
|
||||
{{ registerUi.password }} <span class="text-destructive">*</span>
|
||||
</Label>
|
||||
<Input
|
||||
:id="`pwd-${formNonce}`"
|
||||
v-model="formData.password"
|
||||
@@ -192,7 +194,7 @@
|
||||
|
||||
<!-- Confirm Password -->
|
||||
<div class="space-y-2">
|
||||
<Label :for="`pwd-confirm-${formNonce}`">确认密码 <span class="text-destructive">*</span></Label>
|
||||
<Label :for="`pwd-confirm-${formNonce}`">{{ registerUi.confirmPassword }} <span class="text-destructive">*</span></Label>
|
||||
<Input
|
||||
:id="`pwd-confirm-${formNonce}`"
|
||||
v-model="formData.confirmPassword"
|
||||
@@ -200,7 +202,7 @@
|
||||
autocomplete="new-password"
|
||||
disable-autofill
|
||||
:name="`pwd-confirm-${formNonce}`"
|
||||
placeholder="再次输入密码"
|
||||
:placeholder="registerUi.confirmPasswordPlaceholder"
|
||||
required
|
||||
:disabled="isLoading"
|
||||
/>
|
||||
@@ -208,7 +210,7 @@
|
||||
v-if="formData.confirmPassword && formData.password !== formData.confirmPassword"
|
||||
class="text-xs text-destructive"
|
||||
>
|
||||
两次输入的密码不一致
|
||||
{{ registerUi.passwordMismatch }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -216,7 +218,7 @@
|
||||
v-if="inviteCode"
|
||||
class="rounded-lg border border-primary/20 bg-primary/5 px-3 py-2 text-xs text-muted-foreground"
|
||||
>
|
||||
已识别邀请码 <span class="font-mono font-semibold text-foreground">{{ inviteCode }}</span>
|
||||
{{ inviteCodeText }}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -230,38 +232,38 @@
|
||||
@update:checked="privacyAccepted = !!$event"
|
||||
/>
|
||||
<span class="leading-6">
|
||||
我已阅读并同意
|
||||
{{ registerUi.privacyPrefix }}
|
||||
<button
|
||||
type="button"
|
||||
class="font-medium text-primary underline-offset-4 hover:underline"
|
||||
@click="privacyDialogOpen = true"
|
||||
>
|
||||
隐私政策
|
||||
{{ registerUi.privacyTitle }}
|
||||
</button>
|
||||
<RouterLink
|
||||
to="/privacy-policy"
|
||||
target="_blank"
|
||||
class="ml-1 text-xs text-muted-foreground underline-offset-4 hover:text-foreground hover:underline"
|
||||
>
|
||||
新窗口打开
|
||||
{{ registerUi.openInNewWindow }}
|
||||
</RouterLink>
|
||||
</span>
|
||||
</label>
|
||||
<p class="mt-2 text-xs text-muted-foreground">
|
||||
当前版本:{{ privacyPolicyVersion }}
|
||||
{{ privacyVersionText }}
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- 登录链接 -->
|
||||
<div class="text-center text-sm">
|
||||
已有账户?
|
||||
{{ registerUi.hasAccount }}
|
||||
<Button
|
||||
variant="link"
|
||||
class="h-auto p-0"
|
||||
@click="handleSwitchToLogin"
|
||||
>
|
||||
立即登录
|
||||
{{ registerUi.switchToLogin }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -274,14 +276,14 @@
|
||||
:disabled="isLoading"
|
||||
@click="handleCancel"
|
||||
>
|
||||
取消
|
||||
{{ registerUi.cancel }}
|
||||
</Button>
|
||||
<Button
|
||||
class="w-full sm:w-auto bg-primary hover:bg-primary/90 text-white border-0"
|
||||
:disabled="isLoading || !canSubmit"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ isLoading ? loadingText : '注册' }}
|
||||
{{ isLoading ? loadingText : registerUi.submit }}
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
@@ -289,7 +291,7 @@
|
||||
<Dialog
|
||||
v-model="privacyDialogOpen"
|
||||
size="2xl"
|
||||
title="隐私政策"
|
||||
:title="registerUi.privacyTitle"
|
||||
>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
@@ -302,7 +304,7 @@
|
||||
type="button"
|
||||
@click="privacyDialogOpen = false"
|
||||
>
|
||||
我知道了
|
||||
{{ registerUi.acknowledge }}
|
||||
</Button>
|
||||
</template>
|
||||
</Dialog>
|
||||
@@ -314,6 +316,7 @@ import { RouterLink } from 'vue-router'
|
||||
import { marked } from 'marked'
|
||||
import { authApi, type RegisterRequest, type RegistrationPrivacyPolicySettings } from '@/api/auth'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import { useI18n } from '@/i18n'
|
||||
import { parseApiError } from '@/utils/errorParser'
|
||||
import { sanitizeHtml, sanitizeMarkdown } from '@/utils/sanitize'
|
||||
import {
|
||||
@@ -365,6 +368,35 @@ interface Emits {
|
||||
}
|
||||
|
||||
const { success, error: showError } = useToast()
|
||||
const { t } = useI18n()
|
||||
|
||||
const registerUi = computed(() => ({
|
||||
title: t('auth.register.title'),
|
||||
fillEmailInfo: t('auth.register.fillEmailInfo'),
|
||||
fillBasicInfo: t('auth.register.fillBasicInfo'),
|
||||
email: t('auth.register.email'),
|
||||
optional: t('auth.register.optional'),
|
||||
turnstile: t('auth.register.turnstile'),
|
||||
verificationCode: t('auth.register.verificationCode'),
|
||||
username: t('auth.register.username'),
|
||||
usernamePlaceholder: t('auth.register.usernamePlaceholder'),
|
||||
password: t('auth.register.password'),
|
||||
confirmPassword: t('auth.register.confirmPassword'),
|
||||
confirmPasswordPlaceholder: t('auth.register.confirmPasswordPlaceholder'),
|
||||
passwordMismatch: t('auth.register.passwordMismatch'),
|
||||
inviteCode: (code: string) => t('auth.register.inviteCode', { code }),
|
||||
privacyPrefix: t('auth.register.privacyPrefix'),
|
||||
privacyTitle: t('site.privacy.title'),
|
||||
openInNewWindow: t('auth.register.openInNewWindow'),
|
||||
hasAccount: t('auth.register.hasAccount'),
|
||||
switchToLogin: t('auth.register.switchToLogin'),
|
||||
submit: t('auth.register.submit'),
|
||||
acknowledge: t('auth.register.acknowledge'),
|
||||
cancel: t('common.cancel'),
|
||||
}))
|
||||
|
||||
const inviteCodeText = computed(() => inviteCode.value ? registerUi.value.inviteCode(inviteCode.value) : '')
|
||||
const privacyVersionText = computed(() => t('site.privacy.currentVersion', { version: privacyPolicyVersion.value }))
|
||||
|
||||
// Form nonce for password fields (prevent autofill)
|
||||
const formNonce = ref(createFormNonce())
|
||||
@@ -468,7 +500,7 @@ const formData = ref({
|
||||
})
|
||||
|
||||
const isLoading = ref(false)
|
||||
const loadingText = ref('注册中...')
|
||||
const loadingText = ref(t('auth.register.submit'))
|
||||
const isSendingCode = ref(false)
|
||||
const emailVerified = ref(false)
|
||||
const verificationError = ref(false)
|
||||
@@ -494,7 +526,7 @@ const resetTurnstile = () => {
|
||||
}
|
||||
|
||||
const handleTurnstileError = (message: string) => {
|
||||
showError(message, '人机验证失败')
|
||||
showError(message, t('auth.register.turnstile'))
|
||||
}
|
||||
|
||||
const inviteCode = ref<string | null>(null)
|
||||
@@ -504,7 +536,7 @@ const privacyPolicyEnabled = computed(() => !!props.privacyPolicy?.enabled)
|
||||
const privacyPolicyVersion = computed(() => props.privacyPolicy?.version || '1')
|
||||
const renderedPrivacyPolicy = computed(() => {
|
||||
const policy = props.privacyPolicy
|
||||
if (!policy?.content) return '<p>暂无隐私政策内容</p>'
|
||||
if (!policy?.content) return `<p>${t('site.privacy.empty')}</p>`
|
||||
if (policy.format === 'html') {
|
||||
return sanitizeHtml(policy.content)
|
||||
}
|
||||
@@ -536,28 +568,28 @@ const canSendCode = computed(() => {
|
||||
})
|
||||
|
||||
const sendCodeButtonText = computed(() => {
|
||||
if (isSendingCode.value) return '发送中...'
|
||||
if (emailVerified.value) return '验证成功'
|
||||
if (cooldownSeconds.value > 0) return `${cooldownSeconds.value}秒后重试`
|
||||
if (isSendingCode.value) return t('auth.register.sendingCode')
|
||||
if (emailVerified.value) return t('auth.register.verified')
|
||||
if (cooldownSeconds.value > 0) return t('auth.register.retryAfterSeconds', { seconds: cooldownSeconds.value })
|
||||
if (
|
||||
turnstileRequired.value &&
|
||||
currentTurnstileAction.value === 'send_verification_code' &&
|
||||
!turnstileToken.value
|
||||
) return '请先完成人机验证'
|
||||
if (codeSentAt.value) return '重新发送验证码'
|
||||
return '发送验证码'
|
||||
) return t('auth.register.completeTurnstile')
|
||||
if (codeSentAt.value) return t('auth.register.resendCode')
|
||||
return t('auth.register.sendCode')
|
||||
})
|
||||
|
||||
const sendCodeLoadingText = computed(() => '正在发送验证码...')
|
||||
const sendCodeLoadingText = computed(() => t('auth.register.sendingCode'))
|
||||
|
||||
// 用户名验证
|
||||
const usernameRegex = /^[a-zA-Z0-9_.-]+$/
|
||||
const usernameError = computed(() => {
|
||||
const username = formData.value.username.trim()
|
||||
if (!username) return ''
|
||||
if (username.length < 3) return '用户名长度至少为3个字符'
|
||||
if (username.length > 30) return '用户名长度不能超过30个字符'
|
||||
if (!usernameRegex.test(username)) return '用户名只能包含字母、数字、下划线、连字符和点号'
|
||||
if (username.length < 3) return t('auth.register.usernameTooShort')
|
||||
if (username.length > 30) return t('auth.register.usernameTooLong')
|
||||
if (!usernameRegex.test(username)) return t('auth.register.usernameInvalid')
|
||||
return ''
|
||||
})
|
||||
|
||||
@@ -743,14 +775,14 @@ const resetForm = () => {
|
||||
|
||||
const handleSendCode = async () => {
|
||||
if (!formData.value.email) {
|
||||
showError('请输入邮箱')
|
||||
showError(t('auth.register.emailRequired'))
|
||||
return
|
||||
}
|
||||
|
||||
// Basic email validation
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
if (!emailRegex.test(formData.value.email)) {
|
||||
showError('请输入有效的邮箱地址', '邮箱格式错误')
|
||||
showError(t('auth.register.emailInvalid'), t('auth.register.emailFormatError'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -769,7 +801,7 @@ const handleSendCode = async () => {
|
||||
expireMinutes.value = response.expire_minutes
|
||||
}
|
||||
|
||||
success(`请查收邮件,验证码有效期 ${expireMinutes.value} 分钟`, '验证码已发送')
|
||||
success(t('auth.register.codeSent', { minutes: expireMinutes.value }), t('auth.register.codeSentTitle'))
|
||||
|
||||
// Start 60 second cooldown
|
||||
startCooldown(60)
|
||||
@@ -780,11 +812,11 @@ const handleSendCode = async () => {
|
||||
})
|
||||
} else {
|
||||
resetTurnstile()
|
||||
showError(response.message || '请稍后重试', '发送失败')
|
||||
showError(response.message || t('auth.register.tryAgainLater'), t('auth.register.sendFailed'))
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
resetTurnstile()
|
||||
showError(parseApiError(error, '网络错误,请重试'), '发送失败')
|
||||
showError(parseApiError(error, t('auth.register.networkRetry')), t('auth.register.sendFailed'))
|
||||
} finally {
|
||||
isSendingCode.value = false
|
||||
resetTurnstile()
|
||||
@@ -798,7 +830,7 @@ const handleCodeComplete = async (code: string) => {
|
||||
if (emailVerified.value) return
|
||||
|
||||
isLoading.value = true
|
||||
loadingText.value = '验证中...'
|
||||
loadingText.value = t('auth.register.verifying')
|
||||
verificationError.value = false
|
||||
|
||||
try {
|
||||
@@ -806,16 +838,16 @@ const handleCodeComplete = async (code: string) => {
|
||||
|
||||
if (response.success) {
|
||||
emailVerified.value = true
|
||||
success('邮箱验证通过,请继续完成注册', '验证成功')
|
||||
success(t('auth.register.emailVerified'), t('auth.register.verifySuccess'))
|
||||
} else {
|
||||
verificationError.value = true
|
||||
showError(response.message || '验证码错误', '验证失败')
|
||||
showError(response.message || t('auth.register.codeInvalid'), t('auth.register.verifyFailed'))
|
||||
// Clear the code input
|
||||
clearCodeInputs()
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
verificationError.value = true
|
||||
showError(parseApiError(error, '验证码错误,请重试'), '验证失败')
|
||||
showError(parseApiError(error, t('auth.register.codeRetry')), t('auth.register.verifyFailed'))
|
||||
// Clear the code input
|
||||
clearCodeInputs()
|
||||
} finally {
|
||||
@@ -826,18 +858,18 @@ const handleCodeComplete = async (code: string) => {
|
||||
const handleSubmit = async () => {
|
||||
// Validate password match
|
||||
if (formData.value.password !== formData.value.confirmPassword) {
|
||||
showError('两次输入的密码不一致', '密码不匹配')
|
||||
showError(t('auth.register.passwordMismatch'), t('auth.register.passwordError'))
|
||||
return
|
||||
}
|
||||
|
||||
if (passwordError.value) {
|
||||
showError(passwordError.value, '密码错误')
|
||||
showError(passwordError.value, t('auth.register.passwordError'))
|
||||
return
|
||||
}
|
||||
|
||||
// Check email verification if required
|
||||
if (props.requireEmailVerification && !emailVerified.value) {
|
||||
showError('请先完成邮箱验证')
|
||||
showError(t('auth.register.completeEmailVerification'))
|
||||
return
|
||||
}
|
||||
if (
|
||||
@@ -845,17 +877,17 @@ const handleSubmit = async () => {
|
||||
currentTurnstileAction.value === 'register' &&
|
||||
!turnstileToken.value
|
||||
) {
|
||||
showError('请先完成人机验证')
|
||||
showError(t('auth.register.completeTurnstile'))
|
||||
return
|
||||
}
|
||||
|
||||
if (privacyPolicyEnabled.value && !privacyAccepted.value) {
|
||||
showError('请先阅读并同意隐私政策')
|
||||
showError(t('auth.register.agreePrivacy'))
|
||||
return
|
||||
}
|
||||
|
||||
isLoading.value = true
|
||||
loadingText.value = '注册中...'
|
||||
loadingText.value = t('auth.register.submitting')
|
||||
|
||||
try {
|
||||
// 构建请求数据:邮箱可选
|
||||
@@ -880,13 +912,13 @@ const handleSubmit = async () => {
|
||||
|
||||
const response = await authApi.register(registerData)
|
||||
|
||||
success(response.message || '欢迎加入!请登录以继续', '注册成功')
|
||||
success(response.message || t('auth.register.successMessage'), t('auth.register.successTitle'))
|
||||
|
||||
emit('success')
|
||||
isOpen.value = false
|
||||
} catch (error: unknown) {
|
||||
resetTurnstile()
|
||||
showError(parseApiError(error, '注册失败,请重试'), '注册失败')
|
||||
showError(parseApiError(error, t('auth.register.submitRetry')), t('auth.register.submitFailed'))
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
resetTurnstile()
|
||||
|
||||
@@ -183,16 +183,17 @@ describe('RegisterDialog Turnstile verification flow', () => {
|
||||
emailInput.dispatchEvent(new Event('input'))
|
||||
await settle()
|
||||
|
||||
const sendButtonBeforeToken = Array.from(root.querySelectorAll('button'))
|
||||
.find((button) => button.textContent?.includes('请先完成人机验证')) as HTMLButtonElement
|
||||
expect(sendButtonBeforeToken.disabled).toBe(true)
|
||||
const buttons = Array.from(root.querySelectorAll('button')) as HTMLButtonElement[]
|
||||
const sendButtonBeforeToken = buttons.find((button) => button.type === 'button' && button.disabled && !button.dataset.testid)
|
||||
expect(sendButtonBeforeToken).toBeDefined()
|
||||
expect(sendButtonBeforeToken?.disabled).toBe(true)
|
||||
|
||||
const turnstileButton = root.querySelector('[data-testid="turnstile-widget"]') as HTMLButtonElement
|
||||
turnstileButton.click()
|
||||
await settle()
|
||||
|
||||
const sendButton = Array.from(root.querySelectorAll('button'))
|
||||
.find((button) => button.textContent?.includes('发送验证码')) as HTMLButtonElement
|
||||
const sendButton = buttons.find((button) => button.disabled === false && button.type === 'button' && button !== turnstileButton) as HTMLButtonElement
|
||||
expect(sendButton).toBeDefined()
|
||||
expect(sendButton.disabled).toBe(false)
|
||||
sendButton.click()
|
||||
await settle()
|
||||
|
||||
@@ -186,6 +186,6 @@ describe('RegisterDialog Turnstile flow', () => {
|
||||
await settle()
|
||||
|
||||
expect(registerMock).not.toHaveBeenCalled()
|
||||
expect(toastErrorMock).toHaveBeenCalledWith('人机验证加载失败,请重试', '人机验证失败')
|
||||
expect(toastErrorMock).toHaveBeenCalledWith('人机验证加载失败,请重试', '人机验证')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user