[web-app] ng-alain模版工程初始化
This commit is contained in:
35
web-app/src/app/routes/passport/callback.component.ts
Normal file
35
web-app/src/app/routes/passport/callback.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SocialService } from '@delon/auth';
|
||||
import { SettingsService } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'app-callback',
|
||||
template: ``,
|
||||
providers: [SocialService]
|
||||
})
|
||||
export class CallbackComponent implements OnInit {
|
||||
type = '';
|
||||
|
||||
constructor(private socialService: SocialService, private settingsSrv: SettingsService, private route: ActivatedRoute) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.type = this.route.snapshot.params.type;
|
||||
this.mockModel();
|
||||
}
|
||||
|
||||
private mockModel(): void {
|
||||
const info = {
|
||||
token: '123456789',
|
||||
name: 'cipchk',
|
||||
email: `${this.type}@${this.type}.com`,
|
||||
id: 10000,
|
||||
time: +new Date()
|
||||
};
|
||||
this.settingsSrv.setUser({
|
||||
...this.settingsSrv.user,
|
||||
...info
|
||||
});
|
||||
this.socialService.callback(info);
|
||||
}
|
||||
}
|
||||
21
web-app/src/app/routes/passport/lock/lock.component.html
Normal file
21
web-app/src/app/routes/passport/lock/lock.component.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<div class="ant-card width-lg" style="margin: 0 auto">
|
||||
<div class="ant-card-body">
|
||||
<div class="avatar">
|
||||
<nz-avatar [nzSrc]="user.avatar" nzIcon="user" nzSize="large"></nz-avatar>
|
||||
</div>
|
||||
<form nz-form [formGroup]="f" (ngSubmit)="submit()" role="form" class="mt-md">
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="'validation.password.required' | i18n">
|
||||
<nz-input-group nzSuffixIcon="lock">
|
||||
<input type="password" nz-input formControlName="password" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-row nzType="flex" nzAlign="middle">
|
||||
<nz-col [nzOffset]="12" [nzSpan]="12" style="text-align: right">
|
||||
<button nz-button [disabled]="!f.valid" nzType="primary">{{ 'app.lock' | i18n }}</button>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
12
web-app/src/app/routes/passport/lock/lock.component.less
Normal file
12
web-app/src/app/routes/passport/lock/lock.component.less
Normal file
@@ -0,0 +1,12 @@
|
||||
:host ::ng-deep {
|
||||
.ant-card-body {
|
||||
position: relative;
|
||||
margin-top: 80px;
|
||||
}
|
||||
.avatar {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: 50%;
|
||||
margin-left: -20px;
|
||||
}
|
||||
}
|
||||
44
web-app/src/app/routes/passport/lock/lock.component.ts
Normal file
44
web-app/src/app/routes/passport/lock/lock.component.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Component, Inject } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
|
||||
import { SettingsService, User } from '@delon/theme';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-lock',
|
||||
templateUrl: './lock.component.html',
|
||||
styleUrls: ['./lock.component.less']
|
||||
})
|
||||
export class UserLockComponent {
|
||||
f: FormGroup;
|
||||
|
||||
get user(): User {
|
||||
return this.settings.user;
|
||||
}
|
||||
|
||||
constructor(
|
||||
fb: FormBuilder,
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private settings: SettingsService,
|
||||
private router: Router
|
||||
) {
|
||||
this.f = fb.group({
|
||||
password: [null, Validators.required]
|
||||
});
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
for (const i in this.f.controls) {
|
||||
this.f.controls[i].markAsDirty();
|
||||
this.f.controls[i].updateValueAndValidity();
|
||||
}
|
||||
if (this.f.valid) {
|
||||
console.log('Valid!');
|
||||
console.log(this.f.value);
|
||||
this.tokenService.set({
|
||||
token: '123'
|
||||
});
|
||||
this.router.navigate(['dashboard']);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
web-app/src/app/routes/passport/login/login.component.html
Normal file
74
web-app/src/app/routes/passport/login/login.component.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
|
||||
<nz-tabset [nzAnimated]="false" class="tabs" (nzSelectChange)="switch($event)">
|
||||
<nz-tab [nzTitle]="'app.login.tab-login-credentials' | i18n">
|
||||
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
|
||||
<nz-form-item>
|
||||
<nz-form-control nzErrorTip="Please enter mobile number, muse be: admin or user">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="user">
|
||||
<input nz-input formControlName="userName" placeholder="username: admin or user" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control nzErrorTip="Please enter password">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="lock">
|
||||
<input nz-input type="password" formControlName="password" placeholder="password: admin@123" />
|
||||
</nz-input-group>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</nz-tab>
|
||||
<nz-tab [nzTitle]="'app.login.tab-login-mobile' | i18n">
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="mobileErrorTip">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="user">
|
||||
<input nz-input formControlName="mobile" placeholder="mobile number" />
|
||||
</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>
|
||||
<nz-form-control [nzErrorTip]="'validation.verification-code.required' | i18n">
|
||||
<nz-row [nzGutter]="8">
|
||||
<nz-col [nzSpan]="16">
|
||||
<nz-input-group nzSize="large" nzPrefixIcon="mail">
|
||||
<input nz-input formControlName="captcha" placeholder="captcha" />
|
||||
</nz-input-group>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="8">
|
||||
<button type="button" nz-button nzSize="large" (click)="getCaptcha()" [disabled]="count >= 0" nzBlock [nzLoading]="loading">
|
||||
{{ count ? count + 's' : ('app.register.get-verification-code' | i18n) }}
|
||||
</button>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
</nz-tab>
|
||||
</nz-tabset>
|
||||
<nz-form-item>
|
||||
<nz-col [nzSpan]="12">
|
||||
<label nz-checkbox formControlName="remember">{{ 'app.login.remember-me' | i18n }}</label>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="12" class="text-right">
|
||||
<a class="forgot" routerLink="/passport/register">{{ 'app.login.forgot-password' | i18n }}</a>
|
||||
</nz-col>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button type="submit" nzType="primary" nzSize="large" [nzLoading]="loading" nzBlock>
|
||||
{{ 'app.login.login' | i18n }}
|
||||
</button>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
<div class="other">
|
||||
{{ 'app.login.sign-in-with' | i18n }}
|
||||
<i nz-tooltip nzTooltipTitle="in fact Auth0 via window" (click)="open('auth0', 'window')" nz-icon nzType="alipay-circle" class="icon"></i>
|
||||
<i nz-tooltip nzTooltipTitle="in fact Github via redirect" (click)="open('github')" nz-icon nzType="taobao-circle" class="icon"></i>
|
||||
<i (click)="open('weibo', 'window')" nz-icon nzType="weibo-circle" class="icon"></i>
|
||||
<a class="register" routerLink="/passport/register">{{ 'app.login.signup' | i18n }}</a>
|
||||
</div>
|
||||
53
web-app/src/app/routes/passport/login/login.component.less
Normal file
53
web-app/src/app/routes/passport/login/login.component.less
Normal file
@@ -0,0 +1,53 @@
|
||||
@import '~@delon/theme/index';
|
||||
:host {
|
||||
display: block;
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
::ng-deep {
|
||||
.ant-tabs .ant-tabs-bar {
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
border-bottom: 0;
|
||||
}
|
||||
.ant-tabs-tab {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.ant-input-affix-wrapper .ant-input:not(:first-child) {
|
||||
padding-left: 4px;
|
||||
}
|
||||
.icon {
|
||||
margin-left: 16px;
|
||||
color: rgba(0, 0, 0, 0.2);
|
||||
font-size: 24px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
&:hover {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
.other {
|
||||
margin-top: 24px;
|
||||
line-height: 22px;
|
||||
text-align: left;
|
||||
nz-tooltip {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.register {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
:host ::ng-deep {
|
||||
.icon {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
196
web-app/src/app/routes/passport/login/login.component.ts
Normal file
196
web-app/src/app/routes/passport/login/login.component.ts
Normal file
@@ -0,0 +1,196 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, OnDestroy, Optional } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { StartupService } from '@core';
|
||||
import { ReuseTabService } from '@delon/abc/reuse-tab';
|
||||
import { DA_SERVICE_TOKEN, ITokenService, SocialOpenType, SocialService } from '@delon/auth';
|
||||
import { SettingsService, _HttpClient } from '@delon/theme';
|
||||
import { environment } from '@env/environment';
|
||||
import { NzTabChangeEvent } from 'ng-zorro-antd/tabs';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.less'],
|
||||
providers: [SocialService],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserLoginComponent implements OnDestroy {
|
||||
constructor(
|
||||
fb: FormBuilder,
|
||||
private router: Router,
|
||||
private settingsService: SettingsService,
|
||||
private socialService: SocialService,
|
||||
@Optional()
|
||||
@Inject(ReuseTabService)
|
||||
private reuseTabService: ReuseTabService,
|
||||
@Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService,
|
||||
private startupSrv: StartupService,
|
||||
private http: _HttpClient,
|
||||
private cdr: ChangeDetectorRef
|
||||
) {
|
||||
this.form = fb.group({
|
||||
userName: [null, [Validators.required, Validators.pattern(/^(admin|user)$/)]],
|
||||
password: [null, [Validators.required, Validators.pattern(/^(admin@123)$/)]],
|
||||
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
|
||||
captcha: [null, [Validators.required]],
|
||||
remember: [true]
|
||||
});
|
||||
}
|
||||
|
||||
// #region fields
|
||||
|
||||
get userName(): AbstractControl {
|
||||
return this.form.controls.userName;
|
||||
}
|
||||
get password(): AbstractControl {
|
||||
return this.form.controls.password;
|
||||
}
|
||||
get mobile(): AbstractControl {
|
||||
return this.form.controls.mobile;
|
||||
}
|
||||
get captcha(): AbstractControl {
|
||||
return this.form.controls.captcha;
|
||||
}
|
||||
form: FormGroup;
|
||||
error = '';
|
||||
type = 0;
|
||||
loading = false;
|
||||
|
||||
// #region get captcha
|
||||
|
||||
count = 0;
|
||||
interval$: any;
|
||||
|
||||
// #endregion
|
||||
|
||||
switch({ index }: NzTabChangeEvent): void {
|
||||
this.type = index!;
|
||||
}
|
||||
|
||||
getCaptcha(): void {
|
||||
if (this.mobile.invalid) {
|
||||
this.mobile.markAsDirty({ onlySelf: true });
|
||||
this.mobile.updateValueAndValidity({ onlySelf: true });
|
||||
return;
|
||||
}
|
||||
this.count = 59;
|
||||
this.interval$ = setInterval(() => {
|
||||
this.count -= 1;
|
||||
if (this.count <= 0) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
submit(): void {
|
||||
this.error = '';
|
||||
if (this.type === 0) {
|
||||
this.userName.markAsDirty();
|
||||
this.userName.updateValueAndValidity();
|
||||
this.password.markAsDirty();
|
||||
this.password.updateValueAndValidity();
|
||||
if (this.userName.invalid || this.password.invalid) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this.mobile.markAsDirty();
|
||||
this.mobile.updateValueAndValidity();
|
||||
this.captcha.markAsDirty();
|
||||
this.captcha.updateValueAndValidity();
|
||||
if (this.mobile.invalid || this.captcha.invalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 默认配置中对所有HTTP请求都会强制 [校验](https://ng-alain.com/auth/getting-started) 用户 Token
|
||||
// 然一般来说登录请求不需要校验,因此可以在请求URL加上:`/login?_allow_anonymous=true` 表示不触发用户 Token 校验
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
this.http
|
||||
.post('/login/account?_allow_anonymous=true', {
|
||||
type: this.type,
|
||||
userName: this.userName.value,
|
||||
password: this.password.value
|
||||
})
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
})
|
||||
)
|
||||
.subscribe(res => {
|
||||
if (res.msg !== 'ok') {
|
||||
this.error = res.msg;
|
||||
this.cdr.detectChanges();
|
||||
return;
|
||||
}
|
||||
// 清空路由复用信息
|
||||
this.reuseTabService.clear();
|
||||
// 设置用户Token信息
|
||||
// TODO: Mock expired value
|
||||
res.user.expired = +new Date() + 1000 * 60 * 5;
|
||||
this.tokenService.set(res.user);
|
||||
// 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响
|
||||
this.startupSrv.load().subscribe(() => {
|
||||
let url = this.tokenService.referrer!.url || '/';
|
||||
if (url.includes('/passport')) {
|
||||
url = '/';
|
||||
}
|
||||
this.router.navigateByUrl(url);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// #region social
|
||||
|
||||
open(type: string, openType: SocialOpenType = 'href'): void {
|
||||
let url = ``;
|
||||
let callback = ``;
|
||||
if (environment.production) {
|
||||
callback = `https://ng-alain.github.io/ng-alain/#/passport/callback/${type}`;
|
||||
} else {
|
||||
callback = `http://localhost:4200/#/passport/callback/${type}`;
|
||||
}
|
||||
switch (type) {
|
||||
case 'auth0':
|
||||
url = `//cipchk.auth0.com/login?client=8gcNydIDzGBYxzqV0Vm1CX_RXH-wsWo5&redirect_uri=${decodeURIComponent(callback)}`;
|
||||
break;
|
||||
case 'github':
|
||||
url = `//github.com/login/oauth/authorize?client_id=9d6baae4b04a23fcafa2&response_type=code&redirect_uri=${decodeURIComponent(
|
||||
callback
|
||||
)}`;
|
||||
break;
|
||||
case 'weibo':
|
||||
url = `https://api.weibo.com/oauth2/authorize?client_id=1239507802&response_type=code&redirect_uri=${decodeURIComponent(callback)}`;
|
||||
break;
|
||||
}
|
||||
if (openType === 'window') {
|
||||
this.socialService
|
||||
.login(url, '/', {
|
||||
type: 'window'
|
||||
})
|
||||
.subscribe(res => {
|
||||
if (res) {
|
||||
this.settingsService.setUser(res);
|
||||
this.router.navigateByUrl('/');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.socialService.login(url, '/', {
|
||||
type: 'href'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval$) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<result type="success" [title]="title" description="{{ 'app.register-result.activation-email' | i18n }}">
|
||||
<ng-template #title>
|
||||
<div class="title" style="font-size: 20px">
|
||||
{{ 'app.register-result.msg' | i18n: params }}
|
||||
</div>
|
||||
</ng-template>
|
||||
<button (click)="msg.success('email')" nz-button nzSize="large" [nzType]="'primary'">
|
||||
{{ 'app.register-result.view-mailbox' | i18n }}
|
||||
</button>
|
||||
<button routerLink="/" nz-button nzSize="large">
|
||||
{{ 'app.register-result.back-home' | i18n }}
|
||||
</button>
|
||||
</result>
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NzMessageService } from 'ng-zorro-antd/message';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-register-result',
|
||||
templateUrl: './register-result.component.html'
|
||||
})
|
||||
export class UserRegisterResultComponent {
|
||||
params = { email: '' };
|
||||
email = '';
|
||||
constructor(route: ActivatedRoute, public msg: NzMessageService) {
|
||||
this.params.email = this.email = route.snapshot.queryParams.email || 'ng-alain@example.com';
|
||||
}
|
||||
}
|
||||
100
web-app/src/app/routes/passport/register/register.component.html
Normal file
100
web-app/src/app/routes/passport/register/register.component.html
Normal file
@@ -0,0 +1,100 @@
|
||||
<h3>{{ 'app.register.register' | i18n }}</h3>
|
||||
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
|
||||
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="mailErrorTip">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="user">
|
||||
<input nz-input formControlName="mail" placeholder="Email" />
|
||||
</nz-input-group>
|
||||
<ng-template #mailErrorTip let-i>
|
||||
<ng-container *ngIf="i.errors?.required">{{ 'validation.email.required' | i18n }}</ng-container>
|
||||
<ng-container *ngIf="i.errors?.email">{{ 'validation.email.wrong-format' | i18n }}</ng-container>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="'validation.password.required' | i18n">
|
||||
<nz-input-group
|
||||
nzSize="large"
|
||||
nzAddonBeforeIcon="lock"
|
||||
nz-popover
|
||||
nzPopoverPlacement="right"
|
||||
nzPopoverTrigger="focus"
|
||||
[(nzPopoverVisible)]="visible"
|
||||
nzPopoverOverlayClassName="register-password-cdk"
|
||||
[nzPopoverOverlayStyle]="{ 'width.px': 240 }"
|
||||
[nzPopoverContent]="pwdCdkTpl"
|
||||
>
|
||||
<input nz-input type="password" formControlName="password" placeholder="Password" />
|
||||
</nz-input-group>
|
||||
<ng-template #pwdCdkTpl>
|
||||
<div style="padding: 4px 0">
|
||||
<ng-container [ngSwitch]="status">
|
||||
<div *ngSwitchCase="'ok'" class="success">{{ 'validation.password.strength.strong' | i18n }}</div>
|
||||
<div *ngSwitchCase="'pass'" class="warning">{{ 'validation.password.strength.medium' | i18n }}</div>
|
||||
<div *ngSwitchDefault class="error">{{ 'validation.password.strength.short' | i18n }}</div>
|
||||
</ng-container>
|
||||
<div class="progress-{{ status }}">
|
||||
<nz-progress
|
||||
[nzPercent]="progress"
|
||||
[nzStatus]="passwordProgressMap[status]"
|
||||
[nzStrokeWidth]="6"
|
||||
[nzShowInfo]="false"
|
||||
></nz-progress>
|
||||
</div>
|
||||
<p class="mt-sm">{{ 'validation.password.strength.msg' | i18n }}</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="confirmErrorTip">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="lock">
|
||||
<input nz-input type="password" formControlName="confirm" placeholder="Confirm Password" />
|
||||
</nz-input-group>
|
||||
<ng-template #confirmErrorTip let-i>
|
||||
<ng-container *ngIf="i.errors?.required">{{ 'validation.confirm-password.required' | i18n }}</ng-container>
|
||||
<ng-container *ngIf="i.errors?.matchControl">{{ 'validation.password.twice' | i18n }}</ng-container>
|
||||
</ng-template>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<nz-form-control [nzErrorTip]="mobileErrorTip">
|
||||
<nz-input-group nzSize="large" [nzAddOnBefore]="addOnBeforeTemplate">
|
||||
<ng-template #addOnBeforeTemplate>
|
||||
<nz-select formControlName="mobilePrefix" style="width: 100px">
|
||||
<nz-option [nzLabel]="'+86'" [nzValue]="'+86'"></nz-option>
|
||||
<nz-option [nzLabel]="'+87'" [nzValue]="'+87'"></nz-option>
|
||||
</nz-select>
|
||||
</ng-template>
|
||||
<input formControlName="mobile" nz-input placeholder="Phone number" />
|
||||
</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>
|
||||
<nz-form-control [nzErrorTip]="'validation.verification-code.required' | i18n">
|
||||
<nz-row [nzGutter]="8">
|
||||
<nz-col [nzSpan]="16">
|
||||
<nz-input-group nzSize="large" nzAddonBeforeIcon="mail">
|
||||
<input nz-input formControlName="captcha" placeholder="Captcha" />
|
||||
</nz-input-group>
|
||||
</nz-col>
|
||||
<nz-col [nzSpan]="8">
|
||||
<button type="button" nz-button nzSize="large" (click)="getCaptcha()" [disabled]="count > 0" nzBlock [nzLoading]="loading">
|
||||
{{ count ? count + 's' : ('app.register.get-verification-code' | i18n) }}
|
||||
</button>
|
||||
</nz-col>
|
||||
</nz-row>
|
||||
</nz-form-control>
|
||||
</nz-form-item>
|
||||
<nz-form-item>
|
||||
<button nz-button nzType="primary" nzSize="large" type="submit" [nzLoading]="loading" class="submit">
|
||||
{{ 'app.register.register' | i18n }}
|
||||
</button>
|
||||
<a class="login" routerLink="/passport/login">{{ 'app.register.sign-in' | i18n }}</a>
|
||||
</nz-form-item>
|
||||
</form>
|
||||
@@ -0,0 +1,42 @@
|
||||
@import '~@delon/theme/index';
|
||||
:host {
|
||||
display: block;
|
||||
width: 368px;
|
||||
margin: 0 auto;
|
||||
::ng-deep {
|
||||
h3 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.submit {
|
||||
width: 50%;
|
||||
}
|
||||
.login {
|
||||
float: right;
|
||||
line-height: @btn-height-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
::ng-deep {
|
||||
.register-password-cdk {
|
||||
.success,
|
||||
.warning,
|
||||
.error {
|
||||
transition: color 0.3s;
|
||||
}
|
||||
.success {
|
||||
color: @success-color;
|
||||
}
|
||||
.warning {
|
||||
color: @warning-color;
|
||||
}
|
||||
.error {
|
||||
color: @error-color;
|
||||
}
|
||||
.progress-pass > .progress {
|
||||
.ant-progress-bg {
|
||||
background-color: @warning-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
139
web-app/src/app/routes/passport/register/register.component.ts
Normal file
139
web-app/src/app/routes/passport/register/register.component.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { _HttpClient } from '@delon/theme';
|
||||
import { MatchControl } from '@delon/util/form';
|
||||
import { NzSafeAny } from 'ng-zorro-antd/core/types';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'passport-register',
|
||||
templateUrl: './register.component.html',
|
||||
styleUrls: ['./register.component.less'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class UserRegisterComponent implements OnDestroy {
|
||||
constructor(fb: FormBuilder, private router: Router, private http: _HttpClient, private cdr: ChangeDetectorRef) {
|
||||
this.form = fb.group(
|
||||
{
|
||||
mail: [null, [Validators.required, Validators.email]],
|
||||
password: [null, [Validators.required, Validators.minLength(6), UserRegisterComponent.checkPassword.bind(this)]],
|
||||
confirm: [null, [Validators.required, Validators.minLength(6)]],
|
||||
mobilePrefix: ['+86'],
|
||||
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
|
||||
captcha: [null, [Validators.required]]
|
||||
},
|
||||
{
|
||||
validators: MatchControl('password', 'confirm')
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// #region fields
|
||||
|
||||
get mail(): AbstractControl {
|
||||
return this.form.controls.mail;
|
||||
}
|
||||
get password(): AbstractControl {
|
||||
return this.form.controls.password;
|
||||
}
|
||||
get confirm(): AbstractControl {
|
||||
return this.form.controls.confirm;
|
||||
}
|
||||
get mobile(): AbstractControl {
|
||||
return this.form.controls.mobile;
|
||||
}
|
||||
get captcha(): AbstractControl {
|
||||
return this.form.controls.captcha;
|
||||
}
|
||||
form: FormGroup;
|
||||
error = '';
|
||||
type = 0;
|
||||
loading = false;
|
||||
visible = false;
|
||||
status = 'pool';
|
||||
progress = 0;
|
||||
passwordProgressMap: { [key: string]: 'success' | 'normal' | 'exception' } = {
|
||||
ok: 'success',
|
||||
pass: 'normal',
|
||||
pool: 'exception'
|
||||
};
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region get captcha
|
||||
|
||||
count = 0;
|
||||
interval$: any;
|
||||
|
||||
static checkPassword(control: FormControl): NzSafeAny {
|
||||
if (!control) {
|
||||
return null;
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const self: any = this;
|
||||
self.visible = !!control.value;
|
||||
if (control.value && control.value.length > 9) {
|
||||
self.status = 'ok';
|
||||
} else if (control.value && control.value.length > 5) {
|
||||
self.status = 'pass';
|
||||
} else {
|
||||
self.status = 'pool';
|
||||
}
|
||||
|
||||
if (self.visible) {
|
||||
self.progress = control.value.length * 10 > 100 ? 100 : control.value.length * 10;
|
||||
}
|
||||
}
|
||||
|
||||
getCaptcha(): void {
|
||||
if (this.mobile.invalid) {
|
||||
this.mobile.markAsDirty({ onlySelf: true });
|
||||
this.mobile.updateValueAndValidity({ onlySelf: true });
|
||||
return;
|
||||
}
|
||||
this.count = 59;
|
||||
this.cdr.detectChanges();
|
||||
this.interval$ = setInterval(() => {
|
||||
this.count -= 1;
|
||||
this.cdr.detectChanges();
|
||||
if (this.count <= 0) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
submit(): void {
|
||||
this.error = '';
|
||||
Object.keys(this.form.controls).forEach(key => {
|
||||
this.form.controls[key].markAsDirty();
|
||||
this.form.controls[key].updateValueAndValidity();
|
||||
});
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = this.form.value;
|
||||
this.loading = true;
|
||||
this.cdr.detectChanges();
|
||||
this.http
|
||||
.post('/register?_allow_anonymous=true', data)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.loading = false;
|
||||
this.cdr.detectChanges();
|
||||
})
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.router.navigate(['passport', 'register-result'], { queryParams: { email: data.mail } });
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.interval$) {
|
||||
clearInterval(this.interval$);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user