開始實作會員管理的功能模塊。
-src
|-app
|-cms
|-customer
|-dialog-level
|-dialog-level.component.html
|-dialog-level.component.css
|-dialog-level.component.ts
|-list
|-dialog-detail
|-dialog-detail.component.html
|-dialog-detail.component.css
|-dialog-detail.component.ts
|-list.component.html
|-list.component.css
|-list.component.ts
|-customer-routing.module.ts
|-customer.component.ts
|-customer.module.ts
list/list.component.ts
:export class CustomerListComponent implements OnInit {
ACCOUNTSTATUS = ACCOUNTSTATUS;
isLoadingToggle = true; //讀取list的特效
tab: ITabBase; //此頁面元素資料
result: ICustomer[] = []; //顯示在畫面的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.setLevelDatas();
}
/*換分頁 */
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(
"customers", [{ key: "_expand", val: "level" }]
);
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 = <ICustomer[]>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('level')">
<mat-icon>add</mat-icon>
<span>{{ "level_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>{{ "account" | translate }}</span></div>
</th>
<th>
<div><span>{{ "name" | translate }}</span></div>
</th>
<th>
<div><span>{{ "insert_date" | translate }}</span></div>
</th>
<th>
<div><span>{{ "level_type" | 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]="'account' | translate"
class="sticky-col">
<span>{{ r.account }}</span>
</td>
<td [attr.data-title]="'name' | translate">
<span>{{ r.name }}</span>
</td>
<td [attr.data-title]="'insert_date' | translate">
<span>{{ r.inserted | pipetime }}</span>
</td>
<td [attr.data-title]="'level_type' | translate">
<span>{{ r.level.name }}</span>
</td>
<td [attr.data-title]="'status' | translate">
<span>
{{ r.status | pipetag: ACCOUNTSTATUS | 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-detail/dialog-detail.component.ts
:export interface IDetailCustomer {
customer: ICustomer;
levels: ILevel[];
}
@Component({
...
})
export class DialogCustomerDetailComponent implements OnInit {
ACCOUNTSTATUS = ACCOUNTSTATUS;
form: FormGroup;
constructor(
public dialogRef: MatDialogRef<DialogCustomerDetailComponent>,
@Inject(MAT_DIALOG_DATA) public data: IDetailCustomer,
private fb: FormBuilder
) {}
ngOnInit() {
if (!!this.data && !!this.data.levels) {
this.createForm();
if (!!this.data.customer) {
this.editForm();
}
}
}
createForm() {
let obj = {
name: ["", Validators.required],
levelId: ["", Validators.required],
status: ["", Validators.required]
};
if (!this.data.customer) {
//insert
obj["account"] = [
"",
[
Validators.required,
Validators.minLength(6),
ValidationService.userValidator
]
];
obj["password"] = [
"",
[
Validators.required,
Validators.minLength(6),
ValidationService.userValidator
]
];
}
this.form = this.fb.group(obj);
}
editForm() {
let customer = this.data.customer;
this.form.setValue({
name: customer.name || "",
levelId: customer.levelId.toString() || "",
status: customer.status.toString() || ""
});
}
getDetailData(): ICustomer {
return <ICustomer>{
account: this.form.value.account,
password: this.form.value.password,
name: this.form.value.name,
levelId: +this.form.value.levelId,
status: +this.form.value.status
};
}
onNoClick(): void {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.getDetailData());
}
}
--
dialog-detail/dialog-detail.component.html
:<form [formGroup]="form">
<div mat-dialog-title class="flex center">
<mat-icon svgIcon="alert"></mat-icon>
<span *ngIf="!data.customer">
{{ "alert_customer_insert" | translate }}
</span>
<span *ngIf="!!data.customer">
{{ "alert_customer_update" | translate }}
</span>
</div>
<div mat-dialog-content>
<div class="item-wrapper two pink" *ngIf="!data.customer">
<div>
<span>*{{ "account" | translate }}</span>
</div>
<div>
<input
type="text"
formControlName="account"
[placeholder]="'import_account' | translate"
required
/>
</div>
<validation-messages [control]="form.controls.account">
</validation-messages>
</div>
<div class="item-wrapper two pink" *ngIf="!data.customer">
<div>
<span> *{{ "password" | translate }} </span>
</div>
<div>
<input
type="password"
formControlName="password"
[placeholder]="'import_password' | translate"
required
/>
</div>
<validation-messages [control]="form.controls.password">
</validation-messages>
</div>
<div class="item-wrapper two pink">
<div>
<span> *{{ "name" | translate }} </span>
</div>
<div>
<input
type="text"
formControlName="name"
[placeholder]="'import_name' | translate"
required
/>
</div>
<validation-messages [control]="form.controls.name">
</validation-messages>
</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 status of ACCOUNTSTATUS"
[value]="status.id">
{{status.name | translate}}
</option>
</select>
</div>
<validation-messages [control]="form.controls.status">
</validation-messages>
</div>
<div class="item-wrapper two pink">
<div>
<span>*{{ "level_type" | translate }}</span>
</div>
<div>
<select name="se_level" id="se_level"
formControlName="levelId">
<option value="" disabled>{{ "select" | translate }}</option>
<option *ngFor="let level of data.levels" [value]="level.id">
{{ level.name | translate }}
</option>
</select>
</div>
<validation-messages [control]="form.controls.levelId">
</validation-messages>
</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()"
[disabled]="!form.valid"
[ngClass]="{ disable: !form.valid }"
class="button pb pink radius-5"
>
{{ "enter" | translate }}
</button>
</div>
</form>
--
list/list.component.ts
:export class CustomerListComponent implements OnInit {
...
levels: ILevel[] = []; //所有等級類別
constructor(...) {}
/*初始 */
init() {
...
this.setLevelDatas();
}
...
/*裝所有levels資料 */
setLevelDatas() {
let url = this.dataService.setUrl("levels");
this.dataService.getData(url).subscribe((data: IData) => {
if (!!data.errorcode) {
this.openStatusDialog(data.errorcode);
} else {
if (!!data.res) {
this.levels = <ILevel[]>data.res;
}
}
});
}
/*開Dialog */
openDialog(action: string, select?: ICustomer) {
switch (action) {
case "insert":
this.openDetailDialog();
break;
case "update":
this.openDetailDialog(select);
break;
case "level":
this.openLevelDialog();
break;
}
}
/*開啟新增/更新的dialog */
openDetailDialog(select?: ICustomer) {
if (!!this.levels && !!this.levels.length) {
let obj = <IDetailCustomer>{
customer: null,
levels: this.levels
};
if (!!select) {
obj.customer = select;
}
let dialogRef = this.dialog.open(DialogCustomerDetailComponent, {
width: "600px",
data: obj
});
dialogRef.afterClosed().subscribe((o: ICustomer) => {
if (!!o) {
if (!select) {
this.insertCustomer(o);
} else {
this.updateCustomer(o, select);
}
}
});
}
}
/*新增會員 */
insertCustomer(o: ICustomer) {
let url = this.dataService.setUrl("customers");
o = <ICustomer>this.dataService
.checkData(o, this.userService.getUser().id);
this.dataService.insertOne(url, o)
.subscribe((data: IData) => {
this.openStatusDialog(data.errorcode);
});
}
/*修改會員 */
updateCustomer(o: ICustomer, select: ICustomer) {
let url = this.dataService.setUrl("customers", null, select.id);
o = <ICustomer>this.dataService
.checkData(o, this.userService.getUser().id, false);
this.dataService.updateOne(url, o)
.subscribe((data: IData) => {
this.openStatusDialog(data.errorcode);
});
}
/*打開 alert-dialog 提示視窗 */
openStatusDialog(errorcode: number) {
let dialogRef = this.dialog.open(DialogAlertComponent, {
width: "250px",
data: {
errorcode: errorcode
}
});
dialogRef.afterClosed().subscribe(() => {
this.setDatas();
});
}
}
dialog-level/dialog-level.component.ts
:export class DialogCustomerLevelComponent implements OnInit {
ipContent = new FormControl("", [Validators.required]);
isUpdateNameIndex = 0;
constructor(
public dialogRef: MatDialogRef<DialogCustomerLevelComponent>,
@Inject(MAT_DIALOG_DATA) public data: { levelNames: string[] }
) {}
ngOnInit() {
if (
!this.data ||
!this.data.levelNames ||
!this.data.levelNames.length
) {
this.data.levelNames = [];
}
}
onDelete(index: number) {
this.data.levelNames.splice(index, 1);
}
onInsert() {
if (!!this.ipContent.value && this.ipContent.valid) {
this.data.levelNames.push(this.ipContent.value);
this.ipContent.setValue("");
}
}
onSave(index: number, value: string) {
this.data.levelNames[index] = value;
this.isUpdateNameIndex = 0;
}
onNoClick() {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.data.levelNames);
}
}
--
dialog-level/dialog-level.component.html
:<div mat-dialog-title class="flex center">
<mat-icon svgIcon="alert"></mat-icon>
<span> {{ "alert_customer_level" | translate }} </span>
</div>
<div mat-dialog-content>
<div class="flex center">
<div style="margin-right:10px;">
<input
[formControl]="ipContent"
[placeholder]="'import_level_name' | 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.levelNames && !!data.levelNames.length">
<tbody>
<tr *ngFor="let name of data.levelNames; 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 CustomerListComponent implements OnInit {
...
levels: ILevel[] = []; //所有等級類別
constructor(...) {}
...
/*開Dialog */
openDialog(action: string, select?: ICustomer) {
switch (action) {
case "insert":
this.openDetailDialog();
break;
case "update":
this.openDetailDialog(select);
break;
case "level":
this.openLevelDialog();
break;
}
}
/*開啟新增等級類型的dialog */
openLevelDialog() {
if (!!this.levels && !!this.levels.length) {
let obj = {
levelNames: this.levels.map((level: ILevel) => {
return level.name;
})
};
let dialogRef = this.dialog
.open(DialogCustomerLevelComponent, {
width: "600px",
data: obj
});
dialogRef.afterClosed().subscribe((levelNames: string[]) => {
if (!!levelNames && !!levelNames.length) {
levelNames.forEach((name: string, index: number) => {
let length = this.levels.length;
if (index < length) {
//update level name
let url = this.dataService
.setUrl("levels", null, index + 1);
this.dataService.updateOne(url, { name: name })
.subscribe();
} else {
//insert level
let url = this.dataService.setUrl("levels", null);
this.dataService.insertOne(url, <ILevel>{ name: name })
.subscribe();
}
if (index === levelNames.length - 1) {
this.setLevelDatas();
this.openStatusDialog(0);
}
});
}
});
}
}
/*打開 alert-dialog 提示視窗 */
openStatusDialog(errorcode: number) {
let dialogRef = this.dialog.open(DialogAlertComponent, {
width: "250px",
data: {
errorcode: errorcode
}
});
dialogRef.afterClosed().subscribe(() => {
this.setDatas();
});
}
}
更新等級功能是不能刪除的。
這是因為會員列表本身跟等級有關聯性levelId
,
如果刪除的話,連結到的levelId
就失效了。
特別要注意的是更新 等級類別array 的流程,
在openLevelDialog()
裡面,會先判斷舊有的陣列長度,
再去判斷dialog-levels
送來的 等級類別array 的長度。
假設舊有長度是3,新長度是5,
根據新舊長度判斷來做,
前3個更新舊有的值,後2個新增新的值。
此為完整專案範例碼,連線方式為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