開始實作商品管理的功能模塊。
-src
|-app
|-cms
|-product
|-dialog-type
|-dialog-type.component.html
|-dialog-type.component.css
|-dialog-type.component.ts
|-list
|-dialog-detail
|-dialog-detail.component.html
|-dialog-detail.component.css
|-dialog-detail.component.ts
|-file-uploader
|-file-uploader.component.html
|-file-uploader.component.css
|-file-uploader.component.ts
|-list.component.html
|-list.component.css
|-list.component.ts
|-product-routing.module.ts
|-product.component.ts
|-product.module.ts
list/list.component.ts
:export class ProductListComponent implements OnInit {
PRODUCTSTATUS = PRODUCTSTATUS;
isLoadingToggle = true; //讀取list的特效
tab: ITabBase; //此頁面元素資料
result: IProduct[] = [];//顯示在畫面的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);
this.setTypes();
}
/*換分頁 */
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(
"products",
[{ key: "_expand", val: "type" }]
);
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 = <IProduct[]>data.res;
this.setLoadingDatas(isDataInit);
}
}
});
}
/*儲存storage */
setLoadingDatas(isDataInit = false) {
if (!isDataInit) {
this.tabService.nextTabMain(<ITabMain>{
request: "update",
content: this.tab
});
}
}
...
}
--
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>
<button class="button small-icons radius-5 pr-10 pink"
(click)="openDialog('type')">
<mat-icon>add</mat-icon>
<span>{{ "type_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>
<div><span>{{ "product_img" | translate }}</span></div>
</th>
<th>
<div><span>{{ "product_name" | translate }}</span></div>
</th>
<th>
<div><span>{{ "product_type" | translate }}</span></div>
</th>
<th>
<div><span>{{ "product_price" | 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]="'product_img' | translate">
<app-preview [imageSrc]="r.file"></app-preview>
</td>
<td [attr.data-title]="'product_name' | translate">
<span>{{ r.name }}</span>
</td>
<td [attr.data-title]="'product_type' | translate">
<span>{{ r.type.name }}</span>
</td>
<td [attr.data-title]="'product_price' | translate">
<span>{{ r.price }}</span>
</td>
<td [attr.data-title]="'status' | translate">
<span>{
{ r.status | pipetag: PRODUCTSTATUS | translate
</span>
</td>
<td>
<button (click)="openDialog('update', r)"
class="button blue pb">
{{ "update" | translate }}
</button>
</td>
</tr>
</ng-container>
</tbody>
<tbody *ngIf="!result || !result.length">
<ng-container
*ngTemplateOutlet="empty; context: { $implicit: 7 }">
</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-type/dialog-type.component.ts
:export class DialogProductTypeComponent implements OnInit {
ipContent = new FormControl("", [Validators.required]);
isUpdateNameIndex = 0;
constructor(
public dialogRef: MatDialogRef<DialogProductTypeComponent>,
@Inject(MAT_DIALOG_DATA) public data: { typeNames: string[] }
) {}
ngOnInit() {
if (
!this.data ||
!this.data.typeNames ||
!this.data.typeNames.length
) {
this.data.typeNames = [];
}
}
onDelete(index: number) {
this.data.typeNames.splice(index, 1);
}
onInsert() {
if (!!this.ipContent.value && this.ipContent.valid) {
this.data.typeNames.push(this.ipContent.value);
this.ipContent.setValue("");
}
}
onSave(index: number, value: string) {
this.data.typeNames[index] = value;
this.isUpdateNameIndex = 0;
}
onNoClick() {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.data.typeNames);
}
}
--
dialog-type/dialog-type.component.html
:<div mat-dialog-title class="flex center">
<mat-icon svgIcon="alert"></mat-icon>
<span> {{ "alert_product_type" | translate }} </span>
</div>
<div mat-dialog-content>
<div class="flex center">
<div style="margin-right:10px;">
<input
[formControl]="ipContent"
[placeholder]="'import_product_type' | translate"
style="margin-bottom:5px;"
/>
<validation-messages [control]="ipContent"></validation-messages>
</div>
<button
(click)="onInsert()"
class="button pb pink radius insert"
style="height: 35px;"
[disabled]="!ipContent.valid"
[ngClass]="{ disable: !ipContent.valid }"
>
{{ "insert" | translate }}
</button>
</div>
<div class="flex center">
<table class="table base"
*ngIf="!!data && !!data.typeNames && !!data.typeNames.length">
<tbody>
<tr *ngFor="let name of data.typeNames; let i = index">
<td class="flex center" class="skin">
<span *ngIf="isUpdateNameIndex != i + 1">
{{ name }}
</span>
<input
type="text"
[value]="name"
[hidden]="isUpdateNameIndex != i + 1"
style="width:100px"
#ipname
/>
<button *ngIf="isUpdateNameIndex != i + 1"
(click)="isUpdateNameIndex = i + 1">
{{ "update" | translate }}
</button>
<button *ngIf="isUpdateNameIndex == i + 1"
(click)="onSave(i, ipname.value)">
{{ "save" | translate }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
</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()" class="button pb pink radius-5">
{{ "enter" | translate }}
</button>
</div>
--
list/list.component.ts
:export class ProductListComponent implements OnInit {
...
types: IType[] = [];
constructor(...) {}
ngOnInit() {...}
/*初始 */
init() {
if (!this.tab.pageObj) {
this.tab.pageObj = <IPage>{
pageIndex: 0,
pageSize: PAGESIZE,
length: 0
};
}
this.setDatas(true);
this.setTypes();
}
...
/*裝所有types資料 */
setTypes() {
let url = this.dataService.setUrl("types");
this.dataService.getData(url)
.subscribe((data: IData) => {
if (!!data.errorcode) {
this.openStatusDialog(data.errorcode);
} else {
if (!!data.res) {
this.types = <IType[]>data.res;
}
}
});
}
/*開Dialog */
openDialog(action: string, select?: IProduct) {
switch (action) {
case "insert":
this.openDetailDialog();
break;
case "update":
this.openDetailDialog(select);
break;
case "type":
this.openTypeDialog();
break;
}
}
/*開啟新增產品類型的dialog */
openTypeDialog() {
if (!!this.types && !!this.types.length) {
let obj = {
typeNames: this.types.map((type: IType) => {
return type.name;
})
};
let dialogRef = this.dialog.open(DialogProductTypeComponent, {
width: "600px",
data: obj
});
dialogRef.afterClosed().subscribe((typeNames: string[]) => {
if (!!typeNames && !!typeNames.length) {
this.runAction(0, typeNames);
}
});
}
}
/*
遞迴迴圈,有在舊資料範圍內就是更新,
在舊資料外並在新資料範圍內就是新增,
超過新資料則遞迴停止
*/
runAction(index: number, typeNames: string[]) {
let leng = typeNames.length;
let oldLeng = this.types.length;
if (index < oldLeng) {
this.updateTypeNames(index, typeNames);
} else {
if (index < leng) {
this.insertTypeNames(index, typeNames);
} else {
this.setTypes();
this.openStatusDialog(0);
}
}
}
updateTypeNames(index: number, typeNames: string[]) {
this.dataService.updateOne(
"types",
{ name: typeNames[index] },
index + 1
)
.subscribe(() => {
this.runAction(++index, typeNames);
});
}
insertTypeNames(index: number, typeNames: string[]) {
this.dataService.insertOne(
"types",
<IType>{ name: typeNames[index] }
)
.subscribe(() => {
this.runAction(++index, typeNames);
});
}
openStatusDialog(errorcode: number) {
let dialogRef = this.dialog
.open(DialogAlertComponent, {
width: "250px",
data: {
errorcode: errorcode
}
});
dialogRef.afterClosed().subscribe(() => {
this.setDatas();
});
}
}
此為完整專案範例碼,連線方式為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