開始實作權限管理的功能模塊。
Dialog
新增管理者子組件
查看管理者-src
|-app
|-cms
|-admin
|-list
|-insert
|-dialog-insert.component.html
|-dialog-insert.component.css
|-dialog-insert.component.ts
|-power
|-power.component.html
|-power.component.css
|-power.component.ts
|-power-main
|-power-main.component.html
|-power-main.component.css
|-power-main.component.ts
|-list.component.html
|-list.component.css
|-list.component.ts
|-admin-routing.module.ts
|-admin.component.ts
|-admin.module.ts
因Demo的規模較小,Menu只有大選單沒有次選單。
但路由上的設計基本上還是會預設未來有可能會增加次選單的方向。
跟UserModule不同的是,最外層會有一個admin.component.ts
,
因為內層可能有多個次選單,
但是Demo沒有次選單,所以我們內層只有一個(list
)。
//admin.component.ts
@Component({
selector: "app-admin",
template: `
<router-outlet></router-outlet>
`
})
export class AdminComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
list/list.component.ts
:export class AdminListComponent implements OnInit, AfterViewInit, OnDestroy {
ACCOUNTSTATUS = ACCOUNTSTATUS;
isLoadingToggle = true; //讀取list的特效
tab: ITabBase; //此頁面元素資料
result: IAdmin[]; //顯示在畫面的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();
}
});
}
/*更新管理者的相關Function */
...
/*初始 */
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("admins");
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 = <IAdmin[]>data.res;
this.setLoadingDatas(isDataInit)
}
}
});
}
/*儲存storage */
setLoadingDatas(isDataInit = false) {
if (!isDataInit) {
this.tabService.nextTabMain(<ITabMain>{
request: 'update',
content: this.tab
});
}
}
/*新增Admin的相關 Dialog Function */
...
/*查看Admin的相關 子組件 Function */
...
/*打開 alert-dialog 提示視窗 */
openStatusDialog(errorcode: number) {
let dialogRef = this.dialog.open(DialogAlertComponent, {
width: "250px",
data: {
errorcode: errorcode
}
});
dialogRef.afterClosed().subscribe(() => {
this.setDatas();
});
}
}
新增/更新完資料通常會出現 dialog 提示視窗,
所以重裝畫面的時機就會放在openStatusDialog()
裡。
每次重新裝畫面資料時,代表頁面元素會有變動,
所以最後一定要送storage暫存,
這樣重新整理後才會長得一模一樣。
幾乎所有功能模塊的list流程一模一樣,
所以後續不會再贅述了。
其實是在列表中點兩下即可修改,
點空白處後完成修改。
能修改的值只有名字跟狀態。
list/list.component.ts
:@Component({
selector: "app-admin-list",
templateUrl: "./list.component.html",
styleUrls: ["./list.component.css"]
})
export class AdminListComponent implements OnInit, AfterViewInit, OnDestroy {
...
subscriptionClick: Subscription;
clickToggle = new ClickToggle(
0,
"",
["change-status", "change-name"]
);
inputVal = new FormControl("", [Validators.required]);
constructor(
public dialog: MatDialog,
private route: ActivatedRoute,
private dataService: DataService,
private userService: UserService,
private tabService:TabService
) {}
ngOnInit() {
...
}
ngOnDestroy() {
if (!!this.subscriptionClick) {
this.subscriptionClick.unsubscribe();
}
}
ngAfterViewInit() {
this.subscriptionClick = merge(
observableFromEvent(document, "click"),
observableFromEvent(document, "touchstart")
).subscribe((e: any) => {
let isClick = this.clickToggle.atrTag.find(item => {
if (!!e.srcElement.attributes.getNamedItem(item)) {
let v = e.srcElement.attributes.getNamedItem(item);
this.clickToggle.atrId = parseInt(v.nodeValue);
this.clickToggle.atrTagSel = item;
return true;
}
});
if (!isClick) {
if (!this.inputVal || !this.inputVal.valid) {
this.clickReset();
return;
}
//inputName exist
if (!!this.inputVal && this.inputVal.valid) {
this.saveClick(this.inputVal.value);
this.clickReset();
}
}
});
}
clickReset() {
this.clickToggle.reset();
this.inputVal.patchValue("");
}
saveClick(val: string) {
switch (this.clickToggle.atrTagSel) {
case "change-status":
this.saveStatus(this.clickToggle.atrId, val);
break;
case "change-name":
this.saveName(this.clickToggle.atrId, val);
break;
}
}
saveStatus(id: number, val: string) {
let url = this.dataService.setUrl("admins", null, id);
this.dataService.updateOne(url, <IAdmin>{ status: +val })
.subscribe((data: IData) => {
if (!!data.errorcode) {
this.openStatusDialog(data.errorcode);
} else {
this.setDatas();
}
});
}
saveName(id: number, val: string) {
let url = this.dataService.setUrl("admins", null, id);
this.dataService.updateOne(url, { name: val })
.subscribe((data: IData) => {
if (!!data.errorcode) {
this.openStatusDialog(data.errorcode);
} else {
this.setDatas();
}
});
}
...
}
上述過程簡單來說
當點擊的時候 => 判斷點擊的位置,
如果點擊是點在需要修改的欄位,
則會自動變成 input。
當點擊不是需要修改的地方,
則會判斷input有沒有值。
有值就驗證 => 驗證成功後更新資料,
沒值就reset。
--
list/list.component.html
: <td [attr.data-title]="'name' | translate">
<span
*ngIf="!(clickToggle.atrId == r.id && clickToggle.atrTagSel == 'change-name')"
[attr.change-name]="r.id"
>
{{ r.name }}
</span>
<div *ngIf="clickToggle.atrId == r.id && clickToggle.atrTagSel == 'change-name'">
<div class="flex center">
<input [attr.change-name]="r.id" [formControl]="inputVal"
cusAutofocus />
<mat-icon
*ngIf="!inputVal.valid"
(click)="clickReset()"
svgIcon="cancel"
style="margin-left:2px;"
></mat-icon>
</div>
<validation-messages [control]="inputVal"></validation-messages>
</div>
</td>
<td [attr.data-title]="'status' | translate">
<span
*ngIf="!(clickToggle.atrId == r.id && clickToggle.atrTagSel == 'change-status')"
[attr.change-status]="r.id"
>
{{ r.status | pipetag: ACCOUNTSTATUS | translate }}
</span>
<div
*ngIf="clickToggle.atrId == r.id && clickToggle.atrTagSel == 'change-status'"
>
<div class="flex center">
<select
name="se_status"
id="se_status"
[formControl]="inputVal"
[attr.change-status]="r.id"
>
<option value="" disabled>{{ "select" | translate }}</option>
<option
*ngFor="let status of ACCOUNTSTATUS"
[ngValue]="status.id"
[selected]="status.id == r.status"
>
{{ status.name | translate }}
</option>
</select>
<mat-icon
*ngIf="!inputVal.valid"
(click)="clickReset()"
svgIcon="cancel"
style="margin-left:2px;"
></mat-icon>
</div>
<validation-messages [control]="inputVal"></validation-messages>
</div>
</td>
[attr.change-name]="r.id"
、[attr.change-status]="r.id"
在我們要開始做新增跟查看權限之前,
我們要有個思維:就是能夠複用的功能有哪些。
比如說這是新增的畫面:
這是查看權限畫面
有沒有發現這兩者有一塊地方長得相同,
就是權限區是相同的。
因此我們應該判定最小的零件複用就是權限區
,
然後在新增的時候用一個 dialog Component 把權限區包起來,
在查看的時候用一個component把權限區包起來,
並做一個可以修改的開關。
|-admin
|-list
|-insert
|-dialog-insert.component.html
|-dialog-insert.component.css
|-dialog-insert.component.ts
|-power
|-power.component.html
|-power.component.css
|-power.component.ts
|-power-main
|-power-main.component.html
|-power-main.component.css
|-power-main.component.ts
--
power.component.ts
:export class AdminPowerComponent implements OnInit {
@Input() toggleUpdate: string; //view, update, close-update, insert
@Input() holds: IHold[] = [];
powers: IPower[] = [];
constructor(private dataService: DataService) {}
ngOnInit() {
if (!!this.holds) {
this.initPowers();
}
}
initPowers() {
let url = this.dataService.setUrl(`powers`);
this.dataService.getData(url).subscribe((data: IData) => {
if (!data.errorcode && !!data.res) {
this.powers = <IPower[]>data.res;
this.powers.forEach((power: IPower) => {
power.check = false;
});
this.setPowers();
}
});
}
setPowers() {
for (let i = 0; i < this.powers.length; i++) {
for (let y = 0; y < this.holds.length; y++) {
if (this.holds[y].powerId === this.powers[i].id) {
this.powers[i].check = true;
}
}
}
}
getPowers(): IPower[] {
return this.powers;
}
}
--
power.component.html
:<mat-card *ngIf="!!powers">
<div class="flex straight-flex slides">
<div class="title">
{{ "power_setting" | translate }}
</div>
<ng-container *ngFor="let p of powers">
<mat-slide-toggle
[(ngModel)]="p.check"
[disabled]="toggleUpdate === 'view' || toggleUpdate === 'close-update'"
>
{{ p.name + "_manage" | translate }}
</mat-slide-toggle>
</ng-container>
</div>
</mat-card>
Dialog
新增管理者dialog-insert.component.ts
:export interface IDetailAdmin {
admin: IAdmin;
powers: IPower[];
}
@Component({
selector: "dialog-admin-insert",
templateUrl: "./dialog-insert.component.html",
styleUrls: ["./dialog-insert.component.css"]
})
export class DialogAdminInsertComponent implements OnInit {
@ViewChild(AdminPowerComponent,
{ static: false }) powerComp: AdminPowerComponent;
form: FormGroup;
constructor(
public dialogRef: MatDialogRef<DialogAdminInsertComponent>,
private fb: FormBuilder
) {}
ngOnInit() {
this.createForm();
}
createForm() {
let obj = {
account: [
"",
[
Validators.required,
Validators.minLength(6),
ValidationService.userValidator
]
],
password: [
"",
[
Validators.required,
Validators.minLength(6),
ValidationService.userValidator
]
],
name: ["", Validators.required]
};
this.form = this.fb.group(obj);
}
getDetailData(): IDetailAdmin {
let powers: IPower[] = [];
this.powerComp.getPowers().forEach((power: IPower) => {
if (power.check) {
powers.push(power);
}
});
let admin = <IAdmin>{
account: this.form.value.account,
password: this.form.value.password,
name: this.form.value.name,
status: 1
};
return <IDetailAdmin>{
admin: admin,
powers: powers
};
}
onNoClick(): void {
this.dialogRef.close();
}
onEnter() {
this.dialogRef.close(this.getDetailData());
}
}
--
dialog-insert.component.html
:<form [formGroup]="form">
<div mat-dialog-title class="flex center">
<mat-icon svgIcon="alert"></mat-icon>
<span> {{ "alert_admin_insert" | translate }} </span>
</div>
<div mat-dialog-content>
<div class="item-wrapper two pink">
<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">
<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>
<app-admin-power [toggleUpdate]="'insert'"></app-admin-power>
</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>
子組件
查看管理者listComponent.ts 打開子組件寫法請參照
day17 List常見問題(二)
list.component.html
:...
<tbody *ngIf="!!result && !!result.length">
<ng-container *ngFor="let r of result; let i = index">
...
<tr>
<td>
<button
(click)="onOpenChild('power', r)"
class="button blue pb"
[ngClass]="{
active:
childToggle.selectTag == 'power' &&
childToggle.selectId == r[childToggle.selectMarkID]
}"
>
{{ "view_power" | translate }}
</button>
</td>
</tr>
<tr
*ngIf="
childToggle.selectTag == 'power' &&
childToggle.selectId == r[childToggle.selectMarkID]
"
>
<td colspan="6" class="skin">
<app-admin-power-main [adminId]="r.id"></app-admin-power-main>
</td>
</tr>
</ng-container>
</tbody>
--
power-main.component.ts
:透過父組件list.component.ts
送來的 adminId,
來跟 Database 要此管理者的權限holds
。
export class AdminPowerMainComponent implements OnInit {
@ViewChild(AdminPowerComponent, { static: false }) powerComp: AdminPowerComponent;
@Input() adminId: number = 0;
toggleUpdate: string = "close-update";
admin: IAdmin = null;
constructor(
public dialog: MatDialog,
private dataService: DataService
) {}
ngOnInit() {
if (!!this.adminId) {
this.init();
}
}
init() {
let url = this.dataService.setUrl(
`admins`,
[{ key: "_embed", val: "holds" }],
this.adminId
);
this.dataService.getData(url).subscribe((data: IData) => {
if (!data.errorcode && !!data.res) {
this.admin = <IAdmin>data.res;
}
});
}
onSave() {
this.resetHold();
}
/* 新增管理者權限*/
resetHold() {
if (!!this.admin.holds && !!this.admin.holds.length) {
this.runDelHold(0,this.admin.holds,[])
} else {
this.runInsertHolds(0, this.powerComp.getPowers(), []);
}
}
/*
舊有的管理者權限要先全部刪除,
此為一筆一筆刪,所以是遞迴迴圈。
*/
runDelHold(index: number, holds: IHold[], errHolds: IHold[]) {
let errHoldArr = errHolds;
let leng = holds.length;
if (index < leng) {
this.delHold(index, holds, errHolds);
} else {
if (!!errHoldArr.length) {
this.openStatusDialog(5);
} else {
this.runInsertHolds(0, this.powerComp.getPowers(), []);
}
}
}
/*
新增的管理者權限要一筆一筆加,所以是遞迴迴圈。
*/
runInsertHolds(index: number, powers: IPower[], errHolds: IPower[]) {
let errHoldArr = errHolds;
let leng = powers.length;
if (index < leng) {
this.insertHolds(index, powers, errHolds);
} else {
if (!!errHoldArr.length) {
this.openStatusDialog(5);
} else {
this.toggleUpdate = "close-update";
this.init();
}
}
}
insertHolds(index: number, powers: IPower[], errHolds: IPower[]) {
if (powers[index].check) {
let obj: IHold = {
adminId: this.adminId,
powerId: powers[index].id
};
this.dataService.insertOne("holds", obj)
.subscribe((data: IData) => {
if (!!data.errorcode) {
errHolds.push(powers[index]);
} else {
this.runInsertHolds(++index, powers, errHolds);
}
});
}else{
this.runInsertHolds(++index, powers, errHolds);
}
}
openStatusDialog(errorcode: number) {
this.dialog.open(DialogAlertComponent, {
width: "250px",
data: {
errorcode: errorcode
}
});
}
}
不然還要判斷哪些已經是原本有的,
哪有原本有的被刪掉,哪些原本沒有要新增,
會太多判斷式!
--
power-main.component.html
:<div class="container" *ngIf="!!admin">
<div class="flex flex-end">
<button
class="button green pb"
*ngIf="toggleUpdate === 'close-update'"
(click)="toggleUpdate = 'update'"
>
{{ "update" | translate }}
</button>
<button class="button green pb" *ngIf="toggleUpdate === 'update'" (click)="onSave()">
{{ "save" | translate }}
</button>
</div>
<div>
<app-admin-power [holds]="admin.holds" [toggleUpdate]="toggleUpdate">
</app-admin-power>
</div>
</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