iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 23
0
Modern Web

用Angular打造完整後台系列 第 23

day23 OrderModule

  • 分享至 

  • xImage
  •  

簡述

開始實作訂單管理的功能模塊。

功能

orderlist

  • 訂單列表(換頁搜尋)。
  • Dialog新增/修改訂單。
  • 子組件查看商品。

檔案結構

-src
  |-app
    |-cms
       |-order
           |-list
               |-detail
                   |-dialog-detail.component.html
                   |-dialog-detail.component.css
                   |-dialog-detail.component.ts
               |-car
                   |-car.component.html
                   |-car.component.css
                   |-car.component.ts
               |-list.component.html
               |-list.component.css
               |-list.component.ts
           |-order-routing.module.ts
           |-order.component.ts
           |-order.module.ts

--

實作

(一) 訂單列表

orderlistview

list/list.component.ts

export class OrderListComponent implements OnInit {
  ORDERSTATUS = ORDERSTATUS;
  isLoadingToggle = true; //讀取list的特效
  tab: ITabBase;          //此頁面元素資料
  result: IOrder[] = [];  //顯示在畫面的list array
  ...

  constructor(
    public dialog: MatDialog,
    private route: ActivatedRoute,
    private dataService: DataService,
    private userService: UserService,
    private tabService: TabService
  ) {}

  /*get resolve */
  ngOnInit() {
    this.route.data.subscribe(resolversData => {
      this.tab = resolversData.listTab;
      if (!!this.tab) {
        this.init();
      }
    });
  }

  /*初始 */
  init() {
    if (!this.tab.pageObj) {
      this.tab.pageObj = <IPage>{
        pageIndex: 0,
        pageSize: PAGESIZE,
        length: 0
      };
    }
    this.setDatas(true);
  }

  /*換分頁 */
  onSetPage(pageObj: IPage) {
    this.tab.pageObj.pageIndex = pageObj.pageIndex;
    this.tab.pageObj.pageSize = pageObj.pageSize;
    this.setDatas();
  }

  /*裝畫面資料 */
  setDatas(isDataInit = false) {
    this.isLoadingToggle = true;
    let url = this.dataService.setUrl("orders",
    [{ key: "_expand", val: "customer" }]);
    this.dataService.getData(url, this.tab.pageObj)
    .subscribe((data: IData) => {
      this.isLoadingToggle = false;
      if (!!data.errorcode) {
        this.openStatusDialog(data.errorcode);
      } else {
        if (!!data.res) {
          this.result = <IOrder[]>data.res;
          this.setLoadingDatas(isDataInit);
        }
      }
    });
  }

  /*新增/修改 Order的相關 Dialog Function */
  ...


  /*查看Order的相關 子組件 Function */
  ...

  /*儲存storage */
  setLoadingDatas(isDataInit = false) {
    if (!isDataInit) {
      this.tabService.nextTabMain(<ITabMain>{
        request: "update",
        content: this.tab
      });
    }
  }

  /*打開 alert-dialog 提示視窗 */
  openStatusDialog(errorcode: number) {
    let dialogRef = this.dialog.open(DialogAlertComponent, {
      width: "250px",
      data: {
        errorcode: errorcode
      }
    });
    dialogRef.afterClosed().subscribe(() => {
      this.setDatas();
    });
  }
}
  • 幾個時機點要重新裝畫面資料與其他功能模塊皆相同。

--

list/list.component.html

<div class="container">
  <div class="search-box">
    <div class="flex">
      <div class="search-action">
        <button class="button small-icons radius-5 pr-10 pink" 
        (click)="openDialog('insert')">
          <mat-icon>add</mat-icon>
          <span>{{ "insert" | translate }}</span>
        </button>
      </div>
    </div>
  </div>
  <div class="content">
    <div class="content-container">
      <div class="content-main">
        <table class="table base">
          <thead>
            <tr>
              <th>
                <div><span>{{ "id" | translate }}</span></div>
              </th>
              <th class="sticky-col">
                <div><span>{{ "insert_date" | translate }}</span></div>
              </th>
              <th>
                <div><span>{{ "customer_id" | translate }}</span></div>
              </th>
              <th>
                <div><span>{{ "customer_name" | translate }}</span></div>
              </th>
              <th>
                <div><span>{{ "status" | translate }}</span></div>
              </th>
              <th>
                <div><span>{{ "action" | translate }}</span></div>
              </th>
            </tr>
          </thead>

          <tbody *ngIf="!!result && !!result.length">
            <ng-container *ngFor="let r of result; let i = index">
              <tr>
                <td [attr.data-title]="'id' | translate">
                  <span>{{ r.id }}</span>
                </td>

                <td [attr.data-title]="'insert_date' | translate" 
                class="sticky-col">
                  <span>{{ r.inserted | pipetime }}</span>
                </td>

                <td [attr.data-title]="'customer_id' | translate">
                  <span>{{ r.customerId }}</span>
                </td>

                <td [attr.data-title]="'customer_name' | translate">
                  <span>{{ r.customer.name }}</span>
                </td>

                <td [attr.data-title]="'status' | translate">
                  <span>
                    {{ r.status | pipetag: ORDERSTATUS | translate }}
                  </span>
                </td>

                <td>
                  <button (click)="openDialog('update', r)" 
                  class="button blue pb">
                    {{ "update" | translate }}
                  </button>
                  <button
                    (click)="onOpenChild('product', r)"
                    class="button blue pb"
                    [ngClass]="{
                  active:
                    childToggle.selectTag == 'product' &&
                    childToggle.selectId == r[childToggle.selectMarkID]
                }"
                  >
                    {{ "view_product" | translate }}
                  </button>
                </td>
              </tr>
              <tr
                *ngIf="
                  childToggle.selectTag == 'product' &&
                  childToggle.selectId == r[childToggle.selectMarkID]
                "
              >
                <td colspan="6" class="skin">
                  <app-order-car [order]="r"></app-order-car>
                </td>
              </tr>
            </ng-container>
          </tbody>
          <tbody *ngIf="!result || !result.length">
            <ng-container 
            *ngTemplateOutlet="empty; context: { $implicit: 6 }">
            </ng-container>
          </tbody>
        </table>
      </div>
    </div>
  </div>
  <div class="pages">
    <mat-paginator
      *ngIf="!!result && !!result.length"
      [pageIndex]="tab.pageObj.pageIndex"
      [length]="tab.pageObj.length"
      [pageSize]="tab.pageObj.pageSize"
      [pageSizeOptions]="PAGESIZEOPTIONS"
      (page)="onSetPage($event)"
    >
    </mat-paginator>
  </div>
</div>
<ng-template #empty let-col>
  <tr>
    <td [attr.colspan]="col" *ngIf="!isLoadingToggle">
      <span>{{ "nodata" | translate }}</span>
    </td>
    <td [attr.colspan]="col" *ngIf="isLoadingToggle">
      <app-loading></app-loading>
    </td>
  </tr>
</ng-template>

(二) 用Dialog新增/修改訂單

orderupdate

list/list.component.ts

export class OrderListComponent implements OnInit {
  ...
  cars: IOrderCar[] = [];

  constructor(...) {}

  ...

  /*開Dialog */
  openDialog(action: string, select?: IOrder) {
    this.childToggle.reset();
    switch (action) {
      case "insert":
        this.openDetailDialog();
        break;
      case "update":
        this.openDetailDialog(select);
        break;
    }
  }

  /*要給Dialog的資料 */
  openDetailDialog(select?: IOrder) {
    let obj = <IDetailOrder>{
      order: null,
      oldCars: [],
      cars: []
    };
    if (!!select) {
      obj.order = select;
    }
    let dialogRef = this.dialog.open(DialogOrderDetailComponent, {
      width: "800px",
      data: obj
    });
    dialogRef.afterClosed().subscribe((o: IDetailOrder) => {
      if (!!o && !!o.order) {
        if (!select) {
          this.insertOrder(o);
        } else {
          this.updateOrder(o, select);
        }
      }
    });
  }

  /*新增訂單 */
  insertOrder(o: IDetailOrder) {
    let url = this.dataService.setUrl("orders");
    o.order = <IOrder>this.dataService.checkData(
                                        o.order, 
                                        this.userService.getUser().id
                                       );

    this.dataService.insertOne(url, o.order)
    .subscribe((data: IData) => {
      if (!!data.errorcode) {
        this.openStatusDialog(data.errorcode);
      } else {
        let order = <IOrder>data.res;
        this.runInsertCars(0, o.cars, order.id);
      }
    });
  }

  /*更新訂單 */
  updateOrder(o: IDetailOrder, select: IOrder) {
    let url = this.dataService.setUrl("orders", null, select.id);
    o.order = <IOrder>this.dataService.checkData(
                                          o.order, 
                                          this.userService.getUser().id, 
                                          false
                                        );
    this.dataService.updateOne(url, o.order)
    .subscribe((data: IData) => {
      if (!!data.errorcode) {
        this.openStatusDialog(data.errorcode);
      } else {
        if (!!o.oldCars.length) {
          this.runDelCars(0, o.oldCars, select.id, o.cars);
        } else {
          this.runInsertCars(0, o.cars, select.id);
        }
      }
    });
  }

  /*
    遞迴迴圈,一筆一筆刪除原本的購物車清單
    每一筆刪除都是異步加載,所以用遞迴。
  */
  runDelCars(index: number, oldCars: IOrderCar[], orderId: number, cars: IOrderCar[]) {
    let leng = oldCars.length;
    if (index < leng) {
      this.delCars(index, oldCars, orderId, cars);
    } else {
      this.runInsertCars(0, cars, orderId);
    }
  }

  //刪除某筆購物車
  delCars(index: number, oldCars: IOrderCar[], orderId: number, cars: IOrderCar[]) {
    this.dataService.deleteOne("cars", oldCars[index].id).subscribe(() => {
      this.runDelCars(++index, oldCars, orderId, cars);
    });
  }

 /*
    遞迴迴圈,一筆一筆新增購物車清單
    每一筆新增都是異步加載,所以用遞迴。
  */
  runInsertCars(index: number, cars: IOrderCar[], orderId: number) {
    let leng = cars.length;
    if (index < leng) {
      this.insertCars(index, cars, orderId);
    } else {
      this.openStatusDialog(0);
    }
  }

  //新增一筆購物車明細
  insertCars(index: number, cars: IOrderCar[], orderId: number) {
    let obj = <IOrderCar>{
      orderId: orderId,
      productId: cars[index].productId,
      amount: cars[index].amount
    };
    this.dataService.insertOne("cars", obj).subscribe(() => {
      this.runInsertCars(++index, cars, orderId);
    });
  }

  /*打開 alert-dialog 提示視窗 */
  openStatusDialog(errorcode: number) {
    let dialogRef = this.dialog.open(DialogAlertComponent, {
      width: "250px",
      data: {
        errorcode: errorcode
      }
    });
    dialogRef.afterClosed().subscribe(() => {
      this.setDatas();
    });
  }
}

--

list/detail/dialog-detail.component.ts

export interface IDetailOrder {
  order: IOrder;
  oldCars: IOrderCar[];
  cars: IOrderCar[];
}

@Component(...)
export class DialogOrderDetailComponent implements OnInit {
  ORDERSTATUS = ORDERSTATUS;
  form: FormGroup;
  customerName: string = "";
  cars: IOrderCar[] = [];

  constructor(
    public dialogRef: MatDialogRef<DialogOrderDetailComponent>,
    @Inject(MAT_DIALOG_DATA) public data: IDetailOrder,
    private fb: FormBuilder,
    private dataService: DataService
  ) {}

  ngOnInit() {
    if (!!this.data) {
      this.createForm();
      if (!!this.data.order) {
        this.editForm();
      }
    }
  }

  createForm() {
    let obj = {
      customerId: [
        "", 
        [
          Validators.required, 
          ValidationService.numberOnlyValidator
        ]
      ],
      status: [
        "", 
        [
          Validators.required, 
          ValidationService.numberOnlyValidator
        ]
      ],
      arrs: this.fb.array([], Validators.required)
    };
    this.form = this.fb.group(obj);
  }

  editForm() {
    let order = this.data.order;
    this.form.patchValue({
      customerId: order.customerId.toString() || "",
      status: order.status.toString() || ""
    });
    this.setCustomerName(order.customerId.toString());
    this.setCars();
  }

  get arrs(): FormArray {
    return this.form.get("arrs") as FormArray;
  }

  setCustomerName(customerId: string) {
    this.customerName = "";
    if (!!+customerId) {
      let url = this.dataService.setUrl("customers", null, +customerId);
      this.dataService.getData(url).subscribe((data: IData) => {
        if (!data.errorcode && !!data.res) {
          let customer = <ICustomer>data.res;
          this.customerName = customer.name;
        } else {
          let result = ErrorCodeMsg.filter(item => item.code === 8);
          let msg = result[0].name;
          let o = <ValidationErrors>{};
          o[msg] = true;
          this.form.controls["customerId"].setErrors(o);
        }
      });
    }
  }

  setCars() {
    let url = this.dataService.setUrl("cars", [
      { key: "orderId", val: this.data.order.id },
      { key: "_expand", val: "product" }
    ]);
    this.dataService.getData(url).subscribe((data: IData) => {
      let carsArr = <IOrderCar[]>data.res;
      this.data.oldCars = carsArr.slice(0);
      this.cars = carsArr;
      this.setArrs();
    });
  }

  setArrs() {
    if (!!this.cars && !!this.cars.length) {
      this.cars.forEach((car: IOrderCar, index: number) => {
        this.arrs.push(
          this.fb.group({
            productId: [
              car.productId.toString(),
              [
                Validators.required, 
                ValidationService.numberOnlyValidator
              ]
            ],
            amount: [
              car.amount.toString(),
              [
                Validators.required, 
                ValidationService.numberOnlyValidator
              ]
            ]
          })
        );
      });
    }
  }

  setProductData(index: number, productId: string) {
    if (!!+productId) {
      let url = this.dataService.setUrl("products", null, +productId);
      this.dataService.getData(url).subscribe((data: IData) => {
        if (!data.errorcode && !!data.res) {
          let product = <IProduct>data.res;
          this.cars[index].product = product;
          this.cars[index].productId = product.id;
        } else {
          let result = ErrorCodeMsg.filter(item => item.code === 9);
          let msg = result[0].name;
          let o = <ValidationErrors>{};
          o[msg] = true;
          let control = (<FormArray>this.form.controls.arrs).controls;
          control[index]["controls"]["productId"].setErrors(o);
          this.resetProduct(index)
        }
      });
    } else {
      this.resetProduct(index)
    }
  }

  resetProduct(index:number){
    this.cars[index].product = <IProduct>{
      id: 0,
      typeId: 0,
      name: '',
      price: 0,
      file: ''
    };
    this.cars[index].productId = 0;
  }

  addArr() {
    this.arrs.push(
      this.fb.group({
        productId: [
          "", 
          [
            Validators.required, 
            ValidationService.numberOnlyValidator
          ]
        ],
        amount: [
          "", 
          [
            Validators.required, 
            ValidationService.numberOnlyValidator
          ]
        ]
      })
    );
    this.cars.push(<IOrderCar>{
      id: 0,
      orderId: 0,
      amount: 0,
      productId: 0,
      product: {}
    });
  }

  delArr(index: number) {
    this.arrs.removeAt(index);
    this.cars.splice(index, 1);
  }

  turnArrs() {
    if (!!this.form.value.arrs && !!this.form.value.arrs.length) {
      this.form.value.arrs.forEach((item: IOrderCar) => {
        item.productId = +item.productId;
        item.amount = +item.amount;
      });
    }
  }

  getDetailData(): IDetailOrder {
    let order = <IOrder>{
      customerId: +this.form.value.customerId,
      status: +this.form.value.status
    };
    this.turnArrs();
    return <IDetailOrder>{
      order: order,
      cars: this.form.value.arrs,
      oldCars: this.data.oldCars
    };
  }

  getSum(index: number, price = 0, amount = 0): number {
    let sum = price * amount;
    this.cars[index]["sum"] = sum;
    return sum;
  }

  getTotal(): number {
    let t = 0;
    if (!!this.cars && !!this.cars.length) {
      t = this.cars
        .map((car: IOrderCar) => {
          return car["sum"];
        })
        .reduce(
          (accumulator, currentValue) => accumulator + currentValue
        );
    }
    return t;
  }

  onNoClick(): void {
    this.dialogRef.close();
  }

  onEnter() {
    this.dialogRef.close(this.getDetailData());
  }
}
  • 要有基礎的FormArray的知識以及相關的驗證。
  • 當輸入會員的編號完馬上要去比對Database的會員資料,
    這裡沒有用異步驗證,但有動態綁定控件,
    一但驗證錯誤就設定控件的 error 屬性。
let o = <ValidationErrors>{};
o[msg] = true;
let control = (<FormArray>this.form.controls.arrs).controls;
control[index]["controls"]["productId"].setErrors(o);

ordererrorexist

--

list/detail/dialog-detail.component.html

<form [formGroup]="form">
  <div mat-dialog-title class="flex center">
    <mat-icon svgIcon="alert"></mat-icon>
    <span *ngIf="!!data && !data.order">
      {{ "alert_order_insert" | translate }}
    </span>
    <span *ngIf="!!data && !!data.order">
      {{ "alert_order_update" | translate }}
    </span>
  </div>

  <div mat-dialog-content>
    <div class="item-wrapper two pink">
      <div>
        <span>*{{ "customer_id" | translate }}</span>
      </div>
      <div>
        <input
          type="text"
          formControlName="customerId"
          [placeholder]="'import_customer_id' | translate"
          (keyup)="setCustomerName($event.target.value)"
          required
        />
      </div>
      <validation-messages [control]="form.controls.customerId"> 
      </validation-messages>
    </div>
    <div class="item-wrapper two pink">
      <div>
        <span>*{{ "customer_name" | translate }}</span>
      </div>
      <div>
        <span *ngIf="!!customerName">{{ customerName }}</span>
      </div>
    </div>
    <div class="item-wrapper two pink">
      <div>
        <span>*{{ "status" | translate }}</span>
      </div>
      <div>
        <select name="se_status" id="se_status" 
        formControlName="status">
          <option value="" disabled>
            {{ "select" | translate }}
          </option>
          <option *ngFor="let o of ORDERSTATUS" [value]="o.id">
            {{ o.name | translate }}
          </option>
        </select>
      </div>
      <validation-messages [control]="form.controls.status"> 
      </validation-messages>
    </div>
    <table class="table base" style="margin-bottom: 10px" 
    formArrayName="arrs">
      <thead>
        <tr>
          <th><span>{{ "product_id" | translate }}</span></th>
          <th><span>{{ "product_name" | translate }}</span></th>
          <th><span>{{ "product_img" | translate }}</span></th>
          <th><span>{{ "product_price" | translate }}</span></th>
          <th><span>{{ "product_amount" | translate }}</span></th>
          <th><span>{{ "product_sum" | translate }}</span></th>
          <th><span>{{ "action" | translate }}</span></th>
        </tr>
      </thead>
      <tbody *ngIf="!!cars && !!arrs && !!arrs.controls">
        <tr *ngFor="let arr of arrs.controls; let i = index" 
        [formGroupName]="i">
          <td [attr.data-title]="'productId' | translate">
            <input
              type="text"
              formControlName="productId"
              (keyup)="setProductData(i, $event.target.value)"
              class="product"
            />
            <validation-messages [control]="arr.controls.productId"> 
            </validation-messages>
          </td>
          <td *ngIf="!!cars[i] && !!cars[i].product" 
          [attr.data-title]="'productName' | translate">
            <span>{{ cars[i].product.name }}</span>
          </td>
          <td *ngIf="!!cars[i] && !!cars[i].product" 
          [attr.data-title]="'productFile' | translate">
            <app-preview [imageSrc]="cars[i].product.file"> 
            </app-preview>
          </td>
          <td *ngIf="!!cars[i] && !!cars[i].product" 
          [attr.data-title]="'productPrice' | translate">
            <span>{{ cars[i].product.price }}</span>
          </td>
          <td [attr.data-title]="'amount' | translate">
            <input type="text" formControlName="amount" 
            #amount class="product" />

            <validation-messages [control]="arr.controls.amount"> 
            </validation-messages>
          </td>
          <td *ngIf="!!cars[i] && !!cars[i].product" 
          [attr.data-title]="'total' | translate">
            <span>
              {{ getSum(i, cars[i].product.price, amount.value) }}
            </span>
          </td>
          <td>
            <button (click)="delArr(i)" class="button blue pb">
              {{ "delete" | translate }}
            </button>
          </td>
        </tr>
        <tr>
          <td colspan="7">
            <div class="flex center">
              <button class="button small-icons radius-5 pr-10 pink" 
              (click)="addArr()">
                <mat-icon>add</mat-icon>
                <span>{{ "insert" | translate }}</span>
              </button>
            </div>
          </td>
        </tr>
        <tr class="total">
          <td colspan="2">
            <span>{{ "product_total" | translate }}</span>
          </td>
          <td colspan="5" class="text-right">
            <span>{{ getTotal() }}</span>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
  <div mat-dialog-actions class="flex center">
    <button (click)="onNoClick()" class="button pb radius-5" 
    style="margin-right:10px;">
      {{ "cancel" | translate }}
    </button>
    <button
      (click)="onEnter()"
      [disabled]="!form.valid"
      [ngClass]="{ disable: !form.valid }"
      class="button pb pink radius-5"
    >
      {{ "enter" | translate }}
    </button>
  </div>
</form>

(三) 用子組件查看商品

ordercarview

list/list.component.ts

export class OrderListComponent implements OnInit {
  ...
  childToggle = new ChildToggle("id", "", 0);

  constructor(...) {}

   ...

  /*開子組件 */
  onOpenChild(action: string, select: IOrder) {
    if (!!this.childToggle && !!select) {
      if (
        select[this.childToggle.selectMarkID] == 
        this.childToggle.selectId &&
        action == this.childToggle.selectTag
      ) {
        this.childToggle.reset();
        return;
      }
      this.childToggle.setData(
                          action, 
                          select[this.childToggle.selectMarkID]
                        );
    }
  }
}

--

list.component.html

...
<tbody *ngIf="!!result && !!result.length">
  <ng-container *ngFor="let r of result; let i = index">
    ...
    <tr>
      <td>
        <button
          (click)="onOpenChild('product', r)"
          class="button blue pb"
          [ngClass]="{
            active:
              childToggle.selectTag == 'product' &&
              childToggle.selectId == r[childToggle.selectMarkID]
          }"
        >
          {{ "view_product" | translate }}
        </button>
      </td>
    </tr>
    <tr
      *ngIf="
        childToggle.selectTag == 'product' &&
        childToggle.selectId == r[childToggle.selectMarkID]
      "
    >
      <td colspan="6" class="skin">
        <app-order-car [order]="r"></app-order-car>
      </td>
    </tr>
  </ng-container>
</tbody>
  • 將裝著被選中的商品以及數量。

childToggle的用法也與其他功能模塊皆相同。

--

list/car/car.component.ts

export class OrderCarComponent implements OnInit {
  @Input() order: IOrder = null;
  cars: IOrderCar[] = [];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    if (!!this.order) {
      this.setDatas();
    }
  }

  setDatas() {
    let url = this.dataService.setUrl("cars", [
      { key: "orderId", val: this.order.id },
      { key: "_expand", val: "product" }
    ]);
    this.dataService.getData(url).subscribe((data: IData) => {
      if (!data.errorcode && !!data.res) {
        this.cars = <IOrderCar[]>data.res;
      }
    });
  }

  getSum(index: number, price = 0, amount = 0): number {
    let sum = price * amount;
    this.cars[index]["sum"] = sum;
    return sum;
  }

  getTotal(): number {
    let t = 0;
    if (!!this.cars && !!this.cars.length) {
      t = this.cars
        .map((car: IOrderCar) => {
          return car["sum"];
        })
        .reduce((accumulator, currentValue) => accumulator + currentValue);
    }
    return t;
  }
}

--

list/car/car.component.html

<div *ngIf="!!order">
  <table class="table base" style="margin-bottom: 10px">
    <thead>
      <tr>
        <th><span>{{ "product_id" | translate }}</span></th>
        <th><span>{{ "product_name" | translate }}</span></th>
        <th><span>{{ "product_img" | translate }}</span></th>
        <th><span>{{ "product_price" | translate }}</span></th>
        <th><span>{{ "product_amount" | translate }}</span></th>
        <th><span>{{ "product_total" | translate }}</span></th>
      </tr>
    </thead>
    <tbody *ngIf="!!cars && !!cars.length">
      <tr *ngFor="let car of cars; let i = index">
        <ng-container *ngIf="!!car.product">
          <td [attr.data-title]="'product_id' | translate">
            <span>{{ car.product.id }}</span>
          </td>
          <td [attr.data-title]="'product_name' | translate">
            <span>{{ car.product.name }}</span>
          </td>
          <td [attr.data-title]="'product_img' | translate">
            <app-preview [imageSrc]="car.product.file"></app-preview>
          </td>
          <td [attr.data-title]="'product_price' | translate">
            <span>{{ car.product.price }}</span>
          </td>
          <td [attr.data-title]="'product_amount' | translate">
            <span>{{ car.amount }}</span>
          </td>
          <td [attr.data-title]="'product_total' | translate">
            <span>
              {{ getSum(i, car.product.price, car.amount) }}
            </span>
          </td>
        </ng-container>
      </tr>
      <tr class="total">
        <td colspan="2">
          <span>{{ "product_total" | translate }}</span>
        </td>
        <td colspan="4" class="text-right">
          <span>{{ getTotal() }}</span>
        </td>
      </tr>
    </tbody>
  </table>
</div>

範例碼

此為完整專案範例碼,連線方式為json-server。

https://stackblitz.com/edit/ngcms-json-server

Start

一開始會跳出提示視窗顯示fail為正常,
請先從範例專案裡下載或是複製db.json到本地端,
並下指令:

json-server db.json

json-server開啟成功後請連結此網址:
https://ngcms-json-server.stackblitz.io/cms?token=bc6e113d26ce620066237d5e43f14690


上一篇
day22 ProductModule(二):Image
下一篇
day24 SearchModule(一)
系列文
用Angular打造完整後台30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言