| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614 |
- import { NodeContext } from './nodeCtx';
- export interface TreeRaw {
- ID: string;
- parentID: string;
- seq: number;
- [props: string]: any;
- }
- export interface TreeNode extends TreeRaw {
- getCtx: () => NodeContext; // ctx对象改为getCtx(),因为handsontable数据循环引用会报错
- }
- export interface TreeIDMap {
- [propName: string]: TreeNode;
- }
- export interface CtxIDMap {
- [propName: string]: NodeContext;
- }
- export interface TreeParentMap {
- [propName: string]: TreeNode[];
- }
- export interface ParentMap {
- [propName: string]: (TreeRaw | TreeNode)[];
- }
- export interface UpdateItem {
- parentID?: string;
- seq?: number;
- [propName: string]: any;
- }
- export interface UpdateData {
- ID: string;
- update: UpdateItem;
- }
- export type None = null | undefined;
- export class Tree {
- // 原始数据,不经过排序的数据
- rawData: TreeNode[];
- // 按照树结构拼装好、排好序的数据。实际只是原始数据进行排序,内部元素跟原始数据内部元素的引用是一致的
- data: TreeNode[];
- rootID: string;
- // 默认初始顺序号。在对树结构进行操作后,没必要保证seq连号,只需保证正确排序就行,以减少需要更新的节点。
- readonly seqStartIndex = 0;
- // ID与原始数据条目映射
- IDMap: TreeIDMap;
- // parentID与多条同parentID的原始数据条目映射,parentMap内部数组要始终保持正确排序
- parentMap: TreeParentMap;
- // 节点上下文与节点ID映射
- ctxMap: CtxIDMap;
- constructor(rawData: TreeRaw[], rootID = '-1') {
- this.rootID = rootID;
- this.rawData = this.genNodeContext(rawData);
- this.rawData = Tree.sort(this.rawData);
- this.data = [];
- this.IDMap = {};
- this.parentMap = {};
- this.ctxMap = {};
- this.genMap(this.rawData);
- this.genData();
- }
- // 根据seq进行排序
- static sort<T extends TreeNode | TreeRaw>(nodes: T[]): T[] {
- return nodes.sort((a, b) => a.seq - b.seq);
- }
- // 获取节点的所有parentID
- static getParentIDList(nodes: TreeNode[]): Set<string> {
- const parentIDList: Set<string> = new Set();
- nodes.forEach(node => parentIDList.add(node.parentID));
- return parentIDList;
- }
- // 生成节点getCtx方法(由于handsontable循环引用bug,将ctx对象改为getCtx方法),用于获取节点上下文,挂载相关节点方法,TreeRaw转换为TreeNode
- private genNodeContext(rawData: TreeRaw[]): TreeNode[] {
- rawData.forEach(item => {
- item.getCtx = () => this.ctxMap[item.ID];
- });
- return rawData as TreeNode[];
- }
- // 生成映射表
- private genMap(data: TreeNode[]): void {
- data.forEach(item => {
- this.IDMap[item.ID] = item;
- this.ctxMap[item.ID] = new NodeContext(item, this);
- (
- this.parentMap[item.parentID] || (this.parentMap[item.parentID] = [])
- ).push(item);
- });
- }
- // 获取顶层原始数据
- getRoots(): TreeNode[] {
- return this.parentMap[this.rootID] || [];
- }
- // 生成按照树结构排好序的数据
- private genData(): void {
- // genData时不需要排序,因为rawData已经排好序
- const roots = this.getRoots();
- const pushNodesToData = (nodes: TreeNode[]): void => {
- nodes.forEach(node => {
- this.data.push(node);
- const children = this.parentMap[node.ID];
- if (children && children.length) {
- pushNodesToData(children);
- }
- });
- };
- pushNodesToData(roots);
- }
- // 重新生成排序序好的数据,不改变原引用
- private reGenData(): void {
- this.data.splice(0, this.data.length);
- this.genData();
- }
- // 将相关parentMap内的数据、data进行重新排序。新增、删除等操作进行完后,数据需要重新排序
- reSortData(nodes: TreeNode[]): void {
- const toSortList = Tree.getParentIDList(nodes);
- toSortList.forEach(parentID => {
- if (this.parentMap[parentID] && this.parentMap[parentID].length) {
- Tree.sort(this.parentMap[parentID]);
- }
- });
- this.reGenData();
- }
- // 根据parentID对parentMap进行重新排序,并重新排序生成data
- resortDataByID(parentIDList: string[]): void {
- parentIDList.forEach(parentID => {
- if (this.parentMap[parentID] && this.parentMap[parentID].length) {
- Tree.sort(this.parentMap[parentID]);
- }
- });
- this.reGenData();
- }
- // 查找ID节点
- find(ID: string): TreeNode | None {
- return this.IDMap[ID];
- }
- // 查找ID节点的父节点
- findParent(ID: string): TreeNode | None {
- const node = this.find(ID);
- if (!node) {
- return null;
- }
- return this.find(node.parentID);
- }
- // 查找ID节点的下一个节点
- findNext(ID: string): TreeNode | None {
- const node = this.find(ID);
- if (!node) {
- return null;
- }
- const nodes = this.parentMap[node.parentID];
- const nodeIndex = nodes.indexOf(node);
- if (nodeIndex < 0) {
- return null;
- }
- return nodes[nodeIndex + 1];
- }
- // 查找ID节点的上一个节点
- findPrev(ID: string): TreeNode | None {
- const node = this.find(ID);
- if (!node) {
- return null;
- }
- const nodes = this.parentMap[node.parentID];
- const nodeIndex = nodes.indexOf(node);
- if (nodeIndex < 0) {
- return null;
- }
- return nodes[nodeIndex - 1];
- }
- // 查询ID节点的子节点
- findChildren(ID: string): TreeNode[] {
- return this.parentMap[ID] || [];
- }
- updateValue(updateData: UpdateData[]): void {
- if (updateData.length) {
- updateData.forEach(updateItem => {
- const node = this.find(updateItem.ID);
- if (node) {
- Object.assign(node, updateItem.update);
- }
- });
- }
- }
- // 递归获取节点
- getNodesPosterity(treeNodes: TreeNode[], includeSelf = true): TreeNode[] {
- const rst: TreeNode[] = [];
- const pushNodes = (nodes: TreeNode[]): void => {
- nodes.forEach(node => {
- if (includeSelf || !treeNodes.includes(node)) {
- rst.push(node);
- }
- const children = this.findChildren(node.ID);
- if (children.length) {
- pushNodes(children);
- }
- });
- };
- pushNodes(treeNodes);
- return rst;
- }
- // 节选中点块是否是可操作的节点块,选中一批节点进行升降、上下移的时候可能需要用到这个判断。
- // 以第一个节点为基准,如果后续的节点深度均大于于于等于第一个节点的深度,且节点间是连续的,则此为可操作节点块
- isOperable(nodes: TreeNode[]): boolean {
- const baseDepth = nodes[0].getCtx().depth();
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
- const depth = node.getCtx().depth();
- if (depth < baseDepth) {
- return false;
- }
- const nextRowNode = nodes[i + 1];
- if (
- nextRowNode &&
- node.getCtx().row() + 1 !== nextRowNode.getCtx().row()
- ) {
- return false;
- }
- }
- return true;
- }
- // 从节点块中获取相同深度的节点
- sameDepthNodes(nodes: TreeNode[], depth: number | None): TreeNode[] {
- if (!depth) {
- depth = nodes[0].getCtx().depth();
- }
- return nodes.filter(node => node.getCtx().depth() === depth);
- }
- // 准备插入节点,插入节点前,计算出需要更新的数据。(可能会直接覆盖插入数据的seq)
- // 可调用完此方法后,将需要更新、插入的数据提交至数据库,成功响应后调用插入节点更新缓存的方法
- prepareInsert(rawData: TreeRaw[]): UpdateData[] {
- const updateData: UpdateData[] = [];
- const insertParentMap: ParentMap = {};
- // 将相同父项的插入数据和已存在数据进行合并
- rawData.forEach(item => {
- if (typeof item.seq === 'undefined') {
- item.seq = this.seqStartIndex;
- }
- (
- insertParentMap[item.parentID] || (insertParentMap[item.parentID] = [])
- ).push(item);
- });
- const combineMap: ParentMap = {};
- Object.entries(insertParentMap).forEach(([parentID, insertItems]) => {
- const items = this.parentMap[parentID];
- combineMap[parentID] = [...insertParentMap[parentID]];
- if (items) {
- combineMap[parentID].push(...items);
- }
- // 重新排序
- const combineItems = combineMap[parentID];
- Tree.sort(combineItems);
- combineItems.forEach((item, index) => {
- // 插入数据重新赋值
- if (insertItems.includes(item)) {
- item.seq = index;
- } else if (item.seq !== index) {
- // 需要更新的原数据
- updateData.push({
- ID: item.ID,
- update: {
- seq: index,
- },
- });
- }
- });
- });
- return updateData;
- }
- // 插入节点数据
- insert(items: TreeRaw[], updateData: UpdateData[] = []): TreeNode[] {
- // 更新需要更新的节点(一般为更新seq)
- this.updateValue(updateData);
- // 建立映射、插入数据
- const nodes = this.genNodeContext(items);
- this.genMap(nodes);
- this.rawData.push(...nodes);
- // 排序
- this.reSortData(nodes);
- return nodes;
- }
- // 准备删除,返回所有需要删除的节点,包括嵌套节点
- prepareDelete(deleteNodes: TreeNode[]): TreeNode[] {
- return this.getNodesPosterity(deleteNodes);
- }
- /**
- * 删除节点
- * @param {TreeNode[]} treeNodes - 要删除的节点,不需要包含嵌套节点
- * @return {TreeNode[]} 返回被删除的节点
- */
- delete(treeNodes: TreeNode[]): TreeNode[] {
- const allDeletedNodes: TreeNode[] = [];
- // 递归删除节点
- const deleteNodes = (nodes: TreeNode[]): void => {
- // 删除映射、删除数据
- const toDels: { nodes: TreeNode[]; delNode: TreeNode }[] = [];
- nodes.forEach(node => {
- allDeletedNodes.push(node);
- delete this.IDMap[node.ID];
- delete this.ctxMap[node.ID];
- const children = this.parentMap[node.ID];
- delete this.parentMap[node.ID];
- const nodesInParentMap = this.parentMap[node.parentID];
- if (nodesInParentMap && nodesInParentMap.length) {
- const nIndex = nodesInParentMap.indexOf(node);
- if (nIndex >= 0) {
- toDels.push({ nodes: nodesInParentMap, delNode: node });
- }
- }
- const index = this.rawData.indexOf(node);
- if (index >= 0) {
- this.rawData.splice(index, 1);
- }
- if (children && children.length) {
- deleteNodes(children);
- }
- });
- // 删除parentMap的数据
- toDels.forEach(delItem => {
- const delIndex = delItem.nodes.indexOf(delItem.delNode);
- if (delIndex >= 0) {
- delItem.nodes.splice(delIndex, 1);
- }
- });
- };
- deleteNodes(treeNodes);
- // 排序
- this.reSortData(allDeletedNodes);
- return allDeletedNodes;
- }
- // IDList 返回所有需要删除的节点,包括嵌套节点
- prepareDeleteByID(IDList: string[]): TreeNode[] {
- const deleteNodes: TreeNode[] = [];
- IDList.forEach(ID => {
- const node = this.find(ID);
- if (node) {
- deleteNodes.push(node);
- }
- });
- return this.prepareDelete(deleteNodes);
- }
- /**
- * 根据ID删除节点
- * @param {string[]} IDList - 要删除的节点的ID列表(不包含嵌套节点ID)
- * @return {TreeNode[]} - 返回被删除的所有节点
- */
- deleteByID(IDList: string[]): TreeNode[] {
- const deleteNodes: TreeNode[] = [];
- IDList.forEach(ID => {
- const node = this.find(ID);
- if (node) {
- deleteNodes.push(node);
- }
- });
- this.delete(deleteNodes);
- return deleteNodes;
- }
- // 准备上移节点块(连续的兄弟节点),注意节点的seq可能不连号
- prepareUpMove(nodes: TreeNode[]): UpdateData[] {
- const updateData: UpdateData[] = [];
- const firstNode = nodes[0];
- const firstNodePrev = this.findPrev(firstNode.ID);
- if (!firstNodePrev) {
- return [];
- }
- let tempSeq = firstNodePrev.seq;
- nodes.forEach(node => {
- const orgSeq = node.seq;
- updateData.push({
- ID: node.ID,
- update: { seq: tempSeq },
- });
- tempSeq = orgSeq;
- });
- updateData.push({
- ID: firstNodePrev.ID,
- update: { seq: tempSeq },
- });
- return updateData;
- }
- // 准备下移节点块(连续的兄弟节点),注意节点的seq可能不连号
- prepareDownMove(nodes: TreeNode[]): UpdateData[] {
- const updateData: UpdateData[] = [];
- const lastNode = nodes[nodes.length - 1];
- const lastNodeNext = this.findNext(lastNode.ID);
- if (!lastNodeNext) {
- return [];
- }
- let tempSeq = lastNodeNext.seq;
- for (let i = nodes.length - 1; i >= 0; i--) {
- const node = nodes[i];
- const orgSeq = node.seq;
- updateData.push({
- ID: node.ID,
- update: { seq: tempSeq },
- });
- tempSeq = orgSeq;
- }
- updateData.push({
- ID: lastNodeNext.ID,
- update: { seq: tempSeq },
- });
- return updateData;
- }
- // 上下移
- move(nodes: TreeNode[], updateData: UpdateData[]): void {
- this.updateValue(updateData);
- this.reSortData(nodes);
- }
- // 准备升级节点块(连续的兄弟节点),不维护seq连号
- prepareUpLevel(nodes: TreeNode[]): UpdateData[] {
- const updateData: UpdateData[] = [];
- const firstNode = nodes[0];
- const lastNode = nodes[nodes.length - 1];
- const parent = this.findParent(firstNode.ID);
- if (!parent) {
- return [];
- }
- const baseSeq = parent.seq + 1;
- nodes.forEach((node, index) => {
- updateData.push({
- ID: node.ID,
- update: { parentID: parent.parentID, seq: baseSeq + index },
- });
- });
- const parentNextBrothers = parent.getCtx().nextBrothers();
- // 因为seq可能是不连号的,如果上移的最末节点seq,小于下一节点的seq,那就不更新所有下兄弟节点的seq,减少更新的数据量
- const lastNodeCurSeq = updateData[updateData.length - 1].update.seq;
- const firstBrohter = parentNextBrothers[0];
- if (lastNodeCurSeq && lastNodeCurSeq >= firstBrohter.seq) {
- parentNextBrothers.forEach((node, index) => {
- updateData.push({
- ID: node.ID,
- update: { seq: baseSeq + index + nodes.length },
- });
- });
- }
- // 最末节点的所有后兄弟节点,成为最末节点的子节点
- const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
- const lastNodeLastChild = lastNode.getCtx().lastChild();
- const lastNodeLastChildSeq = lastNodeLastChild ? lastNodeLastChild.seq : 0;
- lastNodeNextBrothers.forEach((node, index) => {
- updateData.push({
- ID: node.ID,
- update: { parentID: lastNode.ID, seq: index + lastNodeLastChildSeq },
- });
- });
- return updateData;
- }
- upLevel(nodes: TreeNode[], updateData: UpdateData[]): void {
- const firstNode = nodes[0];
- const lastNode = nodes[nodes.length - 1];
- if (!firstNode.getCtx().canUpLevel()) {
- return;
- }
- const orgParentID = firstNode.parentID;
- const orgBrothers = this.parentMap[orgParentID];
- const lastNodeNextBrothers = lastNode.getCtx().nextBrothers();
- orgBrothers.splice(
- orgBrothers.indexOf(firstNode),
- nodes.length + lastNodeNextBrothers.length
- );
- (this.parentMap[lastNode.ID] || (this.parentMap[lastNode.ID] = [])).push(
- ...lastNodeNextBrothers
- );
- this.updateValue(updateData);
- const newParentID = firstNode.parentID;
- this.parentMap[newParentID].push(...nodes);
- this.resortDataByID([orgParentID, newParentID, lastNode.ID]);
- }
- // 准备降级节点块(连续的兄弟节点),不维护seq连号
- prepareDownLevel(nodes: TreeNode[]): UpdateData[] {
- const updateData: UpdateData[] = [];
- const firstNode = nodes[0];
- const prevNode = this.findPrev(firstNode.ID);
- if (!prevNode) {
- return [];
- }
- // 节点块成为前节点的子节点
- const prevNodeLastChild = prevNode.getCtx().lastChild();
- const baseSeq = prevNodeLastChild
- ? prevNodeLastChild.seq + 1
- : this.seqStartIndex;
- nodes.forEach((node, index) => {
- updateData.push({
- ID: node.ID,
- update: { parentID: prevNode.ID, seq: baseSeq + index },
- });
- });
- return updateData;
- }
- downLevel(nodes: TreeNode[], updateData: UpdateData[]): void {
- const firstNode = nodes[0];
- if (!firstNode.getCtx().canDownLevel()) {
- return;
- }
- const prevNode = this.findPrev(firstNode.ID);
- if (!prevNode) {
- return;
- }
- const orgBrothers = this.parentMap[firstNode.parentID];
- orgBrothers.splice(orgBrothers.indexOf(firstNode), nodes.length);
- (this.parentMap[prevNode.ID] || (this.parentMap[prevNode.ID] = [])).push(
- ...nodes
- );
- this.updateValue(updateData);
- this.reGenData();
- }
- // 准备移动节点块(连续的兄弟节点),不维护seq连号
- prepareMoveTo(
- nodes: TreeNode[],
- parent: TreeNode | None,
- next: TreeNode | None
- ): UpdateData[] {
- const updateData: UpdateData[] = [];
- let prev: TreeNode | None;
- if (next) {
- prev = next.getCtx().prev();
- } else {
- prev = parent ? parent.getCtx().lastChild() : null;
- }
- const baseSeq = prev ? prev.seq + 1 : this.seqStartIndex;
- updateData.push(
- ...nodes.map((node, index) => ({
- ID: node.ID,
- update: {
- parentID: (parent && parent.ID) || this.rootID,
- seq: baseSeq + index,
- },
- }))
- );
- const curBaseSeq = baseSeq + nodes.length;
- const nextBrothers = prev ? prev.getCtx().nextBrothers() : [];
- updateData.push(
- ...nextBrothers.map((node, index) => ({
- ID: node.ID,
- update: {
- seq: curBaseSeq + index,
- },
- }))
- );
- return updateData;
- }
- moveTo(
- nodes: TreeNode[],
- parent: TreeNode | None,
- updateData: UpdateData[]
- ): void {
- const firstNode = nodes[0];
- const newParentID = parent ? parent.ID : this.rootID;
- const orgParentID = firstNode.parentID;
- const orgBrothers = this.parentMap[orgParentID];
- orgBrothers.splice(orgBrothers.indexOf(firstNode), nodes.length);
- (this.parentMap[newParentID] || (this.parentMap[newParentID] = [])).push(
- ...nodes
- );
- this.updateValue(updateData);
- this.resortDataByID([orgParentID, newParentID]);
- }
- // 清除所有节点(不变更data引用)
- clear(): void {
- this.rawData.splice(0, this.rawData.length);
- this.data.splice(0, this.data.length);
- Object.keys(this.parentMap).forEach(key => delete this.parentMap[key]);
- Object.keys(this.IDMap).forEach(key => delete this.IDMap[key]);
- Object.keys(this.ctxMap).forEach(key => delete this.ctxMap[key]);
- }
- }
|