注册接口非必要
实现效果
模型定义
type/dto/user
/**
* 用户账号注册
*/
export interface UserAccountRegisterDto {
/**
* 验证码
*/
code: string;
/**
* 用户登录密码
*/
password: string;
/**
* 用户登录名称
*/
username: string;
}
/**
* 更新用户信息
*/
export interface UpdateUserInfoDto {
/**
* 用户地址
*/
addressCode: string;
/**
* 头像路径
*/
avatarUrl: string;
/**
* 性别
*/
gender: number;
/**
* 昵称
*/
nickName: string;
/**
* 手机号码
*/
phoneNumber: string;
}
types/vo/upload.ts
/**
* 上传文件信息 FileInfo
*/
export interface UploadFileInfo {
attr: {
empty: boolean;
};
basePath: string;
contentType: string;
createTime: Date;
ext: string;
fileAcl: { [key: string]: any };
filename: string;
id: string;
metadata: { [key: string]: string };
objectId: string;
objectType: string;
originalFilename: string;
path: string;
platform: string;
size: number;
thContentType: string;
thFileAcl: { [key: string]: any };
thFilename: string;
thMetadata: { [key: string]: string };
thSize: number;
thUrl: string;
thUserMetadata: { [key: string]: string };
url: string;
userMetadata: { [key: string]: string };
}
接口函数
/**
* 用户账号注册
*
* @param body 注册 DTO
*/
export const postUserAccountRegisterApi = (body: UserAccountRegisterDto) => {
return useHttpPost<LoginDetails>({
url: '/user/register',
body,
});
};
/**
* 更新当前用户信息
*/
export const postUpdateMyInfoApi = (body: Partial<UpdateUserInfoDto>) => {
return useHttpPost({
url: '/my/update',
body,
});
};
页面主内容[pages/register/index.vue]
- BaseRegister
- FillRegister
<script lang="ts" setup>
import BaseRegister from './components/BaseRegister.vue';
import FillRegister from './components/FillRegister.vue';
definePageMeta({
layout: 'single',
});
// 步骤索引
const { count: stepsIndex, inc, dec } = useCounter(1, { min: 1, max: 3 });
const baseRegisterRef = ref<InstanceType<typeof BaseRegister>>();
const fillRegisterRef = ref<InstanceType<typeof FillRegister>>();
// 重置表单
const onResetForm = () => {
baseRegisterRef.value?.onReset();
fillRegisterRef.value?.onReset();
};
/**
* 表单加载中
*/
const formLoading = computed(() => {
return baseRegisterRef.value?.loading || fillRegisterRef.value?.loading;
});
/**
* 触发注册用户
*/
const onRegisterUser = async () => {
const b = await baseRegisterRef.value!.onSubmit();
if (b) {
inc(1);
}
};
/**
* 触发更新用户信息
*/
const onUpdateUserInfo = async () => {
const b = await fillRegisterRef.value!.onSubmit();
if (b) {
inc(1);
}
};
</script>
<template>
<div class="register-container">
<a-card
:bordered="false"
:size="'small'"
class="pl-12 pr-6 pb-6 shadow-md rounded-3xl!"
>
<LogoBanner />
<a-divider />
<!-- 填写表单数据, 仅在 1、2 步骤时显示 -->
<a-space v-if="stepsIndex < 3" :align="'start'" fill>
<a-steps
:current="stepsIndex"
:direction="'vertical'"
:type="'dot'"
label-placement="vertical"
>
<a-step description="信息">基本信息填写</a-step>
<a-step description="非必要填写信息">附带信息填写</a-step>
<a-step description="欢迎使用">欢迎</a-step>
</a-steps>
<!-- main -->
<BaseRegister v-if="stepsIndex === 1" ref="baseRegisterRef" />
<FillRegister v-if="stepsIndex === 2" ref="fillRegisterRef" />
<!-- main -->
</a-space>
<!-- 注册完成页面 -->
<div v-if="stepsIndex === 3">
<svg class="i-banner-onboarding-animate text-[18rem]" />
</div>
<a-space class="justify-between w-full pt-6">
<a-space>
<ThemeButton />
<a-button :shape="'circle'">
<template #icon>
<i
class="i-streamline-ai-settings-spark hover:i-streamline-ai-settings-spark-solid"
/>
</template>
</a-button>
</a-space>
<a-space v-if="stepsIndex < 3" class="steps-active">
<a-button @click="onResetForm">
<span>重置表单</span>
<template #icon>
<i class="i-material-symbols-refresh" />
</template>
</a-button>
<!-- 步骤一中注册用户 -->
<a-button
v-if="stepsIndex == 1"
:loading="formLoading"
type="primary"
@click="onRegisterUser"
>
<span>下一步</span>
<template #icon>
<i class="i-line-md-chevron-small-double-left rotate-180" />
</template>
</a-button>
<!-- 直接跳过步骤二 -->
<a-button v-if="stepsIndex == 2" type="secondary" @click="inc(1)">
<span>跳过</span>
<template #icon>
<i class="i-line-md-chevron-small-double-left rotate-180" />
</template>
</a-button>
<!-- 步骤二中进行更新操作 -->
<a-button
v-if="stepsIndex == 2"
type="primary"
@click="onUpdateUserInfo"
>
<span>下一步</span>
<template #icon>
<i class="i-solar-cloud-upload-bold-duotone" />
</template>
</a-button>
</a-space>
<a-button
v-else
:shape="'round'"
:size="'large'"
:type="'primary'"
@click="
navigateTo({
path: '/',
replace: true,
})
"
>
<a-space>
<i class="i-streamline-braille-blind" />
<span>进入系统</span>
</a-space>
</a-button>
</a-space>
</a-card>
</div>
</template>
<style scoped>
.register-container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
.register-form {
width: 450px;
padding-top: 0.5em;
}
}
</style>
页面步骤一[pages/register/components/BaseRegister.vue]
<script lang="ts" setup>
import type Form from '@arco-design/web-vue/es/form';
import { formRules } from '~/pages/rules';
import { useRequest, useUserStore } from '#imports';
import { getImageCaptchaApi } from '~/api/captcha';
import type { UserAccountRegisterDto } from '~/types/dto/user';
import { postUserAccountRegisterApi } from '~/api/user';
const userStore = useUserStore();
// 请求 图形验证码接口
const {
data: imageCaptchaData,
refresh: imageCaptchaRefresh,
loading: captchaLoading,
} = useRequest(getImageCaptchaApi, {
manual: false,
onSuccess: () => {
registerForm.value.code = '';
},
});
const registerForm = ref<
UserAccountRegisterDto & { confirmPassword: string }
>({
username: 'admin123',
password: 'admin123',
confirmPassword: 'admin123',
code: '',
});
const registerFormRef = ref<InstanceType<typeof Form>>();
/**
* 确认密码校验
*/
const confirmPasswordValidator = [
{
required: true,
message: '用户密码不能为空',
},
{
validator: (_: string, cb: (m: string) => void) => {
const password = registerForm.value.password;
const confirmPassword = registerForm.value.confirmPassword;
if (!!password && !!confirmPassword && password !== confirmPassword) {
cb('两次输入的密码不一致');
}
},
},
];
const { runAsync: accountRegisterRunAsync, loading: accountRegisterLoading } =
useRequest(postUserAccountRegisterApi, {
onError: () => {
registerForm.value.code = '';
},
onSuccess: (userDetail) => {
console.log(userDetail);
userStore.loadUserDetails(userDetail);
},
});
/**
* 提交表单数据
*/
const onSubmit = async () => {
const validate = await registerFormRef.value?.validate();
if (validate) {
Message.info('请完成表单内容填写');
return false;
}
if (registerForm.value.code.length !== 5) {
imageCaptchaRefresh();
registerFormRef.value?.setFields({
code: {
status: 'error',
message: '验证码格式错误',
},
});
return false;
}
// 发送注册请求
await accountRegisterRunAsync({ ...unref(registerForm) });
return true;
};
/**
* 重置表单
*/
const onReset = () => {
registerFormRef.value?.resetFields();
};
defineExpose({
/**
* 重置表单
*/
onReset,
/**
* 提交表单
*/
onSubmit,
/**
* 提交表单中的加载状态
*/
loading: accountRegisterLoading,
});
</script>
<template>
<a-form
ref="registerFormRef"
:model="registerForm"
:rules="formRules"
:wrapper-col-props="{ span: 20, offset: 2 }"
class="register-form"
>
<a-form-item :validate-trigger="'input'" field="username" hide-asterisk>
<a-input
v-model="registerForm.username"
:max-length="30"
placeholder="请输入账号"
>
<template #prefix>
<i class="i-streamline-user-profile-focus pr-1" />
</template>
</a-input>
</a-form-item>
<a-form-item :validate-trigger="'input'" field="password" hide-asterisk>
<a-input-password
v-model="registerForm.password"
:max-length="30"
placeholder="请输入密码"
>
<template #prefix>
<i
class="i-streamline-interface-user-lock-actions-lock-geometric-human-person-single-up-user pr-1"
/>
</template>
</a-input-password>
</a-form-item>
<a-form-item
:rules="confirmPasswordValidator"
:validate-trigger="'input'"
field="confirmPassword"
hide-asterisk
>
<a-input-password
v-model="registerForm.confirmPassword"
:max-length="30"
placeholder="请重新输入密码"
>
<template #prefix>
<i
class="i-streamline-interface-user-lock-actions-lock-geometric-human-person-single-up-user pr-1"
/>
</template>
</a-input-password>
</a-form-item>
<a-form-item
:rules="[]"
:validate-trigger="'input'"
field="code"
hide-asterisk
>
<a-space :size="'large'" class="pt-2.5">
<a-spin :loading="captchaLoading">
<a-image
:height="58"
:preview="false"
:src="imageCaptchaData?.base64Data"
:width="120"
class="rounded-md! cursor-pointer"
@click="imageCaptchaRefresh"
/>
</a-spin>
<a-verification-code
v-model="registerForm.code"
:length="5"
style="width: 230px"
/>
</a-space>
</a-form-item>
</a-form>
</template>
<style scoped></style>
页面步骤二[pages/register/components/FillRegister.vue]
<script lang="ts" setup>
import type Form from '@arco-design/web-vue/es/form';
import type { UpdateUserInfoDto } from '~/types/dto/user';
import { useRequest } from '#imports';
import { postUpdateMyInfoApi } from '~/api/user';
const fillForm = ref<UpdateUserInfoDto>({
avatarUrl: '',
nickName: '',
gender: 0,
phoneNumber: '',
addressCode: '',
});
const fillFormRef = ref<InstanceType<typeof Form>>();
const { loading: updateMyInfoLoading, runAsync: updateMyInfoRunAsync } =
useRequest(postUpdateMyInfoApi);
// 重置表单数据
const onReset = () => {
fillFormRef.value?.resetFields();
};
// 提交表单
const onSubmit = async () => {
const validate = await fillFormRef.value?.validate();
if (validate) {
Message.info('请完成表单内容填写');
return false;
}
// 发送更新请求
await updateMyInfoRunAsync({ ...unref(fillForm) });
return true;
};
defineExpose({
// 重置表单数据
onReset,
// 提交表单 加载中
loading: updateMyInfoLoading,
// 提交表单
onSubmit,
});
</script>
<template>
<a-form
ref="fillFormRef"
:model="fillForm"
:wrapper-col-props="{ span: 20, offset: 2 }"
class="register-form"
>
<a-form-item>
<a-space class="justify-between w-full">
<svg class="i-banner-upload_image w-[10em] h-[10em]" />
<UploadAvatar
v-model="fillForm.avatarUrl"
action-url="http://localhost:7000/my/upload/avatar"
/>
</a-space>
</a-form-item>
<a-form-item
:rules="[
{
match: /^[\u4E00-\u9FA5a-zA-Z0-9·._\s]{1,20}$/,
message: '用户名称格式错误',
},
]"
hide-asterisk
>
<a-input
v-model="fillForm.nickName"
:max-length="30"
placeholder="请输入名称"
>
<template #prefix>
<i class="i-streamline-user-profile-focus pr-1" />
</template>
</a-input>
</a-form-item>
<a-form-item>
<AreaCascader
v-model:address="fillForm.addressCode"
placeholder="请选择地址"
/>
</a-form-item>
// TODO 请求后端获取字典
<a-form-item>
<a-radio-group v-model="fillForm.gender" :type="'radio'">
<a-radio :value="0" label="true">男性</a-radio>
<a-radio :value="1" label="true">女性</a-radio>
<a-radio :value="2" label="true">保密</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</template>
<style scoped></style>