開始實作訂單管理的功能模塊。
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
--
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
新增/修改訂單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
的知識以及相關的驗證。let o = <ValidationErrors>{};
o[msg] = true;
let control = (<FormArray>this.form.controls.arrs).controls;
control[index]["controls"]["productId"].setErrors(o);
--
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>
子組件
查看商品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
一開始會跳出提示視窗顯示fail為正常,
請先從範例專案裡下載或是複製db.json
到本地端,
並下指令:
json-server db.json
json-server開啟成功後請連結此網址:
https://ngcms-json-server.stackblitz.io/cms?token=bc6e113d26ce620066237d5e43f14690