了解最新技术文章
首先,启动应用程序生成器并选择“创建新应用程序”。接下来,导航到“示例应用程序”部分并选择 HR 仪表板作为您项目的基础设计。从那里,您可以扩展和自定义设计以满足您的特定需求。
打开示例后,您会注意到已经为您创建了几个页面。但是,为了更好地满足我们项目的要求,您将需要对其中一些页面进行修改并创建新页面。
现在,让我们创建登录和注册页面。导航到“视图”选项卡并选择加号图标以创建一个新页面,我们将其命名为“注册页面”。
为了保持一致性,我们也可以将主页面的背景重新用于注册页面。我们需要添加一个列布局。在此布局中,我们将包含一个带有注册内容的标题和五个输入字段以及一个按钮。值得注意的是,所有输入字段都应该是必填的,并且应该指定适当的类型。
接下来,我们可以创建登录屏幕。此屏幕仅需要两个输入字段 - 一个用于电子邮件,一个用于密码。为了简化流程,我们可以复制注册页面并删除任何不必要的元素。
创建两个页面后,我们可以在提交按钮下添加指向每个页面的链接。
主页将具有仪表板,它将显示当前用户在卡片组件中参加的所有事件。如果用户具有管理员角色,他们还将看到一个包含平台上所有事件的网格,允许他们根据需要执行 CRUD 操作。
接下来,我们将创建一个用于添加新事件的页面。只有具有管理员角色的用户才能访问此页面,但我们将在本教程的后面部分介绍这一点。对于每个活动,我们都需要标题、类别、参加用户的电子邮件以及活动日期。
此外,我们需要创建一个类似的页面来更改用户的角色。与活动页面类似,此功能只有管理员才能访问。出于本演示的目的,我们仅支持授予其他用户管理员权限。
创建所有页面后,我们可以将它们链接到侧边栏导航中。
幸运的是,手动连接我们的 API 是不必要的,因为我们可以通过 App Builder 将其作为数据源上传来直接完成此操作。首先,我们必须确保 API 正在运行,然后导航到“数据源”选项卡,选择加号图标,然后选择 REST API。从那里,我们有两个选择:
添加Swagger定义
或者使用 JSON URL
出于我们的目的,我们将利用Swagger 方法并添加 URL。
我们需要指定数据源的名称并继续下一步。然后,我们必须确定要包含哪些端点。对于此演示,我们将选择所有可用端点。但是,请务必注意,事件的所有端点都需要授权才能成功。因此,我们需要在 API 中从用户那里获取 JWT 令牌并将其添加到授权选项卡中。
在本教程的后面,我们将用当前用户的令牌替换它。设置授权后,我们可以继续选择数据,确保选择所有字段,然后单击完成。
数据源上传成功后,我们就可以在仪表板页面继续连接网格了。首先,选择网格并从数据字段更新数据源。从那里,我们可以添加更新和删除操作,这些操作将链接到 API 中的端点,从而允许通过与网格交互来实时修改数据。
创建所有页面后,我们可以通过选择右上角的绿色按钮来预览应用程序。然后我们需要下载应用程序以方便进一步定制。
下载应用程序后,解压缩项目并在 Visual Studio Code 中打开它。在终端中,运行“npm install”,然后运行“npm run start”以开始运行应用程序。
下一步是将登录和注册页面与我们的 API 连接起来。为此,我们必须添加调用 API 的函数。这些函数应添加到存储我们所有服务的 services/hrdashboard.service.ts 文件中。我们还必须添加两项功能,一项用于登录,一项用于注册。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
……
公共 registerUser (数据:任何, contentType : string = 'application/json-patch+json, application/json, text/json, application/*+json' ) {
常量 选项 = {
标题: {
'内容类型' : 内容类型
}
} ;
常量 主体 = 数据;
返回 这个。http 。post ( ` ${ API_ENDPOINT } /Auth/Register ` , body , options ) ;
}
公共 登录用户(数据:任何, 内容类型:字符串 = 'application / json-patch + json,application / json,text / json,application / * + json' ) {
常量 选项 = {
标题: {
'内容类型' : 内容类型
}
} ;
常量 主体 = 数据;
返回 这个。http 。post ( ` ${ API_ENDPOINT } /Auth/Login ` , body , options ) ;
}
……
在下一步中,导航到 register-page.component.ts文件并添加输入属性的绑定。创建一个变量来存储错误消息,并在请求不成功时向用户显示验证信息。另外,添加一个在提交表单时触发的函数。此函数将检查是否所有字段都是必需的,如果是,则将 JWT 令牌存储在localStorage中并导航到主页。如果缺少任何字段,该函数应向用户显示错误消息。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
导出 类 RegisterPageComponent {
电子邮件:号码;
名字:字符串;
姓氏:字符串;
密码:字符串;
确认密码:字符串;
错误消息:字符串;
构造函数(
私人 hRAPIService:HRAPIService ,
私有 路由器:路由器
) { }
onSubmit (事件) {
event.preventDefault ( ) ;
if ( this.password ! == this.confirmedPassword ) { _
这。errorMessage = '密码应该匹配!'
}
else if ( this.email && this.firstName && this.lastName && this.password ) { _ _ _ _
这。hRAPI服务。registerUser ({ 名字: this.firstName , 姓氏: this.lastName , 电子邮件: this.email , 密码: this.password , 确认密码: this.confirmedPassword })
。订阅({
下一个:(响应 )= > {
我们还需要更新register-page.component.html以绑定输入。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
class = "列布局背景"
class = "列布局组"
类= "h2"
登记
class = "error-message" }
type = "border" class = "input"
类型= "text"必需igxInput [( ngModel )] = "firstName"
igxLabel 名字
类型= "border"类= "input_1"
类型= "text"必需igxInput [( ngModel )] = "lastName"
igxLabel 姓氏
类型= "border"类= "input_1"
类型= "电子邮件"必需igxInput [( ngModel )] = "电子邮件"
igxLabel 电子邮件
类型= "border"类= "input_1"
类型= "密码"必需igxInput [( ngModel )] = "密码"
igxLabel 密码
要设置errorMessage的样式,我们需要在 register-page.component.scss 中添加样式。
全屏
1
2
3
4
5
6
.错误消息 {
文本对齐:居中;
保证金:2雷姆 0;
字体粗细:粗体;
颜色:红色;
}
与注册页面一样,我们需要创建属性来绑定输入和为登录页面提交表单时执行的函数。此函数将调用登录服务,通过发送电子邮件和密码来验证用户身份。如果身份验证成功,它将把jwt令牌存储在localStorage中并导航到主页。如果失败,它将向用户显示一条错误消息。我们还需要更新login-page.component.html以绑定输入。
登录.页面.组件.ts
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
导出 类 LoginComponent {
电子邮件:号码;
名字:字符串;
姓氏:字符串;
密码:字符串;
确认密码:字符串;
错误消息:字符串;
构造函数(
私人 hRAPIService:HRAPIService ,
私有 路由器:路由器
) { }
onSubmit (事件) {
event.preventDefault ( ) ;
if (这个.电子邮件 && 这个.密码) {
这。hRAPI服务。loginUser ({ 电子邮件: this.email , 密码: this.password })
。订阅({
下一个:(响应 )= > {
本地存储。setItem ( 'hr_app_token' , 响应[ '值' ]) ;
这。路由器。导航通过网址('/' );
} ,
登录页面.component.html
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
class = "列布局背景"
class="column-layout group"
class="h2"
Login
class="error-message"{{errorMessage}}
type="border" class="input"
type="text" igxInput [(ngModel)]="email"
igxLabelEmail
type="border" class="input_1"
type="password" igxInput [(ngModel)]="password"
igxLabelPassword
(点击)= “onSubmit($event)” igxButton = “引发” igxRipple类= “按钮”
登录
我们需要创建一个AuthService来帮助我们解码令牌,检查用户是否具有特定角色,并删除会话。为此,我们将创建一个名为auth.service.ts的新文件,我们应该将其导入到app.module.ts中。为了解码令牌,我们需要安装jwt-decode包。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
从“@angular/core”导入{ 可注入 } ;
import jwt_decode from 'jwt-decode
';
type Token = {
Id?: string,
email?: string,
firstName?: string,
exp?: number,
role?: string,
sub?: string,
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
decodeToken(token) {
return jwt_decode(token);
}
getEmail() {
const token = localStorage.getItem('hr_app_token');
const {email}: Token = this.decodeToken(token);
为了增强安全性,我们需要为端点实施防护。我们将创建三个守卫,从anonymous-guard.ts开始。此防护确保登录和注册页面仅可供未登录的用户访问。如果已登录的用户尝试访问这些页面,则应将其重定向到主页。
为了实现这个防护,我们将在应用程序目录中创建一个防护文件夹,并创建一个名为anonymous-guard.ts的新文件。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AnonymousGuard implements CanActivate {
constructor(private router: Router,
private authService: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable
<boolean | UrlTree>
| Promise
<boolean | UrlTree>
| boolean | UrlTree {
if (!this.authService.isAuthenticated()) {
return true;
}
我们需要实现的下一个防护称为auth-guard。该防护将确保某些页面只能由经过身份验证的用户访问。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../services/auth.service';
@Injectable({
providedIn: 'root'
})
export class AuthGuard implements CanActivate {
constructor(private router: Router,
private authService: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable
<boolean | UrlTree>
| Promise
<boolean | UrlTree>
| boolean | UrlTree {
if (this.authService.isAuthenticated()) {
return true;
}
return this.router.parseUrl("/login-page");
第三个防护称为 admin-guard 将限制仅具有管理员角色的用户对某些页面的访问。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
@Injectable({
providedIn: 'root'
})
export class AdminGuard implements CanActivate {
constructor(private router: Router,
private authService: AuthService) { }
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable
<boolean | UrlTree>
| Promise
<boolean | UrlTree>
| boolean | UrlTree {
if (this.authService.isAdmin()) {
return true;
}
return this.router.parseUrl("/login-page");
}
}
一旦我们创建了守卫,我们需要将它们应用到适当的路线。为此,请打开app-routing.module.ts并向每个路由添加一个canActivate属性以及相应的防护。
例如,登录和注册页面只能由尚未登录的用户访问,因此我们将AnonymousGuard添加到他们的路由中。母版页应该只能由经过身份验证的用户访问,因此我们将AuthGuard添加到该路由。最后,添加事件和添加角色页面只能由具有管理员角色的用户访问,因此我们将AdminGuard附加到这些路由。
全屏
1
2
3
4
5
6
7
8
9
10
export const routes: Routes = [
{ path: '', redirectTo: 'master-page', pathMatch: 'full' },
{ path: 'error', component: UncaughtErrorComponent },
{ path: 'master-page', loadChildren: () => import('./master-page/master-page.module').then(m => m.MasterPageModule), canActivate: [AuthGuard]},
{ path: 'register-page', component: RegisterPageComponent, data: { text: 'Register Page' }, canActivate: [AnonymousGuard]},
{ path: 'login-page', component: LoginPageComponent, data: { text: 'Login' }, canActivate: [AnonymousGuard]},
{ path: 'add-event', component: AddEventComponent, data: { text: 'Add Event' }, canActivate: [AdminGuard]},
{ path: 'add-role-to-user', component: AddRoleToUserComponent, data: { text: 'Add Role' }, canActivate: [AdminGuard]},
{ path: '**', component: PageNotFoundComponent } // must always be last
];
为了确保没有权限的用户无法访问导航中的某些链接,我们必须隐藏它们。为此,我们可以将*ngIf=”isUserAdmin()”指令添加到与 master-page.component.html 文件中的 ADD EVENT 和 ADD ROLE TO USER 选项相对应的igx-list-item元素中。通过这样做,我们确保这些链接仅对具有管理员权限的用户可见。
为了实现此功能,我们还需要更新master-page.component.ts文件。我们可以创建一个函数来检查当前用户是否是管理员,并在我们之前添加的*ngIf指令中使用它。
此外,我们需要创建一个注销函数并将其附加到与 LOGOUT 链接对应的igx-list-item的单击事件。这将允许用户在必要时注销系统。
它应该看起来像这样:
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
igxListThumbnail
icon = "stars" [ roundShape ] = "true" class = "avatar_1"
igxListLine
class="ig-typography__subtitle-2 text_3"
ADD EVENT
[isHeader]="false" (click)="logout()"
igxListThumbnail
icon="exit_to_app" [roundShape]="true" class="avatar_1"
igxListLine
class="ig-typography__subtitle-2 text_3"
LOGOUT
…
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
export class MasterPageComponent {
public listItemVisible = false;
constructor(private authService: AuthService,
private router: Router){}
isUserAdmin() {
return this.authService.isAdmin();
}
logout() {
this.authService.logout();
this.router.navigateByUrl('/login-page');
}
}
我们的下一步是在添加事件页面和 API 之间建立连接。为此,我们需要在HRDashboard服务中创建一个新函数,该函数将处理用于创建新事件的HTTP POST请求。
全屏
1
2
3
4
5
6
7
8
9
10
public postEvent(data: any, contentType: string = 'application/json-patch+json, application/json, text/json, application/*+json'): Observable<any>{
const options = {
headers: {
'content-type': contentType,
Authorization: `Bearer ${localStorage.getItem('hr_app_token')}`,
},
};
const body = data;
return this.http.post(`${API_ENDPOINT}/Event`, body, options);
}
此外,在add-event.component.ts文件中,我们需要定义将输入字段绑定到组件的属性并添加onSubmit函数。由于电子邮件将在以逗号分隔的单个字段中接收,因此我们需要在将它们发送到 API 之前将其拆分。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
export class AddEventComponent {
emails: string;
category: string;
title: string;
date: string;
errorMessage: string;
constructor(private hrApiService: HRDashboardService,
private router: Router) {}
onSubmit(event) {
event.preventDefault();
const splitEmails = this.emails.split(', ');
if (this.emails && this.category && this.title && this.date) {
this.hrApiService.postEvent({ category: this.category, title: this.title, date: this.date, userEmails: splitEmails})
.subscribe({
next: (response) => {
this.router.navigateByUrl('/');
},
error: (error) => {
console.log(error)
this.errorMessage = error.error["errors"] ? Object.values(error.error["errors"])[0] as string : error.error;
完成上述步骤后,我们可以继续更新仪表板。在此之前,我们必须修改HRDashboardService,以在具有授权标头的请求中将所有出现的“Bearer <auth-token>”替换为 Bearer ${localStorage.getItem('hr_app_token' )}。这确保我们使用正确的 JWT 令牌获取数据。
此外,我们需要向HRDashboardService添加一个新函数,该函数仅获取与当前用户相关的事件。
全屏
1
2
3
4
5
6
7
8
public getMyEvents(): Observable<any>{
const options = {
headers: {
Authorization: `Bearer ${localStorage.getItem('hr_app_token')}`,
},
};
return this.http.get(`${API_ENDPOINT}/Event`, options);
}
接下来,我们将使用卡片组件在仪表板页面上显示用户的事件。为此,我们需要打开dashboard.component.html 文件并使用*ngFor指令基于myEvents属性呈现igx-card组件,该属性将包含当前用户的事件。此外,我们可以使用*ngIf=”isUserAdmin()”来确保网格仅对管理员可见。此外,我们应该更新仪表板上的问候语以显示当前用户的电子邮件,例如“早上好,{{email}}!”。
生成的文件应如下所示:
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
22
23
<div class="row-layout group">
<div class="column-layout group_1">
<div class="column-layout group_2">
<div class="row-layout group_3">
<div class="column-layout group_4">
<h5 class="h5">
Good Morning, {{email}}!
</h5>
<p class="text">
Your Highlights
</p>
</div>
</div>
<div class="row-layout group_5">
<igx-card *ngFor="let event of myEvents; index as i;"type="outlined" class="card">
<igx-card-media height="200px">
<img src="/assets/Illustration1.svg" class="image" />
</igx-card-media>
<igx-card-header>
<h3 igxCardHeaderTitle>
{{event.title}}
在dashboard.component.ts文件中,我们需要定义两个属性:myEvents和email。这些属性应使用ngOnInit函数中的相关数据填充。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
export class DashboardComponent implements OnInit {
public hRDashboardEventAll: any = null;
myEvents: any;
email: any;
constructor(
private hRDashboardService: HRDashboardService,
private authService: AuthService,
) {}
ngOnInit() {
this.hRDashboardService.getEventAll().subscribe(data => this.hRDashboardEventAll = data);
this.hRDashboardService.getMyEvents().subscribe(data => this.myEvents = data);
this.email = this.authService.getEmail();
}
public isUserAdmin() {
return this.authService.isAdmin();
}
…
现在,我们可以在仪表板上看到当前用户的所有事件,如果用户是管理员,他们可以从网格中更新和删除条目。
最后一步是在添加角色到用户页面和API之间建立连接。为此,我们需要在HRDashboard服务中创建一个函数,用于从API获取用户数据并填充电子邮件选择组件。
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
public getUsers(): Observable
<any>
{
const options = {
headers: {
Authorization: `Bearer ${localStorage.getItem('hr_app_token')}`,
},
};
return this.http.get(`${API_ENDPOINT}/User`, options);
}
public changeUserRole(data: any): Observable
<any>
{
const options = {
headers: {
Authorization: `Bearer ${localStorage.getItem('hr_app_token')}`,
},
};
const body = data;
return this.http.post(`${API_ENDPOINT}/User/Role`, body, options);
完成后,我们可以转到add-role-to-user.component.ts并获取它们。我们还应该创建onSubmit函数,该函数将从HRDashboard服务调用changeUserRole。最终结果应如下所示:
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
export class AddRoleToUserComponent {
users: any;
email: string;
role: string;
constructor(private hrApiService: HRDashboardService,
private router: Router) { }
ngOnInit() {
this.hrApiService.getUsers()
.subscribe(data => {
this.users = data;
});
}
onSubmit(event) {
event.preventDefault();
if (this.email && this.role) {
this.hrApiService.changeUserRole({ email: this.email, role: this.role})
.subscribe({
next: (response) => {
this.router.navigateByUrl('/');
},
全屏
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 号
18
19
20
21
class="row-layout group"
class="column-layout group_1"
class="h2"
Add Role to user
class="row-layout group_2"
type="border" class="select"[(ngModel)]="email"
*ngFor="let user of users; index as i;" value="{{user.email}}"
{{user.email}}
igxLabelEmail
type="border" class="select"[(ngModel)]="role"
value="Administrator"
Administrator
igxLabelRole
igxButton = "raised" ( click ) = "onSubmit($event)" igxRipple class = "button"
提交
遵循必要的步骤并完成所需的任务后,我们现在拥有一个功能齐全的应用程序,该应用程序已使用 App Builder 构建并与 API 集成。这意味着我们的应用程序现在能够与 API 进行通信,使用户能够执行各种操作,例如创建新事件、向用户添加角色以及查看其个性化仪表板。