vue + g6 实现树级结构(compactBox 紧凑树)

G6文档
vue + g6 实现树级结构(compactBox 紧凑树)_第1张图片

自定义节点

G6.registerNode(
  "dom-node",
  {
    draw: (cfg, group) => {
      let str = `
        
${ cfg.id }" style="width: ${cfg.size[0] - 5}px;"> ${ cfg.status ? `${getLabel( ISSUE_STATUS, cfg.status )}` : "" } ${ cfg?.manager?.name ? `

${cfg?.manager?.avatar}" /> ${cfg.manager.name}

`
: "" }
${ cfg.typeName === "Bug" ? `class='tipText'` : "" }>${cfg.title}
`
; return group.addShape("dom", { attrs: { width: cfg.size[0], height: nodeHeight(cfg), // 传入 DOM 的 html html: str, }, draggable: true, }); }, }, "single-node" );

在pc端,自定义的节点,绑定的点击事件起作用,但是移动端模式,不会起作用;

解决方法:
文档中也说明了,节点的选中事件,需要将Mode切换到edit模式。graph.setMode("edit");(模式可自定义)
vue + g6 实现树级结构(compactBox 紧凑树)_第2张图片
如:

graph.setMode("edit");
graph.on("nodeselectchange", (e) => {
  // 当前操作的 item
  alert("node");
  console.log(e.target);
  // 当前操作后,所有被选中的 items 集合
  console.log(e.selectedItems);
  // 当前操作时选中(true)还是取消选中(false)
  console.log(e.select);
});

此处,直接用自定节点的事件了。

以上只是解决了移动端不能触发点击事件,但是移动端存在拖动canvas与点击事件冲突问题,所以为了PC端与移动端都能起效果,做了以下处理。

  1. 设置两个模式,default: [“drag-node”, “drag-canvas”, “click-select”]; edit: [‘click-select’]。edit 模式还需要添加个自定义的Behavior(G6.registerBehavior(“behavior-name”, {})),自定义鼠标拖动事件;
  2. 所以,先判断是否是移动端,如果是移动端,setMode(‘edit’);

全部代码

<template>
  <div id="container">div>
template>

<script>
// 引入antv-G6
import G6 from "@antv/g6";
import { ISSUE_STATUS } from "@/utils/constant";
import { getLabel } from "@/utils";

// G6的配置项
G6.registerNode(
  "icon-node",
  {
    options: {
      size: [60, 20], // 宽高
      stroke: "#91d5ff", // 变颜色
      fill: "#fff", // 填充色
    },
    // draw是绘制后的附加操作-节点的配置项  图形分组,节点中图形对象的容器
    draw(cfg, group) {
      // 获取节点的配置
      const styles = this.getShapeStyle(cfg);
      // 解构赋值
      const { labelCfg = {} } = cfg;

      const w = styles.width;
      const h = styles.height;
      // 向分组中添加新的图形 图形 配置 rect矩形 xy 代表左上角坐标 w h是宽高
      const keyShape = group.addShape("rect", {
        attrs: {
          ...styles,
          x: -w / 2,
          y: -h / 2,
        },
      });

      // 文本文字的配置
      if (cfg.title) {
        group.addShape("text", {
          attrs: {
            ...labelCfg.style,
            text: cfg.title,
            x: 50 - w / 2,
            y: 25 - h / 2,
          },
        });
      }

      return keyShape;
    },
    // 更新节点后的操作,一般同 afterDraw 配合使用
    update: undefined,
  },
  "rect"
);

const nodeHeight = (obj) => {
  // if (obj.depth == 0) {
  //   return 100;
  // }
  const l = ["manager", "title"];

  const arr = l.filter((item) => {
    return obj[item];
  });
  return arr.length * 25 + 50;
};
G6.registerNode(
  "dom-node",
  {
    draw: (cfg, group) => {
      let str = `
        
${ cfg.id }" style="width: ${cfg.size[0] - 5}px;"> ${ cfg.status ? `${getLabel( ISSUE_STATUS, cfg.status )}` : "" } ${ cfg?.manager?.name ? `

${cfg?.manager?.avatar}" /> ${cfg.manager.name}

`
: "" }
${ cfg.typeName === "Bug" ? `class='tipText'` : "" }>${cfg.title}
`
; return group.addShape("dom", { attrs: { width: cfg.size[0], height: nodeHeight(cfg), // 传入 DOM 的 html html: str, }, draggable: true, }); }, }, "single-node" ); // 绘制层级之间的连接线 G6.registerEdge("flow-line", { // 绘制后的附加操作 draw(cfg, group) { // 边两端与起始节点和结束节点的交点; const startPoint = cfg.startPoint; const endPoint = cfg.endPoint; // 边的配置 const { style } = cfg; const shape = group.addShape("path", { attrs: { stroke: style.stroke, // 边框的样式 endArrow: style.endArrow, // 结束箭头 // 路径 path: [ ["M", startPoint.x, startPoint.y], ["L", startPoint.x, (startPoint.y + endPoint.y) / 2], ["L", endPoint.x, (startPoint.y + endPoint.y) / 2], ["L", endPoint.x, endPoint.y], ], }, }); return shape; }, }); // 默认连接边线的颜色 末尾箭头 const defaultEdgeStyle = { stroke: "#ccc", }; // 默认布局 // compactBox 紧凑树布局 // 从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。 const defaultLayout = { type: "compactBox", // 布局类型树 direction: "TB", // TB 根节点在上,往下布局 getId: function getId(d) { // 节点 id 的回调函数 return d.id; }, getHeight: function getHeight() { // 节点高度的回调函数 return 16; }, getWidth: function getWidth() { // 节点宽度的回调函数 return 16; }, getVGap: function getVGap(d) { // 节点纵向间距的回调函数 if (d.parId === "0") return 70; return 80; }, getHGap: function getHGap(d) { // 节点横向间距的回调函数 if (d.parId === "0") return 100; return 150; }, }; // 自定义拖动事件 G6.registerBehavior("finger-drag-canvas", { dragging: false, offset: 0, getEvents() { return { touchstart: "onDragStart", touchmove: "onDrag", touchend: "onDragEnd", }; }, onDragStart(e) { const self = this; self.dragging = false; self.offset = 0; const clientX = +e.clientX; const clientY = +e.clientY; this.origin = { x: clientX, y: clientY, }; }, onDrag(e) { const { graph } = this; if (!this.dragging) { this.dragging = true; } this.updateViewport(e); }, onDragEnd(evt) { const edges = graph.getEdges(); const nodes = graph.getNodes(); const node = evt.item; const point = { x: evt.x, y: evt.y }; // this.updateViewport(e); this.dragging = false; // 这里开始识别点击事件 if (this.offset < 30) { // 触发点击事件(或者依靠e.target,e.type去做相应的业务操作) console.log(evt, evt.type); } console.log(evt, evt.type); this.updateViewport(evt); }, updateViewport(e) { const { origin } = this; const clientX = +e.clientX; const clientY = +e.clientY; if (isNaN(clientX) || isNaN(clientY)) { return; } let dx = clientX - origin.x; let dy = clientY - origin.y; if (this.get("direction") === "x") { dy = 0; } else if (this.get("direction") === "y") { dx = 0; } this.origin = { x: clientX, y: clientY, }; const width = graph.get("width"); const height = graph.get("height"); const graphCanvasBBox = graph.get("canvas").getCanvasBBox(); if ( (graphCanvasBBox.minX <= width && graphCanvasBBox.minX + dx > width) || (graphCanvasBBox.maxX >= 0 && graphCanvasBBox.maxX + dx < 0) ) { dx = 0; } if ( (graphCanvasBBox.minY <= height && graphCanvasBBox.minY + dy > height) || (graphCanvasBBox.maxY >= 0 && graphCanvasBBox.maxY + dy < 0) ) { dy = 0; } if (dx === 0 && dy === 0) return; // 增加拖动距离统计 this.offset += Math.abs(dx) + Math.abs(dy); graph.translate(dx, dy); }, }); let graph; export default { name: "Home", props: { treeListData: { type: Array, default: () => [], }, options: { type: Object, default: () => { return {}; }, }, }, emits: ["handleSelected"], data() { return { listData: [], selectedId: "", // 选中的节点Id initOptions: { isFitView: true, // 是否默认适应全局 isFitCenter: true, // 是否居中 isHiddenRoot: true, // 是否显示根元素 }, flag: false, // 如果是移动端,true }; }, methods: { G6init() { if (typeof window !== "undefined") { window.onresize = () => { if (!graph || graph.get("destroyed")) return; if (!container || !container.scrollWidth || !container.scrollHeight) return; graph.changeSize(container.scrollWidth, container.scrollHeight); }; } // 获取容器 const container = document.getElementById("container"); // 获取容器的宽高 const width = container.scrollWidth; const height = container.scrollHeight - 30 || 500; // Graph 是 G6 图表的载体-实例化 graph = new G6.TreeGraph({ container: "container", // 图的 DOM 容器 width, height, linkCenter: true, // 指定边是否连入节点的中心 modes: { // default 模式中包含点击选中节点行为和拖拽画布行为; default: [ { type: "zoom-canvas", enableOptimize: true, //开启性能优化 }, "drag-node", "drag-canvas", // "zoom-canvas", "click-select", ], edit: ["click-select"], }, // 默认状态下节点的配置 defaultNode: { type: "dom-node", // 'icon-node', size: [250, 60], }, // 默认状态下边线的配置, defaultEdge: { type: "flow-line", style: defaultEdgeStyle, }, // 布局配置项 layout: defaultLayout, renderer: "svg", }); graph.data([...this.listData][0]); graph.render(); // 让画布内容适应视口。 if (this.initOptions.isFitView) { graph.fitView(); } if (this.initOptions.isFitCenter) { graph.fitCenter(); } if (!this.initOptions.isHiddenRoot) { // 是否要移除根节点 const item = graph.findById([...this.listData][0].id); graph.removeItem(item); } // 改变视口的缩放比例,在当前画布比例下缩放,是相对比例。 graph.zoom(1); }, async init() { let _this = this; if (graph) { // 如果原来有画布,需要先清除 graph.destroy(); } this.initOptions = Object.assign(this.initOptions, this.options); this.listData = [...this.treeListData]; function setSelectFalse(obj) { obj.forEach((element) => { element.isSelected = false; if (element.children) { setSelectFalse(element.children); } }); } window.handleDetail = (id) => { const item = graph.findById(id); if (item?._cfg?.parent) { _this.$emit("handleSelected", id); } }; this.G6init(); this.isMobile(); }, isMobile() { // 判断是否是移动端 this.flag = navigator.userAgent.match( /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i ); if (this.flag) { graph.setMode("edit"); graph.addBehaviors("finger-drag-canvas", "edit"); } }, }, beforeDestroy() { console.log("推出"); }, };
script> <style lang="scss" scoped> @import "@/assets/styles/common.scss"; #container { height: 100%; width: 100%; border: 1px solid #efefef; ::v-deep .title { font-size: 15px; display: block; // text-align: center; position: relative; margin: 10px 0; padding-left: 15px; color: #1199ff; cursor: pointer; } ::v-deep .item-box { background-color: #fff; border-radius: 5px; padding: 5px; // height: 100%; border: 1px solid; position: relative; p { margin-bottom: 2px; display: flex; align-items: center; color: #333; } &.is-selected { border: 1px solid #1199ff; } .tipText { color: red; } .logs { height: 70px; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; } .title-txt { display: inline-block; width: 80px; color: rgb(169, 169, 169); } .avatar-img { width: 35px; height: 35px; margin-right: 15px; img { width: 100%; height: 100%; border-radius: 100%; } } .status { position: absolute; right: 15px; top: 15px; border: 1px solid; padding: 0 5px; font-size: 12px; border-radius: 4px; // Wait 未开始、Doing进行中、Pause暂停、Verify待验证、Done已完成、Cancel已取消 } } } ::v-deep g g g:not(:first-child) foreignObject { font-size: 14px; } foreignObject { overflow: initial !important; } style>

你可能感兴趣的:(vue,其他,vue.js,javascript,前端)