Commit 47b8f385 authored by 梁桐铭's avatar 梁桐铭 🏅
Browse files

完成(ASP.NET Core和Angular版本)的电话薄功能

parent 71e100b0
......@@ -7,8 +7,7 @@
> 源码下载: [https://code.52abp.com/52abp](https://code.52abp.com/52abp) </br>
从本文档开始,我们将使用52ABP-PRO创建一个**电话簿应用**实例的完整开发指南,完成这个内容后,你可以完整的了解多租户、本地化、授权、可配置项、单元测试等内容。
从本文档开始,我们将使用52ABP-PRO创建一个**电话簿应用**实例的完整开发指南,完成这个内容后,你可以完整的了解**多租户、本地化、授权、可配置项、单元测试**等内容。
本文档适用于**ASP.NET Core 后端和Angular UI**前后端分离的项目类型。
......@@ -18,7 +17,33 @@
您还可以通过查看[其他项目类型文档](/docs/Getting-Started.md)
## 下一章
## 开始
[创建并运行一个ABP项目](Angular/1.Creating-Running-Project.md)
## 完整目录
- [创建并运行一个ABP项目](Angular/1.Creating-Running-Project.md)
- [Angular应用中添加一个菜单栏](Angular/2.Adding-New-Menu-Item.md)
- [创建一个前端PhoneBook组件](Angular/3.Creating-PhoneBook-Component.md)
- [在ABP框架中定义Person实体类](Angular/4.Creating-Person-Entity.md)
- [同步Person实体到数据库中创建映射表](Angular/5.Database-Migrations-Person-Entity.md)
- [在ABP框架中创建Person的应用服务](Angular/6.Creating-Person-Application-Service.md)
- [ABP框架中为Person应用服务创建单元测试](Angular/7.Creating-Unit-Tests-for-Person-Application-Service.md)
- [在Angular组件中实现查询People的方法](Angular/8.Using-GetPeople-Method-from-Angular.md)
- [如何在ABP框架中添加一个联系人信息](Angular/9.Creating-New-Person-Method.md)
- [在ABP框架中使用单元测试Xunit测试添加方法](Angular/10.Creating-Testing-Create-Person-Method.md)
- [在Angular客户端创建一个模态框完成添加联系人功能](Angular/11.Creating-Testing-Creating-Modal-New-Person.md)
- [ABP框架中为我们的电话薄应用程序添加权限验证内容](Angular/12.Authorization-PhoneBook.md)
- [在ABP框架中完成删除联系人功能](Angular/13.Deleting-Person.md)
- [模糊搜索联系人信息](Angular/14.Filtering-People.md)
- [在ABP框架中实现一对多的关联关系](Angular/15.Creating-Phone-Entity.md)
- [针对Phone实体信息的数据库迁移](Angular/16.Migrations-Phone-Entity.md)
- [在ABP中的多表联动查询的实现](Angular/17.Changing-GetPeople-Method.md)
- [给PersonApplicatonService应用服务添加电话和删除电话两个方法](Angular/18.Adding-AddPhone-DeletePhone-Methods.md)
- [添加并查看联系人的电话信息](Angular/19.Edit-Mode-Phone-Numbers.md)
- [完成编辑联系人信息](Angular/20.Compleate-EditPerson.md)
- [如何改造既有功能让它支持多租户功能](Angular/21.Multi-Tenancy.md)
- [运行应用程序验证多租户下的数据隔离](Angular/22.Running-Application.md)
- [52ABPPRO源代码下载和总结](Angular/23.Develop-Angular-The-End.md)
......@@ -68,4 +68,4 @@
## 下篇
- [在Angular客户端创建一个模态框完成添加功能](11.Creating-Testing-Creating-Modal-New-Person.md)
\ No newline at end of file
- [在Angular客户端创建一个模态框完成添加联系人功能](11.Creating-Testing-Creating-Modal-New-Person.md)
\ No newline at end of file
# 在Angular客户端创建一个模态框完成添加功能
# 在Angular客户端创建一个模态框完成添加联系人功能
我们将创建一个模态框来添加联系人的信息。52ABP-PRO使用的是Ng-zorro的库,当然也可以添加其他库比如Bootstrap。
......
# 删除一个联系人
# 在ABP框架中完成删除联系人功能
让我们在人员列表中添加一个**删除按钮**,如下所示:
......@@ -97,4 +97,4 @@ public async Task DeletePerson(EntityDto<Guid> input)
## 下一篇
* [模糊搜索联系人信息](14.Filtering-People.md)
\ No newline at end of file
* [完成模糊搜索联系人信息](14.Filtering-People.md)
\ No newline at end of file
......@@ -71,6 +71,6 @@ public ListResultDto<PersonListDto> GetPeople(GetPeopleInput input)
## Next
## 接下来
- [Adding AddPhone and DeletePhone Methods](Developing-Step-By-Step-Angular-Adding-AddPhone-DeletePhone-Methods)
\ No newline at end of file
- [给PersonApplicatonService应用服务添加电话和删除电话两个方法](18.Adding-AddPhone-DeletePhone-Methods.md)
\ No newline at end of file
......@@ -77,15 +77,10 @@ configuration.CreateMap<AddPhoneInput, Phone>();
> 请注意;通常不需要调用CurrentUnitOfWork.SaveChangesAsync,它会在方法的最后自动调用。而现在调用它是因为我们需要获取我们保存到数据库实体的Id信息。可以查看[工作单元](https://www.52abp.com/Wiki/abp-cn/latest/3.5ABP%E9%A2%86%E5%9F%9F%E5%B1%82-%E5%B7%A5%E4%BD%9C%E5%8D%95%E5%85%83.md)文档。
当然是用 AddPhone方法还有其他的用法,比如您可以直接使用`_personRepository`来插入新电话信息。它们都有各自的优缺点。你可以自己选择。
## 接下来
- [Edit Mode for Phone Numbers](Developing-Step-By-Step-Angular-Edit-Mode-Phone-Numbers)
\ No newline at end of file
- [添加并查看联系人的电话信息](19.Edit-Mode-Phone-Numbers.md)
\ No newline at end of file
# Edit Mode For People
Now we want to edit name, surname and e-mail of people:
<img src="images/edit-person-core1.png" alt="Edit Person" class="img-thumbnail" />
First of all, we create the necessary DTOs to transfer people's id, name,
surname and e-mail. We can optionally configure auto-mapper, but this is not necessary because all properties match automatically. Then we create the functions in PersonAppService for
editing people:
```csharp
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task<GetPersonForEditOutput> GetPersonForEdit(GetPersonForEditInput input)
{
var person = await _personRepository.GetAsync(input.Id);
return ObjectMapper.Map<GetPersonForEditOutput>(person);
}
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task EditPerson(EditPersonInput input)
{
var person = await _personRepository.GetAsync(input.Id);
person.Name = input.Name;
person.Surname = input.Surname;
person.EmailAddress = input.EmailAddress;
await _personRepository.UpdateAsync(person);
}
```
Then we add configuration for AutoMapper into CustomDtoMapper.cs like below:
```csharp
configuration.CreateMap<Person, GetPersonForEditOutput>();
```
## View
Create edit-person-modal.component.html:
```html
<div bsModal #modal="bs-modal" (onShown)="onShown()" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modal" aria-hidden="true" [config]="{backdrop: 'static'}">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<form *ngIf="active" #personForm="ngForm" novalidate (ngSubmit)="save()">
<div class="modal-header">
<h4 class="modal-title">
<span>{{"EditPerson" | localize}}</span>
</h4>
<button type="button" class="close" (click)="close()" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>{{"Name" | localize}}</label>
<input #nameInput class="form-control" type="text" name="name" [(ngModel)]="person.name" required maxlength="32">
</div>
<div class="form-group">
<label>{{"Surname" | localize}}</label>
<input class="form-control" type="email" name="surname" [(ngModel)]="person.surname" required maxlength="32">
</div>
<div class="form-group">
<label>{{"EmailAddress" | localize}}</label>
<input class="form-control" type="email" name="emailAddress" [(ngModel)]="person.emailAddress" required maxlength="255" pattern="^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$">
</div>
</div>
<div class="modal-footer">
<button [disabled]="saving" type="button" class="btn btn-secondary" (click)="close()">{{"Cancel" | localize}}</button>
<button type="submit" class="btn btn-primary" [disabled]="!personForm.form.valid" [buttonBusy]="saving" [busyText]="l('SavingWithThreeDot' | localize)"><i class="fa fa-save"></i> <span>{{"Save" | localize}}</span></button>
</div>
</form>
</div>
</div>
</div>
```
Add those lines to **phonebook.component.html:**:
```html
// Other Code lines...
<button (click)="editPerson(person)" title="{{'Edit' | localize}}" class="btn btn-outline-hover-primary btn-icon">
<i class="fa fa-plus"></i>
</button>
<button *ngIf="'Pages.Tenant.PhoneBook.EditPerson' | permission" (click)="editPersonModal.show(person.id)" title="{{'EditPerson' | localize}}" class="btn btn-outline-hover-success btn-icon">
<i class="fa fa-pencil"></i>
</button>
<button id="deletePerson" (click)="deletePerson(person)" title="{{'Delete' | localize}}" class="btn btn-outline-hover-danger btn-icon" href="javascript:;">
<i class="fa fa-times"></i>
</button>
// Other Code lines...
<createPersonModal #createPersonModal(modalSave)="getPeople()"></createPersonModal>
<editPersonModal #editPersonModal (modalSave)="getPeople()"></editPersonModal>
```
## Controller
Create edit-person-modal.component.ts:
```typescript
import { Component, ViewChild, Injector, ElementRef, Output, EventEmitter } from '@angular/core';
import { ModalDirective } from 'ngx-bootstrap';
import { PersonServiceProxy, EditPersonInput } from '@shared/service-proxies/service-proxies';
import { AppComponentBase } from '@shared/common/app-component-base';
@Component({
selector: 'editPersonModal',
templateUrl: './edit-person-modal.component.html'
})
export class EditPersonModalComponent extends AppComponentBase {
@Output() modalSave: EventEmitter<any> = new EventEmitter<any>();
@ViewChild('modal') modal: ModalDirective;
@ViewChild('nameInput') nameInput: ElementRef;
person: EditPersonInput = new EditPersonInput();
active: boolean = false;
saving: boolean = false;
constructor(
injector: Injector,
private _personService: PersonServiceProxy
) {
super(injector);
}
show(personId): void {
this.active = true;
this._personService.getPersonForEdit(personId).subscribe((result)=> {
this.person = result;
this.modal.show();
});
}
onShown(): void {
// this.nameInput.nativeElement.focus();
}
save(): void {
this.saving = true;
this._personService.editPerson(this.person)
.subscribe(() => {
this.notify.info(this.l('SavedSuccessfully'));
this.close();
this.modalSave.emit(this.person);
});
this.saving = false;
}
close(): void {
this.modal.hide();
this.active = false;
}
}
```
Add those lines to **main.module.ts:**:
```typescript
import { EditPersonModalComponent } from './phonebook/edit-person-modal.component';
// Other Code lines...
declarations: [
DashboardComponent,
PhoneBookComponent,
CreatePersonModalComponent,
EditPersonModalComponent
]
// Other Code lines...
```
## Next
- [Multi Tenancy](Developing-Step-By-Step-Angular-Multi-Tenancy)
\ No newline at end of file
# 完成编辑联系人信息
目前我们已经实现了添加、删除联系人功能。以及查询电话薄和添加、删除电话的功能。还欠缺一个编辑联系人的功能。
![编辑联系人](images/20.1.png)
## 编辑联系人的后端代码
首先在`IPersonApplicationService`中定义两个方法。
```csharp
Task<GetPersonForEditOutput> GetPersonForEdit(NullableIdDto<Guid> input);
Task EditPerson(PersonEditDto input);
```
然后在`PersonApplicationService`中实现两个方法:
```csharp
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task<GetPersonForEditOutput> GetPersonForEdit(NullableIdDto<Guid> input)
{
var output = new GetPersonForEditOutput();
PersonEditDto editDto;
if (input.Id.HasValue)
{
var entity = await _personRepository.GetAsync(input.Id.Value);
editDto = ObjectMapper.Map<PersonEditDto>(entity);
}
else
{
editDto = new PersonEditDto();
}
output.Person = editDto;
return output;
}
[AbpAuthorize(AppPermissions.Pages_Tenant_PhoneBook_EditPerson)]
public async Task EditPerson(PersonEditDto input)
{
var entity = await _personRepository.GetAsync(input.Id.Value);
//将input属性的值赋值到entity中
ObjectMapper.Map(input, entity);
await _personRepository.UpdateAsync(entity);
}
```
我们需要创建两个Dto,用于数据进行传输和转换。
```csharp
public class GetPersonForEditOutput
{
public PersonEditDto Person { get; set; }
}
/// <summary>
/// 联系人编辑Dto
/// </summary>
public class PersonEditDto
{
public Guid? Id { get; set; }
[Required]
[MaxLength(Person.MaxNameLength)]
public string Name { get; set; }
/// <summary>
/// 地址信息
/// </summary>
[MaxLength(Person.MaxAddressLength)]
public string Address { get; set; }
[EmailAddress]
[MaxLength(Person.MaxEmailAddressLength)]
public string EmailAddress { get; set; }
}
```
最后,将AutoMapper的配置添加到CustomerAppDtoMapper.cs中,如下所示:
```c#
configuration.CreateMap<PersonEditDto, Person>().ReverseMap();
```
## 创建前端组件edit-person
我们将现在的`phonebook`文件夹中,创建`edit-person`组件如下:
- edit-person.component.html
- edit-person.component.less
- edit-person.component.ts
edit-person.component.html 视图的代码如下:
```html
<form nz-form #validateForm="ngForm" (ngSubmit)="submitForm()" nzLayout="horizontal" autocomplete="off">
<!-- 模态框头部 -->
<div class="modal-header">
<div class="modal-title">
<i class="iconfont icon-medicine-box mr-sm"></i>
<span>{{ l('EditPerson') }}</span>
</div>
</div>
<!-- 模态框内容 -->
<fieldset>
<!-- 提示信息 -->
<nz-form-item nz-row>
<nz-form-label [nzSm]="6" [nzXs]="24" nzRequired nzFor="name">
{{ l('Name') }}
</nz-form-label>
<nz-form-control nz-col [nzSm]="14" [nzXs]="24" required nzHasFeedback>
<!-- 控件位置 -->
<input
nz-input
name="name"
type="text"
#personName="ngModel"
[(ngModel)]="entity.name"
placeholder="请输入名字"
required
/>
</nz-form-control>
<nz-form-control nzExtra *ngIf="personName.control.dirty && personName.control.errors">
<ng-container *ngIf="personName.control.hasError('required')">{{ l('ThisFieldIsRequired') }} </ng-container>
</nz-form-control>
</nz-form-item>
<nz-form-item nz-row>
<nz-form-label nz-col [nzSm]="6" [nzXs]="24" nzFor="address">
{{ l('Address') }}
</nz-form-label>
<nz-form-control nz-col [nzSm]="14" [nzXs]="24" nzHasFeedback>
<input nz-input name="address" #address="ngModel" [(ngModel)]="entity.address" placeholder="请输入地址" />
</nz-form-control>
</nz-form-item>
<nz-form-item nz-row>
<nz-form-label nz-col [nzSm]="6" [nzXs]="24" nzRequired nzFor="EmailAddress">
{{ l('EmailAddress') }}
</nz-form-label>
<nz-form-control nz-col [nzSm]="14" [nzXs]="24" nzHasFeedback>
<input
nz-input
name="EmailAddress"
#personEmailAddress="ngModel"
[(ngModel)]="entity.emailAddress"
placeholder="请输入邮箱地址"
type="email"
required
/>
</nz-form-control>
<nz-form-control nzExtra *ngIf="personEmailAddress.control.dirty && personEmailAddress.control.errors">
<ng-container *ngIf="personEmailAddress.control.hasError('required')"
>{{ l('ThisFieldIsRequired') }}
</ng-container>
</nz-form-control>
</nz-form-item>
</fieldset>
<!-- 模态框底部 -->
<div class="modal-footer">
<button nz-button [nzType]="'default'" type="button" (click)="close()">
<i class="iconfont icon-close-circle-o"></i> {{ l('Cancel') }}
</button>
<button nz-button [nzType]="'primary'" type="submit" [disabled]="!validateForm.form.valid" [nzLoading]="saving">
<i class="iconfont icon-save"></i> {{ l('Save') }}
</button>
</div>
</form>
```
edit-person.component.ts的代码如下:
```typescript
import { Component, Injector, OnInit } from '@angular/core';
import { ModalComponentBase } from '@shared/component-base';
import { PersonServiceProxy } from '@shared/service-proxies';
import { finalize } from 'rxjs/operators';
import { PersonEditDto } from '../../../../shared/service-proxies/service-proxies';
@Component({
selector: 'app-edit-person',
templateUrl: './edit-person.component.html',
styleUrls: ['./edit-person.component.less'],
})
export class EditPersonComponent extends ModalComponentBase implements OnInit {
/**
* 构造函数,在此处配置依赖注入
*/
constructor(injector: Injector, private _PersonService: PersonServiceProxy) {
super(injector);
}
entity: PersonEditDto = new PersonEditDto();
personId: any = null;
ngOnInit() {
if (this.personId) {
this._PersonService.getPersonForEdit(this.personId).subscribe((result) => {
this.entity = result.person;
});
}
}
/**
* 保存方法,提交form表单
*/
submitForm(): void {
this.saving = true;
console.log(this.entity);
this._PersonService
.editPerson(this.entity)
.pipe(finalize(() => (this.saving = false)))
.subscribe(() => {
this.notify.success(this.l('SavedSuccessfully'));
this.saving = false;
this.success();
});
}
}
```
然后在main.module.ts组件中注册组件
```ts
@NgModule({
imports: [CommonModule, HttpClientModule, SharedModule, AbpModule, CustomNgZorroModule, MainRoutingModule],
declarations: [DashboardComponent, AboutComponent, AdvertisingComponent, PhoneBookComponent, EditPersonComponent, CreatePersonComponent, AddphoneComponent],
entryComponents: [AdvertisingComponent, CreatePersonComponent, AddphoneComponent, EditPersonComponent],
providers: [],
})
```
在视图组件phonebook.component.ts中添加editPerson方法,如下
```ts
// 编辑联系人
editPerson(entity: PersonListDto) {
console.log(entity);
this.modalHelper.static(EditPersonComponent, { personId: entity.id }).subscribe(res => {
if (res) {
this.getPeople();
}
});
}
```
这样我们的编辑联系人功能就完成了。
## 最后的一个疑问
我们现在实现编辑联系人的功能,我们创建了一个**EditPerson**组件来实现。我们在一些复用性很强的环境下,其实是可以实现一个组件完成编辑和添加功能的。
因为我们可以使用一个组件实现添加和编辑联系人的功能,添加和编辑人的信息的时候,只需要去判断Id是否存在即可。
这个功能我们在后面配合我们的代码生成器来进行实现,平时需要半天一天的工作量,[使用代码生成器10分钟完成一个需要2天的功能](使用代码生成器完成一个需要2天的功能.md)
## 接下来
[如何改造既有功能让它支持多租户功能](21.Multi-Tenancy.md)
# Multi Tenancy
# 如何改造既有功能让它支持多租户功能
We have built a fully functional application until here. Now, we will
see how to convert it to a multi-tenant application easily. Logout from
the application before any change.
到目前为止,我们已经构建了功能齐全电话薄的应用程序(当然除了有点丑)。现在,我们将看到如何轻松将其转换为多租户应用程序。在进行任何代码修改前,请从当前登录注销。
## Enable Multi Tenancy
## 在52ABP中启用多租户
We disabled multi-tenancy at the beginning of this document. Now,
re-enabling it in **PhoneBookDemoConsts** class:
在本系统文章开头,我们禁用了多租户。现在,在`appsettings.json`类中重新启用它: