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

一、 首先 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.mfbz.cn/a/419916.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

openGauss学习笔记-231 openGauss性能调优-系统调优-资源负载管理-资源负载管理概述

文章目录 openGauss学习笔记-231 openGauss性能调优-系统调优-资源负载管理-资源负载管理概述231.1 功能描述231.2 相关概念**231.2.1 资源管理****231.2.2 控制组****231.2.3 资源池** openGauss学习笔记-231 openGauss性能调优-系统调优-资源负载管理-资源负载管理概述 231.…

java009 - Java调试debugger

1、debugger概述 程序的调试工具&#xff0c;用于查看追踪程序的执行流程&#xff0c;也可以调试程序。 2、debugger调试流程 2.1 如何加断点 2.2 如何运行加了断点的程序 在代码区域右键---->debugger执行 2.3 看哪里 看console窗口 2.4 点哪里 点step into(F7)这个箭…

异常处理(黑马学习笔记)

当前问题 登录功能和登录校验功能我们都实现了&#xff0c;下面我们学习下今天最后一块技术点&#xff1a;异常处理。首先我们先来看一下系统出现异常之后会发生什么现象&#xff0c;再来介绍异常处理的方案。 我们打开浏览器&#xff0c;访问系统中的新增部门操作&#xff0…

potplayer安装

官网 解压运行即可

小(2)型土石坝安全监测设施配置详解

小(2)型土石坝的安全监测是确保大坝稳定、安全运行的重要环节。为此&#xff0c;合理配置安全监测设施显得尤为重要。以下是对小(2)型土石坝安全监测设施配置的详细介绍。 一、渗流量监测 渗流量是反映大坝安全状况的关键指标之一。为准确监测渗流量&#xff0c;我们采用仪器量…

DC28V270V转AC36V115V航空逆变器

在当今的航空航天电源行业中&#xff0c;DC28V270V转AC36V115V航空逆变器发挥着至关重要的作用。作为一种关键的逆变器电源设备&#xff0c;DC28V270V转AC36V115V航空逆变器不仅在航空领域有着广泛的应用&#xff0c;还在许多其他领域发挥着重要作用。 一、DC28V270V转AC36V11…

C语言 常量

常量是固定值&#xff0c;在程序执行期间不会改变。这些固定的值&#xff0c;又叫做字面量。 常量可以是任何的基本数据类型&#xff0c;比如整数常量、浮点常量、字符常量&#xff0c;或字符串字面值&#xff0c;也有枚举常量。 常量就像是常规的变量&#xff0c;只不过常量的…

【前端素材】推荐优质在线高端蜂蜜商城电商网页Beejar平台模板(附源码)

一、需求分析 1、系统定义 在线高端蜂蜜商城是指一个专门销售高品质、高端蜂蜜产品的电子商务平台。这种商城致力于向消费者提供各种经过精心挑选、具有高营养价值和健康功效的蜂蜜产品。 2、功能需求 在线高端蜂蜜商城是指一个专门销售高品质、高端蜂蜜产品的电子商务平台…

JOSEF约瑟 FHP-33Q/4跳位、合位、电源监视综合控制继电器 凸出式板前接线 0.1-10S

FHP-33系列跳位、合位、电源监视综合控制继电器系列型号&#xff1a;FHP-33A/1跳位、合位、电源监视综合控制继电器&#xff1b;FHP-33A/2跳位、合位、电源监视综合控制继电器&#xff1b;FHP-33A/3跳位、合位、电源监视综合控制继电器&#xff1b;FHP-33A/4跳位、合位、电源监…

数据迁移方法论

文章目录 一. 什么是数据迁移1. 数据迁移与数据整合2. 数据迁移和数据复制3. 数据迁移的主要类型1. Storage migration2. 数据库迁移3. 应用迁移4. 数据中心迁移5. Business process migration&#xff08;业务流程迁移&#xff09;6. 云迁移 二. 数据迁移方法1.大爆炸式数据迁…

说一说kong日志级别

Kong官网&#xff1a;The Platform Powering the API World | Kong Inc. Kong Gateway&#xff1a;Kong Gateway | Kong Docs Kong Admin API&#xff1a;Admin API - Kong Gateway - v3.4.x | Kong Docs Kong 企业版社区&#xff1a;API Community for Developers and Industr…

Freesia项目目录结构

目录结构 前端目录&#xff1a; &#xff08;目录结构来自layui-vue-admin&#xff09; src文件下 api&#xff08;前端请求后端服务的路由&#xff09;assert&#xff08;一些内置或必要的资源文件&#xff09;layouts&#xff08;全局框架样式组件&#xff09;router&…

OPPO打响AI手机第一枪

明敏 发自 凹非寺 量子位 | 公众号 QbitAI 2024开年&#xff0c;AI趋势依旧高歌猛进。 一边&#xff0c;Sora爆火成为现象级AIGC应用&#xff0c;带动AI再度成为春节后全民热议的第一话题。另一边&#xff0c;手机厂商开始大举All in AI&#xff0c;“放弃传统智能手机”、“…

071:vue+cesium 实现下雨效果

第071个 点击查看专栏目录 本示例的目的是介绍如何在vue+cesium中实现下雨效果,这里使用着色器来实现实例特效。 直接复制下面的 vue+cesium源代码,操作2分钟即可运行实现效果. 文章目录 示例效果配置方式示例源代码(共120行)着色代码实现心得:专栏目标示例效果

基于java+springboot动物检疫信息管理系统设计和实现

基于java SSM springboot动物检疫信息管理系统设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文…

文件基础和文件fd

文章目录 预备知识C语言的文件接口系统调用文件fd 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 预备知识 我们平时说文件就是说文件里…

【前端素材】推荐优质在线通用果蔬商城电商网页eStore平台模板(附源码)

一、需求分析 1、系统定义 通用果蔬网站是指专门提供各类果蔬产品展示和销售的在线平台。它将不同种类的新鲜水果、蔬菜、干果、坚果等聚集在一起&#xff0c;为消费者提供方便、快捷的购物渠道。 2、功能需求 通用果蔬网站是指专门提供各类果蔬产品展示和销售的在线平台。…

关键对话_

关键对话 https://www.bilibili.com/video/BV1Vh4y1E7sY 关键对话&#xff0c;是那些让你觉得很艰难&#xff0c;同时又非常重要的谈话 关键对话三个特征 1、对话双方的观点有很大差距&#xff0c; 比如说你和你父母观点差异很大&#xff0c;父母觉得到了一定年龄就该结婚啊…

C++:非静态成员默认初始化

C11之前只有常静态成员变量才能进行默认初始化&#xff0c;其它变量初始化时总要进行繁琐的过程 class A{int a; public:A():a(10){} };C11开始支持非静态成员的默认初始化&#xff0c;默认初始化和初始化参数列表同时初始化一个变量时会使用初始化参数列表&#xff0c;不进行…

IIS发布PHP网站字体404解决办法

最近在使用 IIS 发布 PHP 网站时&#xff0c;我遇到了一个前端问题&#xff0c;即字体库文件 404 错误。这个问题的根本原因是 IIS 未能正确识别字体文件类型&#xff0c;导致浏览器在加载页面时无法正确获取所需字体资源&#xff0c;进而触发了404错误。这样的问题会导致网站页…
最新文章