.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-layout class="full-screen">
  <nz-sider nzCollapsible [(nzCollapsed)]="isRetraction" [nzWidth]  ="240">
    <!-- 侧边栏 -->
    <app-sider [isRetraction]="isRetraction"></app-sider>
  </nz-sider>

  <nz-layout>
    <nz-content class="container">
      <!-- 面包屑部分 -->
      <div class="container_head">
        <nz-breadcrumb [nzAutoGenerate]="true"></nz-breadcrumb>
      </div>

      <!-- 页面主体部分 -->
      <div class="container_box">
        <router-outlet></router-outlet>
      </div>

      <!-- 页脚部分 -->
      <div class="container_footer">
        Graduation Project ©2020 By 7
      </div>
    </nz-content>
  </nz-layout>
</nz-layout>

路由配置

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

  通过图可以观察到,主要路由都放在 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。

<button nz-button nzType="primary"  routerLink="add" class="add_button">
  发起会议
</button>

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

add-meeting.component.html

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

<form
  nz-form
  [formGroup]="validateForm"
  (ngSubmit)="submitForm($event, validateForm.value)"
>
  <nz-form-item>
    <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')"
        >
          会议名称不能为空
        </ng-container>
      </nz-form-explain>
    </nz-form-control>
  </nz-form-item>

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

  我们使用 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<meeting> {
    return this.httpclient.post<meeting>("/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<IActionResult> 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<bool> 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">
    <thead>
        <tr>
          <th>名称</th>
          <th>会议日期</th>
          <th>地址</th>
          <th>状态</th>
          <th>操作</th>
        </tr>
    </thead>

    <tbody>
        <tr *ngFor="let data of basicTable.data; let i = index">
          <td>{{ data.meetingName }}</td>
          <td>{{ data.dateTime }}</td>
          <td>{{ data.address }}</td>
        </tr>
    </tbody>
</nz-table>

返回列表 Action

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

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

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

取消会议按钮

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

取消会议事件

//按钮单击事件
//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<IActionResult> Patch([FromRoute]intmeetingId, [FromBody]JsonPatchDocument<MeetingInputDto> 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  阅读(341)  评论(0编辑  收藏  举报