import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { GlobalService } from './../../../../service/global.service';
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
// export interface TreeNodeInterface {
// key: number;
// name: string;
// age?: number;
// level?: number;
// expand?: boolean;
// address?: string;
// children?: TreeNodeInterface[];
// parent?: TreeNodeInterface;
// }
//create by huang.l 2020.2.17 17.28
@Component({
selector: 'app-menu',
templateUrl: './menu.component.html',
styleUrls: ['./menu.component.less']
})
export class MenuComponent implements OnInit {
public pageSize: number = 15;
public page: number = 1;
public listOfMapData: any[];
public mapOfExpandedData: { [key: string]: any[] } = {};
public isVisible: boolean = false;
public codePrefix: string = '';
public rootId: string;
public parentMenu: any;
public currentArray: any[];
validateForm: FormGroup;
constructor(
private globalService: GlobalService,
private fb: FormBuilder,
) {
}
async collapse(array: any[], data: any, $event: boolean, rootId: string) {
if ($event === false) {
if (data.children) {
data.children.forEach(d => {
if (array) {
const target = array.find(a => a.id === d.id)!;
if (target) {//如果当前节点还存在
target.expand = false;
console.log(target.id);
this.collapse(array, target, false, rootId);
}
}
});
} else {
return;
}
} else {
if (data.children) {
return;
}
const submenus: any[] = await this.getChildren(data.id);
const submenuArray: any[] = [];
submenus.forEach(item => {
submenuArray.push({ ...item, level: data.level + 1, expand: false, haveChild: (item.haveChild > 0 ? true : false), parent: data });
});
let target: any = null;
const index = array.findIndex(item => item.id === data.id);
target = array[index];
if (target != null) {
target.children = submenuArray;
}
//顺序调整
// array = [...array, ...submenuArray];
array[index] = target;
array.splice(index + 1, 0, ...submenuArray);
// this.mapOfExpandedData[rootId] = array;
}
}
async ngOnInit() {
this.validateForm = this.fb.group({
id: [null],
parentId: [null],
parentName: [null],
name: [null, [Validators.required, Validators.maxLength(20)]],
code: [null, [Validators.required, Validators.maxLength(20), Validators.pattern('^[A-Za-z0-9]+$')]],
router: [null],
orderNum: [10, [Validators.required]],
icon: [null],
leaf: ['0'],
visible: ['1'],
openNewPage: ['0']
});
const menus: any[] = await this.getChildren(null);
this.listOfMapData = menus;
this.reload();
}
reload(): void {
this.listOfMapData.forEach(item => {
this.mapOfExpandedData[item.id] = this.convertTreeToList(item);
});
}
add(): void {
this.isVisible = true;
this.setParentWithRoot();
}
convertTreeToList(root: any): any[] {
const stack: any[] = [];
const array: any[] = [];
const hashMap = {};
stack.push({ ...root, level: 0, expand: false, haveChild: (root.haveChild > 0 ? true : false) });
while (stack.length !== 0) {
const node = stack.pop()!;
this.visitNode(node, hashMap, array);
if (node.children) {
for (let i = node.children.length - 1; i >= 0; i--) {
stack.push({ ...node.children[i], level: node.level! + 1, expand: false, parent: node });
}
}
}
return array;
}
visitNode(node: any, hashMap: { [id: string]: boolean }, array: any[]): void {
if (!hashMap[node.id]) {
hashMap[node.id] = true;
array.push(node);
}
}
async getChildren(parentId: string) {
let parentIdCondition: string;
if (parentId === '' || parentId === null || parentId === undefined) {
parentIdCondition = 'parentId=';
} else {
parentIdCondition = 'parentId=' + parentId;
this.page = 1;
this.pageSize = 9999;
}
const url = `api/menu/list/by/parentId?page=${this.page}&pageSize=${this.pageSize}&${parentIdCondition}`;
try {
const res: any = await this.globalService.axios.get(url);
return res.data.list;
} catch (e) {
alert(JSON.stringify(e));
}
}
async submitForm() {
for (const i in this.validateForm.controls) {
this.validateForm.controls[i].markAsDirty();
this.validateForm.controls[i].updateValueAndValidity();
}
if (this.validateForm.valid) {
let submitData: any = this.validateForm.getRawValue();
submitData = { ...submitData, code: (this.codePrefix + submitData.code) };
const res = await this.globalService.axios.post('api/menu/edit', submitData);
if (res.data.success === true) {
this.isVisible = false;
this.validateForm.reset();
//刷新表格数据
const parentId = submitData.parentId;
//新增根目录数据
let editMenu: any = res.data.item;
if (parentId === null || parentId === '' || parentId === undefined) {
this.listOfMapData = [editMenu, ...this.listOfMapData];
this.listOfMapData.forEach(item => {
if (!this.mapOfExpandedData[item.id]) {
this.mapOfExpandedData[item.id] = this.convertTreeToList(item);
}
});
} else {
//新增子目录数据
editMenu = { ...editMenu, haveChild: false, level: (this.parentMenu.level! + 1), expand: false, parent: this.parentMenu };
const index = this.currentArray.findIndex((item) => item.id === this.parentMenu.id);
//设置父节点包含子节点
//当节点未打开时,打开节点!!这句很重要
if(!this.parentMenu.expand){
this.parentMenu = { ...this.currentArray[index], haveChild: true, expand: true };
}
const chidlren: any[] = this.parentMenu['children'] ? this.parentMenu['children'] : [];
chidlren.push(editMenu);
this.parentMenu['children'] = chidlren;
editMenu = { ...editMenu, parent: this.parentMenu };
this.currentArray.splice(index + 1, 0, editMenu);
this.currentArray[index] = this.parentMenu;
// this.mapOfExpandedData[this.rootId] = array;
}
this.globalService.showSuccess();
} else {
this.globalService.showFail();
}
}
}
handleCancelMiddle(): any {
this.isVisible = false;
this.validateForm.reset();
}
getParent(): void {
return;
}
setParentWithRoot(): void {
this.validateForm.controls['parentId'].reset();
this.validateForm.controls['parentName'].setValue('/');
}
setCode(code): void {
this.validateForm.controls['code'].setValue(code);
}
addSubMenu(menu: any, array: any[], rootId: string): void {
this.isVisible = true;
this.validateForm.controls['parentName'].setValue(menu.name);
this.validateForm.controls['parentId'].setValue(menu.id);
this.codePrefix = menu.code + ':';
this.rootId = rootId;
this.parentMenu = menu;
this.currentArray = array;
}
async deleteMenu(menu, rootId) {
const res = await this.globalService.axios.get('api/menu/delete/' + menu.id);
if (res.data.success === true) {
//刷新表格
if (menu.id === rootId) {
delete this.mapOfExpandedData[rootId];
//
const rootIndex = this.listOfMapData.findIndex(item => item.id === rootId);
this.listOfMapData.splice(rootIndex, 1);//清除列表中的数据
} else {
const array = this.mapOfExpandedData[rootId];
const index = array.findIndex((item) => item.id === menu.id);
array.splice(index, 1);
const parentIndex: number = array.findIndex(item => item.id === menu.parentId);
let thizParent: any = array[parentIndex];
const parentChildrenIndex = thizParent.children.findIndex(item => item.id === menu.id);
thizParent.children.splice(parentChildrenIndex, 1);
if (thizParent.children.length <= 0) {//设置父节点的可删除条件
thizParent = { ...thizParent, haveChild: false };
}
array[parentIndex] = thizParent;
this.mapOfExpandedData[rootId] = array;
//当所有子节点删完之后,更改唯一父节点的是否有子节点状态,让其变得可以删除
// if (array.length === 1) {
// this.mapOfExpandedData[rootId] = [{ ...array[0], haveChild: false }];
// }
}
this.globalService.showSuccess();
} else {
this.globalService.showFail();
}
}
editMenu(menu): void {
const menuCode: string = menu.code;
this.codePrefix = menuCode.substr(0, menuCode.lastIndexOf(':'));
this.isVisible = true;
if (menu['parent']) {
this.validateForm.controls['parentName'].setValue(menu.parent.name);
this.validateForm.controls['parentId'].setValue(menu.parent.id);
} else {
this.validateForm.controls['parentName'].setValue('/');
}
const menuArray: string[] = menuCode.split(':');
menu = {
...menu, leaf: menu.leaf + '', visible: menu.visible + '',
openNewPage: menu['openNewPage'] ? menu.openNewPage + '' : '0',
code: menuArray[menuArray.length - 1]
};
this.validateForm.patchValue(menu);
}
}
1,以上是组件代码。
<nz-page-header>
<!-- <nz-page-header-title>Title</nz-page-header-title>
<nz-page-header-subtitle>This is a subtitle</nz-page-header-subtitle> -->
<nz-page-header-extra>
<button nz-button (click)="add($event)">添加根目录</button>
<button nz-button>Operation</button>
<button nz-button nzType="primary">Primary</button>
</nz-page-header-extra>
<nz-page-header-content>
<!-- <div class="content">
<div class="main">
<nz-descriptions [nzColumn]="2">
<nz-descriptions-item nzTitle="Created" [nzSpan]="1">Lili Qu</nz-descriptions-item>
<nz-descriptions-item nzTitle="Association" [nzSpan]="1"><a>421421</a></nz-descriptions-item>
<nz-descriptions-item nzTitle="Creation Time" [nzSpan]="1">2017-01-10</nz-descriptions-item>
<nz-descriptions-item nzTitle="Effective Time" [nzSpan]="1">2017-10-10</nz-descriptions-item>
<nz-descriptions-item nzTitle="Remarks" [nzSpan]="2">
Gonghu Road, Xihu District, Hangzhou, Zhejiang, China
</nz-descriptions-item>
</nz-descriptions>
</div>
<div class="extra">
<div>
<nz-statistic nzTitle="Status" nzValue="Pending"></nz-statistic>
<nz-statistic nzTitle="Price" [nzValue]="568.08" nzPrefix="$" style="margin: 0 32px">
</nz-statistic>
</div>
</div>
</div> -->
</nz-page-header-content>
<nz-page-header-footer>
</nz-page-header-footer>
</nz-page-header>
<nz-table #expandTable [nzData]="listOfMapData" [nzScroll]="{x:'150',y: '800px' }">
<thead>
<tr>
<th nzWidth="30%">名称</th>
<th nzWidth="20%">编码</th>
<th>菜单图标</th>
<th>是否显示</th>
<th>排序</th>
<th nzWidth="20%">操作</th>
</tr>
</thead>
<tbody>
<ng-container *ngFor="let data of expandTable.data">
<ng-container *ngFor="let item of mapOfExpandedData[data.id]">
<tr *ngIf="(item.parent && item.parent.expand) || !item.parent">
<td [nzIndentSize]="item.level * 20" [nzShowExpand]="!!item.haveChild" [(nzExpand)]="item.expand" (nzExpandChange)="collapse(mapOfExpandedData[data.id], item, $event,data.id)">
{{ item.name }}
</td>
<td>
{{item.id}}
</td>
<!-- <td>
{{item.icon}}
</td>
<td>
{{item.visible === 1?'是':'否'}}
</td>
<td>
{{item.orderNum}}
</td> -->
<td>
<a *ngIf="((item.leaf === 0 && item.expand) || (item.leaf === 0 && !item.haveChild))" (click)="addSubMenu(item,mapOfExpandedData[data.id],data.id)">添加子菜单</a>
<a (click)="editMenu(item)">编辑</a>
<a *ngIf="item.haveChild === false" nz-popconfirm nzPopconfirmTitle="确定要删除?" nzOkText="是" nzCancelText="否" (nzOnConfirm)="deleteMenu(item,data.id)">删除</a>
</td>
</tr>
</ng-container>
</ng-container>
</tbody>
</nz-table>
<nz-modal nzWidth="800" [(nzVisible)]="isVisible" nzTitle="添加菜单" (nzOnCancel)="handleCancelMiddle()" (nzOnOk)="submitForm()">
<form nz-form [formGroup]="validateForm" nzLayout="vertical" (ngSubmit)="submitForm()">
<nz-form-item>
<nz-form-label nzFor="parentName" nzRequired nzSpan="4">父节点</nz-form-label>
<nz-form-control nzSpan="15">
<div nz-row nzGutter="24">
<div nz-col nzSpan="14">
<input nz-input formControlName="parentName" id="parentName" [disabled]="true" />
<input type="hidden" formControlName="parentId" id="parentId" />
<input type="hidden" formControlName="id" id="id" />
</div>
<div nz-col nzSpan="5">
<button nz-button (click)="getParent()" type="button">选择父节点</button>
</div>
<div nz-col nzSpan="5">
<button nz-button (click)="setParentWithRoot()" type="button">设置根目录</button>
</div>
</div>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="name" nzRequired nzSpan="4">名称</nz-form-label>
<nz-form-control [nzErrorTip]="errorTpl" nzSpan="15">
<input nz-input id="name" formControlName="name" placeholder="名称" />
<ng-template #errorTpl let-control>
<ng-container *ngIf="control.hasError('required')">
请输入代码
</ng-container>
<ng-container *ngIf="control.hasError('maxlength')">
长度不能超过20
</ng-container>
</ng-template>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="code" nzRequired nzSpan="4">代码</nz-form-label>
<nz-form-control [nzErrorTip]="errorTpl" nzSpan="15">
<div nz-row nzGutter="24">
<div nz-col nzSpan="14">
<nz-input-group [nzAddOnBefore]="codePrefix">
<input nz-input formControlName="code" id="code" placeholder="代码" />
<ng-template #errorTpl let-control>
<ng-container *ngIf="control.hasError('required')">
请输入代码
</ng-container>
<ng-container *ngIf="control.hasError('maxlength')">
长度不能超过20
</ng-container>
<ng-container *ngIf="control.hasError('pattern')">
只能输入字母和数字
</ng-container>
</ng-template>
</nz-input-group>
</div>
<div nz-col nzSpan="10">
<button nz-button nz-dropdown [nzDropdownMenu]="menu" type="button">预设<i nz-icon nzType="down"></i></button>
<nz-dropdown-menu #menu="nzDropdownMenu">
<ul nz-menu>
<li nz-menu-item>
<a (click)="setCode('add')">新增</a>
</li>
<li nz-menu-item>
<a (click)="setCode('edit')">编辑</a>
</li>
<li nz-menu-item>
<a (click)="setCode('delete')">删除</a>
</li>
<li nz-menu-item>
<a (click)="setCode('query')">查询</a>
</li>
<li nz-menu-item>
<a (click)="setCode('detail')">查看详情</a>
</li>
<li nz-menu-item>
<a (click)="setCode('')">其他(手动输入)</a>
</li>
</ul>
</nz-dropdown-menu>
</div>
</div>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="orderNum" nzRequired nzSpan="4">排序码</nz-form-label>
<nz-form-control nzErrorTip="请输入排序码" nzSpan="15">
<nz-input-number id="orderNum" formControlName="orderNum" placeholder="排序码" [nzMin]="0" [nzMax]="9999" nzStep="10"></nz-input-number>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="router" nzSpan="4">路由</nz-form-label>
<nz-form-control nzErrorTip="请输入路由地址" nzSpan="15">
<input nz-input id="router" formControlName="router" placeholder="路由" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="icon" nzSpan="4">图标名称</nz-form-label>
<nz-form-control nzErrorTip="请输入图标名称!" nzSpan="15">
<input nz-input id="icon" formControlName="icon" placeholder="图标名称" />
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="leaf" nzRequired nzSpan="4">是否叶子节点</nz-form-label>
<nz-form-control nzErrorTip="leaf" nzSpan="15">
<nz-radio-group formControlName="leaf">
<label nz-radio nzValue="1">是</label>
<label nz-radio nzValue="0">否</label>
</nz-radio-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="visible" nzRequired nzSpan="4">是否可见</nz-form-label>
<nz-form-control nzErrorTip="是否可见" nzSpan="15">
<nz-radio-group formControlName="visible">
<label nz-radio nzValue="1">是</label>
<label nz-radio nzValue="0">否</label>
</nz-radio-group>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label nzFor="openNewPage" nzRequired nzSpan="4">是否打开新页面</nz-form-label>
<nz-form-control nzErrorTip="是否打开新页面" nzSpan="15">
<nz-radio-group formControlName="openNewPage">
<label nz-radio nzValue="1">是</label>
<label nz-radio nzValue="0">否</label>
</nz-radio-group>
</nz-form-control>
</nz-form-item>
</form>
</nz-modal>
2,以上是html代码
3,环境版本
4,描述
整体思路在antd官方文档中map数据的格式,围绕着该格式修改数据即可。public mapOfExpandedData: { [key: string]: any[] } = {};
其他的可自由发挥。另ajax框架我用的是axios,以前搞react的时候用惯了。关于后面我业务上的模块会继续完成,框架调好后迫不及待的发个博客。
5,捣鼓这个完全是因为武汉肺炎期间闲的蛋疼。
6,效果视频__
https://v.youku.com/v_show/id_XNDU1MTc1NTYxMg==.html
Google Chrome 2020