显示带有描述文本的 RoomType 枚举,而不仅仅是整数

display RoomType enum with a description text instead of just integer numbers

我的前端是 Angular 12,后端是 ASP.NET Core 5。我有一个房间的 CRUD 页面。问题是 roomType 显示为数字 - 1、2 或 3。我想改为显示描述标签。我该怎么做?

后端

public enum RoomType : int
{
    SmallRoom = 1,
    MediumRoom = 2,
    LectureHall = 3
}

public class RoomDto : IMapFrom<Room>
{
    public int Id { get; set; }
    public string Name { get; set; }
    public RoomType RoomType { get; set; }
    public int Seats { get; set; }
    public int DepartmentId { get; set; }
}

前端

room.ts

export enum RoomType {
  SmallRoom = 1,
  MediumRoom = 2,
  LectureHall = 3
}

export interface Room {
  id?: number;
  name?: string;
  roomType?: RoomType;
  seats?: number;
  departmentId?: number;
}

查看-rooms.component.ts

import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Subject, takeUntil } from 'rxjs';

import { Room } from '../room';
import { RoomService } from '../room.service';
import { ConfirmationService, MessageService, PrimeNGConfig } from 'primeng/api';
import { BreadcrumbService } from '@core/services';
import { Table } from 'primeng/table';

@Component({
  selector: 'app-view-rooms',
  templateUrl: './view-rooms.component.html'
})
export class ViewRoomsComponent implements OnInit, OnDestroy {
  roomDialog: boolean;
  rooms: Room[];
  room: Room;
  selectedRooms: Room[];
  submitted: boolean;
  loading: boolean = true;

  private componentDestroyed$ = new Subject<boolean>();

  @ViewChild('dt') table: Table;

  constructor(
    private roomService: RoomService,
    private messageService: MessageService,
    private confirmationService: ConfirmationService,
    private breadcrumbService: BreadcrumbService,
    private primengConfig: PrimeNGConfig
  ) {
    this.breadcrumbService.setItems([{ label: 'Зали' }, { label: 'Преглед' }]);
  }

  ngOnInit() {
    this.roomService
      .getAllRooms()
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data) => {
        this.rooms = data;
        this.loading = false;
      });

    this.primengConfig.ripple = true;
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true);
    this.componentDestroyed$.complete();
  }

  applyFilterGlobal($event: any) {
    this.table.filterGlobal(($event.target as HTMLInputElement).value, 'contains');
  }

  openNew() {
    this.room = {};
    this.submitted = false;
    this.roomDialog = true;
  }

  deleteSelectedRooms() {
    this.confirmationService.confirm({
      message: 'Сигурни ли сте, че искате да изтриете данните за избраните зали?',
      header: 'Потвърждение',
      icon: 'pi pi-exclamation-triangle',
      acceptLabel: 'Да',
      rejectLabel: 'Не',
      accept: () => {
        this.rooms = this.rooms.filter((val) => !this.selectedRooms.includes(val));
        this.selectedRooms = [];
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за залите са изтрити',
          life: 3000
        });
      }
    });
  }

  editRoom(room: Room) {
    this.room = { ...room };
    this.roomDialog = true;
  }

  deleteRoom(room: Room) {
    this.confirmationService.confirm({
      message: 'Сигурни ли сте, че искате да изтриете данните за зала ' + room.name + '?',
      header: 'Потвърждение',
      icon: 'pi pi-exclamation-triangle',
      acceptLabel: 'Да',
      rejectLabel: 'Не',
      accept: () => {
        this.rooms = this.rooms.filter((val) => val.id !== room.id);
        this.room = {};
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за залата са изтрити',
          life: 3000
        });
      }
    });
  }

  hideDialog() {
    this.roomDialog = false;
    this.submitted = false;
  }

  saveRoom() {
    this.submitted = true;

    if (this.room.name?.trim()) {
      if (this.room.id) {
        this.rooms[this.findIndexById(this.room.id)] = this.room;
        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Данните за залата са обновени',
          life: 3000
        });
      } else {
        this.rooms.push(this.room);

        // update to what's in db instead

        this.messageService.add({
          severity: 'success',
          summary: 'Успешно',
          detail: 'Залата е добавена',
          life: 3000
        });
      }

      this.rooms = [...this.rooms];
      this.roomDialog = false;
      this.room = {};
    }
  }

  findIndexById(id: number): number {
    let index = -1;
    for (let i = 0; i < this.rooms.length; i++) {
      if (this.rooms[i].id === id) {
        index = i;
        break;
      }
    }

    return index;
  }
}

查看-rooms.component.html

<div class="p-grid">
  <div class="p-col-12">
    <p-toast></p-toast>

    <div class="card">
      <p-toolbar styleClass="p-mb-4">
        <ng-template pTemplate="left">
          <button
            pButton
            pRipple
            label="Добавяне"
            icon="pi pi-plus"
            class="p-button-success p-mr-2 p-mb-2"
            (click)="openNew()"
          ></button>
          <button
            pButton
            pRipple
            label="Изтриване"
            icon="pi pi-trash"
            class="p-button-danger p-mb-2"
            (click)="deleteSelectedRooms()"
            [disabled]="!selectedRooms || !selectedRooms.length"
          ></button>
        </ng-template>
      </p-toolbar>

      <p-table
        #dt
        [value]="rooms"
        [(selection)]="selectedRooms"
        dataKey="id"
        styleClass="p-datatable-rooms"
        [rowHover]="true"
        [rows]="10"
        [showCurrentPageReport]="true"
        [rowsPerPageOptions]="[10, 25, 50]"
        [loading]="loading"
        [paginator]="true"
        currentPageReportTemplate="Показват се от {first} до {last} от общо {totalRecords} записа"
        [globalFilterFields]="['name', 'roomType', 'seats', 'departmentId']"
      >
        <ng-template pTemplate="caption">
          <div class="p-d-flex p-flex-column p-flex-md-row p-jc-md-between table-header">
            <h5 class="p-m-0">Зали</h5>
            <span class="p-input-icon-left">
              <i class="pi pi-search"></i>
              <input
                pInputText
                type="text"
                (input)="applyFilterGlobal($event)"
                placeholder="Търсене..."
              />
            </span>
          </div>
        </ng-template>
        <ng-template pTemplate="header">
          <tr>
            <th style="width: 3rem">
              <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
            </th>
            <th pSortableColumn="name">Зала <p-sortIcon field="name"></p-sortIcon></th>
            <th pSortableColumn="roomType">Тип <p-sortIcon field="roomType"></p-sortIcon></th>
            <th pSortableColumn="seats">Места <p-sortIcon field="seats"></p-sortIcon></th>
            <th pSortableColumn="departmentId">
              Катедра <p-sortIcon field="departmentId"></p-sortIcon>
            </th>
            <th></th>
          </tr>
        </ng-template>
        <ng-template pTemplate="body" let-room>
          <tr>
            <td>
              <p-tableCheckbox [value]="room"></p-tableCheckbox>
            </td>
            <td>
              {{ room.name }}
            </td>
            <td>
              {{ room.roomType }}
            </td>
            <td>
              {{ room.seats }}
            </td>
            <td>
              {{ room.departmentId }}
            </td>
            <td>
              <button
                pButton
                pRipple
                icon="pi pi-pencil"
                class="p-button-rounded p-button-success p-mr-2"
                (click)="editRoom(room)"
              ></button>
              <button
                pButton
                pRipple
                icon="pi pi-trash"
                class="p-button-rounded p-button-warning"
                (click)="deleteRoom(room)"
              ></button>
            </td>
          </tr>
        </ng-template>
        <ng-template pTemplate="summary">
          <div class="p-d-flex p-ai-center p-jc-between">
            Общо {{ rooms ? rooms.length : 0 }} зали.
          </div>
        </ng-template>
      </p-table>
    </div>

    <p-dialog
      [(visible)]="roomDialog"
      [style]="{ width: '450px' }"
      header="Данни за зала"
      [modal]="true"
      styleClass="p-fluid"
    >
      <ng-template pTemplate="content">
        <div class="p-field">
          <label for="name">Зала</label>
          <input
            #name="ngModel"
            id="name"
            type="text"
            pInputText
            [(ngModel)]="room.name"
            [ngClass]="{
              'ng-dirty': (name.invalid && submitted) || (name.dirty && name.invalid)
            }"
            required
            autofocus
          />
          <small class="p-error" *ngIf="(name.invalid && submitted) || (name.dirty && name.invalid)"
            >Залата е задължителна.</small
          >
        </div>
        <div class="p-field">
          <label for="roomType">Тип</label>
          <input
            #roomType="ngModel"
            id="roomType"
            type="text"
            pInputText
            [(ngModel)]="room.roomType"
            [ngClass]="{
              'ng-dirty': (roomType.invalid && submitted) || (roomType.dirty && roomType.invalid)
            }"
            required
          />
          <small
            class="p-error"
            *ngIf="(roomType.invalid && submitted) || (roomType.dirty && roomType.invalid)"
            >Типът е задължителен.</small
          >
        </div>
      </ng-template>

      <ng-template pTemplate="footer">
        <button
          pButton
          pRipple
          label="Отказ"
          icon="pi pi-times"
          class="p-button-text"
          (click)="hideDialog()"
        ></button>
        <button
          pButton
          pRipple
          label="Запази"
          icon="pi pi-check"
          class="p-button-text"
          (click)="saveRoom()"
        ></button>
      </ng-template>
    </p-dialog>

    <p-confirmDialog [style]="{ width: '450px' }"></p-confirmDialog>
  </div>
</div>

room.service.ts

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { environment } from '@env';
import { Room } from './room';

@Injectable({
  providedIn: 'root'
})
export class RoomService {
  private actionUrl: string;

  constructor(private httpClient: HttpClient) {
    this.actionUrl = `${environment.apiUrl}/rooms`;
  }

  getAllRooms() {
    return this.httpClient.get<Room[]>(this.actionUrl);
  }

  getRoomById(id: number) {
    return this.httpClient.get<Room>(`${this.actionUrl}/${id}`);
  }

  addRoom(room: Room) {
    return this.httpClient.post<Room>(this.actionUrl, room);
  }

  updateRoom(room: Room) {
    return this.httpClient.put<Room>(`${this.actionUrl}/${room.id}`, room);
  }

  deleteRoom(id: number) {
    return this.httpClient.delete(`${this.actionUrl}/${id}`);
  }
}

一个选项是简单地有一个单独的对象来记录每个枚举值的元数据:

export enum RoomType {
    SmallRoom = 1,
    MediumRoom = 2,
    LectureHall = 3
}

export const RoomDescriptions: Record<RoomType, string> = {
    [RoomType.SmallRoom]: 'A small room',
    [RoomType.MediumRoom]: 'A medium room',
    [RoomType.LectureHall]: 'A lecture hall',
};

const smallRoomDesc: string = RoomDescriptions[RoomType.SmallRoom];

另一个是用一个对象替换你的枚举:

export interface Room {
    name: string;
    description: string;
}

export const Room = (<T extends Record<keyof T, Room>>(types: T) => types)({
    SmallRoom: {
        name: 'Small room',
        description: 'A small room',
    },
    MediumRoom: {
        name: 'Medium room',
        description: 'A medium room',
    },
    LectureHall: {
        name: 'Lecture hall',
        description: 'A lecture hall',
    },
});

export type RoomTypes = keyof typeof Room;
// ^ "SmallRoom" | "MediumRoom" | "LectureHall"

const smallRoomDesc: string = Room.SmallRoom.description;

如注释所示,RoomTypes是所有键的并集。 Room 常量的类型为:

这意味着 Room.SmallRoom 是一个 对象 而不是简单的 string/number。对于序列化,您可能希望使用键而不是对象。

或者,您可以使用此定义:

export const Room = (<T extends Record<keyof T, Room>>(types: T): { [key in keyof T]: Room } => types)({
    SmallRoom: {
        name: 'Small room',
        description: 'A small room',
    },
    MediumRoom: {
        name: 'Medium room',
        description: 'A medium room',
    },
    LectureHall: {
        name: 'Lecture hall',
        description: 'A lecture hall',
    },
});

const Room 的类型更好看:

这两个定义都带有类型检查,即如果您不定义(或使用错误的类型)name 等,它会显示错误。