mirror of
https://gitee.com/wonderful-code/buildadmin
synced 2024-11-21 22:55:36 +00:00
feat:会员注册时通过API获取可用的验证方式、会员注册验证邮件实现
This commit is contained in:
parent
b168debf42
commit
952208934f
@ -14,10 +14,11 @@ use think\exception\ValidateException;
|
||||
use app\api\validate\Account as AccountValidate;
|
||||
use app\common\library\Email;
|
||||
use PHPMailer\PHPMailer\Exception as PHPMailerException;
|
||||
use app\api\validate\User as UserValidate;
|
||||
|
||||
class Account extends Frontend
|
||||
{
|
||||
protected $noNeedLogin = ['sendRetrievePasswordCode', 'retrievePassword'];
|
||||
protected $noNeedLogin = ['sendRetrievePasswordCode', 'sendRegisterCode', 'retrievePassword'];
|
||||
|
||||
protected $model = null;
|
||||
|
||||
@ -131,6 +132,44 @@ class Account extends Frontend
|
||||
]);
|
||||
}
|
||||
|
||||
public function sendRegisterCode()
|
||||
{
|
||||
$data = $this->request->only(['registerType', 'email', 'mobile', 'username', 'password']);
|
||||
|
||||
$validate = new UserValidate();
|
||||
try {
|
||||
$validate->scene('send-register-code')->check($data);
|
||||
} catch (ValidateException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
// 生成一个验证码
|
||||
$captcha = new Captcha();
|
||||
$account = $data['registerType'] == 'email' ? $data['email'] : $data['mobile'];
|
||||
$code = $captcha->create($account);
|
||||
|
||||
if ($data['registerType'] == 'email') {
|
||||
$mail = new Email();
|
||||
if (!$mail->configured) {
|
||||
$this->error(__('Mail sending service unavailable'));
|
||||
}
|
||||
try {
|
||||
$mail->isSMTP();
|
||||
$mail->addAddress($account);
|
||||
$mail->isHTML(true);
|
||||
$mail->setSubject(__('Member registration verification') . '-' . get_sys_config('site_name'));
|
||||
$mail->Body = __('Your verification code is: %s', [$code]);
|
||||
$mail->send();
|
||||
} catch (PHPMailerException $e) {
|
||||
$this->error($mail->ErrorInfo);
|
||||
}
|
||||
|
||||
$this->success(__('Mail sent successfully~'));
|
||||
} else {
|
||||
$this->error(__('Unknown operation'));
|
||||
}
|
||||
}
|
||||
|
||||
public function sendRetrievePasswordCode()
|
||||
{
|
||||
$data = $this->request->only(['type', 'account']);
|
||||
|
@ -52,7 +52,7 @@ class User extends Frontend
|
||||
}
|
||||
|
||||
if ($this->request->isPost()) {
|
||||
$params = $this->request->post(['tab', 'email', 'mobile', 'username', 'password', 'keep', 'captcha', 'captchaId']);
|
||||
$params = $this->request->post(['tab', 'email', 'mobile', 'username', 'password', 'keep', 'captcha', 'captchaId', 'registerType']);
|
||||
if ($params['tab'] != 'login' && $params['tab'] != 'register') {
|
||||
$this->error(__('Unknown operation'));
|
||||
}
|
||||
@ -63,15 +63,17 @@ class User extends Frontend
|
||||
} catch (ValidateException $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$captchaObj = new Captcha();
|
||||
if (!$captchaObj->check($params['captcha'], $params['captchaId'])) {
|
||||
$this->error(__('Please enter the correct verification code'));
|
||||
}
|
||||
|
||||
if ($params['tab'] == 'login') {
|
||||
if (!$captchaObj->check($params['captcha'], $params['captchaId'])) {
|
||||
$this->error(__('Please enter the correct verification code'));
|
||||
}
|
||||
$res = $this->auth->login($params['username'], $params['password'], (bool)$params['keep']);
|
||||
} elseif ($params['tab'] == 'register') {
|
||||
if (!$captchaObj->check($params['captcha'], $params['registerType'] == 'email' ? $params['email'] : $params['mobile'])) {
|
||||
$this->error(__('Please enter the correct verification code'));
|
||||
}
|
||||
$res = $this->auth->register($params['username'], $params['password'], $params['mobile'], $params['email']);
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,13 @@
|
||||
<?php
|
||||
return [
|
||||
'avatar' => '头像',
|
||||
'username' => '用户名',
|
||||
'nickname' => '昵称',
|
||||
'birthday' => '生日',
|
||||
'email' => '电子邮箱',
|
||||
'mobile' => '手机号',
|
||||
'password' => '密码',
|
||||
'captcha' => '验证码',
|
||||
'Old password error' => '旧密码错误',
|
||||
'Data updated successfully~' => '资料更新成功~',
|
||||
'Please input correct password' => '请输入正确的密码',
|
||||
@ -13,9 +15,12 @@ return [
|
||||
'Password has been changed~' => '密码已修改~',
|
||||
'Password has been changed, please login again~' => '密码已修改,请重新登录~',
|
||||
'Retrieve password verification' => '找回密码验证',
|
||||
'Member registration verification' => '会员注册验证',
|
||||
'Your verification code is: %s' => '您的验证码是:%s,十分钟内有效~',
|
||||
'Mail sent successfully~' => '邮件发送成功~',
|
||||
'Account does not exist~' => '账户不存在~',
|
||||
'Failed to modify password, please try again later~' => '修改密码失败,请稍后重试~',
|
||||
'Please enter the correct verification code' => '请输入正确的验证码',
|
||||
'%s has been registered' => '%s已被注册,请直接登录~',
|
||||
'Mail sending service unavailable' => '邮件发送服务不可用,请联系网站管理员进行配置~',
|
||||
];
|
@ -10,8 +10,8 @@ class User extends Validate
|
||||
|
||||
protected $rule = [
|
||||
'username' => 'require|regex:^[a-zA-Z][a-zA-Z0-9_]{2,15}$|unique:user',
|
||||
'email' => 'require|email|unique:user',
|
||||
'mobile' => 'require|mobile|unique:user',
|
||||
'email' => 'email|unique:user',
|
||||
'mobile' => 'mobile|unique:user',
|
||||
'password' => 'require|regex:^[a-zA-Z0-9_]{6,32}$',
|
||||
'captcha' => 'require',
|
||||
'captchaId' => 'require',
|
||||
@ -21,8 +21,9 @@ class User extends Validate
|
||||
* 验证场景
|
||||
*/
|
||||
protected $scene = [
|
||||
'login' => ['password', 'captcha', 'captchaId'],
|
||||
'register' => ['email', 'username', 'password', 'mobile', 'captcha', 'captchaId'],
|
||||
'login' => ['password', 'captcha', 'captchaId'],
|
||||
'register' => ['email', 'username', 'password', 'mobile', 'captcha'],
|
||||
'send-register-code' => ['email', 'username', 'password', 'mobile'],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
|
@ -12,6 +12,11 @@ use PHPMailer\PHPMailer\Exception;
|
||||
*/
|
||||
class Email extends PHPMailer
|
||||
{
|
||||
/**
|
||||
* 是否已在管理后台配置好邮件服务
|
||||
*/
|
||||
public $configured = false;
|
||||
|
||||
/**
|
||||
* 默认配置
|
||||
*/
|
||||
@ -37,14 +42,14 @@ class Email extends PHPMailer
|
||||
$this->setLanguage($this->options['lang'], root_path() . 'vendor' . DIRECTORY_SEPARATOR . 'phpmailer' . DIRECTORY_SEPARATOR . 'phpmailer' . DIRECTORY_SEPARATOR . 'language' . DIRECTORY_SEPARATOR);
|
||||
$this->CharSet = $this->options['charset'];
|
||||
|
||||
$sysMailConfig = get_sys_config('', 'mail');
|
||||
$configured = true;
|
||||
$sysMailConfig = get_sys_config('', 'mail');
|
||||
$this->configured = true;
|
||||
foreach ($sysMailConfig as $item) {
|
||||
if (!$item) {
|
||||
$configured = false;
|
||||
$this->configured = false;
|
||||
}
|
||||
}
|
||||
if ($configured) {
|
||||
if ($this->configured) {
|
||||
$this->Host = $sysMailConfig['smtp_server'];
|
||||
$this->SMTPAuth = true;
|
||||
$this->Username = $sysMailConfig['smtp_user'];
|
||||
|
@ -100,6 +100,19 @@ export function sendRetrievePasswordCode(type: string, account: string): ApiProm
|
||||
) as ApiPromise
|
||||
}
|
||||
|
||||
export function sendRegisterCode(params: anyObj): ApiPromise {
|
||||
return createAxios(
|
||||
{
|
||||
url: accountUrl + 'sendRegisterCode',
|
||||
method: 'POST',
|
||||
data: params,
|
||||
},
|
||||
{
|
||||
showSuccessMessage: true,
|
||||
}
|
||||
) as ApiPromise
|
||||
}
|
||||
|
||||
export function retrievePassword(params: anyObj): ApiPromise {
|
||||
return createAxios(
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ export default {
|
||||
'No account yet? Click Register': '还没有账户?点击注册',
|
||||
'Retrieve password': '找回密码',
|
||||
'Retrieval method': '找回方式',
|
||||
'Registration verification method': '验证方式',
|
||||
'Via email': '通过邮箱',
|
||||
'Via mobile number': '通过手机号',
|
||||
'New password': '新密码',
|
||||
|
@ -10,18 +10,24 @@
|
||||
{{ t('user.user.' + state.form.tab) + t('user.user.reach') + siteConfig.site_name }}
|
||||
</div>
|
||||
<el-form ref="formRef" @keyup.enter="onSubmit(formRef)" :rules="rules" :model="state.form">
|
||||
<!-- 注册邮箱 -->
|
||||
<el-form-item v-if="state.form.tab == 'register'" prop="email">
|
||||
<el-input
|
||||
v-model="state.form.email"
|
||||
:placeholder="t('Please input field', { field: t('user.user.mailbox') })"
|
||||
:clearable="true"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="fa fa-envelope" size="16" color="var(--el-input-icon-color)" />
|
||||
</template>
|
||||
</el-input>
|
||||
<!-- 注册验证方式 -->
|
||||
<el-form-item v-if="state.form.tab == 'register'">
|
||||
<el-radio-group size="large" v-model="state.form.registerType">
|
||||
<el-radio
|
||||
class="register-verification-radio"
|
||||
label="email"
|
||||
:disabled="!state.accountVerificationType.includes('email')"
|
||||
border
|
||||
>{{ t('user.user.Via email') + t('user.user.register') }}</el-radio
|
||||
>
|
||||
<el-radio
|
||||
class="register-verification-radio"
|
||||
label="mobile"
|
||||
:disabled="!state.accountVerificationType.includes('mobile')"
|
||||
border
|
||||
>{{ t('user.user.Via mobile number') + t('user.user.register') }}</el-radio
|
||||
>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 登录注册用户名 -->
|
||||
@ -57,22 +63,8 @@
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 注册手机号 -->
|
||||
<el-form-item v-if="state.form.tab == 'register'" prop="mobile">
|
||||
<el-input
|
||||
v-model="state.form.mobile"
|
||||
:placeholder="t('Please input field', { field: t('user.user.mobile') })"
|
||||
:clearable="true"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="fa fa-tablet" size="16" color="var(--el-input-icon-color)" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 登录注册验证码 -->
|
||||
<el-form-item prop="captcha">
|
||||
<!-- 登录验证码 -->
|
||||
<el-form-item v-if="state.form.tab == 'login'" prop="captcha">
|
||||
<el-row class="w100">
|
||||
<el-col :span="16">
|
||||
<el-input
|
||||
@ -98,6 +90,66 @@
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 注册手机号 -->
|
||||
<el-form-item v-if="state.form.tab == 'register' && state.form.registerType == 'mobile'" prop="mobile">
|
||||
<el-input
|
||||
v-model="state.form.mobile"
|
||||
:placeholder="t('Please input field', { field: t('user.user.mobile') })"
|
||||
:clearable="true"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="fa fa-tablet" size="16" color="var(--el-input-icon-color)" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 注册邮箱 -->
|
||||
<el-form-item v-if="state.form.tab == 'register' && state.form.registerType == 'email'" prop="email">
|
||||
<el-input
|
||||
v-model="state.form.email"
|
||||
:placeholder="t('Please input field', { field: t('user.user.mailbox') })"
|
||||
:clearable="true"
|
||||
size="large"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="fa fa-envelope" size="16" color="var(--el-input-icon-color)" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 注册验证码 -->
|
||||
<el-form-item v-if="state.form.tab == 'register'" prop="captcha">
|
||||
<el-row class="w100">
|
||||
<el-col :span="16">
|
||||
<el-input
|
||||
size="large"
|
||||
v-model="state.form.captcha"
|
||||
:placeholder="t('Please input field', { field: t('user.user.Verification Code') })"
|
||||
autocomplete="off"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon name="fa fa-ellipsis-h" size="16" color="var(--el-input-icon-color)" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col class="captcha-box" :span="8">
|
||||
<el-button
|
||||
size="large"
|
||||
@click="sendRegisterCaptcha(formRef)"
|
||||
:loading="state.sendCaptchaLoading"
|
||||
:disabled="state.codeSendCountdown <= 0 ? false : true"
|
||||
type="primary"
|
||||
>{{
|
||||
state.codeSendCountdown <= 0
|
||||
? t('user.user.send')
|
||||
: state.codeSendCountdown + t('user.user.seconds')
|
||||
}}</el-button
|
||||
>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="state.form.tab != 'register'" class="form-footer">
|
||||
<el-checkbox v-model="state.form.keep" :label="t('user.user.Remember me')" size="default"></el-checkbox>
|
||||
<div
|
||||
@ -190,7 +242,7 @@
|
||||
<el-col class="captcha-box" :span="8">
|
||||
<el-button
|
||||
@click="sendRetrieveCaptcha(retrieveFormRef)"
|
||||
:loading="state.sendRetrieveCaptchaLoading"
|
||||
:loading="state.sendCaptchaLoading"
|
||||
:disabled="state.codeSendCountdown <= 0 ? false : true"
|
||||
type="primary"
|
||||
>{{
|
||||
@ -232,14 +284,16 @@ import { buildCaptchaUrl } from '/@/api/common'
|
||||
import { uuid } from '/@/utils/random'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { buildValidatorData, validatorAccount } from '/@/utils/validate'
|
||||
import { checkIn, sendRetrievePasswordCode, retrievePassword } from '/@/api/frontend/user/index'
|
||||
import { checkIn, sendRetrievePasswordCode, retrievePassword, sendRegisterCode } from '/@/api/frontend/user/index'
|
||||
import { onResetForm } from '/@/utils/common'
|
||||
import { useUserInfo } from '/@/stores/userInfo'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import type { ElForm, FormItemRule } from 'element-plus'
|
||||
var timer: NodeJS.Timer
|
||||
|
||||
const { t } = useI18n()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const userInfo = useUserInfo()
|
||||
const siteConfig = useSiteConfig()
|
||||
@ -256,6 +310,7 @@ interface State {
|
||||
captcha: string
|
||||
keep: boolean
|
||||
captchaId: string
|
||||
registerType: 'email' | 'mobile'
|
||||
}
|
||||
formLoading: boolean
|
||||
showCaptcha: boolean
|
||||
@ -270,7 +325,7 @@ interface State {
|
||||
accountVerificationType: string[]
|
||||
codeSendCountdown: number
|
||||
submitRetrieveLoading: boolean
|
||||
sendRetrieveCaptchaLoading: boolean
|
||||
sendCaptchaLoading: boolean
|
||||
}
|
||||
|
||||
const state: State = reactive({
|
||||
@ -283,6 +338,7 @@ const state: State = reactive({
|
||||
captcha: '',
|
||||
keep: false,
|
||||
captchaId: uuid(),
|
||||
registerType: 'email',
|
||||
},
|
||||
formLoading: false,
|
||||
showCaptcha: false,
|
||||
@ -297,7 +353,7 @@ const state: State = reactive({
|
||||
accountVerificationType: [],
|
||||
codeSendCountdown: 0,
|
||||
submitRetrieveLoading: false,
|
||||
sendRetrieveCaptchaLoading: false,
|
||||
sendCaptchaLoading: false,
|
||||
})
|
||||
|
||||
const rules: Partial<Record<string, FormItemRule[]>> = reactive({
|
||||
@ -382,20 +438,40 @@ const onSubmitRetrieve = (formRef: InstanceType<typeof ElForm> | undefined = und
|
||||
}
|
||||
})
|
||||
}
|
||||
const sendRetrieveCaptcha = (formRef: InstanceType<typeof ElForm> | undefined = undefined) => {
|
||||
|
||||
const sendRegisterCaptcha = (formRef: InstanceType<typeof ElForm> | undefined = undefined) => {
|
||||
if (state.codeSendCountdown > 0) return
|
||||
formRef!.validateField('account').then((valid) => {
|
||||
formRef!.validateField([state.form.registerType, 'username', 'password']).then((valid) => {
|
||||
if (valid) {
|
||||
state.sendRetrieveCaptchaLoading = true
|
||||
sendRetrievePasswordCode(state.retrievePasswordForm.type, state.retrievePasswordForm.account)
|
||||
state.sendCaptchaLoading = true
|
||||
sendRegisterCode(state.form)
|
||||
.then((res) => {
|
||||
state.sendRetrieveCaptchaLoading = false
|
||||
state.sendCaptchaLoading = false
|
||||
if (res.code == 1) {
|
||||
startTiming(60)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
state.sendRetrieveCaptchaLoading = false
|
||||
state.sendCaptchaLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const sendRetrieveCaptcha = (formRef: InstanceType<typeof ElForm> | undefined = undefined) => {
|
||||
if (state.codeSendCountdown > 0) return
|
||||
formRef!.validateField('account').then((valid) => {
|
||||
if (valid) {
|
||||
state.sendCaptchaLoading = true
|
||||
sendRetrievePasswordCode(state.retrievePasswordForm.type, state.retrievePasswordForm.account)
|
||||
.then((res) => {
|
||||
state.sendCaptchaLoading = false
|
||||
if (res.code == 1) {
|
||||
startTiming(60)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
state.sendCaptchaLoading = false
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -423,6 +499,8 @@ onMounted(() => {
|
||||
state.accountVerificationType = res.data.accountVerificationType
|
||||
state.retrievePasswordForm.type = res.data.accountVerificationType.length > 0 ? res.data.accountVerificationType[0] : ''
|
||||
})
|
||||
|
||||
if (route.query.type == 'register') state.form.tab = 'register'
|
||||
})
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', resize)
|
||||
@ -457,16 +535,20 @@ onUnmounted(() => {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
.register-verification-radio {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.captcha-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
justify-content: flex-end;
|
||||
.captcha-img {
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
}
|
||||
.el-button {
|
||||
width: 90%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.form-footer {
|
||||
|
Loading…
Reference in New Issue
Block a user