项目初始化

This commit is contained in:
Taric Xin
2021-11-26 16:34:35 +08:00
parent 66644bcf0a
commit 5287578452
354 changed files with 45736 additions and 0 deletions

View File

@ -0,0 +1,109 @@
<page-header [title]="'查询表格'"></page-header>
<nz-card [nzBordered]="false">
<form nz-form [nzLayout]="'inline'" (ngSubmit)="getData()" class="search__form">
<div nz-row [nzGutter]="{ xs: 8, sm: 8, md: 8, lg: 24, xl: 48, xxl: 48 }">
<div nz-col nzMd="8" nzSm="24">
<nz-form-item>
<nz-form-label nzFor="no">规则编号</nz-form-label>
<nz-form-control>
<input nz-input [(ngModel)]="q.no" name="no" placeholder="请输入" id="no" />
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24">
<nz-form-item>
<nz-form-label nzFor="status">使用状态</nz-form-label>
<nz-form-control>
<nz-select [(ngModel)]="q.status" name="status" id="status" [nzPlaceHolder]="'请选择'" [nzShowSearch]="true">
<nz-option *ngFor="let i of status; let idx = index" [nzLabel]="i.text" [nzValue]="idx"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24" *ngIf="expandForm">
<nz-form-item>
<nz-form-label nzFor="callNo">调用次数</nz-form-label>
<nz-form-control>
<input nz-input id="callNo" />
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24" *ngIf="expandForm">
<nz-form-item>
<nz-form-label nzFor="updatedAt">更新日期</nz-form-label>
<nz-form-control>
<nz-date-picker id="updatedAt"></nz-date-picker>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24" *ngIf="expandForm">
<nz-form-item>
<nz-form-label nzFor="status2">使用状态</nz-form-label>
<nz-form-control>
<nz-select [nzPlaceHolder]="'请选择'" nzId="status2" [nzShowSearch]="true">
<nz-option *ngFor="let i of status; let idx = index" [nzLabel]="i.text" [nzValue]="idx"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col nzMd="8" nzSm="24" *ngIf="expandForm">
<nz-form-item>
<nz-form-label nzFor="status3">使用状态</nz-form-label>
<nz-form-control>
<nz-select [nzPlaceHolder]="'请选择'" nzId="status3" [nzShowSearch]="true">
<nz-option *ngFor="let i of status; let idx = index" [nzLabel]="i.text" [nzValue]="idx"></nz-option>
</nz-select>
</nz-form-control>
</nz-form-item>
</div>
<div nz-col [nzSpan]="expandForm ? 24 : 8" [class.text-right]="expandForm">
<button nz-button type="submit" [nzType]="'primary'" [nzLoading]="loading">查询</button>
<button nz-button type="reset" (click)="reset()" class="mx-sm">重置</button>
<a (click)="expandForm = !expandForm">
{{ expandForm ? '收起' : '展开' }}
<i nz-icon [nzType]="expandForm ? 'up' : 'down'"></i>
</a>
</div>
</div>
</form>
<button nz-button (click)="add(modalContent)" [nzType]="'primary'">
<i nz-icon nzType="plus"></i>
<span>新建</span>
</button>
<ng-container *ngIf="selectedRows.length > 0">
<button nz-button>批量操作</button>
<button nz-button nz-dropdown [nzDropdownMenu]="batchMenu" nzPlacement="bottomLeft">
更多操作
<i nz-icon nzType="down"></i>
</button>
<nz-dropdown-menu #batchMenu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item (click)="remove()">删除</li>
<li nz-menu-item (click)="approval()">批量审批</li>
</ul>
</nz-dropdown-menu>
</ng-container>
<div class="my-md">
<nz-alert [nzType]="'info'" [nzShowIcon]="true" [nzMessage]="message">
<ng-template #message>
已选择
<strong class="text-primary">{{ selectedRows.length }}</strong>&nbsp;&nbsp; 服务调用总计 <strong>{{ totalCallNo
}}</strong>
<a *ngIf="totalCallNo > 0" (click)="st.clearCheck()" class="ml-lg">清空</a>
</ng-template>
</nz-alert>
</div>
<st #st [columns]="columns" [data]="data" [loading]="loading" (change)="stChange($event)">
<ng-template st-row="status" let-i>
<nz-badge [nzStatus]="i.statusType" [nzText]="i.statusText"></nz-badge>
</ng-template>
</st>
</nz-card>
<ng-template #modalContent>
<nz-form-item>
<nz-form-label nzFor="no">描述</nz-form-label>
<nz-form-control>
<input nz-input [(ngModel)]="description" name="description" placeholder="请输入" id="no" />
</nz-form-control>
</nz-form-item>
</ng-template>

View File

@ -0,0 +1,6 @@
@import '~@delon/theme/index';
:host {
::ng-deep {
}
}

View File

@ -0,0 +1,166 @@
import { Component, ChangeDetectionStrategy, ChangeDetectorRef, TemplateRef, ViewChild } from '@angular/core';
import { STComponent, STColumn, STData, STChange } from '@delon/abc/st';
import { _HttpClient } from '@delon/theme';
import { NzSafeAny } from 'ng-zorro-antd/core/types';
import { NzMessageService } from 'ng-zorro-antd/message';
import { NzModalService } from 'ng-zorro-antd/modal';
import { map, tap } from 'rxjs/operators';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.less'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DashboardComponent {
q: {
pi: number;
ps: number;
no: string;
sorter: string;
status: number | null;
statusList: NzSafeAny[];
} = {
pi: 1,
ps: 10,
no: '',
sorter: '',
status: null,
statusList: []
};
data: any[] = [];
loading = false;
status = [
{ index: 0, text: '关闭', value: false, type: 'default', checked: false },
{
index: 1,
text: '运行中',
value: false,
type: 'processing',
checked: false
},
{ index: 2, text: '已上线', value: false, type: 'success', checked: false },
{ index: 3, text: '异常', value: false, type: 'error', checked: false }
];
@ViewChild('st', { static: true })
st!: STComponent;
columns: STColumn[] = [
{ title: '', index: 'key', type: 'checkbox' },
{ title: '规则编号', index: 'no' },
{ title: '描述', index: 'description' },
{
title: '服务调用次数',
index: 'callNo',
type: 'number',
format: item => `${item.callNo}`,
sort: {
compare: (a, b) => a.callNo - b.callNo
}
},
{
title: '状态',
index: 'status',
render: 'status',
filter: {
menus: this.status,
fn: (filter, record) => record.status === filter.index
}
},
{
title: '更新时间',
index: 'updatedAt',
type: 'date',
sort: {
compare: (a, b) => a.updatedAt - b.updatedAt
}
},
{
title: '操作',
buttons: [
{
text: '配置',
click: item => this.msg.success(`配置${item.no}`)
},
{
text: '订阅警报',
click: item => this.msg.success(`订阅警报${item.no}`)
}
]
}
];
selectedRows: STData[] = [];
description = '';
totalCallNo = 0;
expandForm = false;
constructor(private http: _HttpClient, public msg: NzMessageService, private modalSrv: NzModalService, private cdr: ChangeDetectorRef) {}
ngOnInit(): void {
this.getData();
}
getData(): void {
this.loading = true;
this.q.statusList = this.status.filter(w => w.checked).map(item => item.index);
if (this.q.status !== null && this.q.status > -1) {
this.q.statusList.push(this.q.status);
}
this.http
.get('/rule?_allow_anonymous=true', this.q)
.pipe(
map((list: Array<{ status: number; statusText: string; statusType: string }>) =>
list.map(i => {
const statusItem = this.status[i.status];
i.statusText = statusItem.text;
i.statusType = statusItem.type;
return i;
})
),
tap(() => (this.loading = false))
)
.subscribe(res => {
this.data = res;
this.cdr.detectChanges();
});
}
stChange(e: STChange): void {
switch (e.type) {
case 'checkbox':
this.selectedRows = e.checkbox!;
this.totalCallNo = this.selectedRows.reduce((total, cv) => total + cv.callNo, 0);
this.cdr.detectChanges();
break;
case 'filter':
this.getData();
break;
}
}
remove(): void {
this.http.delete('/rule', { nos: this.selectedRows.map(i => i.no).join(',') }).subscribe(() => {
this.getData();
this.st.clearCheck();
});
}
approval(): void {
this.msg.success(`审批了 ${this.selectedRows.length}`);
}
add(tpl: TemplateRef<{}>): void {
this.modalSrv.create({
nzTitle: '新建规则',
nzContent: tpl,
nzOnOk: () => {
this.loading = true;
this.http.post('/rule', { description: this.description }).subscribe(() => this.getData());
}
});
}
reset(): void {
// wait form reset updated finished
setTimeout(() => this.getData());
}
}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'exception-403',
template: ` <exception type="403" style="min-height: 500px; height: 80%;"></exception> `
})
export class Exception403Component {}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'exception-404',
template: ` <exception type="404" style="min-height: 500px; height: 80%;"></exception> `
})
export class Exception404Component {}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'exception-500',
template: ` <exception type="500" style="min-height: 500px; height: 80%;"></exception> `
})
export class Exception500Component {}

View File

@ -0,0 +1,20 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { Exception403Component } from './403.component';
import { Exception404Component } from './404.component';
import { Exception500Component } from './500.component';
import { ExceptionTriggerComponent } from './trigger.component';
const routes: Routes = [
{ path: '403', component: Exception403Component },
{ path: '404', component: Exception404Component },
{ path: '500', component: Exception500Component },
{ path: 'trigger', component: ExceptionTriggerComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ExceptionRoutingModule {}

View File

@ -0,0 +1,19 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { ExceptionModule as DelonExceptionModule } from '@delon/abc/exception';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzCardModule } from 'ng-zorro-antd/card';
import { Exception403Component } from './403.component';
import { Exception404Component } from './404.component';
import { Exception500Component } from './500.component';
import { ExceptionRoutingModule } from './exception-routing.module';
import { ExceptionTriggerComponent } from './trigger.component';
const COMPONENTS = [Exception403Component, Exception404Component, Exception500Component, ExceptionTriggerComponent];
@NgModule({
imports: [CommonModule, DelonExceptionModule, NzButtonModule, NzCardModule, ExceptionRoutingModule],
declarations: [...COMPONENTS]
})
export class ExceptionModule {}

View File

@ -0,0 +1,35 @@
import { Component, Inject } from '@angular/core';
import { DA_SERVICE_TOKEN, ITokenService } from '@delon/auth';
import { _HttpClient } from '@delon/theme';
@Component({
selector: 'exception-trigger',
template: `
<div class="pt-lg">
<nz-card>
<button *ngFor="let t of types" (click)="go(t)" nz-button nzDanger>触发{{ t }}</button>
<button nz-button nzType="link" (click)="refresh()">触发刷新Token</button>
</nz-card>
</div>
`
})
export class ExceptionTriggerComponent {
types = [401, 403, 404, 500];
constructor(private http: _HttpClient, @Inject(DA_SERVICE_TOKEN) private tokenService: ITokenService) {}
go(type: number): void {
this.http.get(`/api/${type}`).subscribe();
}
refresh(): void {
this.tokenService.set({ token: 'invalid-token' });
// 必须提供一个后端地址,无法通过 Mock 来模拟
this.http.post(`https://localhost:5001/auth`).subscribe(
res => console.warn('成功', res),
err => {
console.log('最后结果失败', err);
}
);
}
}

View 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);
}
}

View 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="请输入密码!">
<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">锁屏</button>
</nz-col>
</nz-row>
</form>
</div>
</div>

View 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;
}
}

View 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']);
}
}
}

View File

@ -0,0 +1,77 @@
<form nz-form [formGroup]="form" (ngSubmit)="submit()" role="form">
<nz-tabset [nzAnimated]="false" class="tabs" (nzSelectChange)="switch($event)">
<nz-tab nzTitle="账户密码登录">
<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, muse be: ">
<nz-input-group nzSize="large" nzPrefixIcon="lock">
<input nz-input type="password" formControlName="password" placeholder="password: 随便" />
</nz-input-group>
</nz-form-control>
</nz-form-item>
</nz-tab>
<nz-tab nzTitle="手机号登录">
<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">
请输入手机号!
</ng-container>
<ng-container *ngIf="i.errors.pattern">
手机号格式错误!
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="请输入验证码!">
<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' : '获取验证码' }}
</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">自动登录</label>
</nz-col>
<nz-col [nzSpan]="12" class="text-right">
<a class="forgot" routerLink="/passport/register">忘记密码</a>
</nz-col>
</nz-form-item>
<nz-form-item>
<button nz-button type="submit" nzType="primary" nzSize="large" [nzLoading]="loading" nzBlock>
登录
</button>
</nz-form-item>
</form>
<div class="other">
其他登录方式
<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">注册账户</a>
</div>

View 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;
}
}
}
}

View File

@ -0,0 +1,213 @@
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]],
password: [null, [Validators.required]],
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.befaultLogin();
return;
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().then(() => {
let url = this.tokenService.referrer!.url || '/';
if (url.includes('/passport')) {
url = '/';
}
this.router.navigateByUrl(url);
});
});
}
befaultLogin() {
// 清空路由复用信息
this.reuseTabService.clear();
// 设置用户Token信息
// TODO: Mock expired value
// 重新获取 StartupService 内容,我们始终认为应用信息一般都会受当前用户授权范围而影响
this.startupSrv.load().then(() => {
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$);
}
}
}

View File

@ -0,0 +1,56 @@
<!-- <pro-langs class="pro-passport__langs"></pro-langs> -->
<div class="ant-col-lg-16 pro-passport__bg" style="background-image: url('./assets/tmp/img-big/bg-1.jpeg')">
<div class="pro-passport__bg-overlay"></div>
<div class="text">
<h1>Work with us</h1>
<p>Our researchers are embedded in teams across computer science, to discover, invent, and build at the largest
scale.</p>
</div>
</div>
<div class="ant-col-lg-8 pro-passport__form">
<div class="pro-passport__form-logo"><img src="./assets/logo-color.svg" /></div>
<h3 class="pro-passport__form-title">注册</h3>
<form nz-form #f="ngForm" nzLayout="vertical" [formGroup]="form" (ngSubmit)="submit()" role="form" se-container>
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
<se label="手机号" required error="手机号格式错误!">
<nz-input-group nzSize="large" [nzAddOnBefore]="addOnBeforeTemplate">
<ng-template #addOnBeforeTemplate>
<nz-select formControlName="mobilePrefix" style="width: 80px">
<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 type="tel" placeholder="Phone number" />
</nz-input-group>
</se>
<se label="请输入验证码!" required error="请输入验证码!">
<nz-row [nzGutter]="8">
<nz-col [nzSpan]="12">
<nz-input-group nzSize="large" nzAddonBeforeIcon="anticon anticon-mail">
<input nz-input formControlName="captcha" type="tel" placeholder="Captcha" />
</nz-input-group>
</nz-col>
<nz-col [nzSpan]="12">
<button type="button" nz-button nzSize="large" (click)="getCaptcha()" [disabled]="count > 0" nzBlock
[nzLoading]="http.loading">
{{ count ? count + 's' :'获取验证码' }}
</button>
</nz-col>
</nz-row>
</se>
<se label="密码" required error="请输入密码!">
<input nz-input type="password" nzSize="large" formControlName="password" placeholder="Password" />
</se>
<se>
<div class="flex-center-between">
<button nz-button nzType="primary" nzSize="large" type="submit" [disabled]="f.invalid"
[nzLoading]="http.loading">
注册
</button>
<a routerLink="/passport/login">
使用已有账户登录
</a>
</div>
</se>
</form>
</div>

View File

@ -0,0 +1,28 @@
@import '~@delon/theme/index';
:host ::ng-deep {
.pro-passport__bg {
position: relative;
display: flex;
align-items: center;
padding: 48px;
@media (max-width: @screen-md-max) {
display: none !important;
}
.text {
position: relative;
padding: 0 64px;
color: #fff;
h1 {
margin-bottom: 24px;
color: #fff;
font-weight: 900;
font-size: 56px;
}
p {
font-size: 22px;
line-height: 32px;
}
}
}
}

View File

@ -0,0 +1,78 @@
import { Component, OnDestroy } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { _HttpClient } from '@delon/theme';
import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
selector: 'passport-login2',
templateUrl: './login2.component.html',
styleUrls: ['./login2.component.less'],
host: {
'[class.ant-row]': 'true',
'[class.pro-passport]': 'true'
}
})
export class UserLogin2Component implements OnDestroy {
form: FormGroup;
error = '';
constructor(fb: FormBuilder, private router: Router, private msg: NzMessageService, public http: _HttpClient) {
this.form = fb.group({
mobilePrefix: ['+86'],
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
captcha: [null, [Validators.required]],
password: [null, [Validators.required, Validators.minLength(6)]]
});
}
// #region fields
get password(): AbstractControl {
return this.form.controls.password;
}
get mobile(): AbstractControl {
return this.form.controls.mobile;
}
get captcha(): AbstractControl {
return this.form.controls.captcha;
}
// #endregion
// #region get captcha
count = 0;
interval$: any;
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 = '';
const data = this.form.value;
this.http.post('/register', data).subscribe(() => {
this.router.navigate(['passport', 'register-result'], { queryParams: { email: data.mail } });
});
}
ngOnDestroy(): void {
if (this.interval$) {
clearInterval(this.interval$);
}
}
}

View File

@ -0,0 +1,51 @@
<!-- <pro-langs class="pro-passport__langs"></pro-langs> -->
<div class="pro-passport__bg width-100" style="background-image: url('./assets/tmp/img-big/bg-2.jpeg')">
<div class="pro-passport__bg-overlay"></div>
<div class="pro-passport__form">
<div class="pro-passport__form-logo"><img src="./assets/logo-color.svg" /></div>
<h3 class="pro-passport__form-title">注册</h3>
<form nz-form #f="ngForm" nzLayout="vertical" [formGroup]="form" (ngSubmit)="submit()" role="form" se-container>
<nz-alert *ngIf="error" [nzType]="'error'" [nzMessage]="error" [nzShowIcon]="true" class="mb-lg"></nz-alert>
<se label="手机号" required error="手机号格式错误!">
<nz-input-group nzSize="large" [nzAddOnBefore]="addOnBeforeTemplate">
<ng-template #addOnBeforeTemplate>
<nz-select formControlName="mobilePrefix" style="width: 80px">
<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 type="tel" placeholder="Phone number" />
</nz-input-group>
</se>
<se label="验证码" required error="请输入验证码!">
<nz-row [nzGutter]="8">
<nz-col [nzSpan]="12">
<nz-input-group nzSize="large" nzAddonBeforeIcon="anticon anticon-mail">
<input nz-input formControlName="captcha" type="tel" placeholder="Captcha" />
</nz-input-group>
</nz-col>
<nz-col [nzSpan]="12">
<button type="button" nz-button nzSize="large" (click)="getCaptcha()" [disabled]="count > 0" nzBlock
[nzLoading]="http.loading">
{{ count ? count + 's' : '获取验证码' }}
</button>
</nz-col>
</nz-row>
</se>
<se label="密码" required error="请输入密码!">
<input nz-input type="password" nzSize="large" formControlName="password" placeholder="Password" />
</se>
<se>
<div class="flex-center-between">
<button nz-button nzType="primary" nzSize="large" type="submit" [disabled]="f.invalid"
[nzLoading]="http.loading">
注册
</button>
<a routerLink="/passport/login">
使用已有账户登录
</a>
</div>
</se>
</form>
</div>
</div>

View File

@ -0,0 +1,13 @@
@import '~@delon/theme/index';
:host ::ng-deep {
.pro-passport {
&__form {
position: relative;
max-width: 380px;
margin: 48px auto 0 auto;
background: #fff;
border-radius: 4px;
}
}
}

View File

@ -0,0 +1,78 @@
import { Component, OnDestroy } from '@angular/core';
import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { _HttpClient } from '@delon/theme';
import { NzMessageService } from 'ng-zorro-antd/message';
@Component({
selector: 'passport-login3',
templateUrl: './login3.component.html',
styleUrls: ['./login3.component.less'],
host: {
'[class.ant-row]': 'true',
'[class.pro-passport]': 'true'
}
})
export class UserLogin3Component implements OnDestroy {
form: FormGroup;
error = '';
constructor(fb: FormBuilder, private router: Router, private msg: NzMessageService, public http: _HttpClient) {
this.form = fb.group({
mobilePrefix: ['+86'],
mobile: [null, [Validators.required, Validators.pattern(/^1\d{10}$/)]],
captcha: [null, [Validators.required]],
password: [null, [Validators.required, Validators.minLength(6)]]
});
}
// #region fields
get password(): AbstractControl {
return this.form.controls.password;
}
get mobile(): AbstractControl {
return this.form.controls.mobile;
}
get captcha(): AbstractControl {
return this.form.controls.captcha;
}
// #endregion
// #region get captcha
count = 0;
interval$: any;
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 = '';
const data = this.form.value;
this.http.post('/register', data).subscribe(() => {
this.router.navigate(['passport', 'register-result'], { queryParams: { email: data.mail } });
});
}
ngOnDestroy(): void {
if (this.interval$) {
clearInterval(this.interval$);
}
}
}

View File

@ -0,0 +1,51 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LayoutPassportComponent } from '../../layout/passport/passport.component';
import { CallbackComponent } from './callback.component';
import { UserLockComponent } from './lock/lock.component';
import { UserLoginComponent } from './login/login.component';
import { UserLogin2Component } from './login2/login2.component';
import { UserLogin3Component } from './login3/login3.component';
import { UserRegisterResultComponent } from './register-result/register-result.component';
import { UserRegisterComponent } from './register/register.component';
const routes: Routes = [
// passport
{
path: 'passport',
component: LayoutPassportComponent,
children: [
{
path: 'login',
component: UserLoginComponent,
data: { title: '登录' }
},
{
path: 'register',
component: UserRegisterComponent,
data: { title: '注册' }
},
{
path: 'register-result',
component: UserRegisterResultComponent,
data: { title: '注册结果' }
},
{
path: 'lock',
component: UserLockComponent,
data: { title: '锁屏' }
}
]
},
// 单页不包裹Layout
{ path: 'login2', component: UserLogin2Component },
{ path: 'login3', component: UserLogin3Component },
{ path: 'passport/callback/:type', component: CallbackComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class PassportRoutingModule {}

View File

@ -0,0 +1,67 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { ResultModule } from '@delon/abc/result';
import { SEModule } from '@delon/abc/se';
import { AlainThemeModule } from '@delon/theme';
import { NzAlertModule } from 'ng-zorro-antd/alert';
import { NzAvatarModule } from 'ng-zorro-antd/avatar';
import { NzButtonModule } from 'ng-zorro-antd/button';
import { NzCheckboxModule } from 'ng-zorro-antd/checkbox';
import { NzFormModule } from 'ng-zorro-antd/form';
import { NzGridModule } from 'ng-zorro-antd/grid';
import { NzIconModule } from 'ng-zorro-antd/icon';
import { NzInputModule } from 'ng-zorro-antd/input';
import { NzPopoverModule } from 'ng-zorro-antd/popover';
import { NzProgressModule } from 'ng-zorro-antd/progress';
import { NzSelectModule } from 'ng-zorro-antd/select';
import { NzTabsModule } from 'ng-zorro-antd/tabs';
import { NzToolTipModule } from 'ng-zorro-antd/tooltip';
import { CallbackComponent } from './callback.component';
import { UserLockComponent } from './lock/lock.component';
import { UserLoginComponent } from './login/login.component';
import { UserLogin2Component } from './login2/login2.component';
import { UserLogin3Component } from './login3/login3.component';
import { PassportRoutingModule } from './passport-routing.module';
import { UserRegisterResultComponent } from './register-result/register-result.component';
import { UserRegisterComponent } from './register/register.component';
const COMPONENTS = [
// passport pages
UserLoginComponent,
UserRegisterComponent,
UserRegisterResultComponent,
UserLockComponent,
// single pages
UserLogin2Component,
UserLogin3Component,
CallbackComponent
];
@NgModule({
imports: [
PassportRoutingModule,
CommonModule,
FormsModule,
ReactiveFormsModule,
AlainThemeModule.forChild(),
NzTabsModule,
NzAlertModule,
NzFormModule,
NzGridModule,
NzInputModule,
NzSelectModule,
NzButtonModule,
NzCheckboxModule,
NzIconModule,
NzToolTipModule,
NzPopoverModule,
NzProgressModule,
NzAvatarModule,
SEModule,
ResultModule
],
declarations: COMPONENTS
})
export class PassportModule {}

View File

@ -0,0 +1,13 @@
<result type="success" [title]="title" description="激活邮件已发送到你的邮箱中邮件有效期为24小时。请及时登录邮箱点击邮件中的链接激活帐户。">
<ng-template #title>
<div class="title" style="font-size: 20px">
你的账户:{{params?.email}} 注册成功
</div>
</ng-template>
<button (click)="msg.success('email')" nz-button nzSize="large" [nzType]="'primary'">
查看邮箱
</button>
<button routerLink="/" nz-button nzSize="large">
返回首页
</button>
</result>

View File

@ -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';
}
}

View File

@ -0,0 +1,89 @@
<h3>注册</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">请输入邮箱地址!</ng-container>
<ng-container *ngIf="i.errors?.email">邮箱地址格式错误!</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="请输入密码!">
<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">强度:强</div>
<div *ngSwitchCase="'pass'" class="warning">强度:中</div>
<div *ngSwitchDefault class="error">强度:太短</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">请至少输入 6 个字符。请不要使用容易被猜到的密码。</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">请确认密码!</ng-container>
<ng-container *ngIf="i.errors?.matchControl">两次输入的密码不匹配!</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">请输入手机号!</ng-container>
<ng-container *ngIf="i.errors?.pattern">手机号格式错误!</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control nzErrorTip="请输入验证码!">
<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' : '获取验证码' }}
</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">
注册
</button>
<a class="login" routerLink="/passport/login">使用已有账户登录</a>
</nz-form-item>
</form>

View File

@ -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;
}
}
}
}

View 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$);
}
}
}

View File

@ -0,0 +1,36 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
// layout
import { LayoutProComponent } from '@brand';
import { environment } from '@env/environment';
// dashboard pages
import { DashboardComponent } from './dashboard/dashboard.component';
const routes: Routes = [
{
path: '',
component: LayoutProComponent,
children: [
{ path: '', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
],
},
// passport
{ path: '', loadChildren: () => import('./passport/passport.module').then((m) => m.PassportModule) },
{ path: 'exception', loadChildren: () => import('./exception/exception.module').then((m) => m.ExceptionModule) },
// 单页不包裹Layout
{ path: '**', redirectTo: 'exception/404' },
];
@NgModule({
imports: [
RouterModule.forRoot(routes, {
useHash: environment.useHash,
// NOTICE: If you use `reuse-tab` component and turn on keepingScroll you can set to `disabled`
// Pls refer to https://ng-alain.com/components/reuse-tab
scrollPositionRestoration: 'top',
}),
],
exports: [RouterModule],
})
export class RouteRoutingModule {}

View File

@ -0,0 +1,16 @@
import { NgModule, Type } from '@angular/core';
import { SharedModule } from '@shared';
// dashboard pages
import { DashboardComponent } from './dashboard/dashboard.component';
import { RouteRoutingModule } from './routes-routing.module';
const COMPONENTS: Type<void>[] = [DashboardComponent];
const COMPONENTS_NOROUNT: Type<void>[] = [];
@NgModule({
imports: [SharedModule, RouteRoutingModule],
declarations: [...COMPONENTS, ...COMPONENTS_NOROUNT],
entryComponents: COMPONENTS_NOROUNT,
})
export class RoutesModule {}