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

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

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

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

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

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

三、事件集合

javascript 复制代码
    // 事件集合
    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) {
        //   // 对新创建的边进行插入数据库等持久化操作
        // }
      });
    },

四、画布初始化

javascript 复制代码
    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自定义节点

javascript 复制代码
<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,

});

javascript 复制代码
import CustomNode from '../node';
import { register } from '@antv/x6-vue-shape'; // vue节点


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

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

javascript 复制代码
// 连接线  
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];

通过数据 渲染节点

javascript 复制代码
  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主页面 全部代码

javascript 复制代码
<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>
相关推荐
精进攻城狮@16 分钟前
Redis(value的数据类型)
数据库·redis
爪哇学长23 分钟前
SQL 注入详解:原理、危害与防范措施
xml·java·数据库·sql·oracle
陈随易42 分钟前
农村程序员-关于小孩教育的思考
前端·后端·程序员
云深时现月43 分钟前
jenkins使用cli发行uni-app到h5
前端·uni-app·jenkins
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ1 小时前
mybatisPlus打印sql配置
数据库·sql
昨天今天明天好多天1 小时前
【Node.js]
前端·node.js
弗拉唐1 小时前
将Excel文件的两个表格经过验证后分别读取到Excel表和数据库
数据库·excel
2401_857610031 小时前
深入探索React合成事件(SyntheticEvent):跨浏览器的事件处理利器
前端·javascript·react.js
刘艳兵的学习博客1 小时前
刘艳兵-DBA033-如下那种应用场景符合Oracle ROWID存储规则?
服务器·数据库·oracle·面试·刘艳兵
_xaboy1 小时前
开源项目低代码表单设计器FcDesigner扩展自定义的容器组件.例如col
vue.js·低代码·开源·动态表单·formcreate·低代码表单·可视化表单设计器