.net core3.1 + angular + is4 project records: 2 My meeting module (on)

.net core3.1 + angular + is4 project records: 2 My meeting module (on)

table of Contents

  1. Page set up
  2. Routing Configuration
  3. Add meeting page
  4. My meeting page
  5. Cancel conferencing

Page set up

  Set up a body of the page, page structure set up under MainComponent in the main module, app modules only put a . The overall structure of the page is: + sidebar body of the page. Sidebar referring to sidebar NG-ZORRO introductory tutorial (address: https://github.com/NG-ZORRO/today-ng-steps/blob/legacy-v1/tutorial/2.md ). The body is divided into lower (bread crumbs, page body, footer) on three parts. Overall probably look something like this:

Specific code:

<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>

Routing Configuration

  Now that you have built over the main page, then the next configuration of the route, and before configuring look at before planning module structure:

  FIG observed by main route are placed in main.router.

app-routing codes:

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: ""
  }
];

  Adds two token and associated routing, do not make records, see if you can forget: https://www.cnblogs.com/zyz-Notes/p/12097826.html. Because app.router must be connected to the main module, in order to shorten the length of the address so that the use of "" as the value of the path.

main-routing codes

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"
  }
];

  I chose the main path to the beginning. Because the body of the page we built in MainComponent, the corresponding components so as MainComponent. We Zi is activated by the main origin of the configuration of the router-outlet MainComponent, so the following route sliver 4 correspond to the four modules, and finally as an error when the redirection path.

my-meeting-routing codes

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: ""
  }
];

  The individual components to be generated by cli. Functions corresponding to various components:

  1. AddMeetingComponent- add conference
  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

Guess you like

Origin www.cnblogs.com/zyz-Notes/p/12167449.html