CustomeG6-canvas

news2024/9/21 23:35:13

目录

简介

scss

快速上手 · 语雀

简介

antv/g6是一款基于JavaScript图形可视化引擎,由阿里巴巴的AntV团队开发。

创建各种类型的图形,如流程图、关系图、树形图等。

G6采用了自己的绘图模型渲染引擎,使其具备高性能的图形渲染能力。

它支持SVGCanvas两种渲染方式,并且可以在Web移动端应用中使用。

 注册自定义节点、注册行为

<template>
  <div class="custome-G6">
    <div :id="containerId"></div>
    <mds-modal class="custome-G6-modal" :visibility.sync="moreModal.visibility" title="选择操作" width="300px" :mask="true"
      :footer="false" :showClose="true">
      <div class="more-content">
        <mds-button v-if="currentModel && currentModel.type !== 'node-root'" ghost type="primary"
          @click="editNode">修改指标名称</mds-button>

        <mds-button v-if="currentModel && currentModel.indexFlag === 1" ghost type="primary"
          @click="addNode('sub')">添加下级指标</mds-button>
        <mds-button v-if="currentModel && currentModel.indexFlag === 1" ghost type="primary"
          @click="addNode('leaf')">添加底层指标</mds-button>
        <mds-button v-if="currentModel && currentModel.type !== 'node-root'" ghost type="danger"
          @click="handleDeleteNode">删除指标</mds-button>
      </div>
    </mds-modal>

    <!-- 添加指标弹窗 -->
    <mds-modal class="custome-G6-modal" :visibility.sync="addModal.visibility" :title="addModal.title" width="300px"
      :mask="false" :showClose="true" okText="确定" @ok="handleAddNode" @close="handleClose">
      <div style="height: 100px">
        <template v-if="addModal.nodeType === 'leaf'">
          <mds-select v-model="addModal.leaf" value-key="id" placeholder="请选择" filterable @change="changeLeaf">
            <mds-option v-for="item in quaryScoreIndexList" :key="item.id" :value="item"
              :label="item.indexNm"></mds-option>
          </mds-select>

          <div class="tip-text">请选择1个底层指标</div>
        </template>
        <template v-else>
          <mds-input v-model="addModal.content.indexName" :maxlength="30"></mds-input>
          <div class="tip-text">请填写下级指标名称,不超过30字</div>
        </template>
      </div>
    </mds-modal>

    <mds-modal class="custome-G6-modal" :visibility.sync="deleteModal.visibility" title="删除指标提示" width="300px"
      :mask="false" :showClose="true" okText="确定" @ok="deleteNode" @close="closeDelete">
      <div style="height: 100px">
        <div>将删除 “<span style="font-weight:bold">{{ currentModel && currentModel.indexName }}</span>”
          <template v-if="currentModel && currentModel.type === 'node-sub'">及其<span
              style="font-weight:bold">所有下级指标</span></template>
          ,确定吗?
        </div>
      </div>
    </mds-modal>
  </div>
</template>

<script lang="ts">
import { Component, Vue, Prop, Watch, Emit } from 'vue-property-decorator'
import G6 from '@antv/g6'

@Component({
  components: {}
})

export default class CustomeG6 extends Vue {
  @Prop({ required: true }) private containerId!: string
  @Prop({ required: true }) private indexContent!: any
  @Prop({ required: true }) private quaryScoreIndexList!: any
  @Prop({ required: true }) private disabled!: boolean

  // 更新根节点名称
  @Watch('indexContent.indexName', { deep: true })
  changeIndexName(val: any, old: any) {
    // 获取树的根节点
    let rootNode = this.tree.getNodes()[0];
    // 更新根节点的索引名为新的值
    this.tree.updateItem(rootNode, { indexName: val });
    // 渲染更新后的树
    this.tree.render();
  }

  private tree: any

  private moreModal: any = {
    visibility: false
  }

  private addModal: any = {
    visibility: false,
    title: '添加下级指标',
    leaf: '',
    content: {
      indexName: '',
      indexCode: null
    },
    nodeType: 'leaf',
    opType: 'add'
  }

  private deleteModal: any = {
    visibility: false,
  }

  currentEvt: any = null
  currentModel: any = null

  currentAction = ''

  // 关闭删除指标弹窗
  closeDelete() {
    this.deleteModal.visibility = false
  }

  // 打开删除指标弹窗
  handleDeleteNode() {
    this.deleteModal.visibility = true
  }
  // 确定删除指标
  deleteNode() {
    const parent = this.currentEvt.item.get('parent');
    const model = this.currentEvt.item.get('model');

    this.currentEvt.currentTarget.updateItem(parent, {
      children: (parent.get('model').children || []).filter((e: any) => e.id !== model.id),
    });
    this.currentEvt.currentTarget.layout(false);

    this.closeDelete()
    this.moreModal.visibility = false
    this.$emit('update:indexContent', this.tree.get('data'))
  }

  // 修改指标名称
  editNode() {
    const model = this.currentEvt.item.get('model');

    if (this.currentModel.type === 'node-leaf') {
      this.addModal.content.indexCode = model.indexCode
      this.addModal.leaf = {
        id: model.indexCode,
        indexNm: model.indexName
      }
    } else {
      this.addModal.content.indexCode = ''
    }

    this.addModal.nodeType = this.currentModel.type === 'node-leaf' ? 'leaf' : 'sub'
    this.addModal.content.indexName = model.indexName
    this.addModal.opType = 'edit'
    this.addModal.title = '修改指标名称'
    this.addModal.visibility = true
  }

  // 关闭添加指标弹窗
  handleClose() {
    this.addModal.content.indexName = ''
    this.addModal.content.indexCode = ''
    this.addModal.visibility = false
    console.log('关闭添加指标弹窗')
  }

  addNode(type: string) {
    this.addModal.opType = 'add'
    this.addModal.nodeType = type
    this.addModal.title = `添加${type === 'leaf' ? '底层' : '下级'}指标`
    this.addModal.visibility = true
  }

  // 添加指标
  handleAddNode() {
    if (!this.addModal.content.indexName.trim()) {
      this.$message.error(this.addModal.nodeType === 'sub' ? '请输入下级指标' : '请选择底层指标')
      return
    }

    if (this.addModal.nodeType === 'sub') {
      this.addModal.content.indexCode = ''
    }
    const model = this.currentEvt.item.get('model');
    // console.log('点击的name::::', name)
    const newId = model.id + '-' +
      (((model.children || []).reduce((a: any, b: any) => {
        const num = Number(b.id.split('-').pop());
        return a < num ? num : a;
      }, 0) || 0) +
        1);

    let obj
    if (this.addModal.opType === 'add') {
      obj = {
        children: (model.children || []).concat([{
          id: newId,
          direction: 'right',
          indexFlag: this.addModal.nodeType === 'sub' ? 1 : 2,
          indexCode: this.addModal.content.indexCode,
          indexName: this.addModal.content.indexName,
          children: [],
          type: this.addModal.nodeType === 'sub' ? 'node-sub' : 'node-leaf',
          color: '#aaa',
        },]),
      }
      console.log('添加指标:', this.addModal.nodeType, obj)

    } else {
      obj = {
        indexName: this.addModal.content.indexName,
        indexCode: this.addModal.content.indexCode
      }
    }

    this.currentEvt.currentTarget.updateItem(this.currentEvt.item, obj);
    this.currentEvt.currentTarget.layout(false);
    this.addModal.visibility = false
    this.addModal.content.indexName = ''
    this.addModal.content.indexCode = null
    this.addModal.leaf = ''
    this.moreModal.visibility = false

    this.$emit('update:indexContent', this.tree.get('data'))
  }

  // 选择底层指标
  changeLeaf(val: any) {
    if (!val) {
      this.addModal.content.indexName = ''
      this.addModal.content.indexCode = ''
      return
    }
    this.addModal.content.indexName = val.indexNm
    this.addModal.content.indexCode = val.id
  }

  updateTree() {
    this.tree.data(this.indexContent)
    this.tree.render()
  }

  mounted() {
    const _this = this
    const { Util } = G6;


    // <text style={{ marginLeft: ${width - 16}, marginTop: -18, stroke: '', fill: '#000', fontSize: 16, cursor: 'pointer', opacity: ${cfg.hover ? 0.75 : 0} }} action="add">+</text>

    // <group zIndex=9999>
    //   <rect style={{ width: 100, height: 42, stroke: ${stroke}, fill: ${fill}, marginLeft: ${ width + 30 }, marginTop: -24, cursor: 'pointer', opacity: ${cfg.openMore ? 1 : 0} }} action="addSub">
    //     <Text style={{ marginLeft: ${ width + 42 }, marginTop: 12, cursor: 'pointer', opacity: ${cfg.openMore ? 1 : 0} }} action="addSub">添加下级指标</Text>
    //   </rect>
    //   <rect style={{ width: 100, height: 42, stroke: ${stroke}, fill: ${fill}, marginLeft: ${ width + 30 }, marginTop: -24, cursor: 'pointer', opacity: ${cfg.openMore ? 1 : 0} }} action="addLeaf">
    //     <Text style={{ marginLeft: ${ width + 42 }, marginTop: 12, cursor: 'pointer', opacity: ${cfg.openMore ? 1 : 0} }} action="addLeaf">添加底层指标</Text>
    //   </rect>
    // </group>

    // 根结点
    // 使用 G6.registerNode() 方法注册一个名为 'node-root' 的自定义节点
    G6.registerNode(
      'node-root', // 节点名称,这里为 'node-root'

      {
        // jsx 属性指定节点的渲染函数,用于生成节点的 HTML/SVG 内容
        jsx: (cfg: any) => {
          // 计算节点内容的宽度,以便在渲染时使用
          // 16: 文本字体大小 (font size)
          // 它表示文本的最大宽度。在这里传递 [0] 作为参数,可能意味着测量文本的实际宽度,而不限制其最大宽度
          // 24: 这是在计算节点内容宽度时额外添加的宽度值。在代码中,它被用作一个修正项,可能是为了确保节点的宽度足够容纳文本内容,并且在节点左右两侧留有一定的间隔
          const width = Util.getTextSize(cfg.indexName, 16)[0] + 24;

          // 获取节点样式中的边框颜色,默认为 '#CED4E0'
          const stroke = cfg.style.stroke || '#CED4E0';

          // 获取节点样式中的填充颜色,默认为 '#FFF'
          const fill = cfg.style.fill || '#FFF';

          // 返回节点的 HTML/SVG 内容
          return `
      <group>
        <rect draggable="true" style={{width: ${width}, height: 42, stroke: ${stroke}, fill: ${fill}, radius: 8 }} keyshape>
          <text style={{ fontSize: 16, marginLeft: 12, marginTop: 12 }}>${cfg.indexName}</text>
          <Circle style={{ r: 10, fill: '#FFF', stroke: ${stroke}, marginLeft: ${width + 14}, marginTop: 4 }}>
            <Text style={{ fill: ${_this.disabled ? '#ddd' : '#1564FF'}, fontSize: 18, lineHeight: 24, marginLeft: ${width + 7}, marginTop: -12, cursor: ${_this.disabled ? 'not-allowed' : 'pointer'} }} action="more">...</Text>
          </Circle>
        </rect>
      </group>
    `;
        },

        // getAnchorPoints() 方法定义节点的锚点位置,即连接边的起始和结束点
        getAnchorPoints() {
          // 返回一个数组,数组中包含两个锚点位置
          // 第一个锚点位于节点的左边中点 [0, 0.5]
          // 第二个锚点位于节点的右边中点 [1, 0.5]
          return [
            [0, 0.5],
            [1, 0.5],
          ];
        },
      },

      'single-node' // 节点类型,这里为 'single-node'
    );


    // 子节点
    // <text style={{ marginLeft: ${width - 32}, marginTop: -18, fill: '#000', fontSize: 16, cursor: 'pointer', opacity: ${cfg.hover ? 0.75 : 0} }} action="add">+</text>
    // <text style={{ marginLeft: ${width - 16}, marginTop: -34, fill: '#000', fontSize: 16, cursor: 'pointer', opacity: ${cfg.hover ? 0.75 : 0}, next: 'inline' }} action="delete">-</text>
    G6.registerNode(
      'node-sub', {
      jsx: (cfg: any) => {
        const width = Util.getTextSize(cfg.indexName, 14)[0] + 24;
        const stroke = cfg.style.stroke || '#CED4E0';
        const fill = cfg.style.fill || '#FFF';
        const color = '#f00';

        return `
          <group>
            <rect draggable="true" style={{width: ${width}, height: 42, stroke: ${stroke}, fill: ${fill}, radius: 8 }} keyshape>
              <text style={{ fontSize: 14, marginLeft: 12, marginTop: 12 }}>${cfg.indexName}</text>
              <Circle style={{ r: 10, fill: '#FFF', stroke: ${stroke}, marginLeft: ${width + 14}, marginTop: 4 }}>
                <Text style={{ fill: ${_this.disabled ? '#ddd' : '#1564FF'}, fontSize: 18, marginLeft: ${width + 7}, marginTop: -12, cursor: ${_this.disabled ? 'not-allowed' : 'pointer'}, }} action="more">...</Text>
              </Circle>
            </rect>
          </group>
          `;
      },
      getAnchorPoints() {
        return [
          [0, 0.5],
          [1, 0.5],
        ];
      },
    },
      'single-node',
    );

    // 叶子节点
    // <text style={{ marginLeft: ${width - 16}, marginTop: -18, stroke: ${color}, fill: '#000', cursor: 'pointer', opacity: ${cfg.hover ? 0.75 : 0}, next: 'inline' }} action="delete">-</text>
    G6.registerNode(
      'node-leaf', {
      jsx: (cfg: any) => {
        const width = Util.getTextSize(cfg.indexName, 14)[0] + 24;
        const stroke = cfg.style.stroke || '#CED4E0';
        const fill = cfg.style.fill || '#FFF';
        const color = cfg.color || cfg.style.stroke;

        return `
          <group>
            <rect draggable="true" style={{width: ${width}, height: 42, stroke: ${stroke}, fill: ${fill}, radius: 8}} keyshape>
              <text style={{ fontSize: 14, marginLeft: 12, marginTop: 12 }}>${cfg.indexName}</text>
              <Circle style={{ r: 10, fill: '#FFF', stroke: ${stroke}, marginLeft: ${width + 14}, marginTop: 4 }}>
                <Text style={{ fill: ${_this.disabled ? '#ddd' : '#1564FF'}, fontSize: 18, marginLeft: ${width + 7}, marginTop: -12, cursor: ${_this.disabled ? 'not-allowed' : 'pointer'}, }} action="more">...</Text>
              </Circle>
            </rect>
          </group>
          `;
      },
      getAnchorPoints() {
        return [
          [0, 0.5],
          [1, 0.5],
        ];
      },
    },
      'single-node',
    );

// 双击修改节点名称
editNode(evt: any) {
  const item = evt.item;
  const model = item.get('model');
  // 根结点不能修改名称
  if (model.type === 'node-root') return;

  console.log('model:::---:', model);

  // 获取节点位置
  const { x, y } = item.calculateBBox();

  // 获取图表对象
  const graph = evt.currentTarget;

  // 将节点位置转换为实际位置
  const realPosition = evt.currentTarget.getClientByPoint(x, y);

  // 创建一个文本输入框
  const el = document.createElement('div');
  const fontSizeMap: any = {
    'node-root': 24,
    'node-sub': 18,
    'node-leaf': 18,
  };
  el.style.fontSize = fontSizeMap[model.type] + 'px';
  el.style.position = 'fixed';
  el.style.top = realPosition.y + 4 + 'px';
  el.style.left = realPosition.x + 'px';
  el.style.paddingLeft = '6px';
  el.style.transformOrigin = 'top left';
  el.style.transform = `scale(${evt.currentTarget.getZoom()})`;

  const input = document.createElement('input');
  input.style.border = 'none';
  input.value = model.indexName;
  input.style.width = Util.getTextSize(model.indexName, fontSizeMap[model.type])[0] + 'px';
  input.className = 'dice-input';
  el.className = 'dice-input';
  el.appendChild(input);
  document.body.appendChild(el);

  // 定义销毁文本输入框的函数
  const destroyEl = () => {
    document.body.removeChild(el);
  };

  // 定义处理点击事件的函数
  const clickEvt = (event: any) => {
    if (!(event.target && event.target.className && event.target.className.includes('dice-input'))) {
      // 移除事件监听器
      window.removeEventListener('mousedown', clickEvt);
      window.removeEventListener('scroll', clickEvt);
      
      // 更新节点名称并重新布局图表
      graph.updateItem(item, {
        indexName: input.value,
      });
      graph.layout(false);
      
      // 移除滚轮缩放事件监听器,并销毁文本输入框
      graph.off('wheelZoom', clickEvt);
      destroyEl();
    }
  };

  // 添加事件监听器,处理点击事件
  graph.on('wheelZoom', clickEvt);
  window.addEventListener('mousedown', clickEvt);
  window.addEventListener('scroll', clickEvt);

  // 监听输入框的键盘事件,如果按下 Enter 键,触发点击事件
  input.addEventListener('keyup', (event) => {
    if (event.key === 'Enter') {
      clickEvt({
        target: {},
      });
    }
  });
},
 hoverNode(evt: any) {
        evt.currentTarget.updateItem(evt.item, {
          hover: true,
        });
      },

      hoverNodeOut(evt: any) {
        evt.currentTarget.updateItem(evt.item, {
          hover: false,
        });
      },
    });

 G6 图形库的行为(Behavior),用于在画布上实现缩放和平移操作。当用户在画布上滚动鼠标滚轮时,会根据情况执行缩放或平移操作。如果同时按下了 Ctrl 键,则进行缩放操作,否则进行平移操作。

// 在 G6 中注册名为 'scroll-canvas' 的行为
G6.registerBehavior('scroll-canvas', {
  // 获取事件列表
  getEvents: function getEvents() {
    return {
      wheel: 'onWheel', // 当滚轮滚动事件发生时,调用 onWheel 方法
    };
  },

  // 处理滚轮滚动事件的方法
  onWheel: function onWheel(ev: any) {
    const { graph } = _this; // 从 this 对象中获取 graph,这里的 _this 表示当前行为实例
    if (!graph) {
      return;
    }
    if (ev.ctrlKey) { // 如果按下了 Ctrl 键
      const canvas = graph.get('canvas'); // 获取画布对象
      const point = canvas.getPointByClient(ev.clientX, ev.clientY); // 根据鼠标位置获取画布上的坐标点
      let ratio = graph.getZoom(); // 获取当前图形的缩放比例
      if (ev.wheelDelta > 0) { // 如果滚轮向上滚动
        ratio += ratio * 0.05; // 将缩放比例增加 5%
      } else {
        ratio *= ratio * 0.05; // 否则将缩放比例减少 5%
      }
      graph.zoomTo(ratio, {
        x: point.x, // 设置缩放中心点的 x 坐标
        y: point.y, // 设置缩放中心点的 y 坐标
      });
    } else {
      const x = ev.deltaX || ev.movementX; // 获取水平方向上的滚动距离
      const y = ev.deltaY || ev.movementY || (-ev.wheelDelta * 125) / 3; // 获取垂直方向上的滚动距离
      graph.translate(-x, -y); // 平移图形,向相反方向移动
    }
    ev.preventDefault(); // 阻止默认滚动事件,避免影响整个页面的滚动
  },
});
  • 节点被点击时,触发'node:click'事件,调用'clickNode'函数。
  • 节点被双击时,触发'node:dblclick'事件,原本预计调用'editNode'函数,但该函数体被注释掉了。
  • 鼠标进入节点时,触发'node:mouseenter'事件,调用'hoverNode'函数。
  • 鼠标离开节点时,触发'node:mouseleave'事件,调用'hoverNodeOut'函数。
// 假设这是一个名为G6的图形引擎,通过registerBehavior注册了一个名为'dice-mindmap'的行为

G6.registerBehavior('dice-mindmap', {
  // 获取事件列表的方法
  getEvents() {
    return {
      // 当节点被点击时触发'node:click'事件,调用'clickNode'方法
      'node:click': 'clickNode',
      // 当节点被双击时触发'node:dblclick'事件,但该行为被注释掉了,没有调用对应的方法
      // 'node:dblclick': 'editNode',
      // 当鼠标进入节点时触发'node:mouseenter'事件,调用'hoverNode'方法
      'node:mouseenter': 'hoverNode',
      // 当鼠标离开节点时触发'node:mouseleave'事件,调用'hoverNodeOut'方法
      'node:mouseleave': 'hoverNodeOut',
    };
  },

  // 节点被点击时调用的方法
  clickNode(evt: any) {
    // 获取节点相关信息
    const model = evt.item.get('model');
    const name = evt.target.get('action');
    _this.currentAction = name; // 假设_this是之前定义过的变量,用于保存当前的动作名称

    switch (name) {
      // case 'addSub':
      // case 'addLeaf':
      //   // 添加子节点或叶节点的逻辑代码
      //   // ...
      //   break;

      // case 'delete':
      //   // 删除节点的逻辑代码
      //   // ...
      //   break;

      // case 'edit':
      //   // 编辑节点的逻辑代码
      //   console.log('edit::::')
      //   break;

      case 'more':
        // 如果当前没有被禁用
        if (!_this.disabled) {
          // 假设_this是之前定义过的变量,用于保存当前的事件和节点模型
          _this.currentEvt = evt;
          _this.currentModel = model;

          // 打印当前节点模型信息
          console.log('currentModel::::', _this.currentModel);

          // 假设_moreModal是之前定义过的变量,用于显示更多操作的弹窗
          _this.moreModal.visibility = true;

          // 可以根据需要执行其他操作
          // ...
        }
        break;

      default:
        // 如果没有匹配到任何动作名称,直接返回
        return;
    }

    // 可以在这里添加其他代码逻辑
    // ...
  },

  // 其他方法
  // ...
});

将输入的数据对象进行转换,并根据不同层级进行相应的属性设置。在转换过程中,会对节点的类型、悬停状态、展开状态等进行处理,同时为部分节点设置默认值。如果节点包含子节点,会递归地处理子节点的数据。

// 定义数据转换函数 dataTransform,接收一个参数 data,该参数为任意类型的数据
const dataTransform = (data: any) => {
  // 定义内部递归函数 changeData,接收两个参数:d 表示当前数据节点,level 表示当前数据节点的层级,默认值为 0
  const changeData: any = (d: any, level = 0) => {
    // 创建一个新的数据对象 data,用扩展运算符复制当前数据节点 d 的所有属性到新对象中
    const data = {
      ...d,
    };
    // 使用 switch 语句根据当前节点层级 level 进行不同的处理
    switch (level) {
      case 0:
        // 当层级为 0 时,设置节点的 type 属性为 'node-root'
        data.type = 'node-root';
        break;
      // case 1:
      //   data.type = 'node-sub';
      //   break;
      default:
        // 默认情况下,设置节点的 type 属性为 'node-sub'
        data.type = 'node-sub';
        break;
    }

    // 设置节点的 hover 属性为 false,表示鼠标未悬停在节点上
    data.hover = false;
    // 设置节点的 openMore 属性为 false,表示未展开更多选项
    data.openMore = false;

    // 当节点层级为 1 且没有 direction 属性时,进行下面的处理
    if (level === 1 && !d.direction) {
      // 如果节点没有 direction 属性,则设置 direction 属性为 'right'
      data.direction = 'right';
    }

    // 如果当前节点存在子节点,则递归处理每个子节点,并将返回的新数据添加到当前节点的 children 属性中
    if (d.children) {
      data.children = d.children.map((child: any) => changeData(child, level + 1));
    }

    // 返回处理后的新数据对象
    return data;
  };

  // 调用递归函数 changeData,并传入初始的 data 参数进行数据转换
  return changeData(data);
};
 const container: any = document.getElementById(_this.containerId);
    // const el = document.createElement('pre');
    // el.innerHTML = '双击修改节点标题';
    // container.appendChild(el);

    const width = container.scrollWidth;
    // const height = (container.scrollHeight || 500) - 20;
    this.tree = new G6.TreeGraph({
      container: _this.containerId,
      width: width,
      height: 300,
      fitView: true,
      fitViewPadding: [10, 20],
      layout: {
        type: 'mindmap',
        direction: 'H',
        nodesep: 80, // 可选
        ranksep: 40, // 可选
        // 节点高度
        getHeight: () => {
          return 16;
        },
        // 节点宽度
        getWidth: (node: any) => {
          return node.level === 0 ?
            Util.getTextSize(node.indexName, 16)[0] + 12 :
            Util.getTextSize(node.indexName, 12)[0];
        },
        // 节点之间的垂直间距
        getVGap: () => {
          return 40;
        },
        // 节点之间的水平间距
        getHGap: () => {
          return 84;
        },
        getSide: (node: any) => {
          return node.data.direction;
        },
      },
      defaultEdge: {
        type: 'cubic-horizontal',
        style: {
          lineWidth: 2,
        },
      },
      minZoom: 0.8,
      maxZoom: 1.5,
      modes: {
        default: ['drag-canvas', 'zoom-canvas', 'dice-mindmap'],
      },
    });

    const data = dataTransform(_this.indexContent)
    this.$emit('update:indexContent', data)
    this.tree.data(data);

    this.tree.render();

    if (typeof window !== 'undefined') {
      window.onresize = () => {
        if (!this.tree || this.tree.get('destroyed')) return;
        if (!container || !container.scrollWidth || !container.scrollHeight) return;
        this.tree.changeSize(container.scrollWidth, 300);
      };
    }

scss

<style lang="scss">
.custome-G6-modal {
  .mds-modal {
    min-width: initial;
  }

  .more-content {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: repeat(2, 1fr);
    column-gap: 12px;
    row-gap: 12px;
    padding-bottom: 48px;

    .mds-btn {
      width: auto;
      margin: 0;
    }
  }

  .mds-modal-header,
  .mds-modal-bottom {
    border: none;
  }

  .mds-modal-footer-default {
    justify-content: flex-end;

    button {
      flex: initial;
      width: 80px;
    }

    .mds-modal-button {
      margin-right: 2px;
    }
  }

  .tip-text {
    font-size: 12px;
    line-height: 18px;
    color: rgba(168, 172, 179, 1);
    margin-top: 10px;
  }
}
</style>

<style lang="scss" scoped>
.custome-G6 {
  background-color: #F9F9F9;
}
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/819187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

npm更新和管理已发布的包

目录 1、更改包的可见性 1.1 将公共包设为私有 ​编辑 使用网站 使用命令行 1.2 将私有包公开 使用网站 使用命令行 2、将协作者添加到用户帐户拥有的私有包 2.1 授予对Web上私有用户包的访问权限 2.2 从命令行界面授予私有包访问权限 2.3 授予对私有组织包的访问权限…

InfiniBand,到底是个啥?

对于InfiniBand&#xff0c;很多搞数通的同学肯定不会陌生。 进入21世纪以来&#xff0c;随着云计算、大数据的不断普及&#xff0c;数据中心获得了高速发展。而InfiniBand&#xff0c;就是数据中心里的一项关键技术&#xff0c;地位极为重要。 尤其是今年以来&#xff0c;以Ch…

春秋云镜 CVE-2021-32682

春秋云镜 CVE-2021-32682 elFinder RCE 靶标介绍 elFinder是一套基于Drupal平台的、开源的AJAX文件管理器。该产品提供多文件上传、图像缩放等功能;elFinder 存在安全漏洞&#xff0c;攻击者可利用该漏洞在托管elFinder PHP连接器的服务器上执行任意代码和命令。 启动场景 漏…

金蝶管易云 X Hologres:新一代全渠道电商ERP最佳实践

业务简介 金蝶管易云是金蝶集团旗下专注提供电商企业管理软件服务的子公司&#xff0c;成立于2008年&#xff0c;是国内最早的电商ERP服务商之一&#xff0c;目前已与300主流电商平台建有合作关系&#xff0c;以企业数据为驱动&#xff0c;深度融合线上线下数据&#xff0c;为…

pytorch学习——正则化技术——丢弃法(dropout)

一、概念介绍 在多层感知机&#xff08;MLP&#xff09;中&#xff0c;丢弃法&#xff08;Dropout&#xff09;是一种常用的正则化技术&#xff0c;旨在防止过拟合。&#xff08;效果一般比前面的权重衰退好&#xff09; 在丢弃法中&#xff0c;随机选择一部分神经元并将其输出…

HCIP中期实验

1、该拓扑为公司网络&#xff0c;其中包括公司总部、公司分部以及公司骨干网&#xff0c;不包含运营商公网部分。 2、设备名称均使用拓扑上名称改名&#xff0c;并且区分大小写。 3、整张拓扑均使用私网地址进行配置。 4、整张网络中&#xff0c;运行OSPF协议或者BGP协议的设备…

Hadoop 之 Hive 4.0.0-alpha-2 搭建(八)

Hadoop 之 Hive 搭建与使用 一.Hive 简介二.Hive 搭建1.下载2.安装1.解压并配置 HIVE2.修改 hive-site.xml3.修改 hadoop 的 core-site.xml4.启动 三.Hive 测试 一.Hive 简介 Hive 是基于 Hadoop 的数据仓库工具&#xff0c;可以提供类 SQL 查询能力 二.Hive 搭建 1.下载 H…

linux 安装FTP

检查是否已经安装 $] rpm -qa |grep vsftpd vsftpd-3.0.2-29.el7_9.x86_64出现 vsftpd 信息表示已经安装&#xff0c;无需再次安装 yum安装 $] yum -y install vsftpd此命令需要root执行或有sudo权限的账号执行 /etc/vsftpd 目录 ftpusers # 禁用账号列表 user_list # 账号列…

【Ajax】笔记-设置CORS响应头实现跨域

CORS CORS CORS是什么&#xff1f; CORS(Cross-Origin Resource Sharing),跨域资源共享。CORS是官方的跨域解决方案&#xff0c;它的特点是不需要在客户端做任何特殊的操作&#xff0c;完全在服务器中进行处理&#xff0c;支持get和post请求。跨域资源共享标准新增了一组HTTP首…

VSCode格式化shell脚本

安装格式化插件&#xff1a;shell-format 用VSCode打开shell脚本之后&#xff0c;按格式化快捷键CtrlAltF&#xff0c;会提示没有格式化shell的工具&#xff0c;然后安装插件&#xff0c;我装的是这个插件&#xff1a;shell-format。 介绍&#xff1a;https://marketplace.vis…

2.C语言数据类型

常量与变量 1.**常量&#xff1a;**程序运行中&#xff0c;值不改变的量 变量&#xff1a;int num5&#xff1b;值可以变的量 2 C语言三种简单数据类型&#xff1a;整型&#xff0c;实型&#xff0c;字符型 %c %d %ld %s %f整型-进制的转换 **1.十进制&#xff1a;**默认的进…

SpringBoot使用JKS或PKCS12证书实现https

SpringBoot使用JKS或PKCS12证书实现https 生成JKS类型的证书 可以利用jdk自带的keytool工具来生成证书文件&#xff0c; 默认生成的是JKS证书 cmd命令如下: 执行如下命令&#xff0c;并按提示填写证书内容&#xff0c;最后会生成server.keystore文件 keytool -genkey tomcat…

ChatGPT在商业世界中的创新应用:颠覆传统营销与客户关系管理

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

14、容器初始化器和配置环境后处理器

容器初始化器和配置环境后处理器 容器初始化器 与传统Spring的容器后处理器&#xff08;也可对容器做一些定制&#xff09;对比&#xff0c;此处的容器初始化器的执行时机要更早一些。 容器初始化器负责可对Spring容器执行初始化定制。 就是在启动项目的时候&#xff0c;在容…

eclipse版本与jdk版本对应关系

官网&#xff1a;Eclipse/Installation - Eclipsepedia eclipse历史版本&#xff08;2007-&#xff09;&#xff1a;Older Versions Of Eclipse - Eclipsepedia Eclipse Packaging Project (EPP) Releases | Eclipse Packages

springboot疾病查询网站【纯干货分享,免费领源码01548】

spring boot疾病查询网站 摘 要 随着互联网时代的到来&#xff0c;同时计算机网络技术高速发展&#xff0c;网络管理运用也变得越来越广泛。因此&#xff0c;建立一个B/S结构的疾病查询网站&#xff0c;会使疾病查询工作系统化、规范化&#xff0c;也会提高医院形象&#xff0c…

红外雨量计(光学雨量传感器)检测降雨量,预防内涝

红外雨量计&#xff08;光学雨量传感器&#xff09;检测降雨量&#xff0c;预防内涝 随着城市化进程的加快&#xff0c;城市内涝成为一个愈发严峻的问题。短时间内大量的降雨&#xff0c;不仅会给城市交通带来困难&#xff0c;也会对城市的基础设施和居民的生活造成很大的影响…

ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级

文章目录 1.什么是 OTA2. ESP32cam HTTP_OTA 本地准备2.1 HTTP OTA 升级原理2.2 开发板本地基准程序&#xff08;程序版本&#xff1a;1_0_0&#xff09;2.3 开发板升级程序&#xff08;程序版本&#xff1a;1_0_1&#xff09;2.4 本地 HTTP_OTA 升级测试2.4.1 本地运行一个 HT…

Spring系列二:基于注解配置bean

文章目录 &#x1f497;通过注解配置bean&#x1f35d;基本介绍&#x1f35d;快速入门&#x1f35d;注意事项和细节 &#x1f497;自己实现Spring注解配置Bean机制&#x1f35d;需求说明&#x1f35d;思路分析&#x1f35d;注意事项和细节 &#x1f497;自动装配 Autowired&…

谷粒商城第八天-商品服务之品牌管理的整体实现(直接使用逆向生成的代码;含oss文件上传)

目录 一、总述 二、前端部分 2.1 创建好品牌管理菜单 2.2 复制组件 ​编辑2.3 复制api ​​​编辑 2.4 查看效果 ​编辑2.5 需要优化的地方 2.6 具体优化实现 2.6.1 优化一&#xff1a;将表格的状态列&#xff08;这里是是否显示列&#xff09;修改为开关&#xff…