antv/x6自定义节点+小地图+复制/删除节点+拖拽生成节点

1、 下载x6:

npm i @antv/x6@2.17.1

 同步下载所需要用到的x6插件:

"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包

"@antv/x6-plugin-history": "^2.0.0", // 如果使用撤销重做功能,需要安装此包

"@antv/x6-plugin-keyboard": "^2.0.0", // 如果使用快捷键功能,需要安装此包

"@antv/x6-plugin-minimap": "^2.0.0", // 如果使用小地图功能,需要安装此包

"@antv/x6-plugin-scroller": "^2.0.0", // 如果使用滚动画布功能,需要安装此包

"@antv/x6-plugin-selection": "^2.0.0", // 如果使用框选功能,需要安装此包

"@antv/x6-plugin-snapline": "^2.0.0", // 如果使用对齐线功能,需要安装此包

"@antv/x6-plugin-dnd": "^2.0.0", // 如果使用 dnd 功能,需要安装此包

"@antv/x6-plugin-stencil": "^2.0.0", // 如果使用 stencil 功能,需要安装此包

"@antv/x6-plugin-transform": "^2.0.0", // 如果使用图形变换功能,需要安装此包

"@antv/x6-plugin-export": "^2.0.0", // 如果使用图片导出功能,需要安装此包

"@antv/x6-react-components": "^2.0.0", // 如果使用配套 UI 组件,需要安装此包

"@antv/x6-react-shape": "^2.0.0", // 如果使用 react 渲染功能,需要安装此包

"@antv/x6-vue-shape": "^2.0.0" // 如果使用 vue 渲染功能,需要安装此包

 2、页面中引入x6

<template>
  <div class="entry-container" ref="screenRef">
      <div class="mapBox">
        <div id="mapBody"></div>
      </div>
      <div id="minimap"></div>
      <TeleportContainer />  // 很重要,必须将TeleportContainer标签放置在html结构中
    </div>
  </div>
</template>

<script setup>
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
</script>

3、注册自定义节点:

// 引入自定义节点
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";

// 注册外部节点
register({
  shape: "ArtNode",
  inherit: "vue-shape",
  width: 300,
  height: 72,
  component: ArtNode,
  ports: { ...ports },
});

// 注册群组节点
register({
  shape: "GroupNode",
  inherit: "vue-shape",
  width: 325,
  height: 210,
  component: GroupNode,
  ports: { ...ports },
});

const ports = {
  groups: {
    in: {
      position: "left",
      attrs: {
        circle: {
          r: 5,
          magnet: true,
          stroke: "#ccc",
          strokeWidth: 2,
          fill: "#fff",
        },
      },
    },

    out: {
      position: "right",
      attrs: {
        circle: {
          r: 5,
          magnet: true,
          stroke: "#ccc",
          strokeWidth: 2,
          fill: "#fff",
        },
      },
    },
  },
  items: [
    {
      id: "port1",
      group: "in",
    },
    {
      id: "port2",
      group: "out",
    },
  ],
};

4、注册x6插件

// 注册插件
const handleUsePlugin = () => {
  graph
    .use(
      new Transform({
        resizing: true,
        rotating: true,
      })
    )
    .use(
      new Selection({
        rubberband: true,
        showNodeSelectionBox: true,
      })
    )
    .use(
      new Scroller({
        enabled: true,
        autoResize: true,
        pannable: true, //是否启用画布平移能力
        modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
      })
    )
    .use(
      new MiniMap({
        container: document.getElementById("minimap"),
        width: 200,
        height: 160,
        scalable: false,
        minScale: 0.1,
        maxScale: 2,
        padding: 10,
      })
    )
    .use(new Snapline())
    .use(new Keyboard())
    .use(new Clipboard())
    .use(new History());
};

5、注册边&连接器

Edge.config({
  markup: [
    {
      tagName: "path",
      selector: "wrap",
      attrs: {
        fill: "none",
        cursor: "pointer",
        stroke: "transparent",
        strokeLinecap: "round",
      },
    },
    {
      tagName: "path",
      selector: "line",
      attrs: {
        fill: "none",
        pointerEvents: "none",
      },
    },
  ],
  connector: { name: "curveConnector" },
  attrs: {
    wrap: {
      connection: true,
      strokeWidth: 10,
      strokeLinejoin: "round",
    },
    line: {
      connection: true,
      stroke: "#A2B1C3",
      strokeWidth: 1,
      targetMarker: {
        name: "classic",
        size: 6,
      },
    },
  },
});

// 注册连接器
Graph.registerConnector(
  "curveConnector",
  (sourcePoint, targetPoint) => {
    const hgap = Math.abs(targetPoint.x - sourcePoint.x);
    const path = new Path();
    path.appendSegment(
      Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y)
    );
    path.appendSegment(
      Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y)
    );
    // 水平三阶贝塞尔曲线
    path.appendSegment(
      Path.createSegment(
        "C",
        sourcePoint.x < targetPoint.x
          ? sourcePoint.x + hgap / 2
          : sourcePoint.x - hgap / 2,
        sourcePoint.y,
        sourcePoint.x < targetPoint.x
          ? targetPoint.x - hgap / 2
          : targetPoint.x + hgap / 2,
        targetPoint.y,
        targetPoint.x - 6,
        targetPoint.y
      )
    );
    path.appendSegment(
      Path.createSegment("L", targetPoint.x + 2, targetPoint.y)
    );

    return path.serialize();
  },
  true
);

Graph.registerEdge("data-processing-curve", Edge, true);

6、初始化x6画布

// 创建画布
const handleInit = () => {
  // 创建画布
  graph = new Graph({
    container: document.getElementById("mapBody"),
    grid: true,
    width: 1500,
    height: 1200,
    autoResize: true,
    // 设置画布背景颜色
    background: {
      color: "#f0f3f5",
    },
    // 画布缩放 按住ctrl
    mousewheel: {
      enabled: true,
      global: true,
      modifiers: ["ctrl", "meta"],
    },
    // 设置连接线
    connecting: {
      snap: true, //开启和关闭连线过程中自动吸附
      anchor: "center",
      connectionPoint: "boundary",
      allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true
      allowBlank: false, // 是否允许连接到画布空白位置的点
      highlight: true,
      sourceAnchor: {
        name: "left",
        args: {
          dx: Platform.IS_SAFARI ? 4 : 8,
        },
      },
      targetAnchor: {
        name: "right",
        args: {
          dx: Platform.IS_SAFARI ? 4 : -8,
        },
      },
      //创建边
      createEdge() {
        return graph.createEdge({
          shape: "data-processing-curve",
          attrs: {
            line: {
              stroke: "#A2B1C3",
              strokeWidth: 1,
              targetMarker: {
                name: "block",
                width: 12,
                height: 8,
              },
            },
          },
          zIndex: -1,
        });
      },
      validateConnection({ targetMagnet }) {
        return !!targetMagnet;
      },
    },
    highlighting: {
      magnetAdsorbed: {
        name: "stroke",
        args: {
          attrs: {
            fill: "#006eff",
            stroke: "#006eff",
          },
        },
      },
    },
  });
  graph.centerContent();
  handleUsePlugin();

7、给x6画布绑定事件(实现复制、粘贴、删除、撤销、回复等功能)

// 画布方法&快捷键
const handleGraphMethods = () => {
  // 撤销/复原
  graph.bindKey(["meta+z", "ctrl+z"], () => {
    if (graph.canUndo()) {
      graph.undo();
    }
    return false;
  });
  graph.bindKey(["meta+y", "ctrl+y"], () => {
    if (graph.canRedo()) {
      graph.redo();
    }
    return false;
  });

  //复制
  graph.bindKey(["meta+c", "ctrl+c"], () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.copy(cells);
    }
    return false;
  });

  // 粘贴
  graph.bindKey(["meta+v", "ctrl+v"], () => {
    if (!graph.isClipboardEmpty()) {
      const cells = graph.paste({ offset: 32 });
      cells.forEach((item) => {
        let node = item.store.data;
        node.shape !== "data-processing-curve" && (node.data.id = node.id);
      });
      graph.cleanSelection();
      graph.select(cells);
    }
    return false;
  });

  // 删除
  graph.bindKey("backspace", () => {
    const cells =  graph.getSelectedCells();
      if (cells.length) {
        graph.removeCells(cells);
      }
  });

  // 右键菜单
  graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {
    handleContextmenu(e);
  });

  // 历史改变
  graph.on("history:change", () => {
    canRedo.value = graph.canRedo();
    canUndo.value = graph.canUndo();
  });

  //  选中节点
  graph.on("cell:selected", ({ cell, index, options }) => {
    selectCell.value = cell;
  });
  // 取消选中节点
  graph.on("cell:unselected", ({ cell, index, options }) => {
    selectCell.value = null;
  });

  // 双击节点
  graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {
    if (graph.isNode(cell)) {
      drawerData.value = JSON.parse(JSON.stringify(cell.getData()));
      drawerData.value.showDrawer = true;
    }
  });
};

8、拖拽生成节点


  // 指定画布拖拽区
  dnd = new Dnd({
    target: graph,
    scaled: false,
    dndContainer: document.querySelector(".treeContent"),
    // 确保拖拽节点时和拖拽完成后节点id一致
    getDragNode: (node) => node.clone({ keepId: true }),
    getDropNode: (node) => {
      // 拖拽结束后
      let data = node.getData();
      drawerData.value = {
        ...data,
        id: node.id,
        isCreate: true,
        showDrawer: true,
      };
      return node.clone({ keepId: true });
    },
  });
  handleGraphMethods();
};

// 拖拽生成节点
const handleCreateNode = (type, e) => {
  // graph.cleanSelection();
  const node =
    type === "group"
      ? graph.createNode({
          shape: "GroupNode",
          data: {
            type: "group",
            group: [{ type: "inside" }, { type: "outside" }],
          },
        })
      : graph.createNode({
          shape: "ArtNode",
          data: {
            type,
          },
        });
  dnd.start(node, e);
};

 9、保存/设置节点数据

// 保存
const handleToJson = () => {
  const json = graph.toJSON();
  json.knwlgMapId = route.query.pkId;
  submitNode(json).then(() => {
    proxy.$modal.msgSuccess("保存成功!");
  });
};

// 获取地图数据
const getMapDetail = () => {
  loading.value = true;
  getNodeDetail({ knwlgMapId: route.query.pkId })
    .then(({ data: cells }) => {
      graph.fromJSON({ cells });
      loading.value = false;
    })
    .catch(() => {
      loading.value = false;
    });
};

完整代码:

<template>
  <div class="entry-container" ref="screenRef" v-loading="loading">
    <div class="treeContent" style="width: 240px">
      <div class="flex mb20">
        <div class="mr5">插入节点</div>
        <el-tooltip placement="bottom">
          <template #content>
            * 创建节点方式:<br />
            按住内部节点/外部节点/文章集合后往右侧区域拖拽<br />
            * 节点/边操作快捷键:<br />
            删除:点击选中节点/边后按Backspace键<br />
            撤销:操作后Ctrl+z<br />
            恢复:操作后Ctrl+y<br />
            复制:选中节点/边后Ctrl+c Ctrl+v<br />
            多选:直接框选或按住Ctrl单点<br />
            * 画布操作快捷键:<br />
            按住Ctrl+鼠标左键可拖拽画布<br />
            按住Ctrl+鼠标滚轮可缩放画布<br />
          </template>
          <el-icon class="mr5" :size="14" color="#006eff"
            ><QuestionFilled
          /></el-icon>
        </el-tooltip>
      </div>
      <div class="sigleGroup">
        <div
          class="shapeItem flex"
          v-for="(item, index) in shapeList"
          :key="index"
          @mousedown="handleCreateNode(item.type, $event)"
        >
          <img class="mr10" :src="item.imgSrc" />
          <span>{{ item.name }}</span>
        </div>
      </div>
    </div>
    <div class="mapContent">
      <div class="mapHeader flex">
        <div class="flex">
          <div v-if="!mapInfo.showIpt">{{ mapInfo.knwlgMapName }}</div>
          <el-input
            v-else
            ref="mapNameRef"
            maxlength="20"
            autofocus
            v-model.trim="mapInfo.knwlgMapName"
            placeholder="输入地图名称"
            show-word-limit
            clearable
            style="width: 200px"
            @blur="handleBlur"
          ></el-input>
          <el-button
            v-if="!mapInfo.showIpt"
            link
            type="primary"
            icon="Edit"
            @click="handleShowIpt"
          ></el-button>
        </div>
        <div class="flex">
          <el-tooltip content="撤销">
            <el-button
              :disabled="!canUndo"
              link
              type="primary"
              icon="RefreshLeft"
              @click="handleUndo"
            ></el-button>
          </el-tooltip>
          <el-tooltip content="恢复">
            <el-button
              :disabled="!canRedo"
              link
              type="primary"
              icon="RefreshRight"
              @click="handleRedo"
            ></el-button>
          </el-tooltip>
          <el-tooltip content="删除选中的线或节点">
            <el-button
              :disabled="!selectCell"
              link
              type="primary"
              icon="Delete"
              @click="handleDelete()"
            ></el-button>
          </el-tooltip>
          <el-tooltip content="复制选中的线或节点">
            <el-button
              :disabled="!selectCell"
              link
              type="primary"
              icon="CopyDocument"
              @click="handleCopy"
            ></el-button>
          </el-tooltip>
          <el-tooltip content="全屏">
            <el-button
              link
              type="primary"
              icon="FullScreen"
              @click="toggle"
            ></el-button>
          </el-tooltip>
          <el-divider direction="vertical" />
          <el-button type="primary" @click="handleToJson">保存</el-button>
          <el-button @click="handleClosePage">关闭</el-button>
        </div>
      </div>
      <div class="mapBox">
        <div id="mapBody"></div>
      </div>
      <div id="minimap"></div>
      <TeleportContainer />
    </div>
    <mapDrawer
      v-model="drawerData"
      @handleUpdateData="handleUpdateData"
      @handleDelCell="handleDelete"
    />
  </div>
</template>

<script setup name="Ekms/map/grapth">
import { getNodeDetail, submitNode, editName } from "@/api/ekms/map";
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
import { useFullscreen } from "@vueuse/core";
import ContextMenu from "@imengyu/vue3-context-menu";
import useTagsViewStore from "@/store/modules/tagsView";
import { ports, shapeList } from "./components/constans.js";
import ArtNode from "./components/artNode.vue";
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";

const { proxy } = getCurrentInstance();
const screenRef = ref(null);
const { toggle } = useFullscreen(screenRef);
const route = useRoute();
const router = useRouter();
const mapInfo = ref({
  originName: route.query.knwlgMapName,
  knwlgMapName: route.query.knwlgMapName,
});

let graph = null;
let dnd = null;
const TeleportContainer = getTeleport();
const canRedo = ref(false);
const canUndo = ref(false);
const loading = ref(false);
// const routeId = ref(route.query.pkId);
const selectCell = ref(null);
const drawerData = ref({
  showDrawer: false,
  title: "",
  group: [{ type: "inside" }, { type: "outside" }],
});

onMounted(() => {
  if (!route.query.pkId) return;
  handleInit();
  // window.addEventListener("resize", resizeFn);
});

// const resizeFn = () => {
//   const width = document.querySelector(".mapContent").offsetWidth;
//   const height = document.querySelector(".mapContent").offsetHeight;
//   graph.resize(width, height);
// };

// onActivated(() => {
//   if (routeId.value != route.query.pkId) {
//     routeId.value = route.query.pkId;
//     graph && graph.dispose();
//     handleInit();
//   } else {
//     resizeFn();
//     graph.centerContent();
//   }
// });

const handleShowIpt = () => {
  mapInfo.value.showIpt = true;
  nextTick(() => {
    proxy.$refs.mapNameRef.input.focus();
  });
};

const handleBlur = async () => {
  mapInfo.value.showIpt = false;
  !mapInfo.value.knwlgMapName?.length &&
    (mapInfo.value.knwlgMapName = route.query.knwlgMapName);
  await editName({
    pkId: route.query.pkId,
    knwlgMapName: mapInfo.value.knwlgMapName,
  });
};

// 获取地图数据
const getMapDetail = () => {
  loading.value = true;
  getNodeDetail({ knwlgMapId: route.query.pkId })
    .then(({ data: cells }) => {
      graph.fromJSON({ cells });
      loading.value = false;
    })
    .catch(() => {
      loading.value = false;
    });
};

// 注册外部节点
register({
  shape: "ArtNode",
  inherit: "vue-shape",
  width: 300,
  height: 72,
  component: ArtNode,
  ports: { ...ports },
});

// 注册群组节点
register({
  shape: "GroupNode",
  inherit: "vue-shape",
  width: 325,
  height: 210,
  component: GroupNode,
  ports: { ...ports },
});

Edge.config({
  markup: [
    {
      tagName: "path",
      selector: "wrap",
      attrs: {
        fill: "none",
        cursor: "pointer",
        stroke: "transparent",
        strokeLinecap: "round",
      },
    },
    {
      tagName: "path",
      selector: "line",
      attrs: {
        fill: "none",
        pointerEvents: "none",
      },
    },
  ],
  connector: { name: "curveConnector" },
  attrs: {
    wrap: {
      connection: true,
      strokeWidth: 10,
      strokeLinejoin: "round",
    },
    line: {
      connection: true,
      stroke: "#A2B1C3",
      strokeWidth: 1,
      targetMarker: {
        name: "classic",
        size: 6,
      },
    },
  },
});

// 注册连接器
Graph.registerConnector(
  "curveConnector",
  (sourcePoint, targetPoint) => {
    const hgap = Math.abs(targetPoint.x - sourcePoint.x);
    const path = new Path();
    path.appendSegment(
      Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y)
    );
    path.appendSegment(
      Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y)
    );
    // 水平三阶贝塞尔曲线
    path.appendSegment(
      Path.createSegment(
        "C",
        sourcePoint.x < targetPoint.x
          ? sourcePoint.x + hgap / 2
          : sourcePoint.x - hgap / 2,
        sourcePoint.y,
        sourcePoint.x < targetPoint.x
          ? targetPoint.x - hgap / 2
          : targetPoint.x + hgap / 2,
        targetPoint.y,
        targetPoint.x - 6,
        targetPoint.y
      )
    );
    path.appendSegment(
      Path.createSegment("L", targetPoint.x + 2, targetPoint.y)
    );

    return path.serialize();
  },
  true
);

Graph.registerEdge("data-processing-curve", Edge, true);

// 创建画布
const handleInit = () => {
  // 创建画布
  graph = new Graph({
    container: document.getElementById("mapBody"),
    grid: true,
    width: 1500,
    height: 1200,
    autoResize: true,
    // 设置画布背景颜色
    background: {
      color: "#f0f3f5",
    },
    // 画布缩放 按住ctrl
    mousewheel: {
      enabled: true,
      global: true,
      modifiers: ["ctrl", "meta"],
    },
    // 设置连接线
    connecting: {
      snap: true, //开启和关闭连线过程中自动吸附
      anchor: "center",
      connectionPoint: "boundary",
      allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true
      allowBlank: false, // 是否允许连接到画布空白位置的点
      highlight: true,
      sourceAnchor: {
        name: "left",
        args: {
          dx: Platform.IS_SAFARI ? 4 : 8,
        },
      },
      targetAnchor: {
        name: "right",
        args: {
          dx: Platform.IS_SAFARI ? 4 : -8,
        },
      },
      //创建边
      createEdge() {
        return graph.createEdge({
          shape: "data-processing-curve",
          attrs: {
            line: {
              stroke: "#A2B1C3",
              strokeWidth: 1,
              targetMarker: {
                name: "block",
                width: 12,
                height: 8,
              },
            },
          },
          zIndex: -1,
        });
      },
      validateConnection({ targetMagnet }) {
        return !!targetMagnet;
      },
    },
    highlighting: {
      magnetAdsorbed: {
        name: "stroke",
        args: {
          attrs: {
            fill: "#006eff",
            stroke: "#006eff",
          },
        },
      },
    },
  });
  // resizeFn();
  graph.centerContent();
  getMapDetail();
  handleUsePlugin();
  // 指定画布拖拽区
  dnd = new Dnd({
    target: graph,
    scaled: false,
    dndContainer: document.querySelector(".treeContent"),
    // 确保拖拽节点时和拖拽完成后节点id一致
    getDragNode: (node) => node.clone({ keepId: true }),
    getDropNode: (node) => {
      // 拖拽结束后
      let data = node.getData();
      drawerData.value = {
        ...data,
        id: node.id,
        isCreate: true,
        showDrawer: true,
      };
      return node.clone({ keepId: true });
    },
  });
  handleGraphMethods();
};

// 画布方法&快捷键
const handleGraphMethods = () => {
  // 撤销/复原
  graph.bindKey(["meta+z", "ctrl+z"], () => {
    if (graph.canUndo()) {
      graph.undo();
    }
    return false;
  });
  graph.bindKey(["meta+y", "ctrl+y"], () => {
    if (graph.canRedo()) {
      graph.redo();
    }
    return false;
  });

  //复制
  graph.bindKey(["meta+c", "ctrl+c"], () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.copy(cells);
    }
    return false;
  });

  // 粘贴
  graph.bindKey(["meta+v", "ctrl+v"], () => {
    if (!graph.isClipboardEmpty()) {
      const cells = graph.paste({ offset: 32 });
      cells.forEach((item) => {
        let node = item.store.data;
        node.shape !== "data-processing-curve" && (node.data.id = node.id);
      });
      graph.cleanSelection();
      graph.select(cells);
    }
    return false;
  });

  // 删除
  graph.bindKey("backspace", () => {
    handleDelete();
  });

  // 右键菜单
  graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {
    handleContextmenu(e);
  });

  // 历史改变
  graph.on("history:change", () => {
    canRedo.value = graph.canRedo();
    canUndo.value = graph.canUndo();
  });

  //  选中节点
  graph.on("cell:selected", ({ cell, index, options }) => {
    selectCell.value = cell;
  });
  // 取消选中节点
  graph.on("cell:unselected", ({ cell, index, options }) => {
    selectCell.value = null;
  });

  // 双击节点
  graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {
    if (graph.isNode(cell)) {
      drawerData.value = JSON.parse(JSON.stringify(cell.getData()));
      drawerData.value.showDrawer = true;
    }
  });
};

// 注册插件
const handleUsePlugin = () => {
  graph
    .use(
      new Transform({
        resizing: true,
        rotating: true,
      })
    )
    .use(
      new Selection({
        rubberband: true,
        showNodeSelectionBox: true,
      })
    )
    .use(
      new Scroller({
        enabled: true,
        autoResize: true,
        pannable: true, //是否启用画布平移能力
        modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
      })
    )
    .use(
      new MiniMap({
        container: document.getElementById("minimap"),
        width: 200,
        height: 160,
        scalable: false,
        minScale: 0.1,
        maxScale: 2,
        padding: 10,
      })
    )
    .use(new Snapline())
    .use(new Keyboard())
    .use(new Clipboard())
    .use(new History());
};

// 拖拽生成节点
const handleCreateNode = (type, e) => {
  // graph.cleanSelection();
  const node =
    type === "group"
      ? graph.createNode({
          shape: "GroupNode",
          data: {
            type: "group",
            group: [{ type: "inside" }, { type: "outside" }],
          },
        })
      : graph.createNode({
          shape: "ArtNode",
          data: {
            type,
          },
        });
  dnd.start(node, e);
};

// 修改节点内容
const handleUpdateData = (id, value) => {
  let cell = graph.getCellById(id || selectCell.value.id);
  cell.updateData(value);
  if (drawerData.value.type === "group") {
    cell.resize(325, 50 + drawerData.value.group.length * 82);
  }
  drawerData.value.showDrawer = false;
};

// 撤销
const handleUndo = () => {
  graph.undo();
};

// 恢复
const handleRedo = () => {
  graph.redo();
};

// 复制
const handleCopy = () => {
  const cells = graph.getSelectedCells();
  if (cells.length) {
    graph.copy(cells);
    if (!graph.isClipboardEmpty()) {
      const cells = graph.paste({ offset: 32 });
      graph.cleanSelection();
      graph.select(cells);
    }
  }
};

// 右键菜单
const handleContextmenu = (e) => {
  const cells = graph.getSelectedCells();
  ContextMenu.showContextMenu({
    x: e.pageX,
    y: e.pageY,
    items: [
      {
        label: "复制(Ctrl+c&Ctrl+z)",
        disabled: cells.length === 0 ? true : false,
        onClick: () => {
          if (cells.length) {
            graph.copy(cells);
            if (!graph.isClipboardEmpty()) {
              const cells = graph.paste({ offset: 32 });
              // 解决复制的节点修改失效问题
              cells.forEach((item) => {
                let node = item.store.data;
                node.shape !== "data-processing-curve" &&
                  (node.data.id = node.id);
              });
              graph.cleanSelection();
              graph.select(cells);
            }
          }
        },
      },
      {
        label: "删除(Backspace)",
        disabled: cells.length === 0 ? true : false,
        onClick: () => {
          if (cells.length) {
            graph.removeCells(cells);
          }
        },
      },
    ],
  });
};

// 删除节点/边
const handleDelete = (id) => {
  // 判断是新增删除还是修改删除
  const cells = id ? [graph.getCellById(id)] : graph.getSelectedCells();
  if (cells.length) {
    graph.removeCells(cells);
  }
};

// 保存
const handleToJson = () => {
  const json = graph.toJSON();
  json.knwlgMapId = route.query.pkId;
  submitNode(json).then(() => {
    proxy.$modal.msgSuccess("保存成功!");
  });
};

// 关闭页面
const handleClosePage = () => {
  let tag = useTagsViewStore().visitedViews.find(
    (item) => item.path === "/ekms/map/grapth"
  );
  proxy.$tab.closePage(tag).then(() => {
    router.push("/ekms/map/mapLibrary");
  });
};

onBeforeUnmount(() => {
  graph && graph.dispose();
});
</script>

<style lang="scss" scoped>
.entry-container {
  display: flex;
  position: relative;
  background: #f0f3f5;
  height: calc(100vh - 70px);
  :deep(.treeContent) {
    padding: 20px;
    min-width: 240px;
    .shapeItem {
      padding: 0 10px;
      margin-bottom: 10px;
      height: 44px;
      line-height: 20px;
      font-size: 14px;
      background: #f6f8fd;
      border-radius: 4px;
      user-select: none;
      cursor: move;
      &:hover {
        border: 1px solid rgba(0, 112, 255, 1);
      }
      img {
        width: 28px;
        height: 28px;
      }
    }
  }
  :deep(.mapContent) {
    flex: 1;
    position: relative;
    .mapHeader {
      padding: 0 20px;
      width: 100%;
      height: 40px;
      line-height: 40px;
      background: #fff;
      justify-content: space-between;
      box-shadow: 0px 1px 0px 0px rgba(215, 220, 230, 1);
    }
    .mapBox {
      width: calc(100vw - 245px);
      height: calc(100vh - 110px);
    }
    #minimap {
      position: absolute;
      right: 0;
      bottom: 0;
    }
  }
}
:deep(#mapBody) {
  .x6-widget-selection-box {
    border-color: rgba(0, 110, 255, 1);
  }
  .x6-widget-transform {
    display: none !important;
  }
  .x6-edge-selected path:nth-child(2) {
    stroke: #006eff;
    stroke-dasharray: 8px, 2px;
  }
}
</style>

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值