|
|
@@ -0,0 +1,550 @@
|
|
|
+import { NodeContext } from './nodeCtx';
|
|
|
+
|
|
|
+export interface TreeRaw {
|
|
|
+ ID: string;
|
|
|
+ parentID: string;
|
|
|
+ seq: number;
|
|
|
+ [propName: 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[];
|
|
|
+
|
|
|
+ readonly rootID: string = '-1';
|
|
|
+
|
|
|
+ // 默认初始顺序号。在对树结构进行操作后,没必要保证seq连号,只需保证正确排序就行,以减少需要更新的节点。
|
|
|
+ readonly seqStartIndex = 0;
|
|
|
+
|
|
|
+ // ID与原始数据条目映射
|
|
|
+ IDMap: TreeIDMap;
|
|
|
+
|
|
|
+ // parentID与多条同parentID的原始数据条目映射,parentMap内部数组要始终保持正确排序
|
|
|
+ parentMap: TreeParentMap;
|
|
|
+
|
|
|
+ // 节点上下文与节点ID映射
|
|
|
+ ctxMap: CtxIDMap;
|
|
|
+
|
|
|
+ constructor(rawData: TreeRaw[]) {
|
|
|
+ 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[];
|
|
|
+ }
|
|
|
+ /* genNodeContext(rawData: TreeRaw[]): TreeNode[] {
|
|
|
+ rawData.forEach(item => {
|
|
|
+ item.getCtx() = new NodeContext(item as TreeNode, this);
|
|
|
+ });
|
|
|
+ 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节点的子节点
|
|
|
+ findChilren(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.findChilren(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);
|
|
|
+ });
|
|
|
+ Object.entries(insertParentMap).forEach(([parentID, insertItems]) => {
|
|
|
+ const items = this.parentMap[parentID];
|
|
|
+ if (items) {
|
|
|
+ insertParentMap[parentID].push(...items);
|
|
|
+ }
|
|
|
+ // 重新排序
|
|
|
+ const combineItems = insertParentMap[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);
|
|
|
+ /* nodes.forEach(node => {
|
|
|
+ this.IDMap[node.ID] = node;
|
|
|
+ (
|
|
|
+ this.parentMap[node.parentID] || (this.parentMap[node.parentID] = [])
|
|
|
+ ).push(node);
|
|
|
+ }); */
|
|
|
+ this.rawData.push(...nodes);
|
|
|
+ // 排序
|
|
|
+ this.reSortData(nodes);
|
|
|
+ return this.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 准备删除,返回所有需要删除的节点,包括嵌套节点
|
|
|
+ prepareDelete(deleteNodes: TreeNode[]): TreeNode[] {
|
|
|
+ return this.getNodesPosterity(deleteNodes);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除节点
|
|
|
+ * @param {TreeNode[]} treeNodes - 要删除的节点,不需要包含嵌套节点
|
|
|
+ * @return {TreeNode[]} 返回删除节点后,树的data
|
|
|
+ */
|
|
|
+ delete(treeNodes: TreeNode[]): TreeNode[] {
|
|
|
+ const allDeletedNodes: TreeNode[] = [];
|
|
|
+ // 递归删除节点
|
|
|
+ const deleteNodes = (nodes: TreeNode[]): void => {
|
|
|
+ // 删除映射、删除数据
|
|
|
+ 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) {
|
|
|
+ nodesInParentMap.splice(nIndex, 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const index = this.rawData.indexOf(node);
|
|
|
+ if (index >= 0) {
|
|
|
+ this.rawData.splice(index, 1);
|
|
|
+ }
|
|
|
+ if (children && children.length) {
|
|
|
+ deleteNodes(children);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+ deleteNodes(treeNodes);
|
|
|
+ // 排序
|
|
|
+ this.reSortData(allDeletedNodes);
|
|
|
+ return this.data;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 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();
|
|
|
+ lastNodeNextBrothers.forEach((node, index) => {
|
|
|
+ updateData.push({
|
|
|
+ ID: node.ID,
|
|
|
+ update: { parentID: lastNode.ID, seq: index },
|
|
|
+ });
|
|
|
+ });
|
|
|
+ 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);
|
|
|
+ }
|
|
|
+}
|