antvX6 - Vue自定义节点,并实现多种画布操作,拖拽、缩放、连线、双击、检索等等

news2025/4/26 6:40:53

一、 首先 antv x6 分为两个版本  低版本和高版本

  我这里是使用的2.0版本 并且搭配了相关插件 例如:画布的图形变换、地图等

  个人推荐 2.0版本,高版本配置多,可使用相关插件多,但是文档描述小,仍在更新, 低版本文档描述清晰,但是相关插件少

二、antv x6 支持自定义节点! 

          这里要特别注意  虽然支持自定义节点,但是连线,连线桩也自然只能节点之间互连,所以你看我的例子中,想要列表里的子节点也可以实现 互相连接,但是这是自定义节点无法做到的。
          因为此时这一整个盒子就是 一个节点!

三、事件集合

    // 事件集合
    loadEvents(containerRef) {
      // 节点双击
      this.graph.on('node:dblclick', ({ node }) => {
        const data = node.store.data;
        console.log(data);
        this.$router.push({
          path: '/modeling/homeModeling',
          query: {
            id: data.modelingId,
            name: data.name,
            layerTypeId: data.layerTypeId,
            tableType: data.modelingType,
          },
        });
      });
      // 连线双击
      this.graph.on('edge:dblclick', ({ edge }) => {
        // const data = edge.store.data;
        // const { type, id } = data;
        // alert('连线双击');
        // console.log('edge:dbclick', edge);
        // if (type === 'taskNode') {
        //   this.nodeId = id;
        //   this.showRight = true;
        // } else {
        //   this.nodeId = '';
        //   this.showRight = false;
        // }
      });
      // 节点鼠标移入
      this.graph.on(
        'node:mouseenter',
        FunctionExt.debounce(({ node }) => {
          // 添加删除
          // const x = node.store.data.size.width - 10;
          // node.addTools({
          //   name: 'button-remove',
          //   args: {
          //     x: 0,
          //     y: 0,
          //     offset: { x, y: 15 },
          //   },
          // });
        }),
        500,
      );
      this.graph.on('node:port-contextmenu', ({ e }) => {
        // console.log(
        //   'ports',
        //   e,
        //   e.currentTarget.parentElement.getAttribute('port'),
        // );
      });
      // 连接线鼠标移入
      this.graph.on('edge:mouseenter', ({ edge }) => {
        // edge.addTools([
        //   'source-arrowhead',
        //   'target-arrowhead',
        //   {
        //     name: 'button-remove',
        //     args: {
        //       distance: '50%',
        //     },
        //   },
        // ]);
      });
      // 节点鼠标移出
      this.graph.on('node:mouseleave', ({ node }) => {
        // // 移除删除
        // node.removeTools();
      });
      this.graph.on('edge:mouseleave', ({ edge }) => {
        // edge.removeTools();
      });
      this.graph.on('edge:connected', ({ isNew, edge }) => {
        // console.log('connected', edge.source, edge.target);
        // if (isNew) {
        //   // 对新创建的边进行插入数据库等持久化操作
        // }
      });
    },

四、画布初始化

    graphInit() {
      // 容器生成图表
      const containerRef = this.$refs.containerRef;
      const graph = new Graph({
        container: containerRef,
        background: {
          color: '#F1F6F9',
        },
        grid: {
          size: 10, // 网格大小 10px
          visible: true, // 绘制网格,默认绘制 dot 类型网格
          type: 'fixedDot',
          args: {
            color: '#AFB0B1', // 网点颜色
            thickness: 1, // 网点大小
          },
        },
        panning: true, // 画布拖拽
        history: true, // 启动历史记录
        selecting: {
          // 选择与框选
          enabled: true,
          rubberband: true,
          movable: true,
          strict: true,
          showNodeSelectionBox: true, // 显示节点的选择框(才能进行移动)
          modifiers: ['alt'],
        },
        // Scroller 使画布具备滚动、平移、居中、缩放等能力
        scroller: {
          enabled: true,
          pageVisible: true,
          pageBreak: true,
          pannable: true,
        },
        // 鼠标滚轮的默认行为是滚动页面 使用ctrl+滚轮 实现缩放
        mousewheel: {
          enabled: true,
          modifiers: ['ctrl', 'meta'], // +按键为缩放
          minScale: 0.5,
          maxScale: 2,
        },
        snapline: true, // 对齐线

        // 节点连接
        connecting: {
          router: {
            name: 'er',
            args: {
              offset: 25,
              direction: 'H',
            },
          },
          snap: true, // 自动吸附
          allowBlank: false, // 是否允许连接到画布空白位置的点
          allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
          allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
          createEdge() {
            return new Shape.Edge({
              attrs: {
                line: {
                  stroke: '#1684FC',
                  strokeWidth: 2,
                },
              },
            });
          },
        },
        // 连接桩样式 -- 高亮
        highlighting: {
          magnetAvailable: {
            name: 'stroke',
            args: {
              padding: 4,
              attrs: {
                strokeWidth: 4,
                stroke: '#1684FC',
              },
            },
          },
        },
      });

      // 小地图
      const minimapContainer = this.$refs.minimapContainer;
      graph.use(
        new MiniMap({
          container: minimapContainer,
          width: '250',
          height: '150',
          scalable: true, // 是否可缩放
          minScale: 0.01,
          maxScale: 16,
        }),
      );

      // 图形
      graph.use(
        new Transform({
          enabled: true,
          resizing: map,
        }),
      );

      // 缩放画布内容,使画布内容充满视口
      graph.zoomToFit({ padding: 10, maxScale: 1 });

      // 赋值生成
      this.graph = graph;

      // 事件集合
      this.loadEvents(containerRef);
    },

五、创建Vue自定义节点

<template>
  <div
    ref="node_dom"
    class="node_warp"
    :style="{
      width: node.size.width + 'px',
      height: node.size.height + 'px',
      borderTopColor: color,
    }"
  >
    <div class="head_top" :style="{ backgroundColor }">
      <svg-icon :icon-class="icon" :style="{ color }"></svg-icon>
      <div class="code_warp">
        <span class="code ellipsis_text">{{ node.code }}</span>
        <span class="name ellipsis_text">{{ node.name }}</span>
      </div>
      <el-popover
        ref="popoverDom"
        placement="bottom-end"
        width="60"
        :value="popShow"
        trigger="click"
        popper-class="filter_column_popover"
        @hide="popShow = false"
        @show="popShow = true"
      >
        <svg-icon
          slot="reference"
          class="icon"
          type="primary"
          size="mini"
          style="opacity: 0.5;"
          icon-class="table_column_settings"
        ></svg-icon>
        <p class="header_wrap_filter_column">
          <el-checkbox
            v-model="checkAll"
            :indeterminate="isIndeterminate"
            @change="handleCheckAllChange"
          >
            全选
          </el-checkbox>
          <!-- -->
          <!-- <el-button size="mini" type="text" @click="resetColumn">
            重置
          </el-button> -->
        </p>
        <el-checkbox-group
          v-model="checkList"
          @change="handleCheckedCitiesChange"
        >
          <el-checkbox v-for="item in checkData" :key="item" :label="item">
            {{ item }}
          </el-checkbox>
        </el-checkbox-group>
        <div v-if="!checkData.length" class="empy">暂无数据</div>
      </el-popover>
    </div>
    <div class="main">
      <div
        v-for="(item, index) in node.columnVersions"
        :key="index"
        class="text "
      >
        <svg-icon v-if="item.isPrimaryKey" icon-class="key"></svg-icon>
        <span v-show="checkList.includes('英文名称')" class="ellipsis_text">
          {{ item.code }}
        </span>
        <div v-show="checkList.includes('字段类型')" class="type ellipsis_text">
          {{ item.dataType }}
        </div>
        <span v-show="checkList.includes('中文名称')" class="ellipsis_text">
          {{ item.name }}
        </span>
      </div>
      <div
        v-if="!node.columnVersions || !node.columnVersions.length"
        class="empy flex"
      >
        暂无数据
      </div>
    </div>
    <div class="footer">
      {{ `共${node.columnSize || 0}个字段` }}
    </div>
  </div>
</template>

<script>
import { manage } from './config';
const cityOptions = ['英文名称', '字段类型', '中文名称'];
export default {
  name: 'Node',
  inject: ['getNode'],
  data() {
    return {
      num: 0,
      icon: '',
      color: '',
      node: {},

      popShow: false,
      checkAll: false,
      checkList: ['英文名称', '字段类型'],
      checkData: cityOptions,
      isIndeterminate: true,

      backgroundColor: null,
      typeMap: manage.typeMap,
    };
  },
  watch: {
    checkList(val) {
      console.log(val);
    },
  },
  created() {
    const node = this.getNode();
    const typeMap = this.typeMap;
    this.node = node.store.data;
    const type = this.node.modelingType;
    this.icon = typeMap[type].icon;
    this.color = typeMap[type].color;
    this.backgroundColor = typeMap[type].backgroundColor;
  },
  methods: {
    handleCheckAllChange(val) {
      this.checkList = val ? cityOptions : [];
      this.isIndeterminate = false;
    },
    handleCheckedCitiesChange(value) {
      const checkedCount = value.length;
      this.checkAll = checkedCount === this.checkData.length;
      this.isIndeterminate =
        checkedCount > 0 && checkedCount < this.checkData.length;
    },
    resetColumn() {
      this.checkList = ['英文名称', '字段类型'];
    },
  },
};
</script>

<style lang="scss" scoped>
.node_warp {
  display: flex;
  border-radius: 4px;
  flex-direction: column;
  border: 1px solid #d9dae2;
  border-top: 5px solid #d9dae2;
  position: relative;
  user-select: none;
  transition: all 0.4s ease-in 0.2s;
  transition: width 0.25s;
  -webkit-transition: width 0.25s;
  -moz-transition: width 0.25s;
  -webkit-transition: width 0.25s;
  -o-transition: width 0.25s;
  .head_top {
    width: 100%;
    height: 48px;
    display: flex;
    padding-left: 10px;
    align-items: center;
    position: relative;
    border-bottom: 1px solid #d9dae2;
    .code_warp {
      width: 85%;
      font-size: 12px;
      margin-left: 8px;
      display: flex;
      flex-direction: column;
      .code {
        color: black;
        font-weight: 700;
      }
      .name {
        color: #b3b2bf;
        font-weight: 600;
      }
    }
    .icon {
      position: absolute;
      right: 5px;
      bottom: 5px;
    }
  }
  .main {
    flex: 1;
    width: 100%;
    overflow: auto;
    padding-right: 2px;
    background: #fff;
    .text {
      height: 32px;
      display: flex;
      gap: 1px;
      font-size: 13px;
      position: relative;
      padding-left: 20px;
      align-items: center;
      svg {
        position: absolute;
        left: 4px;
        top: 10px;
      }
      .type {
        flex: 1;
        height: 24px;
        font-size: 12px;
        line-height: 24px;
        text-align: center;
        border-radius: 4px;
        margin-right: 5px;
        display: inline-block;
        background-color: #f7f7f9;
      }
      span {
        flex: 1;
        text-align: center;
      }
      &:hover {
        background: #f8f8fa;
      }
    }
  }
  .footer {
    height: 20px;
    font-size: 12px;
    line-height: 20px;
    padding-left: 10px;
    color: rgb(156, 160, 184);
    border-top: 1px solid #d9dae2;
    background: rgb(247, 247, 249);
  }
  .ellipsis_text {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-all;
    line-height: 18px;
  }
  .empy {
    color: #ccc;
    font-size: 14px;
    margin: 10px auto;
    width: fit-content;
  }
  .flex {
    display: flex;
    height: calc(100% - 30px);
    align-items: center;
  }
}
</style>

六、注册引入Vue自定义节点

1、安装依赖

      "@antv/x6-vue-shape": "2.0.6",

      yarn add antv/x6-vue-shape@2.0.6

2、引入 Vue 自定义组件

      import CustomNode from '../node';

3、引入插件的方法

      import { register } from '@antv/x6-vue-shape'; // vue节点

4、注册节点

        

register({

  shape: 'custom-vue-node',

  component: CustomNode,

});


import CustomNode from '../node';
import { register } from '@antv/x6-vue-shape'; // vue节点


// 注册 Vue component
register({
  shape: 'custom-vue-node',
  component: CustomNode,
});

七、创建节点、创建连线、渲染节点

// 连接线  
const lineNewData = newData.map((item, index) => {
          return {
            id: String(new Date().getTime() + index),
            shape: 'edge',
            // 连接源
            source: {
              cell: item.sourceTableId,
            },
            // 连接目标
            target: {
              cell: item.targetTableId,
            },
            attrs: {
              line: {
                stroke: '#1684FC',
                strokeWidth: 2,
              },
            },
            // 名字
            labels: [
              {
                attrs: {
                  label: {
                    text: item.name || '',
                  },
                },
              },
            ],
            zIndex: 0,
          };
        });

        // 节点
        const nodeData = result.map(item => {
          return {
            ...item,
            id: item.modelingVersionId,
            width: Number(item.width || 300),
            height: Number(item.heigh || 270),
            // 节点类型
            shape: item.shape || 'custom-vue-node',
            position: {
              x: Number(item.posX || this.getRandomInt()),
              y: Number(item.posY || this.getRandomInt()),
            },
          };
        });
        this.erData = [...nodeData, ...lineNewData];

  通过数据 渲染节点

  watch: {
    data(val) {
      const cells = [];
      this.data.forEach(item => {
        console.log(item, item.shape);
        if (item.shape === 'edge') {
          cells.push(this.graph.createEdge(item)); // 创建连线
        } else {
          cells.push(this.graph.createNode(item)); // 创建节点
        }
      });
      // 清空画布并添加用指定的节点/边
      this.graph.resetCells(cells);
    },
  },

八、canvas主页面 全部代码

<template>
  <div id="container" class="antv-x6">
    <div ref="minimapContainer" class="app-mini"></div>
    <div ref="containerRef" class="app-content"></div>
    <div class="operating">
      <el-select
        v-model="value"
        clearable
        filterable
        placeholder="请选择"
        size="mini"
        :popper-append-to-body="false"
        :class="isShow ? 'showSelect' : 'hideSelect'"
        @change="valChange"
      >
        <el-option
          v-for="item in data.filter(i => i.modelingType)"
          :key="item.id"
          :label="item.code"
          :value="item.id"
        >
          <div class="head_top">
            <svg-icon
              :icon-class="typeMap[item.modelingType].icon"
              :style="{ color: typeMap[item.modelingType].color }"
            />
            <div class="code_warp">
              <span class="code ellipsis_text">{{ item.code }}</span>
              <span class="name ellipsis_text">{{ item.name }}</span>
            </div>
          </div>
        </el-option>
      </el-select>
      <div class="icon_oper">
        <el-tooltip
          class="item"
          effect="dark"
          content="搜索"
          placement="bottom"
        >
          <svg-icon icon-class="search_canvas" @click="search" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          content="放大"
          placement="bottom"
        >
          <svg-icon icon-class="amplify_canvas" @click="zoomInFn" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          content="缩小"
          placement="bottom"
        >
          <svg-icon icon-class="reduce_canvas" @click="zoomOutFn" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          content="还原"
          placement="bottom"
        >
          <svg-icon icon-class="1_1_canvas" @click="resetFn" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          content="保存"
          placement="bottom"
        >
          <svg-icon icon-class="saveModel" @click="submit" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          :content="isFullScreen ? '退出全屏' : '全屏'"
          placement="bottom"
        >
          <svg-icon icon-class="screen" @click="fullScreen" />
        </el-tooltip>
        <el-tooltip
          class="item"
          effect="dark"
          content="刷新"
          placement="bottom"
        >
          <svg-icon icon-class="refresh" @click="redoFn" />
        </el-tooltip>
      </div>
    </div>
  </div>
</template>

<script>
import { manage } from '../config';
import CustomNode from '../node';
import { Graph, Shape, FunctionExt } from '@antv/x6';
import { register } from '@antv/x6-vue-shape'; // vue节点
import { MiniMap } from '@antv/x6-plugin-minimap'; // 地图
import { Transform } from '@antv/x6-plugin-transform'; // 图形变换
// import { Scroller } from '@antv/x6-plugin-scroller'; // 滚动画布

const map = {
  enabled: true,
  minWidth: 200,
  maxWidth: 700,
  minHeight: 100,
  maxHeight: 500,
  orthogonal: false,
  restrict: false,
  preserveAspectRatio: false,
};

// 注册 Vue component
register({
  shape: 'custom-vue-node',
  component: CustomNode,
});

export default {
  name: 'Er',
  props: {
    data: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      value: '',
      graph: null,
      isShow: false,
      showRight: false,
      isFullScreen: false,
      typeMap: manage.typeMap,
    };
  },
  watch: {
    data(val) {
      const cells = [];
      this.data.forEach(item => {
        console.log(item, item.shape);
        if (item.shape === 'edge') {
          cells.push(this.graph.createEdge(item)); // 创建连线
        } else {
          cells.push(this.graph.createNode(item)); // 创建节点
        }
      });
      // 清空画布并添加用指定的节点/边
      this.graph.resetCells(cells);
    },
  },
  mounted() {
    this.graphInit();
  },
  methods: {
    graphInit() {
      // 容器生成图表
      const containerRef = this.$refs.containerRef;
      const graph = new Graph({
        container: containerRef,
        background: {
          color: '#F1F6F9',
        },
        grid: {
          size: 10, // 网格大小 10px
          visible: true, // 绘制网格,默认绘制 dot 类型网格
          type: 'fixedDot',
          args: {
            color: '#AFB0B1', // 网点颜色
            thickness: 1, // 网点大小
          },
        },
        panning: true, // 画布拖拽
        history: true, // 启动历史记录
        selecting: {
          // 选择与框选
          enabled: true,
          rubberband: true,
          movable: true,
          strict: true,
          showNodeSelectionBox: true, // 显示节点的选择框(才能进行移动)
          modifiers: ['alt'],
        },
        // Scroller 使画布具备滚动、平移、居中、缩放等能力
        scroller: {
          enabled: true,
          pageVisible: true,
          pageBreak: true,
          pannable: true,
        },
        // 鼠标滚轮的默认行为是滚动页面 使用ctrl+滚轮 实现缩放
        mousewheel: {
          enabled: true,
          modifiers: ['ctrl', 'meta'], // +按键为缩放
          minScale: 0.5,
          maxScale: 2,
        },
        snapline: true, // 对齐线

        // 节点连接
        connecting: {
          router: {
            name: 'er',
            args: {
              offset: 25,
              direction: 'H',
            },
          },
          snap: true, // 自动吸附
          allowBlank: false, // 是否允许连接到画布空白位置的点
          allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
          allowNode: false, // 是否允许边链接到节点(非节点上的链接桩)
          createEdge() {
            return new Shape.Edge({
              attrs: {
                line: {
                  stroke: '#1684FC',
                  strokeWidth: 2,
                },
              },
            });
          },
        },
        // 连接桩样式 -- 高亮
        highlighting: {
          magnetAvailable: {
            name: 'stroke',
            args: {
              padding: 4,
              attrs: {
                strokeWidth: 4,
                stroke: '#1684FC',
              },
            },
          },
        },
      });

      // 小地图
      const minimapContainer = this.$refs.minimapContainer;
      graph.use(
        new MiniMap({
          container: minimapContainer,
          width: '250',
          height: '150',
          scalable: true, // 是否可缩放
          minScale: 0.01,
          maxScale: 16,
        }),
      );

      // 图形
      graph.use(
        new Transform({
          enabled: true,
          resizing: map,
        }),
      );

      // 缩放画布内容,使画布内容充满视口
      graph.zoomToFit({ padding: 10, maxScale: 1 });

      // 赋值生成
      this.graph = graph;

      // 事件集合
      this.loadEvents(containerRef);
    },
    // 事件集合
    loadEvents(containerRef) {
      // 节点双击
      this.graph.on('node:dblclick', ({ node }) => {
        const data = node.store.data;
        console.log(data);
        this.$router.push({
          path: '/modeling/homeModeling',
          query: {
            id: data.modelingId,
            name: data.name,
            layerTypeId: data.layerTypeId,
            tableType: data.modelingType,
          },
        });
      });
      // 连线双击
      this.graph.on('edge:dblclick', ({ edge }) => {
        // const data = edge.store.data;
        // const { type, id } = data;
        // alert('连线双击');
        // console.log('edge:dbclick', edge);
        // if (type === 'taskNode') {
        //   this.nodeId = id;
        //   this.showRight = true;
        // } else {
        //   this.nodeId = '';
        //   this.showRight = false;
        // }
      });
      // 节点鼠标移入
      this.graph.on(
        'node:mouseenter',
        FunctionExt.debounce(({ node }) => {
          // 添加删除
          // const x = node.store.data.size.width - 10;
          // node.addTools({
          //   name: 'button-remove',
          //   args: {
          //     x: 0,
          //     y: 0,
          //     offset: { x, y: 15 },
          //   },
          // });
        }),
        500,
      );
      this.graph.on('node:port-contextmenu', ({ e }) => {
        // console.log(
        //   'ports',
        //   e,
        //   e.currentTarget.parentElement.getAttribute('port'),
        // );
      });
      // 连接线鼠标移入
      this.graph.on('edge:mouseenter', ({ edge }) => {
        // edge.addTools([
        //   'source-arrowhead',
        //   'target-arrowhead',
        //   {
        //     name: 'button-remove',
        //     args: {
        //       distance: '50%',
        //     },
        //   },
        // ]);
      });
      // 节点鼠标移出
      this.graph.on('node:mouseleave', ({ node }) => {
        // // 移除删除
        // node.removeTools();
      });
      this.graph.on('edge:mouseleave', ({ edge }) => {
        // edge.removeTools();
      });
      this.graph.on('edge:connected', ({ isNew, edge }) => {
        // console.log('connected', edge.source, edge.target);
        // if (isNew) {
        //   // 对新创建的边进行插入数据库等持久化操作
        // }
      });
    },
    // 放大
    zoomInFn() {
      this.graph.zoom(0.1);
    },
    // 缩小
    zoomOutFn() {
      const Num = Number(this.graph.zoom().toFixed(1));
      if (Num > 0.1) {
        this.graph.zoom(-0.1);
      }
    },
    // 重置1:1
    resetFn() {
      this.graph.centerContent();
      this.graph.zoomTo(1); // 缩放画布到指定的比例
    },
    // 刷新
    redoFn() {
      this.$emit('detailsEr');
    },
    // 全屏
    fullScreen() {
      // const element = document.documentElement;
      const element = document.getElementById('container');
      // 判断是否已经是全屏
      if (this.isFullScreen) {
        // 退出全屏
        if (document.exitFullscreen) {
          document.exitFullscreen();
        } else if (document.webkitCancelFullScreen) {
          document.webkitCancelFullScreen();
        } else if (document.mozCancelFullScreen) {
          document.mozCancelFullScreen();
        } else if (document.msExitFullscreen) {
          document.msExitFullscreen();
        }
      } else {
        // 全屏
        if (element.requestFullscreen) {
          element.requestFullscreen();
        } else if (element.webkitRequestFullScreen) {
          element.webkitRequestFullScreen();
        } else if (element.mozRequestFullScreen) {
          element.mozRequestFullScreen();
        } else if (element.msRequestFullscreen) {
          // IE11
          element.msRequestFullscreen();
        }
      }
      this.isFullScreen = !this.isFullScreen;
    },
    // 搜索
    search() {
      this.isShow = !this.isShow;
    },
    // 保存
    submit() {
      const data = this.graph.getNodes();
      this.$emit('submitEr', data);
    },
    // 检索
    valChange(val) {
      if (val) {
        // false - 清空
        const nodes = this.graph.getNodes() || [];
        const node = nodes.filter(item => item.id === val)[0] || {};
        this.graph.centerCell(node); // 将节点/边的中心与视口中心对齐
      } else {
        this.resetFn();
      }
    },
  },
};
</script>

<style lang="scss" scoped>
.antv-x6 {
  width: 100%;
  height: 100%;
  padding: 0;
  display: flex;
  position: relative;
  box-sizing: border-box;
  -moz-box-sizing: border-box;
  -webkit-box-sizing: border-box;
  ::v-deep body {
    min-width: auto;
  }
  .node-c {
    width: 200px;
    border-right: 1px solid #eee;
    padding: 20px;
    dl {
      margin-bottom: 20px;
      line-height: 30px;
      display: flex;
      cursor: move;
      dt {
        &.circle {
          width: 30px;
          height: 30px;
          border-radius: 50%;
          &.start {
            border: 1px solid green;
            background: greenyellow;
          }
          &.end {
            border: 1px solid salmon;
            background: red;
          }
        }
        &.rect {
          width: 30px;
          height: 30px;
          border: 1px solid #ccc;
        }
      }
      dd {
        font-size: bold;
        font-size: 14px;
        padding: 0 0 0 10px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
    }
  }
  .template-c {
    padding: 10px 0;
    li {
      line-height: 40px;
      font-size: 14px;
      border-bottom: 1px solid #dcdfe6;
      cursor: pointer;
      display: flex;
      justify-content: space-between;
      span {
        flex: 1;
        padding-right: 10px;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      i {
        font-size: 14px;
        color: #2d8cf0;
        width: 20px;
        line-height: 40px;
      }
    }
  }
  .container {
    flex: 1;
  }
  .operating {
    position: absolute;
    z-index: 999;
    right: 20px;
    top: 10px;
    padding: 5px 10px;
    border-radius: 6px;
    background-color: #ffffff;
    border: 1px solid rgb(187, 187, 187);
    box-shadow: 1px 1px 4px 0 #0a0a0a2e;
    display: flex;
    height: 34px;
    align-items: center;
    .el-select {
      transition: width 0.6s ease-in-out;
      ::v-deep .el-input__inner {
        height: 26px;
        line-height: 26px;
      }
      ::v-deep .el-input--mini .el-input__icon {
        line-height: 26px;
      }
      ::v-deep .el-select-dropdown__item {
        height: 48px;
        max-width: 410px;
        line-height: 48px;
      }
      &.hideSelect {
        width: 0px;
        ::v-deep .el-input__inner {
          display: none;
        }
        ::v-deep .el-input__suffix {
          display: none;
        }
      }
      &.showSelect {
        width: 180px;
        ::v-deep .el-input__inner {
          display: block;
        }
        ::v-deep .el-input__suffix {
          display: block;
        }
      }
    }
    .icon_oper {
      svg {
        font-size: 18px;
        cursor: pointer;
        margin: 0 5px;
        &:hover {
          color: #2d8cf0;
        }
        &.opacity {
          opacity: 0.5;
        }
      }
    }
  }
}
.app-mini {
  position: fixed;
  z-index: 999;
  bottom: 10px;
  right: 20px;
  border-radius: 6px;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
.app-content {
  flex: 1;
  height: 100% !important;
}
::v-deep .x6-graph-scroller {
  border: 1px solid #f0f0f0;
  margin-left: -1px;
  width: 100% !important;
  height: 100% !important;
}

.head_top {
  width: 100%;
  height: 48px;
  display: flex;
  align-items: center;
  .code_warp {
    width: 90%;
    height: 100%;
    font-size: 12px;
    margin-left: 8px;
    display: flex;
    gap: 4px;
    flex-direction: column;
    justify-content: center;
    .code {
      color: black;
      font-weight: 700;
      line-height: normal;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      word-break: break-all;
    }
    .name {
      color: #b3b2bf;
      font-weight: 600;
      line-height: normal;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      word-break: break-all;
    }
  }
}
::v-deep .text {
  height: 32px;
  display: flex;
  gap: 1px;
  font-size: 13px;
  position: relative;
  padding-left: 20px;
  align-items: center;
  svg {
    position: absolute;
    left: 4px;
    top: 10px;
  }
  .type {
    width: 25%;
    height: 24px;
    font-size: 12px;
    line-height: 24px;
    text-align: center;
    border-radius: 4px;
    margin-right: 5px;
    display: inline-block;
    background-color: #f7f7f9;
  }
  span {
    flex: 1;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    word-break: break-all;
    line-height: 18px;
  }

  &:hover {
    background: #f8f8fa;
  }
}
</style>

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

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

相关文章

【书生·浦语大模型实战营】第三节 课后作业

基于 InternLM 和 LangChain 搭建你的知识库 0.课程链接1.课后作业1.1基础作业&#xff1a;1.2 进阶作业&#xff1a; 0.课程链接 课程标题&#xff1a;基于 InternLM 和 LangChain 搭建你的知识库 课程链接&#xff1a;https://github.com/InternLM/tutorial/blob/main/langch…

【体育】体育锻炼之体能耐力、户外运动、中华武术(附大学生体质健康标准)

程序员养生指南之 【体育】体育锻炼之体能耐力、户外运动、中华武术&#xff08;附大学生体质健康标准&#xff09; 文章目录 一、如何增强耐力与体能&#xff1f;1、耐力训练的原理2、如何进行耐力训练3、如何提高长跑成绩 二、户外运动之绳结与下降1、绳结2、下降 三、中华武…

C语言 for 循环语句的基本格式是什么?

一、问题 for 循环语句在C语⾔中是最为常见的循环语句&#xff0c;其功能强⼤&#xff0c;⽽且⽤法灵活&#xff0c;那么它的基本格式是什么呢&#xff1f; 二、解答 for 语句的⼀般形式为&#xff1a; for(表达式1;表达式2;表达3&#xff09;语句; 每条 for 语句包含三个⽤分…

2023国赛样题路由部分【RIP RIPNG ACLRIP ACLRIPNG ISIS NAT64】

RT1串行链路、RT2串行链路、FW1、AC1之间分别运行RIP和RIPng协议&#xff0c;FW1、RT1、RT2的RIP和RIPng发布loopback2地址路由&#xff0c;AC1 RIP发布loopback2地址路由&#xff0c;AC1 RIPng采用route-map匹配prefix-list重发布loopback2地址路由。RT1配置offset值为3的路由…

ES6 | (二)ES6 新特性(下) | 尚硅谷Web前端ES6教程

文章目录 &#x1f4da;迭代器&#x1f407;定义&#x1f407;工作原理&#x1f407;自定义遍历数据 &#x1f4da;生成器函数&#x1f407;声明和调用&#x1f407;生成器函数的参数传递&#x1f407;生成器函数案例 &#x1f4da;Promise&#x1f4da;Set&#x1f407;Set的定…

输入一个整数,输出其最长连续因子。

输入一个整数&#xff0c;输出其最长连续因子。 例如 输入&#xff1a;60 输出&#xff1a;2 3 4 5 6 注意&#xff1a;1不算因子 输入输出格式 输入描述: 输入一个整数N&#xff0c;N<10000。 输出描述: 输出其最长连续因子&#xff0c;如果有多个最长&#xff0c;输出…

vcruntime140.dll为什么都是?解决vcruntime140.dll丢失的七种办法

计算机中一个常见的错误提示是“无法继续执行代码&#xff0c;因为找不到vcruntime140.dll”。这个问题的出现可能是由于vcruntime140.dll文件丢失或损坏导致的。本文将介绍解决这个问题的7个方法&#xff0c;并详细解释vcruntime140.dll丢失的原因以及其属性介绍。 一、vcrun…

解决在 Mac 上安装 Adobe 软件弹出提示:安装包已经被损坏并且不能被打开。

问题&#xff1a; “INSTALLER” is damaged and can’t be opened. You should eject the disk image. 解决方法和步骤&#xff1a; 打开安装包&#xff1b;将安装包 “INSTALLER” 拖动复制到某个文件夹&#xff0c;复制后的文件路径例如像这样&#xff1a;/Users/michael…

Pake 轻松构建轻量级多端桌面应用

Pake 利用 Rust 轻松构建轻量级多端桌面应用&#xff0c;支持 Mac / Windows / Linux。 小白用户&#xff1a;可以使用 「常用包下载」 方式来体验 Pake 的能力&#xff0c;也可试试 Action 方式。 开发用户&#xff1a;可以使用 「命令行一键打包」&#xff0c;对 Mac 比较友…

二维码门楼牌管理系统技术服务:构建智慧城市新标准

文章目录 前言一、二维码门楼牌管理系统的诞生背景二、标准地址编码的定义与作用三、二维码门楼牌管理系统的核心技术四、二维码门楼牌管理系统的应用优势五、二维码门楼牌管理系统在智慧城市建设中的作用六、结论与展望 前言 随着城市化的快速发展&#xff0c;传统的门楼牌管…

波奇学Linux:进程通信之消息队列,操作系统管理ipc资源,信号量

进程通信的前提&#xff1a;必须让不同进程看到同一份资源。 管道&#xff1a;文件缓冲区 共享内存&#xff1a;内存块 消息队列&#xff1a;队列 消息队列 让不同的进程看到同一个队列&#xff0c;允许不同的进程向内核发送带类型的数据块 带类型是为了区分数据块是由哪个…

Linux——匿名管道

Linux——匿名管道 什么是管道匿名管道的底层原理观察匿名管道现象读写端的几种情况写端慢&#xff0c;读端快写端快&#xff0c;读端慢 管道的大小写端关闭&#xff0c;读端一直读写端一直写&#xff0c;读端关闭 我们之前一直用的是vim来编写代码&#xff0c;现在有了vscode这…

Vue路由(黑马程序员)

路由介绍 将资代码/vue-project(路由)/vue-project/src/views/tlias/DeptView.vue拷贝到我们当前EmpView.vue同级&#xff0c;其结构如下&#xff1a; 此时我们希望&#xff0c;实现点击侧边栏的部门管理&#xff0c;显示部门管理的信息&#xff0c;点击员工管理&#xff0c;显…

CK98-数学家键盘配置

官方驱动和说明书下载地址 https://www.coolkiller.cn/download/lists_6.html 介绍&#xff1a;https://new.qq.com/rain/a/20221229A09B1M00 官方CK-98数学家驱动版本&#xff08;谨慎更新&#xff09; 如果升级驱动出现问题&#xff0c;重启驱动软件后会默认让你恢复的。 …

vue ts html 中如何遍历 Enum 类型构建页面结构

vue ts html 中如何遍历 Enum 类型构建页面结构 Enum 被用在一些有明确有限数量的值的时候&#xff0c;比如定义菜单的类型 一、需求 定义了一个 Enum 用来标记菜单类型&#xff1a; enum EnumMenuType {目录 1,菜单,按钮,外链 }你需要知道 Enum 它的序号是随第一个定义的值…

第四十七回 一丈青单捉王矮虎 宋公明二打祝家庄-强大而灵活的python装饰器

四面全是埋伏&#xff0c;宋江和众人一直绕圈跑不出去。正在慌乱之时&#xff0c;石秀及时赶到&#xff0c;教大家碰到白杨树就转弯走。走了一段时间&#xff0c;发现围的人越来越多&#xff0c;原来祝家庄以灯笼指挥号令。花荣一箭射下来红灯龙&#xff0c;伏兵自己就乱起来了…

简单控件属性设置

1、设置文本的内容 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

计算机毕业设计分享-ssm心理咨询预约管理系统 19086(赠送源码数据库)JAVA、PHP,node.js,C++、python,大屏数据可视化等

本科生毕业设计&#xff08;论文&#xff09; 题 目心理咨询预约管理系统的设计与实现 学 院 XXXXX 专业班级 XXXXX 学生姓名 XXXX 指导岗位 XXXX 撰写日期&#xff1a;2023年4月 目 录 摘要 1 绪论 1.1背景及意义 …

【K8S类型系统】一文梳理 K8S 各类型概念之间的关系(GVK/GVR/Object/Schema/RestMapper)

参考 k8s 官方文档 https://kubernetes.io/zh-cn/docs/reference/https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/ 重点 Kubernetes源码学习-kubernetes基础数据结构 - 知乎 重点 Kubernetes类型系统 | 李乾坤的博客 重点 k8s源码学习-三大核心数…

Linux如何查看端口是否占用

在Linux中&#xff0c;有多种方法可以用来检查端口是否被占用。以下是一些常用的命令&#xff1a; netstat&#xff1a;这是一个非常通用的命令&#xff0c;可以用来查看所有端口的使用情况。如果你想查找特定的端口是否被占用&#xff0c;可以使用netstat命令配合grep。例如&…