• .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。

  • 相关阅读:
    无标题
    OSI七层模型介绍
    Microsoft Visual Studio .NET 系统必备
    如何得到硬盘序列号[C#]
    session变量
    使用Installshield制作asp,asp.net应用的安装程序
    如何远程备份sql server数据库
    VS.NET打印思想与2003/5DataGrid、DataGridView及二维数据如ListView等终极打印实现(全部源码)
    6.22打包建立ISS虚拟目录,安装完运行你想运行的程序
    关于网关的精典描述通俗易懂
  • 原文地址:https://www.cnblogs.com/zyz-Notes/p/12167449.html
Copyright © 2020-2023  润新知