ForgotPassword

This commit is contained in:
MaxKey
2022-04-25 22:00:45 +08:00
parent d6856b1f54
commit 7ddde38019
25 changed files with 599 additions and 160 deletions

View File

@@ -106,4 +106,4 @@ import { Observable } from 'rxjs';
providers: [...LANG_PROVIDES, ...INTERCEPTOR_PROVIDES, ...I18NSERVICE_PROVIDES, ...APPINIT_PROVIDES],
bootstrap: [AppComponent]
})
export class AppModule {}
export class AppModule { }

View File

@@ -0,0 +1,120 @@
<h3>{{ 'mxk.forgot.forgot' | i18n }}</h3>
<form nz-form [formGroup]="formGroup" role="form">
<nz-form-item style="width: 100%">
<nz-steps nzType="navigation" [nzCurrent]="step" style="width: 100%">
<nz-step nzTitle="{{ 'mxk.forgot.step1' | i18n }}" nzDescription=""> </nz-step>
<nz-step nzTitle="{{ 'mxk.forgot.step2' | i18n }}" nzDescription=""></nz-step>
</nz-steps>
</nz-form-item>
<nz-form-item style="width: 100%" *ngIf="step === 0">
<nz-form-item style="width: 100%">
<nz-radio-group
[(ngModel)]="forgotType"
[ngModelOptions]="{ standalone: true }"
style="margin-bottom: 8px; width: 100%"
nzSize="large"
nzButtonStyle="solid"
(ngModelChange)="ngModelChange()"
>
<label nz-radio-button [nzValue]="'mobile'" style="width: 50%">{{ 'mxk.forgot.type.mobile' | i18n }}</label>
<label nz-radio-button [nzValue]="'email'" style="width: 50%">{{ 'mxk.forgot.type.email' | i18n }}</label>
</nz-radio-group>
</nz-form-item>
<nz-form-item style="width: 100%" *ngIf="forgotType === 'email'">
<nz-form-control nzErrorTip="">
<nz-input-group nzSize="large" nzPrefixIcon="mail">
<input nz-input formControlName="email" placeholder="{{ 'mxk.forgot.email' | i18n }}" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%" *ngIf="forgotType === 'mobile'">
<nz-form-control [nzErrorTip]="">
<nz-input-group nzSize="large" nzPrefixIcon="mobile">
<input nz-input formControlName="mobile" placeholder="{{ 'mxk.forgot.mobile' | i18n }}" />
</nz-input-group>
<ng-template #mobileErrorTip let-i>
<ng-container *ngIf="i.errors.required">
{{ 'validation.phone-number.required' | i18n }}
</ng-container>
<ng-container *ngIf="i.errors.pattern">
{{ 'validation.phone-number.wrong-format' | i18n }}
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%">
<nz-form-control nzErrorTip="">
<nz-input-group nzSearch nzSize="large" nzPrefixIcon="lock" nzSearch [nzAddOnAfter]="suffixImageCaptchaButton">
<input type="text" formControlName="captcha" nz-input placeholder="{{ 'mxk.forgot.captcha' | i18n }}" />
</nz-input-group>
<ng-template #suffixImageCaptchaButton>
<img src="{{ imageCaptcha }}" (click)="getImageCaptcha()" />
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%">
<nz-form-control [nzErrorTip]="'' | i18n">
<nz-input-group nzSize="large" nzPrefixIcon="mail" nzSearch [nzAddOnAfter]="suffixSendOtpCodeButton">
<input nz-input formControlName="otpCaptcha" placeholder="{{ 'mxk.login.text.captcha' | i18n }}" />
</nz-input-group>
<ng-template #suffixSendOtpCodeButton>
<button type="button" nz-button nzSize="large" (click)="sendOtpCode()" [disabled]="count > 0" nzBlock [nzLoading]="loading">
{{ count ? count + 's' : ('mxk.forgot.sendCaptcha' | i18n) }}
</button>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%">
<button nz-button nzType="primary" nzSize="large" class="submit" (click)="onNextReset($event)">
{{ 'mxk.forgot.next' | i18n }}
</button>
<a class="login" routerLink="/passport/login">{{ 'mxk.forgot.login' | i18n }}</a>
</nz-form-item>
</nz-form-item>
<nz-form-item style="width: 100%" *ngIf="step === 1">
<nz-form-item style="width: 100%">
<nz-form-label nzRequired nzFor="password">{{ 'mxk.password.password' | i18n }}</nz-form-label>
</nz-form-item>
<nz-form-item style="width: 100%">
<nz-form-control nzErrorTip="The input is not valid password!">
<nz-input-group nzSize="large" [nzSuffix]="suffixPasswordTemplate" style="width: 100%">
<input
[type]="passwordVisible ? 'text' : 'password'"
nz-input
placeholder="new password"
[(ngModel)]="form.model.password"
formControlName="password"
id="password"
/>
</nz-input-group>
<ng-template #suffixPasswordTemplate>
<i nz-icon [nzType]="passwordVisible ? 'eye-invisible' : 'eye'" (click)="passwordVisible = !passwordVisible"></i>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%">
<nz-form-label nzRequired nzFor="confirmPassword">{{ 'mxk.password.confirmPassword' | i18n }}</nz-form-label>
</nz-form-item>
<nz-form-item style="width: 100%">
<nz-form-control nzErrorTip="The input is not valid confirmPassword!">
<input
nz-input
type="password"
nzSize="large"
placeholder="confirm password"
[(ngModel)]="form.model.confirmPassword"
formControlName="confirmPassword"
name="confirmPassword"
id="confirmPassword"
/>
</nz-form-control>
</nz-form-item>
<nz-form-item style="width: 100%">
<button nz-button nzType="primary" nzSize="large" class="submit" (click)="onSubmit($event)">
{{ 'mxk.forgot.submit' | i18n }}
</button>
<a class="login" routerLink="/passport/login">{{ 'mxk.forgot.login' | i18n }}</a>
</nz-form-item>
</nz-form-item>
</form>

View File

@@ -0,0 +1,21 @@
@import '@delon/theme/index';
:host {
display: block;
width: 460px;
margin: 0 auto;
::ng-deep {
.ant-tabs .ant-tabs-bar {
margin-bottom: 24px;
text-align: center;
border-bottom: 0;
}
}
.login {
float: right;
line-height: @btn-height-lg;
}
}

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ForgotComponent } from './forgot.component';
describe('ForgotComponent', () => {
let component: ForgotComponent;
let fixture: ComponentFixture<ForgotComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ForgotComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ForgotComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,176 @@
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzStepsModule } from 'ng-zorro-antd/steps';
import { ChangePassword } from '../../../entity/ChangePassword';
import { ForgotPasswordService } from '../../../service/forgot-password.service';
import { ImageCaptchaService } from '../../../service/image-captcha.service';
import { PasswordService } from '../../../service/password.service';
@Component({
selector: 'app-forgot',
templateUrl: './forgot.component.html',
styleUrls: ['./forgot.component.less']
})
export class ForgotComponent implements OnInit {
form: {
submitting: boolean;
model: ChangePassword;
} = {
submitting: false,
model: new ChangePassword()
};
imageCaptcha = '';
formGroup: FormGroup;
state = '';
step = 0;
forgotType = 'mobile';
passwordVisible = false;
loading = false;
count = 0;
interval$: any;
userId = '';
username = '';
constructor(
fb: FormBuilder,
private forgotPasswordService: ForgotPasswordService,
private imageCaptchaService: ImageCaptchaService,
private msg: NzMessageService,
private cdr: ChangeDetectorRef
) {
this.formGroup = fb.group({
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
email: [null, [Validators.required]],
password: [null, [Validators.required]],
captcha: [null, [Validators.required]],
otpCaptcha: [null, [Validators.required]],
confirmPassword: [null, [Validators.required]]
});
}
get email(): AbstractControl {
return this.formGroup.get('email')!;
}
get password(): AbstractControl {
return this.formGroup.get('password')!;
}
get confirmPassword(): AbstractControl {
return this.formGroup.get('confirmPassword')!;
}
get mobile(): AbstractControl {
return this.formGroup.get('mobile')!;
}
get captcha(): AbstractControl {
return this.formGroup.get('captcha')!;
}
get otpCaptcha(): AbstractControl {
return this.formGroup.get('otpCaptcha')!;
}
ngOnInit(): void {
this.getImageCaptcha();
}
getImageCaptcha(): void {
this.imageCaptchaService.captcha({}).subscribe(res => {
this.imageCaptcha = res.data.image;
this.state = res.data.state;
this.cdr.detectChanges();
});
}
//send sms
sendOtpCode(): void {
if (this.forgotType == 'mobile' && this.mobile.invalid) {
this.mobile.markAsDirty({ onlySelf: true });
this.mobile.updateValueAndValidity({ onlySelf: true });
return;
}
if (this.forgotType == 'email' && this.email.invalid) {
this.email.markAsDirty({ onlySelf: true });
this.email.updateValueAndValidity({ onlySelf: true });
return;
}
if (this.forgotType == 'mobile') {
this.forgotPasswordService
.produceOtp({ mobile: this.mobile.value, state: this.state, captcha: this.captcha.value })
.subscribe(res => {
if (res.code !== 0) {
this.msg.success(`发送失败`);
this.getImageCaptcha();
this.cdr.detectChanges();
}
this.userId = res.data.userId;
this.username = res.data.username;
//console.log(res.data);
});
} else if (this.forgotType == 'email') {
this.forgotPasswordService
.produceEmailOtp({ email: this.email.value, state: this.state, captcha: this.captcha.value })
.subscribe(res => {
if (res.code !== 0) {
this.msg.success(`发送失败`);
this.getImageCaptcha();
this.cdr.detectChanges();
}
this.userId = res.data.userId;
this.username = res.data.username;
//console.log(res.data);
});
}
this.count = 59;
this.interval$ = setInterval(() => {
this.count -= 1;
if (this.count <= 0) {
clearInterval(this.interval$);
}
this.cdr.detectChanges();
}, 1000);
}
onNextReset(e: MouseEvent) {
if (this.otpCaptcha.invalid) {
this.otpCaptcha.markAsDirty({ onlySelf: true });
this.otpCaptcha.updateValueAndValidity({ onlySelf: true });
return;
}
this.step = 1;
}
onSubmit(e: MouseEvent) {
this.forgotPasswordService
.setPassWord({
forgotType: this.forgotType,
userId: this.userId,
username: this.username,
password: this.password.value,
confirmPassword: this.confirmPassword.value,
otpCaptcha: this.otpCaptcha.value,
state: this.state
})
.subscribe(res => {
if (res.code !== 0) {
this.msg.success(`密码修改失败`);
this.getImageCaptcha();
this.step = 0;
this.cdr.detectChanges();
}
this.msg.success(`密码修改成功`);
});
}
ngModelChange() {
if (this.forgotType == 'email') {
this.mobile.reset();
}
if (this.forgotType == 'mobile') {
this.email.reset();
}
}
}

View File

@@ -89,7 +89,7 @@
<label nz-checkbox formControlName="remember">{{ 'mxk.login.remember-me' | i18n }}</label>
</nz-col>
<nz-col [nzSpan]="12" class="text-right">
<a class="forgot" routerLink="/passport/register">{{ 'mxk.login.forgot-password' | i18n }}</a></nz-col
<a class="forgot" routerLink="/passport/forgot">{{ 'mxk.login.forgot-password' | i18n }}</a></nz-col
>
</nz-form-item>
<nz-form-item *ngIf="loginType == 'normal' || loginType == 'mobile'">

View File

@@ -3,6 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { LayoutPassportComponent } from '../../layout/passport/passport.component';
import { CallbackComponent } from './callback.component';
import { ForgotComponent } from './forgot/forgot.component';
import { UserLockComponent } from './lock/lock.component';
import { UserLoginComponent } from './login/login.component';
import { UserRegisterResultComponent } from './register-result/register-result.component';
@@ -29,6 +30,11 @@ const routes: Routes = [
component: UserRegisterResultComponent,
data: { title: '注册结果', titleI18n: 'app.register.register' }
},
{
path: 'forgot',
component: ForgotComponent,
data: { title: '忘记密码', titleI18n: 'app.forgot.forgot' }
},
{
path: 'lock',
component: UserLockComponent,

View File

@@ -1,7 +1,9 @@
import { NgModule } from '@angular/core';
import { SharedModule } from '@shared';
import { NzStepsModule } from 'ng-zorro-antd/steps';
import { CallbackComponent } from './callback.component';
import { ForgotComponent } from './forgot/forgot.component';
import { UserLockComponent } from './lock/lock.component';
import { UserLoginComponent } from './login/login.component';
import { PassportRoutingModule } from './passport-routing.module';
@@ -11,7 +13,7 @@ import { UserRegisterComponent } from './register/register.component';
const COMPONENTS = [UserLoginComponent, UserRegisterResultComponent, UserRegisterComponent, UserLockComponent, CallbackComponent];
@NgModule({
imports: [SharedModule, PassportRoutingModule],
declarations: [...COMPONENTS]
imports: [SharedModule, PassportRoutingModule, NzStepsModule],
declarations: [...COMPONENTS, ForgotComponent]
})
export class PassportModule {}
export class PassportModule { }

View File

@@ -0,0 +1,20 @@
import { Injectable, Inject } from '@angular/core';
import { _HttpClient, User } from '@delon/theme';
@Injectable({
providedIn: 'root'
})
export class ForgotPasswordService {
constructor(private http: _HttpClient) { }
produceOtp(param: any) {
return this.http.get('/forgotpassword/produceOtp?_allow_anonymous=true', param);
}
produceEmailOtp(param: any) {
return this.http.get(`/forgotpassword/produceEmailOtp?_allow_anonymous=true`, param);
}
setPassWord(param: any) {
return this.http.get('/forgotpassword/setpassword?_allow_anonymous=true', param);
}
}

View File

@@ -21,6 +21,22 @@
"text.captcha": "Captcha",
"text.smscode": "Code"
},
"forgot":{
"forgot":"Forgot Password",
"step1":"Authentication",
"step2":"Resetting Password",
"type.mobile":"By Mobile",
"type.email":"By Email",
"mobile":"Mobile Phone Number",
"email":"Email",
"captcha":"Picture Captcha",
"sendCaptcha":"Send Captcha",
"password":"New Password",
"confirmPassword":"Confirm Password",
"login":"Back Login",
"next":"Next",
"submit":"Confirm"
},
"menu": {
"applist": "Apps",
"sessions": "Sessions",
@@ -807,6 +823,7 @@
"app.login.text.password": "Password",
"app.login.text.captcha": "CAPTCHA",
"app.login.text.smscode": "Code",
"app.forgot.forgot":"Forgot Password",
"app.register.register": "Register",
"app.register.get-verification-code": "Get code",
"app.register.sign-in": "Already have an account?",

View File

@@ -16,11 +16,27 @@
"signup": "用户注册",
"login": "登录",
"text.username": "用户名",
"text.mobile": "手机号",
"text.mobile": "手机号",
"text.password": "密码",
"text.captcha": "验证码",
"text.smscode": "验证码"
},
"forgot":{
"forgot":"忘记密码",
"step1":"验证身份",
"step2":"设置新密码",
"type.mobile":"手机找回",
"type.email":"邮箱找回",
"mobile":"手机号码",
"email":"邮箱",
"captcha":"图形验证码",
"sendCaptcha":"发送验证码",
"password":"新密码",
"confirmPassword":"确认新密码",
"login":"返回登录",
"next":"下一步",
"submit":"提交"
},
"menu": {
"applist": "应用",
"sessions": "会话",
@@ -805,6 +821,7 @@
"app.login.text.password": "密码",
"app.login.text.captcha": "验证码",
"app.login.text.smscode": "验证码",
"app.forgot.forgot":"忘记密码",
"app.register.register": "注册",
"app.register.get-verification-code": "获取验证码",
"app.register.sign-in": "使用已有账户登录",