feat: 新增邮箱注册
This commit is contained in:
parent
67f6bad76b
commit
cf1ee9cbb0
@ -1,4 +1,10 @@
|
||||
import type { LoginDTO, LoginVO } from './types';
|
||||
import type { EmailCodeDTO, LoginDTO, LoginVO, RegisterDTO } from './types';
|
||||
import { post } from '@/utils/request';
|
||||
|
||||
export const login = (data: LoginDTO) => post<LoginVO>('/auth/login', data);
|
||||
|
||||
// 邮箱验证码
|
||||
export const emailCode = (data: EmailCodeDTO) => post('/resource/email/code', data);
|
||||
|
||||
// 注册账号
|
||||
export const register = (data: RegisterDTO) => post('/auth/register', data);
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
export interface LoginDTO {
|
||||
username: string;
|
||||
password: string;
|
||||
code?: string;
|
||||
// 二次确认密码
|
||||
confirmPassword?: string;
|
||||
}
|
||||
|
||||
export interface LoginVO {
|
||||
@ -116,3 +119,28 @@ export interface RoleDTO {
|
||||
*/
|
||||
roleName?: string;
|
||||
}
|
||||
|
||||
// 邮箱验证码
|
||||
export interface EmailCodeDTO {
|
||||
username?: string;
|
||||
}
|
||||
|
||||
// 邮箱注册
|
||||
export interface RegisterDTO {
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
username: string;
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
password: string;
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* 确认密码
|
||||
*/
|
||||
confirmPassword?: string;
|
||||
}
|
||||
|
||||
@ -6,10 +6,12 @@ import { reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { login } from '@/api';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useLoginFromStore } from '@/stores/modules/loginFrom';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const sessionStore = useSessionStore();
|
||||
const loginFromStore = useLoginFromStore();
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
@ -45,9 +47,15 @@ async function handleSubmit() {
|
||||
|
||||
<template>
|
||||
<div class="custom-form">
|
||||
<el-form ref="formRef" :model="formModel" :rules="rules" style="width: 230px">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formModel"
|
||||
:rules="rules"
|
||||
style="width: 230px"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="formModel.username" placeholder="请输入用户名" clearable>
|
||||
<el-input v-model="formModel.username" placeholder="请输入用户名">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<User />
|
||||
@ -59,7 +67,6 @@ async function handleSubmit() {
|
||||
<el-input
|
||||
v-model="formModel.password"
|
||||
placeholder="请输入密码"
|
||||
clearable
|
||||
type="password"
|
||||
show-password
|
||||
>
|
||||
@ -71,11 +78,22 @@ async function handleSubmit() {
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="width: 100%" @click="handleSubmit">
|
||||
<el-button type="primary" style="width: 100%" native-type="submit">
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 注册登录 -->
|
||||
<div class="form-tip font-size-12px flex items-center">
|
||||
<span>没有账号?</span>
|
||||
<span
|
||||
class="c-[var(--el-color-primar,#409eff)] cursor-pointer"
|
||||
@click="loginFromStore.setLoginFormType('RegistrationForm')"
|
||||
>
|
||||
立即注册
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@ -0,0 +1,200 @@
|
||||
<!-- 注册表单 -->
|
||||
<script lang="ts" setup>
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import type { RegisterDTO } from '@/api/auth/types';
|
||||
import { useCountdown } from '@vueuse/core';
|
||||
import { reactive, ref } from 'vue';
|
||||
import { emailCode, register } from '@/api';
|
||||
import { useLoginFromStore } from '@/stores/modules/loginFrom';
|
||||
|
||||
const loginFromStore = useLoginFromStore();
|
||||
const countdown = shallowRef(60);
|
||||
const { start, stop, resume } = useCountdown(countdown, {
|
||||
onComplete() {
|
||||
resume();
|
||||
},
|
||||
onTick() {
|
||||
countdown.value--;
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const formModel = ref<RegisterDTO>({
|
||||
username: '',
|
||||
password: '',
|
||||
code: '',
|
||||
confirmPassword: '',
|
||||
});
|
||||
|
||||
const rules = reactive<FormRules<RegisterDTO>>({
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
confirmPassword: [
|
||||
{ required: true, message: '请输入确认密码', trigger: 'blur' },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (value !== formModel.value.password) {
|
||||
return new Error('两次输入的密码不一致');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
username: [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!isEmail(value)) {
|
||||
return new Error('请输入正确的邮箱');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function isEmail(email: string) {
|
||||
const emailRegex = /^[\w.-]+@[a-z0-9.-]+\.[a-z]{2,4}$/i;
|
||||
return emailRegex.test(email);
|
||||
}
|
||||
async function handleSubmit() {
|
||||
try {
|
||||
await formRef.value?.validate();
|
||||
const params: RegisterDTO = {
|
||||
username: formModel.value.username,
|
||||
password: formModel.value.password,
|
||||
code: formModel.value.code,
|
||||
};
|
||||
await register(params);
|
||||
ElMessage.success('注册成功');
|
||||
formRef.value?.resetFields();
|
||||
resume();
|
||||
}
|
||||
catch (error) {
|
||||
console.error('请求错误:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取验证码
|
||||
async function getEmailCode() {
|
||||
if (formModel.value.username === '') {
|
||||
ElMessage.error('请输入邮箱');
|
||||
return;
|
||||
}
|
||||
if (!isEmail(formModel.value.username)) {
|
||||
return;
|
||||
}
|
||||
if (countdown.value > 0 && countdown.value < 60) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
start();
|
||||
await emailCode({ username: formModel.value.username });
|
||||
ElMessage.success('验证码发送成功');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('请求错误:', error);
|
||||
stop();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="custom-form">
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="formModel"
|
||||
:rules="rules"
|
||||
style="width: 230px"
|
||||
@submit.prevent="handleSubmit"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input v-model="formModel.username" placeholder="请输入邮箱" autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Message />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="code">
|
||||
<el-input v-model="formModel.code" placeholder="请输入验证码" autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Bell />
|
||||
</el-icon>
|
||||
</template>
|
||||
|
||||
<template #suffix>
|
||||
<div class="font-size-14px cursor-pointer bg-[var(0,0,0,0.4)]" @click="getEmailCode">
|
||||
{{ countdown === 0 || countdown === 60 ? "获取验证码" : `${countdown} s` }}
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="password">
|
||||
<el-input v-model="formModel.password" placeholder="请输入密码" autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Unlock />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="confirmPassword">
|
||||
<el-input v-model="formModel.confirmPassword" placeholder="请确认密码" autocomplete="off">
|
||||
<template #prefix>
|
||||
<el-icon>
|
||||
<Lock />
|
||||
</el-icon>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" style="width: 100%" native-type="submit">
|
||||
注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 返回登录 -->
|
||||
<div class="form-tip font-size-12px flex items-center">
|
||||
<span>已有账号,</span>
|
||||
<span
|
||||
class="c-[var(--el-color-primar,#409eff)] cursor-pointer"
|
||||
@click="loginFromStore.setLoginFormType('AccountPassword')"
|
||||
>
|
||||
返回登录
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.custom-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
margin-top: 24px;
|
||||
padding: 12px;
|
||||
background: #409eff;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@ -3,10 +3,15 @@ import { ref, watch } from 'vue';
|
||||
import logoPng from '@/assets/images/logo.png';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { useUserStore } from '@/stores';
|
||||
import { useLoginFromStore } from '@/stores/modules/loginFrom';
|
||||
import AccountPassword from './components/FormLogin/AccountPassword.vue';
|
||||
import RegistrationForm from './components/FormLogin/RegistrationForm.vue';
|
||||
import QrCodeLogin from './components/QrCodeLogin/index.vue';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const loginFromStore = useLoginFromStore();
|
||||
|
||||
const loginFormType = computed(() => loginFromStore.LoginFormType);
|
||||
|
||||
// 使用 defineModel 定义双向绑定的 visible(需 Vue 3.4+)
|
||||
const visible = defineModel<boolean>('visible');
|
||||
@ -27,7 +32,7 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// 切换登录模式
|
||||
// 切换二维码登录
|
||||
function toggleLoginMode() {
|
||||
isQrMode.value = !isQrMode.value;
|
||||
}
|
||||
@ -68,18 +73,29 @@ function onAfterLeave() {
|
||||
<SvgIcon v-else name="zhanghaodenglu" />
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<span class="content-title"> 登录后免费使用完整功能 </span>
|
||||
|
||||
<div v-if="!isQrMode" class="form-box">
|
||||
<!-- 表单容器,父组件可以自定定义表单插槽 -->
|
||||
<slot name="form">
|
||||
<!-- 父组件不用插槽则显示默认表单 默认账号密码登录 -->
|
||||
<div class="form-container">
|
||||
<!-- 父组件不用插槽则显示默认表单 默认使用 AccountPassword 组件 -->
|
||||
<div v-if="loginFormType === 'AccountPassword'" class="form-container">
|
||||
<span class="content-title"> 登录后免费使用完整功能 </span>
|
||||
|
||||
<el-divider content-position="center">
|
||||
账号密码登录
|
||||
</el-divider>
|
||||
|
||||
<AccountPassword />
|
||||
</div>
|
||||
|
||||
<div v-if="loginFormType === 'RegistrationForm'" class="form-container">
|
||||
<span class="content-title"> 登录后免费使用完整功能 </span>
|
||||
|
||||
<el-divider content-position="center">
|
||||
邮箱注册账号
|
||||
</el-divider>
|
||||
|
||||
<RegistrationForm />
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div v-else class="qr-container">
|
||||
@ -216,14 +232,12 @@ function onAfterLeave() {
|
||||
}
|
||||
|
||||
.right-section .content-title {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 55px;
|
||||
}
|
||||
|
||||
.right-section .mode-toggle {
|
||||
|
||||
@ -33,18 +33,18 @@ export function useWindowWidthObserver(
|
||||
case 'alwaysExpanded':
|
||||
designStore.setCollapse(false);
|
||||
if (isAbove) {
|
||||
// 大于的时候执行关闭动画
|
||||
// 大于的时候执行关闭动画 (豆包是有的,第一版本暂未添加)
|
||||
console.log('执行关闭动画');
|
||||
}
|
||||
else {
|
||||
// 小于的时候执行打开动画
|
||||
// 小于的时候执行打开动画 (豆包是有的,第一版本暂未添加)
|
||||
console.log('小于的时候执行打开动画');
|
||||
}
|
||||
break;
|
||||
case 'narrowExpandWideCollapse':
|
||||
designStore.setCollapse(isAbove);
|
||||
}
|
||||
console.log('最终的折叠状态:', designStore.isCollapse);
|
||||
// console.log('最终的折叠状态:', designStore.isCollapse);
|
||||
|
||||
if (!designStore.isCollapse) {
|
||||
document.documentElement.style.setProperty(
|
||||
|
||||
@ -20,10 +20,6 @@ const router = createRouter({
|
||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||
});
|
||||
|
||||
console.group('Routes');
|
||||
console.log('routes', router.getRoutes());
|
||||
console.groupEnd();
|
||||
|
||||
// 路由前置守卫
|
||||
router.beforeEach(
|
||||
async (
|
||||
|
||||
18
src/stores/modules/loginForm.ts
Normal file
18
src/stores/modules/loginForm.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// 登录表单状态管理
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
type LoginFormType = 'AccountPassword' | 'VerificationCode' | 'RegistrationForm';
|
||||
|
||||
export const useLoginFromStore = defineStore('loginFrom', () => {
|
||||
const LoginFormType = ref<LoginFormType>('AccountPassword');
|
||||
|
||||
// 设置登录表单类型
|
||||
const setLoginFormType = (type: LoginFormType) => {
|
||||
LoginFormType.value = type;
|
||||
};
|
||||
|
||||
return {
|
||||
LoginFormType,
|
||||
setLoginFormType,
|
||||
};
|
||||
});
|
||||
5
types/components.d.ts
vendored
5
types/components.d.ts
vendored
@ -3,7 +3,7 @@
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
// biome-ignore lint: disable
|
||||
export {}
|
||||
export {};
|
||||
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
@ -11,6 +11,7 @@ declare module 'vue' {
|
||||
AccountPassword: typeof import('./../src/components/LoginDialog/components/FormLogin/AccountPassword.vue')['default']
|
||||
DeepThinking: typeof import('./../src/components/DeepThinking/index.vue')['default']
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElButtom: typeof import('element-plus/es')['ElButtom']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
@ -21,6 +22,7 @@ declare module 'vue' {
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElImage: typeof import('element-plus/es')['ElImage']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElLink: typeof import('element-plus/es')['ElLink']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
FilesSelect: typeof import('./../src/components/FilesSelect/index.vue')['default']
|
||||
IconSelect: typeof import('./../src/components/IconSelect/index.vue')['default']
|
||||
@ -28,6 +30,7 @@ declare module 'vue' {
|
||||
ModelSelect: typeof import('./../src/components/ModelSelect/index.vue')['default']
|
||||
Popover: typeof import('./../src/components/Popover/index.vue')['default']
|
||||
QrCodeLogin: typeof import('./../src/components/LoginDialog/components/QrCodeLogin/index.vue')['default']
|
||||
RegistrationForm: typeof import('./../src/components/LoginDialog/components/FormLogin/RegistrationForm.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SvgIcon: typeof import('./../src/components/SvgIcon/index.vue')['default']
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user