.net core3.1+angular+is4 项目记录:2 我的会议模块(上)

.net core3.1+angular+is4 项目记录:2 我的会议模块(上)

目录

  1. 页面搭建
  2. 路由配置
  3. 添加会议页面
  4. 我的会议页面
  5. 取消会议功能

页面搭建

  首先搭建页面的主体,在 main 模块中的 MainComponent 下搭建页面结构,app 模块只放个。页面整体结构为:侧边栏+页面主体。侧边栏参照 NG-ZORRO 入门教程使用的侧边栏(地址: https://github.com/NG-ZORRO/today-ng-steps/blob/legacy-v1/tutorial/2.md )。主体分为上中下(面包屑,页面主体,页脚)三部分。整体大概是这个样子:

具体代码:


<nz-sider nzCollapsible [(nzCollapsed)]="isRetraction" [nzWidth] ="240">

<app-sider [isRetraction]="isRetraction">





<nz-breadcrumb [nzAutoGenerate]="true">








路由配置

  页面主体既然已经搭建完了,那么接下来该配置路由了,配置之前先看一下之前规划的模块结构:

  通过图可以观察到,主要路由都放在 main.router 中。

app-routing 代码:

const routes: Routes = [
{
//token相关
path: "signin-oidc",
component: SignInComponent
},
{
//token相关
path: "refresh-oidc",
component: RefreshOidcComponent
},
{
path: "",
//添加认证守卫
canActivate: [AuthGuard],
loadChildren: "./main/ main.module#MainModule"
},
{
path: "**",
redirectTo: ""
}
];

  添加了两个和 token 相关的路由,这里不做记录,如果忘了可以看: https://www.cnblogs.com/zyz-Notes/p/12097826.html 。因为app.router一定是连接到main模块,所以为了缩短地址的长度使用""作为path的值。

main-routing 代码

const routes: Routes = [
{
path: "main",
component: MainComponent,
children: [
{
path: "Home",
loadChildren: "./home/ home.module#HomeModule",
data: {
breadcrumb: "首页"
}
},
{
path: "myMeeting",
loadChildren: "./my-meeting/ my-meeting. module#MyMeetingModule",
data: {
breadcrumb: "我的会议"
}
},
{
path: "myAudience",
loadChildren: "./my-audience/ my-audience. module#MyAudienceModule",
data: {
breadcrumb: "参加的会议"
}
},
{
path: "statistics",
loadChildren: "./statistics/ statistics. module#StatisticsModule",
data: {
breadcrumb: "统计"
}
},
{
path: "",
redirectTo: "Home"
}
]
},
{
path: "
",
redirectTo: "main"
}
];

  我选择路径以 main 开头。因为我们搭建的页面的主体在 MainComponent 中,所以对应组件为 MainComponent。我们通过配置 main 的子路由来激活 MainComponent 中的 router-outlet ,所以下面 4 条子路由分别对应着 4 个模块,最后一条作为路径错误时候的重定向。

my-meeting-routing 代码

const routes: Routes = [
{
path: "add",
component: AddMeetingComponent,
data: {
breadcrumb: "发起会议"
}
},
{
path: "modify/:meetingId",
component: ModifyMeetingComponent,
data: {
breadcrumb: "修改会议"
}
},
{
path: "details/:meetingId",
component: DetailsMeetingComponent,
data: {
breadcrumb: "会议详情"
}
},
{
//模块首页
path: "",
component: MyMeetingComponent
},
{
path: "**",
redirectTo: ""
}
];

  各个组件的通过 cli 来进行生成。各个组件对应功能:

  1. AddMeetingComponent-添加会议
  2. ModifyMeetingComponent-修改会议
  3. DetailsMeetingComponent-会议详情
  4. MyMeetingComponent-会议列表和会议取消

添加会议页面

my-meetingComponent.html

  因为我们默认路由为"",所以单击我的会议项,跳转到 MyMeetingComponent 组件中,所以我们需要在 MyMeetingComponent 添加一个按钮跳转到 AddMeetingComponent。

  这个时候应该是没有图中的表格的,注意 routerLink="add"要和路由中的 path 对应起来

add-meeting.component.html

  我们需要在这个页面上写一个表单,让用户来填写会议的信息。由于这个页面代码较多,所以我这里只展示一个 input 和一个日期选择器,其他的都只是改下名字。

<form
nz-form
[formGroup]="validateForm"
(ngSubmit)="submitForm($event, validateForm.value)"
>

<nz-form-label [nzSpan]="7" nzRequired>会议名称</ nz-form-label>
<nz-form-control [nzSpan]="12" nzHasFeedback>
<input
nz-input
formControlName="meetingName"
placeholder="请输入会议名称"
[(ngModel)]="meeting.meetingName"
/>
<nz-form-explain
*ngIf="
validateForm.get('meetingName')?.dirty &&
validateForm.get('meetingName')?.errors
"
>
<ng-container
*ngIf="validateForm.get('meetingName')? .hasError('required')"
>
会议名称不能为空




<nz-form-label [nzSpan]="7" nzRequired>日期</ nz-form-label>
<nz-form-control [nzSpan]="12">
<nz-date-picker
formControlName="datePicker"
[(ngModel)]="datetime.date"
>


  我们使用 nz-zorro 提供的表单样式配合 FormBuilder 类来进行表单验证。
FormBuilder 生成表单控件:

this.validateForm = this.fb.group({
meetingName: ["", [Validators.required]],
address: ["", [Validators.required]],
content: ["", [Validators.required]],
datePicker: ["", [Validators.required]],
timePicker: [null, [Validators.required]]
});

  用到的 nz-zorro 组件,具体 API 请查阅官方文档。

nz-zorro 提供的组件来调整表单的样式。
nz-form:一个启用 nz 表单。
nz-form-item:一个表单组(包括文字,input,扩展信息)。 nz-form-label:左边的文字。nz-form-explain:扩展信息通常配合 控件状态显示不同的信息

笔记:

创建组和控件:

this.fb.group({
控件名:["","同步验证器","异步验证器"]
})

获取控件的状态:

是否被污染(用户有没有动过)
validateForm.get('meetingName')?.dirty

是否有错误
validateForm.get('meetingName')?.errors

是否有一个必须的错误
validateForm.get('meetingName')? .hasError('required')

是不是所有验证都通过了
!validateForm.valid

form 标签:

[formGroup]="组名:validateForm"

(ngSubmit)="提交事件:submitForm($event,validateForm.value)"

input 标签:

formControlName="控件名:meetingName"

[(ngModel)]="双向绑定对象:datetime.date"

最后结果应该是这样的:

  效果图已经出来了,那么我们现在需要写逻辑了。写前先想一下我们需要做什么。

  1. 在提交按钮事件中调用一个服务将请求发出(需要提交按钮事件,需要一个服务)
  2. 后端把数据存进数据库(一个 Action 和一个保存数据的方法)

Meeting 与 MeetingService

//meeting对象
export class meeting {
public id: number;
public userId: string;
public userName: string;
public meetingName: string;
public address: string;
public content: string;
public dateTime: string;
public state: meetingStatsEnum;
public inviteCode: string;
}

//创建一个会议
public addMeeting(meetingDto: meeting) :Observable {
return this.httpclient.post("/api/Meeting",meetingDto);
}

提交事件

//提交按钮
submitForm = () => {
//整合数据
this.compose();
//发送
this.meetingServer.addMeeting(this.meeting).subscribe(x => {
console.log(x);
this.createMessage("success", "会议创建成功!");
this.router.navigate(["/main/myMeeting"]);
});
//重置控件状态
this.dirtyForm();
};

//组合post的对象
public compose(): void {
this.meeting.dateTime =
this.datetimeService.getDate(this.datetime.date) +
" " +
this.datetimeService.getHour(this.datetime.time);

this.meeting.state = meetingStatsEnum.NotStarted;
this.meeting.userId = this.oidc.user.profile.sub;
}

//重置控件状态
private dirtyForm(): void {
for (const key in this.validateForm.controls) {
this.validateForm.controls[key].markAsDirty();
this.validateForm.controls[key].updateValueAndValidity();
}
}

  我们需要在 API 中进行接收,这个就比较简单了。

dto 与 model:

public class MeetingOutputDto
{
public int Id { get; set; }
public string UserId { get; set; }
public string UserName { get; set; }
public string MeetingName { get; set; }
public string Address { get; set; }
public string Content { get; set; }
public MeetingStatsEnum State { get; set; }
public string DateTime { get; set; }
public string InviteCode { get; set; }

}

public class MeetingInputDto {
public int Id { get; set; }
public string UserId { get; set; }
public string MeetingName { get; set; }
public string Address { get; set; }
public string Content { get; set; }
public MeetingStatsEnum State { get; set; }
public string DateTime { get; set; }
}

public class Meeting
{
public int Id { get; set; }
public string UserId { get; set; }
public string MeetingName { get; set; }
public string Address { get; set; }
public string Content { get; set; }
public DateTime DateTime { get; set; }
public MeetingStatsEnum State { get; set; }
public string InviteCode { get; set; }
public List<User_Meeting> User_Meetings { get; set; }
}

Action

[HttpPost]
public async Task Post([FromBody]MeetingInputDto meetingAddDto)
{
_logger.LogDebug("创建会议");
var meeting= _mapper.Map<MeetingInputDto, Meeting(meetingAddDto);

_meetingRepository.addMeeting(meeting);

if (! await _unitOfWork.save()) {
_logger.LogError("创建会议失败!");
throw new Exception();
}
_logger.LogDebug("会议创建成功");

//创建邀请码
await _inviteCodeHelper.create(meeting);

var resultDto = _mapper.Map<Meeting, MeetingOutputDto(meeting);

return Created("location:5000/api/Meeting/"+resultDto.Id, resultDto);
}

public void addMeeting(Meeting meeting) {
_dbContext.Meeting.Add(meeting);
}

public async Task save() {
return await dbContext.SaveChangesAsync() > 0;
}

  写好 automapper 映射关系。这里要注意,我这里写的邀请码就是把会议报名的 url 进行加密,具体加密过程参考: https://www.cnblogs.com/tianma3798/p/8807816.html 。其实写到这里的时候我发现在写的过程中大部分时间都在写angular(应该是我太菜了)。。还有一些是在改设计错误。。真正API使用的时间不多。到这里添加功能就完成了,源码地址放在文章结尾。

我的会议页面

  这个页面比较简单,使用 nz-zorro 的 table 组件。先想想需要做什么:

  1. 前端 页面加载请求会议列表(需要一个请求列表方法,展示到页面上)
  2. 前端 单击按钮取消会议(需要取消会议按钮和方法,成功后显示提示信息)
  3. 后端 返回列表方法(get)更新会议状态(patch)

获取用户的会议列表

//页面加载时请求数据
//ts文件中
ngOnInit() {
this.meetingService.getMeetings().subscribe(x => {
this.meetings = x;
console.log(this.meetings);
});
}

//查询该用户下的所有会议
//meetingService中
public getMeetings(): Observable<meeting[]> {
return this.httpclient.get<meeting[]>(/api/Meeting, {
params: { userId: this.oidc.user.profile.sub }
});
}

//展示数据
//html中
<nz-table #basicTable [nzData]="meetings">


名称
会议日期
地址
状态
操作


<tr *ngFor="let data of basicTable.data; let i = index">
{{ data.meetingName }}
{{ data.dateTime }}
{{ data.address }}


返回列表 Action

[HttpGet]
public async Task Get([FromQuery]stringuserId) {
var list= await _meetingRepository.getMeetings(userId);
var result= _mapper.Map<IEnumerableIEnumerable>(list);
return Ok(result);
}

public async Task<IEnumerable> getMeetings(string userId)
{
return await _dbContext.Meeting.Wher(x=>x.UserId==userId).ToListAsync();
}

  这个页面比较简单,没什么好记的,看取消会议功能。

取消会议按钮

//分别添加详情、修改、取消按钮
//html中

<a [routerLink]="['details', data.id]">详情
<nz-divider
nzType="vertical"
*ngIf="data.state == meetingStats.NotStarted"
>
<a
*ngIf="data.state == meetingStats.NotStarted"
[routerLink]="['modify', data.id]"
>修改
<nz-divider
nzType="vertical"
*ngIf="data.state == meetingStats.NotStarted"
>
<a
*ngIf="data.state == meetingStats.NotStarted"
nz-popconfirm
//使用nz-zorro添加一个取人框
nzTitle="确定取消这个会议?"
nzPopconfirmPlacement="top"
(nzOnConfirm)="cancelMeeting(data.id, i)"
>取消

取消会议事件

//按钮单击事件
//ts中
public cancelMeeting(meetingId: number): void {
this.meetingService
.updateMeetingsState(meetingId, [
{ op: "replace", path: "/State", value: "已完成" }
])
.subscribe();
}

//局部更新会议
//service中
ublic updateMeetingsState(meetingId: number, body: any): bservable<{}> {
return this.httpclient.patch(/api/Meeting/${meetingId}, body, {
headers: new HttpHeaders({
"Content-type": "application/json"
})
});

修改状态 Action

[HttpPatch("{meetingId}")]
public async Task Patch([FromRoute]intmeetingId, [FromBody]JsonPatchDocument meetingDoc)
{
//根据id查询出会议
var entity = await _meetingRepository.getMeetin(meetingId);

if (entity == null)
return NotFound("未找到该会议");

//将会议传转成dto
var meetingdto= _mapper.Map<Meeting,MeetingInputDto>(entity);

//修补操作到dto
meetingDoc.ApplyTo(meetingdto, ModelState);

//重新验证模型状态
TryValidateModel(meetingdto);

if (!ModelState.IsValid) {
return BadRequest("传送数据错误");
}

//转换回module
_mapper.Map(meetingdto,entity);

//更新操作
_meetingRepository.UpdateMeeting(entity);

//验证更新结果
if (!await _unitOfWork.save())
{
throw new Exception("更新失败!");
}

return NoContent();
}

  这里需要注意:我们使用 JsonPatchDocument<修补的对象>这个类,这个类针对于修补对象进行修补操作,具体形式为:

[{ op: "操作符", path: "/对象名", value: "修改后的值" },{...}]

  操作符一共由五种:Add(增加)、Move(移动)、Replace(替换)、Remove(删除)、Copy(复制)。只需要使用 meetingDoc.ApplyTo(meetingdto, ModelState) 就可以将具体改动操作到类中。

特别注意:使用JsonPatchDocument需要安装Microsoft.VisualStudio.Web.CodeGeneration.Design这个包,不然接收不到数据

总结

  虽然有点水,但是写的手腕疼。源码地址: https://github.com/netLearner7/graduation

posted @ 2020-01-08 16:39  netLearner7  阅读(398)  评论(0)    收藏  举报