Compare commits

...
2 Commits
Author SHA1 Message Date
TsMask 10603c0298 fix: 注册功能接口变更 2024-11-30 16:16:01 +08:00
zhongzm 8f198f789d fix:添加查询是否注册,页面问题修复 2024-11-29 18:07:43 +08:00
8 changed files with 362 additions and 228 deletions

No files matched your search

+7 -9
View File
@@ -1,8 +1,8 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useCountDown, useLoading } from '@sa/hooks'; import { useCountDown, useLoading } from '@sa/hooks';
import { $t } from '@/locales'; import { $t } from '@/locales';
import {REG_EMAIL} from '@/constants/reg'; import { REG_EMAIL } from '@/constants/reg';
import {useAuthStore} from "@/store/modules/auth"; import { useAuthStore } from '@/store/modules/auth';
export function useCaptcha() { export function useCaptcha() {
const { loading, startLoading, endLoading } = useLoading(); const { loading, startLoading, endLoading } = useLoading();
@@ -39,20 +39,17 @@ export function useCaptcha() {
return true; return true;
} }
//获取验证码方法 //获取验证码方法
async function getCaptcha(email: string) { async function getCaptcha(email: string) {
console.log(email) const valid = isEmailValid(email);
//const valid = isPhoneValid(phone);
const valid = isEmailValid(email);
if (!valid || loading.value) { if (!valid || loading.value) {
return; return null;
} }
startLoading(); startLoading();
await authStore.captcha( const data = await authStore.captcha(email);
email,
);
await new Promise(resolve => { await new Promise(resolve => {
setTimeout(resolve, 500); setTimeout(resolve, 500);
}); });
@@ -62,6 +59,7 @@ export function useCaptcha() {
start(); start();
endLoading(); endLoading();
return data;
} }
return { return {
+11
View File
@@ -228,6 +228,17 @@ const local: any = {
birthDate: 'Birth Date', birthDate: 'Birth Date',
birthDatePlaceholder: 'Please select birth date', birthDatePlaceholder: 'Please select birth date',
birthDateRequired: 'Please select birth date', birthDateRequired: 'Please select birth date',
usernameLengthLimit:"Username is too short",
usernameExists:"The username is already registered",
usernameRequired:"The username cannot be empty",
phoneInvalid:"The mobile phone number is incorrect",
phoneExists:"The mobile phone number has been registered",
emailInvalid:"The email is incorrect",
emailExists:"The email has been registered",
emailRequired:"The email cannot be empty",
codeRequired:"The code cannot be empty",
passwordRequired:"The password cannot be empty",
passwordLength:"The password is too short",
}, },
resetPwd: { resetPwd: {
title: 'Reset Password' title: 'Reset Password'
+11
View File
@@ -228,6 +228,17 @@ const local:any = {
birthDate: '出生日期', birthDate: '出生日期',
birthDatePlaceholder: '请选择出生日期', birthDatePlaceholder: '请选择出生日期',
birthDateRequired: '请选择出生日期', birthDateRequired: '请选择出生日期',
usernameLengthLimit:"用户名太短",
usernameExists:"用户名已经注册",
usernameRequired:"用户名不能为空",
phoneInvalid:"手机号格式不正确",
phoneExists:"手机号已经注册",
emailInvalid:"邮箱格式不正确",
emailExists:"邮箱已经注册",
emailRequired:"邮箱不能为空",
codeRequired:"验证码不能为空",
passwordRequired:"密码不能为空",
passwordLength:"密码太短",
}, },
resetPwd: { resetPwd: {
title: '重置密码' title: '重置密码'
+14 -4
View File
@@ -13,14 +13,23 @@ export function fetchLogin(body: Api.Auth.LoginBody) {
data: body data: body
}); });
} }
//邮箱验证码接口 //邮箱验证码接口
export function sendCaptcha(body:Api.Auth.EmailCaptcha){ export function sendCaptcha(body: Api.Auth.EmailCaptcha) {
return request({ return request({
url:`/system/email/code?email=${body.email}`, url: `/code?email=${body.email}`,
method:'get', method: 'get'
}) });
} }
//验证注册 //验证注册
export function doCheckUserRepeat(body: Api.Auth.CheckBody) {
return request<boolean>({
url: '/auth/checkRepeat',
method: 'post',
data: body
});
}
//添加注册 //添加注册
export function fetchRegister(body: Api.Auth.RegisterBody) { export function fetchRegister(body: Api.Auth.RegisterBody) {
@@ -30,6 +39,7 @@ export function fetchRegister(body: Api.Auth.RegisterBody) {
data: body data: body
}); });
} }
/** logout */ /** logout */
export function doDeleteLogout() { export function doDeleteLogout() {
return request<App.Service.Response<null>>({ return request<App.Service.Response<null>>({
+16 -9
View File
@@ -7,7 +7,7 @@ import { localStg } from '@/utils/storage';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { useRouteStore } from '../route'; import { useRouteStore } from '../route';
import { clearAuthStorage, emptyInfo, getToken } from './shared'; import { clearAuthStorage, emptyInfo, getToken } from './shared';
import {sendCaptcha} from "@/service/api/auth"; import { doCheckUserRepeat, sendCaptcha } from '@/service/api/auth';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => { export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const routeStore = useRouteStore(); const routeStore = useRouteStore();
@@ -122,6 +122,15 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
} }
return false; return false;
} }
/**
* 检查用户信息是否已存在
*/
async function checkUserRepeat(checkForm: Api.Auth.CheckBody) {
const { data, error } = await doCheckUserRepeat(checkForm);
return { exists: data, error };
}
/** /**
* Register new user * Register new user
*/ */
@@ -139,16 +148,13 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
endLoading(); endLoading();
return !error; return !error;
} }
async function captcha(email:string){
async function captcha(email: string) {
if (!email) { if (!email) {
return; return null;
}
try {
await sendCaptcha({ email }); // 这里调用后端接口发送验证码
} catch (error) {
} }
const { data, error } = await sendCaptcha({ email }); // 这里调用后端接口发送验证码
return { data, error };
} }
return { return {
@@ -163,5 +169,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
refreshUserInfo, refreshUserInfo,
register, register,
captcha, captcha,
checkUserRepeat
}; };
}); });
+6
View File
@@ -152,6 +152,12 @@ declare namespace Api {
interface EmailCaptcha{ interface EmailCaptcha{
email:string; email:string;
} }
interface CheckBody{
username?: string;
email?: string;
phonenumber?:string;
authType: string;
}
} }
/** /**
+1
View File
@@ -53,6 +53,7 @@ declare global {
const doAddDict: typeof import('../service/api/dict')['doAddDict'] const doAddDict: typeof import('../service/api/dict')['doAddDict']
const doAddMenu: typeof import('../service/api/menu')['doAddMenu'] const doAddMenu: typeof import('../service/api/menu')['doAddMenu']
const doAddPost: typeof import('../service/api/post')['doAddPost'] const doAddPost: typeof import('../service/api/post')['doAddPost']
const doCheckUserRepeat: typeof import('../service/api/auth')['doCheckUserRepeat']
const doDeleteDept: typeof import('../service/api/dept')['doDeleteDept'] const doDeleteDept: typeof import('../service/api/dept')['doDeleteDept']
const doDeleteDict: typeof import('../service/api/dict')['doDeleteDict'] const doDeleteDict: typeof import('../service/api/dict')['doDeleteDict']
const doDeleteLogout: typeof import('../service/api/auth')['doDeleteLogout'] const doDeleteLogout: typeof import('../service/api/auth')['doDeleteLogout']
+296 -206
View File
@@ -1,24 +1,26 @@
<script setup lang="ts"> <script setup lang="ts">
import {computed, reactive, ref} from 'vue'; import { computed, reactive, ref } from 'vue';
import {useI18n} from 'vue-i18n'; // 添加这行 import { useI18n } from 'vue-i18n';
import {useAuthStore} from '@/store/modules/auth'; import { useAuthStore } from '@/store/modules/auth';
import {useRouterPush} from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import {useAntdForm, useFormRules} from '@/hooks/common/form'; import { useFormRules } from '@/hooks/common/form';
import {useCaptcha} from '@/hooks/business/captcha'; import { useCaptcha } from '@/hooks/business/captcha';
import {useWindowSize} from '@vueuse/core'; import { useWindowSize } from '@vueuse/core';
import {registerTerms} from '@/views/_builtin/login/modules/terms'; import { registerTerms } from '@/views/_builtin/login/modules/terms';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import type {Rule} from 'ant-design-vue/es/form'; import type { Rule } from 'ant-design-vue/es/form';
const { t } = useI18n(); const { t } = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();
// 添加两个表单引用
const basicFormRef = ref();
const securityFormRef = ref();
defineOptions({ defineOptions({
name: 'Register' name: 'Register'
}); });
const { toggleLoginModule } = useRouterPush(); const { toggleLoginModule } = useRouterPush();
const { formRef, validate } = useAntdForm();
const { label, isCounting, loading, getCaptcha } = useCaptcha(); const { label, isCounting, loading, getCaptcha } = useCaptcha();
const { width } = useWindowSize(); const { width } = useWindowSize();
@@ -29,17 +31,19 @@ const currentStep = ref(0);
// 是否同意协议 // 是否同意协议
const agreeTerms = ref(false); const agreeTerms = ref(false);
// 定义一个统一的数据模型 // 定义一个统一的数据模型
interface RegisterModel { interface RegisterModel {
username: string; username: string;
password: string; password: string;
email: string; email: string;
fullName: string; fullName: string;
age:0, age: 0;
gender: string; gender: string;
phone: string; phone: string;
address: string; address: string;
code: string; code: string;
uuid: string;
authType: string; authType: string;
} }
@@ -49,11 +53,12 @@ const model = reactive<RegisterModel>({
password: '', password: '',
email: '', email: '',
fullName: '', fullName: '',
age:0, age: 0,
gender: '', gender: '',
phone: '', phone: '',
address: '', address: '',
code: '', code: '',
uuid: '',
authType: 'u' authType: 'u'
}); });
@@ -64,7 +69,6 @@ interface BasicFormModel {
birthDate: string; birthDate: string;
gender: string; gender: string;
phone: string; phone: string;
email: string;
address: string; address: string;
} }
@@ -74,7 +78,6 @@ const basicModel = reactive<BasicFormModel>({
birthDate: '', birthDate: '',
gender: '', gender: '',
phone: '', phone: '',
email: '',
address: '' address: ''
}); });
@@ -82,6 +85,7 @@ const basicModel = reactive<BasicFormModel>({
interface SecurityFormModel { interface SecurityFormModel {
email: string; email: string;
code: string; code: string;
uuid: string;
password: string; password: string;
confirmPassword: string; confirmPassword: string;
} }
@@ -89,27 +93,85 @@ interface SecurityFormModel {
const securityModel = reactive<SecurityFormModel>({ const securityModel = reactive<SecurityFormModel>({
email: '', email: '',
code: '', code: '',
uuid: '',
password: '', password: '',
confirmPassword: '' confirmPassword: ''
}); });
// 第一步表单验证规则 // 第一步表单验证规则
const basicRules = computed<Record<string, Rule | Rule[]>>(() => { const basicRules = computed<Record<string, Rule | Rule[]>>(() => {
const { formRules } = useFormRules(); const validateUsername = async (_rule: Rule, value: string) => {
if (value && (value.length < 4 || value.length > 20)) {
return Promise.reject(t('page.login.register.usernameLengthLimit'));
}
if (value) {
const { exists } = await authStore.checkUserRepeat({ username: value, authType: 'u' });
if (exists) {
return Promise.reject(t('page.login.register.usernameExists'));
}
}
return Promise.resolve();
};
const validatePhone = async (_rule: Rule, value: string) => {
if (!value) return Promise.resolve();
const phonePattern = /^1[3-9]\d{9}$/;
if (!phonePattern.test(value)) {
return Promise.reject(t('page.login.register.phoneInvalid'));
}
const { exists } = await authStore.checkUserRepeat({ phonenumber: value, authType: 'u' });
if (exists) {
return Promise.reject(t('page.login.register.phoneExists'));
}
return Promise.resolve();
};
return { return {
username: formRules.username, username: [
email: formRules.email, { required: true, message: t('page.login.register.usernameRequired'), trigger: 'change' },
{ validator: validateUsername, trigger: 'blur' }
],
phone: [{ validator: validatePhone, trigger: 'blur' }],
birthDate: [{ required: true, message: t('page.login.register.birthDateRequired') }], birthDate: [{ required: true, message: t('page.login.register.birthDateRequired') }],
phone: [{ pattern: /^1[3-9]\d{9}$/, message: t('form.phone.invalid'), trigger: 'blur' }] fullName: []
}; };
}); });
// 第三步表单验证规则 // 第三步表单验证规则
const securityRules = computed<Record<string, Rule | Rule[]>>(() => { const securityRules = computed<Record<string, Rule | Rule[]>>(() => {
const { formRules, createConfirmPwdRule } = useFormRules(); const { createConfirmPwdRule } = useFormRules();
const validateEmail = async (_rule: Rule, value: string) => {
if (!value) {
return Promise.reject(t('page.login.register.emailRequired'));
}
const emailPattern = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/;
if (!emailPattern.test(value)) {
return Promise.reject(t('page.login.register.emailInvalid'));
}
const { exists } = await authStore.checkUserRepeat({ email: value, authType: 'u' });
if (exists) {
return Promise.reject(t('page.login.register.emailExists'));
}
return Promise.resolve();
};
return { return {
code: formRules.code, email: [
password: formRules.pwd, { required: true, message: t('page.login.register.emailRequired'), trigger: 'change' },
{ validator: validateEmail, trigger: 'blur' }
],
code: [{ required: true, message: t('page.login.register.codeRequired') }],
password: [
{ required: true, message: t('page.login.register.passwordRequired') },
{ min: 6, message: t('page.login.register.passwordLength') }
],
confirmPassword: createConfirmPwdRule(securityModel.password) confirmPassword: createConfirmPwdRule(securityModel.password)
}; };
}); });
@@ -122,43 +184,61 @@ const terms = computed(() => {
// 步骤控制函数 // 步骤控制函数
async function nextStep() { async function nextStep() {
if (currentStep.value === 0) { try {
await validate(); if (currentStep.value === 0) {
// 复制电话号码到第三步 // 验证第一步表单
securityModel.email = basicModel.email; await basicFormRef.value?.validate();
}
if (currentStep.value === 1) {
if (!agreeTerms.value) {
window.$message?.error(t('page.login.register.agreeTermsFirst'));
return;
} }
if (currentStep.value === 1) {
if (!agreeTerms.value) {
window.$message?.error(t('page.login.register.agreeTermsFirst'));
return;
}
}
// 验证通过后增加步骤
currentStep.value += 1;
} catch (error) {
// 验证失败时不增加步骤
console.error('Validation failed:', error);
} }
currentStep.value += 1;
} }
// 返回
function prevStep() { function prevStep() {
currentStep.value -= 1; currentStep.value -= 1;
} }
//注册按钮
async function handleCaptcha() {
const res = await getCaptcha(securityModel.email);
if (res) {
securityModel.uuid = res.data.uuid;
if (res.data?.text) {
securityModel.code = res.data.text;
}
}
}
// 注册按钮
async function handleSubmit() { async function handleSubmit() {
try { try {
await validate(); await securityFormRef.value?.validate();
// 整合表单数据 // 整合表单数据
model.username = basicModel.username; model.username = basicModel.username;
model.password = securityModel.password; model.password = securityModel.password;
model.email = securityModel.email; model.email = securityModel.email; // 使用第三步的 email
model.fullName = basicModel.fullName; model.fullName = basicModel.fullName;
model.gender = basicModel.gender; // 直接使用 gender 值 model.gender = basicModel.gender;
model.phone = basicModel.phone; model.phone = basicModel.phone;
model.address = basicModel.address; model.address = basicModel.address;
model.code = securityModel.code; model.code = securityModel.code;
model.uuid = securityModel.uuid;
const success = await authStore.register({ const success = await authStore.register({
...model, ...model,
age: dayjs().diff(dayjs(basicModel.birthDate), 'year'), age: dayjs().diff(dayjs(basicModel.birthDate), 'year'),
sex: model.gender, // 直接使用 gender 值,不需要转换 sex: model.gender,
phonenumber: model.phone, phonenumber: model.phone
}); });
if (success) { if (success) {
@@ -175,187 +255,196 @@ const showSteps = computed(() => !isMobile.value);
</script> </script>
<template> <template>
<ASteps <div class="register-container">
v-if="showSteps" <ASteps
:current="currentStep" v-if="showSteps"
size="small" :current="currentStep"
class="max-w-full mb-16px" size="small"
direction="horizontal" class="max-w-full mb-16px"
:responsive="false" direction="horizontal"
> :responsive="false"
<AStep :title="t('page.login.register.basicInfo')" /> >
<AStep :title="t('page.login.register.terms')" /> <AStep :title="t('page.login.register.basicInfo')" />
<AStep :title="t('page.login.register.security')" /> <AStep :title="t('page.login.register.terms')" />
</ASteps> <AStep :title="t('page.login.register.security')" />
</ASteps>
<div v-else class="mobile-step-indicator mb-16px text-center"> <div v-else class="mobile-step-indicator mb-16px text-center">
{{ currentStep + 1 }}/3: {{ {{ currentStep + 1 }}/3: {{
currentStep === 0 currentStep === 0
? t('page.login.register.basicInfo') ? t('page.login.register.basicInfo')
: currentStep === 1 : currentStep === 1
? t('page.login.register.terms') ? t('page.login.register.terms')
: t('page.login.register.security') : t('page.login.register.security')
}} }}
</div> </div>
<div class="step-content"> <div class="step-content">
<!-- 第一步:基本信息 --> <!-- 第一步:基本信息 -->
<div v-if="currentStep === 0"> <div v-show="currentStep === 0">
<AForm <AForm
ref="formRef" ref="basicFormRef"
:model="basicModel" :model="basicModel"
:rules="basicRules" :rules="basicRules"
:label-wrap="true" :label-wrap="true"
:label-col="{ span: 8 }" :label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }" :wrapper-col="{ span: 16 }"
> >
<ARow :gutter="[8,2]"> <ARow :gutter="[8,2]">
<ACol :span="24" :lg="24"> <ACol :span="24" :lg="24">
<AFormItem name="username" :label="t('page.login.register.username')"> <AFormItem name="username" :label="t('page.login.register.username')">
<AInput v-model:value="basicModel.username" /> <AInput v-model:value="basicModel.username" />
</AFormItem> </AFormItem>
</ACol> </ACol>
<ACol :span="24" :lg="24"> <ACol :span="24" :lg="24">
<AFormItem name="fullName" :label="t('page.login.register.fullName')"> <AFormItem name="fullName" :label="t('page.login.register.fullName')">
<AInput v-model:value="basicModel.fullName" /> <AInput v-model:value="basicModel.fullName" />
</AFormItem> </AFormItem>
</ACol> </ACol>
<ACol :xs="12" :sm="12" :lg="24"> <ACol :xs="12" :sm="12" :lg="24">
<AFormItem name="birthDate" :label="t('page.login.register.birthDate')"> <AFormItem name="birthDate" :label="t('page.login.register.birthDate')">
<ADatePicker <ADatePicker
v-model:value="basicModel.birthDate" v-model:value="basicModel.birthDate"
class="!w-full birth-date-picker" class="!w-full birth-date-picker"
:placeholder="t('page.login.register.birthDatePlaceholder')" :placeholder="t('page.login.register.birthDatePlaceholder')"
:disabled-date="(current: dayjs.Dayjs) => current && current.isAfter(dayjs())" :disabled-date="(current: dayjs.Dayjs) => current && current.isAfter(dayjs())"
/>
</AFormItem>
</ACol>
<ACol :xs="12" :sm="12" :lg="24">
<AFormItem name="gender" :label="t('page.login.register.gender')">
<ASelect v-model:value="basicModel.gender">
<ASelectOption value="0">{{ t('page.login.register.male') }}</ASelectOption>
<ASelectOption value="1">{{ t('page.login.register.female') }}</ASelectOption>
</ASelect>
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="phone" :label="t('page.login.register.phone')">
<AInput v-model:value="basicModel.phone" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="address" :label="t('page.login.register.address')">
<ATextarea v-model:value="basicModel.address" :rows="2" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton>
<AButton block size="small" @click="toggleLoginModule('pwd-login')">
{{ t('page.login.common.back') }}
</AButton>
</ASpace>
</ACol>
</ARow>
</AForm>
</div>
<!-- 第二步:协议 -->
<div v-show="currentStep === 1">
<ATextarea
:value="terms"
:rows="12"
readonly
class="mb-16px"
size="small"
:style="{ fontSize: '14px', lineHeight: '1.6' }"
/>
<div class="mb-16px">
<ACheckbox
v-model:checked="agreeTerms"
class="terms-checkbox"
>
{{ t('page.login.register.agreeTerms') }}
</ACheckbox>
</div>
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" :disabled="!agreeTerms" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton>
</ASpace>
</div>
<!-- 第三步:安全信息 -->
<div v-show="currentStep === 2">
<AForm
ref="securityFormRef"
:model="securityModel"
:rules="securityRules"
:label-wrap="true"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
class="compact-form"
>
<AFormItem name="email" :label="t('page.login.register.email')">
<AInput
v-model:value="securityModel.email"
:placeholder="t('page.login.common.emailPlaceholder')"
/>
</AFormItem>
<AFormItem name="code" :label="t('page.login.register.code')">
<div class="w-full flex-y-center gap-8px">
<AInput
v-model:value="securityModel.code"
:placeholder="t('page.login.common.codePlaceholder')"
/> />
</AFormItem> <AButton
</ACol> size="small"
<ACol :xs="12" :sm="12" :lg="24"> :disabled="isCounting"
<AFormItem name="gender" :label="t('page.login.register.gender')"> :loading="loading"
<ASelect v-model:value="basicModel.gender"> @click="handleCaptcha()"
<ASelectOption value="male">{{ t('page.login.register.male') }}</ASelectOption> >
<ASelectOption value="female">{{ t('page.login.register.female') }}</ASelectOption> {{ label }}
</ASelect>
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="phone" :label="t('page.login.register.phone')">
<AInput v-model:value="basicModel.phone" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="email" :label="t('page.login.register.email')">
<AInput v-model:value="basicModel.email" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<AFormItem name="address" :label="t('page.login.register.address')">
<ATextarea v-model:value="basicModel.address" :rows="2" />
</AFormItem>
</ACol>
<ACol :lg="24" :span="24">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton> </AButton>
<AButton block size="small" @click="toggleLoginModule('pwd-login')"> </div>
{{ t('page.login.common.back') }} </AFormItem>
<AFormItem name="password" :label="t('page.login.register.password')">
<AInputPassword
v-model:value="securityModel.password"
:placeholder="t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword" :label="t('page.login.register.confirmPassword')">
<AInputPassword
v-model:value="securityModel.confirmPassword"
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
<AFormItem :wrapper-col="{ xs: { span: 24 }, sm: { span: 24 } }">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="handleSubmit">
{{ t('common.confirm') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton> </AButton>
</ASpace> </ASpace>
</ACol> </AFormItem>
</ARow> </AForm>
</AForm>
</div>
<!-- 第二步:协议 -->
<div v-if="currentStep === 1">
<ATextarea
:value="terms"
:rows="12"
readonly
class="mb-16px"
size="small"
:style="{ fontSize: '14px', lineHeight: '1.6' }"
/>
<div class="mb-16px">
<ACheckbox
v-model:checked="agreeTerms"
class="terms-checkbox"
>
{{ t('page.login.register.agreeTerms') }}
</ACheckbox>
</div> </div>
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" :disabled="!agreeTerms" @click="nextStep">
{{ t('page.login.register.next') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton>
</ASpace>
</div>
<!-- 第三步:安全信息 -->
<div v-if="currentStep === 2">
<AForm
ref="formRef"
:model="securityModel"
:rules="securityRules"
:label-wrap="true"
:label-col="{ span: 8 }"
:wrapper-col="{ span: 16 }"
class="compact-form"
>
<AFormItem name="email" :label="t('page.login.register.email')">
<AInput
v-model:value="securityModel.email"
:placeholder="t('page.login.common.emailPlaceholder')"
/>
</AFormItem>
<AFormItem name="code" :label="t('page.login.register.code')">
<div class="w-full flex-y-center gap-8px">
<AInput
v-model:value="securityModel.code"
:placeholder="t('page.login.common.codePlaceholder')"
/>
<AButton
size="small"
:disabled="isCounting"
:loading="loading"
@click="getCaptcha(securityModel.email)"
>
{{ label }}
</AButton>
</div>
</AFormItem>
<AFormItem name="password" :label="t('page.login.register.password')">
<AInputPassword
v-model:value="securityModel.password"
:placeholder="t('page.login.common.passwordPlaceholder')"
/>
</AFormItem>
<AFormItem name="confirmPassword" :label="t('page.login.register.confirmPassword')">
<AInputPassword
v-model:value="securityModel.confirmPassword"
:placeholder="t('page.login.common.confirmPasswordPlaceholder')"
/>
</AFormItem>
<AFormItem :wrapper-col="{ xs: { span: 24 }, sm: { span: 24 } }">
<ASpace direction="vertical" size="small" class="w-full">
<AButton type="primary" block size="small" @click="handleSubmit">
{{ t('common.confirm') }}
</AButton>
<AButton block size="small" @click="prevStep">
{{ t('page.login.register.prev') }}
</AButton>
</ASpace>
</AFormItem>
</AForm>
</div> </div>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.register-container {
width: 100%;
}
.step-content {
position: relative;
}
.step-form {
width: 100%;
}
@media (max-width: 640px) { @media (max-width: 640px) {
:deep(.ant-form) { :deep(.ant-form) {
.ant-form-item { .ant-form-item {
@@ -433,6 +522,7 @@ const showSteps = computed(() => !isMobile.value);
:deep(.ant-form-item-label) { :deep(.ant-form-item-label) {
white-space: normal; white-space: normal;
text-align: left; text-align: left;
> label { > label {
height: auto !important; height: auto !important;
padding-bottom: 4px; padding-bottom: 4px;