Vue3 + antv/x6 实现流程图

news2024/11/18 6:42:59

新建流程图

在这里插入图片描述

// AddDag.vue
<template>
  <div class="content-main">
    <div class="tool-container">
      <div @click="undo" class="command" title="后退">
        <Icon icon="ant-design:undo-outlined" />
      </div>
      <div @click="redo" class="command" title="前进">
        <Icon icon="ant-design:redo-outlined" />
      </div>
      <el-divider direction="vertical" />
      <div @click="copy" class="command" title="复制">
        <Icon icon="ant-design:copy-filled" />
      </div>
      <div @click="paste" class="command" title="粘贴">
        <Icon icon="fa-solid:paste" />
      </div>
      <div @click="del" class="command" title="删除">
        <Icon icon="ant-design:delete-filled" />
      </div>
      <el-divider direction="vertical" />
      <div @click="save" class="command" title="保存">
        <Icon icon="ant-design:save-filled" />
      </div>
      <el-divider direction="vertical" />
      <div @click="exportPng" class="command" title="导出PNG">
        <Icon icon="ant-design:file-image-filled" />
      </div>
    </div>
    <div class="content-container" id="">
      <div class="content">
        <div class="stencil" ref="stencilContainer"></div>
        <div class="graph-content" id="graphContainer" ref="graphContainer"> </div>

        <div class="editor-sidebar">
          <div class="edit-panel">
            <el-card shadow="never">
              <template #header>
                <div class="card-header">
                  <span>{{ cellFrom.title }}</span>
                </div>
              </template>
              <el-form :model="nodeFrom" label-width="50px" v-if="nodeFrom.show">
                <el-form-item label="label">
                  <el-input v-model="nodeFrom.label" @blur="changeLabel" />
                </el-form-item>
                <el-form-item label="desc">
                  <el-input type="textarea" v-model="nodeFrom.desc" @blur="changeDesc" />
                </el-form-item>
              </el-form>
              <el-form :model="cellFrom" label-width="50px" v-if="cellFrom.show">
                <el-form-item label="label">
                  <el-input v-model="cellFrom.label" @blur="changeEdgeLabel" />
                </el-form-item>
                <!-- <el-form-item label="连线方式">
                    <el-select v-model="cellFrom.edgeType" class="m-2" placeholder="Select"  @change="changeEdgeType">
                      <el-option
                        v-for="item in EDGE_TYPE_LIST"
                        :key="item.type"
                        :label="item.name"
                        :value="item.type"
                      />
                    </el-select>
                  </el-form-item> -->
              </el-form>
            </el-card>
          </div>
          <div>
            <el-card shadow="never">
              <template #header>
                <div class="card-header">
                  <span>Minimap</span>
                </div>
              </template>
              <div class="minimap" ref="miniMapContainer"></div>
            </el-card>
          </div>
        </div>
      </div>
    </div>
    <div v-if="showMenu" class="node-menu" ref="nodeMenu">
      <div
        class="menu-item"
        v-for="(item, index) in PROCESSING_TYPE_LIST"
        :key="index"
        @click="addNodeTool(item)"
      >
        <el-image :src="item.image" style="width: 16px; height: 16px" fit="fill" />
        <span>{{ item.name }}</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Graph, Path, Edge, StringExt, Node, Cell, Model, DataUri } from '@antv/x6'
import { Transform } from '@antv/x6-plugin-transform'
import { Selection } from '@antv/x6-plugin-selection'
import { Snapline } from '@antv/x6-plugin-snapline'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { History } from '@antv/x6-plugin-history'
import { MiniMap } from '@antv/x6-plugin-minimap'
//import { Scroller } from '@antv/x6-plugin-scroller'
import { Stencil } from '@antv/x6-plugin-stencil'
import { Export } from '@antv/x6-plugin-export'
import { ref, onMounted, reactive, toRefs, nextTick, onUnmounted } from 'vue'
import '@/styles/animation.less'
import { ElMessage, ElCard, ElForm, ElFormItem, ElInput, ElImage, ElDivider } from 'element-plus'

const stencilContainer = ref()
const graphContainer = ref()
const miniMapContainer = ref()

let graph: any = null

const state = reactive({
  cellFrom: {
    title: 'Canvas',
    label: '',
    desc: '',
    show: false,
    id: '',
    edgeType: 'topBottom'
  },
  nodeFrom: {
    title: 'Canvas',
    label: '',
    desc: '',
    show: false,
    id: ''
  },
  showMenu: false,
  data: {
    nodes: [
      {
        id: 'ac51fb2f-2753-4852-8239-53672a29bb14',
        position: {
          x: -340,
          y: -160
        },
        data: {
          name: '诗名',
          type: 'OUTPUT',
          desc: '春望'
        }
      },
      {
        id: '81004c2f-0413-4cc6-8622-127004b3befa',
        position: {
          x: -340,
          y: -10
        },
        data: {
          name: '第一句',
          type: 'SYNC',
          desc: '国破山河在'
        }
      },
      {
        id: '7505da25-1308-4d7a-98fd-e6d5c917d35d',
        position: {
          x: -140,
          y: 180
        },
        data: {
          name: '结束',
          type: 'INPUT',
          desc: '城春草木胜'
        }
      }
    ],
    edges: [
      {
        id: '6eea5dc9-4e15-4e78-959f-ee13ec59d11c',
        shape: 'processing-curve',
        source: { cell: 'ac51fb2f-2753-4852-8239-53672a29bb14', port: '-out' },
        target: { cell: '81004c2f-0413-4cc6-8622-127004b3befa', port: '-in' },
        zIndex: -1,
        data: {
          source: 'ac51fb2f-2753-4852-8239-53672a29bb14',
          target: '81004c2f-0413-4cc6-8622-127004b3befa'
        }
      },
      {
        id: '8cbce713-54be-4c07-8efa-59c505f74ad7',
        labels: ['下半句'],
        shape: 'processing-curve',
        source: { cell: '81004c2f-0413-4cc6-8622-127004b3befa', port: '-out' },
        target: { cell: '7505da25-1308-4d7a-98fd-e6d5c917d35d', port: '-in' },
        data: {
          source: '81004c2f-0413-4cc6-8622-127004b3befa',
          target: '7505da25-1308-4d7a-98fd-e6d5c917d35d'
        }
      }
    ]
  },
  // 节点状态列表
  nodeStatusList: [
    {
      id: 'ac51fb2f-2753-4852-8239-53672a29bb14',
      status: 'success'
    },
    {
      id: '81004c2f-0413-4cc6-8622-127004b3befa',
      status: 'success'
    }
  ],

  // 边状态列表
  edgeStatusList: [
    {
      id: '6eea5dc9-4e15-4e78-959f-ee13ec59d11c',
      status: 'success'
    },
    {
      id: '8cbce713-54be-4c07-8efa-59c505f74ad7',
      status: 'executing'
    }
  ],
  // 加工类型列表
  PROCESSING_TYPE_LIST: [
    {
      type: 'SYNC',
      name: '数据同步',
      image: new URL('@/assets/imgs/persimmon.png', import.meta.url).href
    },
    {
      type: 'INPUT',
      name: '结束',
      image: new URL('@/assets/imgs/lime.png', import.meta.url).href
    }
  ],
  //边类型
  EDGE_TYPE_LIST: [
    {
      type: 'topBottom',
      name: '上下'
    },
    {
      type: 'leftRight',
      name: '左右'
    }
  ]
})

const { cellFrom, nodeFrom, showMenu, PROCESSING_TYPE_LIST } = toRefs(state)

let nodeMenu = ref()

// 节点类型
enum NodeType {
  INPUT = 'INPUT', // 数据输入
  FILTER = 'FILTER', // 数据过滤
  JOIN = 'JOIN', // 数据连接
  UNION = 'UNION', // 数据合并
  AGG = 'AGG', // 数据聚合
  OUTPUT = 'OUTPUT', // 数据输出
  SYNC = 'SYNC' //数据同步
}

// 元素校验状态
// enum CellStatus {
//   DEFAULT = 'default',
//   SUCCESS = 'success',
//   ERROR = 'error'
// }

// 节点位置信息
interface Position {
  x: number
  y: number
}

function init() {
  graph = new Graph({
    container: graphContainer.value,
    grid: true,
    panning: {
      enabled: true,
      eventTypes: ['leftMouseDown', 'mouseWheel']
    },
    mousewheel: {
      enabled: true,
      modifiers: 'ctrl',
      factor: 1.1,
      maxScale: 1.5,
      minScale: 0.5
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#fff',
            stroke: '#31d0c6',
            strokeWidth: 4
          }
        }
      }
    },
    connecting: {
      snap: true,
      allowBlank: false,
      allowLoop: false,
      highlight: true,
      // sourceAnchor: {
      //   name: 'bottom',
      //   args: {
      //     dx: 0,
      //   },
      // },
      // targetAnchor: {
      //   name: 'top',
      //   args: {
      //     dx: 0,
      //   },
      // },
      createEdge() {
        return graph.createEdge({
          shape: 'processing-curve',
          attrs: {
            line: {
              strokeDasharray: '5 5'
            }
          },
          zIndex: -1
        })
      },
      // 连接桩校验
      validateConnection({ sourceMagnet, targetMagnet }) {
        // 只能从输出链接桩创建连接
        if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
          return false
        }
        // 只能连接到输入链接桩
        if (!targetMagnet || targetMagnet.getAttribute('port-group') === 'out') {
          return false
        }
        return true
      }
    }
  })
  graph.centerContent()

  // #region 使用插件
  graph
    .use(
      new Transform({
        resizing: true,
        rotating: true
      })
    )
    .use(
      new Selection({
        rubberband: true,
        showNodeSelectionBox: true
      })
    )
    .use(
      new MiniMap({
        container: miniMapContainer.value,
        width: 200,
        height: 260,
        padding: 10
      })
    )
    .use(new Snapline())
    .use(new Keyboard())
    .use(new Clipboard())
    .use(new History())
    .use(new Export())
  //.use(new Scroller({
  //  enabled: true,
  //  pageVisible: true,
  //  pageBreak: false,
  //  pannable: true,

  // }))
  // #endregion

  // #region 初始化图形
  const ports = {
    groups: {
      in: {
        position: 'top',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      out: {
        position: 'bottom',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      }
    }
    // items: [
    //   {
    //     id: state.currentCode + '-in',
    //     group: 'top',
    //   },
    //   {
    //     id: state.currentCode + '-out',
    //     group: 'out',
    //   }
    // ],
  }

  Graph.registerNode(
    'custom-node',
    {
      inherit: 'rect',
      width: 140,
      height: 76,
      attrs: {
        body: {
          strokeWidth: 1
        },
        image: {
          width: 16,
          height: 16,
          x: 12,
          y: 6
        },
        text: {
          refX: 40,
          refY: 15,
          fontSize: 15,
          'text-anchor': 'start'
        },
        label: {
          text: 'Please nominate this node',
          refX: 10,
          refY: 30,
          fontSize: 12,
          fill: 'rgba(0,0,0,0.6)',
          'text-anchor': 'start',
          textWrap: {
            width: -10, // 宽度减少 10px
            height: '70%', // 高度为参照元素高度的一半
            ellipsis: true, // 文本超出显示范围时,自动添加省略号
            breakWord: true // 是否截断单词
          }
        }
      },
      markup: [
        {
          tagName: 'rect',
          selector: 'body'
        },
        {
          tagName: 'image',
          selector: 'image'
        },
        {
          tagName: 'text',
          selector: 'text'
        },
        {
          tagName: 'text',
          selector: 'label'
        }
      ],
      data: {},
      relation: {},
      ports: { ...ports }
    },
    true
  )

  const stencil = new Stencil({
    //新建节点库
    title: '数据集成',
    target: graph,
    search: false, // 搜索
    collapsable: true,
    stencilGraphWidth: 300, //容器宽度
    stencilGraphHeight: 600, //容器长度
    groups: [
      //分组
      {
        name: 'processLibrary',
        title: 'dataSource'
      }
    ],
    layoutOptions: {
      dx: 30,
      dy: 20,
      columns: 1, //列数(行内节点数)
      columnWidth: 130, //列宽
      rowHeight: 100 //行高
    }
  })
  stencilContainer.value.appendChild(stencil.container)

  // 控制连接桩显示/隐藏
  // eslint-disable-next-line no-undef
  const showPorts = (ports: NodeListOf<SVGElement>, show: boolean) => {
    for (let i = 0, len = ports.length; i < len; i += 1) {
      ports[i].style.visibility = show ? 'visible' : 'hidden'
    }
  }
  graph.on('node:mouseenter', () => {
    const container = graphContainer.value
    const ports = container.querySelectorAll('.x6-port-body')
    showPorts(ports, true)
  })
  graph.on('node:mouseleave', () => {
    const container = graphContainer.value
    const ports = container.querySelectorAll(
      '.x6-port-body'
      // eslint-disable-next-line no-undef
    ) as NodeListOf<SVGElement>
    showPorts(ports, false)
  })

  // #region 快捷键与事件
  graph.bindKey(['meta+c', 'ctrl+c'], () => {
    // const cells = graph.getSelectedCells()
    // if (cells.length) {
    //   graph.copy(cells)
    // }
    // return false
    copy()
  })
  graph.bindKey(['meta+x', 'ctrl+x'], () => {
    const cells = graph.getSelectedCells()
    if (cells.length) {
      graph.cut(cells)
    }
    return false
  })
  graph.bindKey(['meta+v', 'ctrl+v'], () => {
    // if (!graph.isClipboardEmpty()) {
    //   const cells = graph.paste({ offset: 32 })
    //   graph.cleanSelection()
    //   graph.select(cells)
    // }
    // return false
    paste()
  })

  // undo redo
  graph.bindKey(['meta+z', 'ctrl+z'], () => {
    // if (graph.canUndo()) {
    //   graph.undo()
    // }
    // return false
    undo()
  })
  graph.bindKey(['meta+y', 'ctrl+y'], () => {
    // if (graph.canRedo()) {
    //   graph.redo()
    // }
    // return false
    redo()
  })
  // select all
  graph.bindKey(['meta+a', 'ctrl+a'], () => {
    const nodes = graph.getNodes()
    if (nodes) {
      graph.select(nodes)
    }
  })

  // delete
  graph.bindKey('backspace', () => {
    // const cells = graph.getSelectedCells()
    // if (cells.length) {
    //   graph.removeCells(cells)
    // }
    del()
  })

  // zoom
  graph.bindKey(['ctrl+1', 'meta+1'], () => {
    const zoom = graph.zoom()
    if (zoom < 1.5) {
      graph.zoom(0.1)
    }
  })
  graph.bindKey(['ctrl+2', 'meta+2'], () => {
    const zoom = graph.zoom()
    if (zoom > 0.5) {
      graph.zoom(-0.1)
    }
  })
  // 节点移入画布事件
  graph.on('node:added', ({ node }: any) => {
    // console.log(node,cell);
    addNodeInfo(node)
  })
  //  节点单击事件
  graph.on('node:click', ({ node }: any) => {
    //  console.log(node,cell)
    addNodeInfo(node)
  })

  //节点被选中时显示添加节点按钮
  graph.on('node:selected', (args: { cell: Cell; node: Node; options: Model.SetOptions }) => {
    if (NodeType.INPUT != args.node.data.type) {
      args.node.removeTools()
      args.node.addTools({
        name: 'button',
        args: {
          x: 0,
          y: 0,
          offset: { x: 160, y: 40 },
          markup: [
            //自定义的删除按钮样式
            {
              tagName: 'circle',
              selector: 'button',
              attrs: {
                r: 8,
                stroke: 'rgba(0,0,0,.25)',
                strokeWidth: 1,
                fill: 'rgba(255, 255, 255, 1)',
                cursor: 'pointer'
              }
            },
            {
              tagName: 'text',
              textContent: '+',
              selector: 'icon',
              attrs: {
                fill: 'rgba(0,0,0,.25)',
                fontSize: 15,
                textAnchor: 'middle',
                pointerEvents: 'none',
                y: '0.3em',
                stroke: 'rgba(0,0,0,.25)'
              }
            }
          ],
          onClick({ e, view }: any) {
            //      console.log(e,cell);
            showNodeTool(e, view)
          }
        }
      })
    }
    // code here
  })

  //节点被取消选中时触发。
  graph.on('node:unselected', (args: { cell: Cell; node: Node; options: Model.SetOptions }) => {
    args.node.removeTools()
  })

  // 添加边事件
  graph.on('edge:added', ({ edge }: any) => {
    // console.log(edge);
    addEdgeInfo(edge)
    edge.data = {
      source: edge.source.cell,
      target: edge.target.cell
    }
  })
  //  线单击事件
  graph.on('edge:click', ({ edge }: any) => {
    //  console.log(node,cell)
    addEdgeInfo(edge)
  })

  //边选中事件
  graph.on('edge:selected', (args: { cell: Cell; edge: Edge; options: Model.SetOptions }) => {
    args.edge.attr('line/strokeWidth', 3)
  })

  //边被取消选中时触发。
  graph.on('edge:unselected', (args: { cell: Cell; edge: Edge; options: Model.SetOptions }) => {
    args.edge.attr('line/strokeWidth', 1)
  })

  const nodeShapes = [
    {
      label: '开始',
      nodeType: 'OUTPUT' as NodeType
    },
    {
      label: '数据同步',
      nodeType: 'SYNC' as NodeType
    },
    {
      label: '结束',
      nodeType: 'INPUT' as NodeType
    }
  ]

  const nodes = nodeShapes.map((item) => {
    const id = StringExt.uuid()
    const node = {
      id: id,
      shape: 'custom-node',
      // label: item.label,
      ports: getPortsByType(item.nodeType, id),
      data: {
        name: `${item.label}`,
        type: item.nodeType
      },
      attrs: getNodeAttrs(item.nodeType)
    }
    const newNode = graph.addNode(node)
    return newNode
  })

  //#endregion
  stencil.load(nodes, 'processLibrary')
}

// 根据节点的类型获取ports
const getPortsByType = (type: NodeType, nodeId: string) => {
  let ports = [] as any
  switch (type) {
    case NodeType.INPUT:
      ports = [
        {
          id: `${nodeId}-in`,
          group: 'in'
        },
        {
          id: `${nodeId}-left`,
          group: 'left'
        },
        {
          id: `${nodeId}-right`,
          group: 'right'
        }
      ]
      break
    case NodeType.OUTPUT:
      ports = [
        {
          id: `${nodeId}-out`,
          group: 'out'
        },
        {
          id: `${nodeId}-left`,
          group: 'left'
        },
        {
          id: `${nodeId}-right`,
          group: 'right'
        }
      ]
      break
    default:
      ports = [
        {
          id: `${nodeId}-in`,
          group: 'in'
        },
        {
          id: `${nodeId}-out`,
          group: 'out'
        },
        {
          id: `${nodeId}-left`,
          group: 'left'
        },
        {
          id: `${nodeId}-right`,
          group: 'right'
        }
      ]
      break
  }
  return ports
}

// 注册连线 --上下
Graph.registerConnector(
  'curveConnectorTB',
  (s, e) => {
    const offset = 4
    const deltaY = Math.abs(e.y - s.y)
    const control = Math.floor((deltaY / 3) * 2)

    const v1 = { x: s.x, y: s.y + offset + control }
    const v2 = { x: e.x, y: e.y - offset - control }

    return Path.normalize(
      `M ${s.x} ${s.y}
         L ${s.x} ${s.y + offset}
         C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
         L ${e.x} ${e.y}
        `
    )
  },
  true
)

// 注册连线--左右
Graph.registerConnector(
  'curveConnectorLR',
  (sourcePoint, targetPoint) => {
    const hgap = Math.abs(targetPoint.x - sourcePoint.x)
    const path = new Path()
    path.appendSegment(Path.createSegment('M', sourcePoint.x - 4, sourcePoint.y))
    path.appendSegment(Path.createSegment('L', sourcePoint.x + 12, sourcePoint.y))
    // 水平三阶贝塞尔曲线
    path.appendSegment(
      Path.createSegment(
        'C',
        sourcePoint.x < targetPoint.x ? sourcePoint.x + hgap / 2 : sourcePoint.x - hgap / 2,
        sourcePoint.y,
        sourcePoint.x < targetPoint.x ? targetPoint.x - hgap / 2 : targetPoint.x + hgap / 2,
        targetPoint.y,
        targetPoint.x - 6,
        targetPoint.y
      )
    )
    path.appendSegment(Path.createSegment('L', targetPoint.x + 2, targetPoint.y))

    return path.serialize()
  },
  true
)

Graph.registerEdge(
  'processing-curve',
  {
    inherit: 'edge',
    markup: [
      {
        tagName: 'path',
        selector: 'wrap',
        attrs: {
          fill: 'none',
          cursor: 'pointer',
          stroke: 'transparent',
          strokeLinecap: 'round'
        }
      },
      {
        tagName: 'path',
        selector: 'line',
        attrs: {
          fill: 'none',
          pointerEvents: 'none'
        }
      }
    ],
    connector: { name: 'smooth' }, //curveConnectorTB
    attrs: {
      wrap: {
        connection: true,
        strokeWidth: 10,
        strokeLinejoin: 'round'
      },
      line: {
        connection: true,
        stroke: '#A2B1C3',
        strokeWidth: 1,
        targetMarker: {
          name: 'classic',
          size: 6
        }
      }
    }
  },
  true
)

// Graph.registerEdge(
//   'processing-curve-lr',
//   {
//   inherit: 'edge',
//   markup: [
//       {
//         tagName: 'path',
//         selector: 'wrap',
//         attrs: {
//           fill: 'none',
//           cursor: 'pointer',
//           stroke: 'transparent',
//           strokeLinecap: 'round',
//         },
//       },
//       {
//         tagName: 'path',
//         selector: 'line',
//         attrs: {
//           fill: 'none',
//           pointerEvents: 'none',
//         },
//       },
//     ],
//     connector: { name: 'curveConnectorLR' },
//     attrs: {
//       wrap: {
//         connection: true,
//         strokeWidth: 10,
//         strokeLinejoin: 'round',
//       },
//       line: {
//         connection: true,
//         stroke: '#A2B1C3',
//         strokeWidth: 1,
//         targetMarker: {
//           name: 'classic',
//           size: 6,
//         },
//       },
//     },
// },
//   true,
// )

//保存
function save() {
  console.log('save')
  const graphData = graph.toJSON()
  console.log(graphData)
}

//撤销
function undo() {
  if (graph.canUndo()) {
    graph.undo()
  }
  return false
}
//取消撤销
function redo() {
  if (graph.canRedo()) {
    graph.redo()
  }
  return false
}
//复制
function copy() {
  const cells = graph.getSelectedCells()
  if (cells.length) {
    graph.copy(cells)
  }
  return false
}
//粘贴
function paste() {
  if (!graph.isClipboardEmpty()) {
    const cells = graph.paste({ offset: 32 })
    graph.cleanSelection()
    graph.select(cells)
  }
  return false
}
//删除
function del() {
  const cells = graph.getSelectedCells()
  if (cells.length) {
    graph.removeCells(cells)
  }
}

//导出PNG
function exportPng() {
  graph.toPNG(
    (dataUri: string) => {
      // 下载
      DataUri.downloadDataUri(dataUri, 'chart.png')
    },
    {
      padding: {
        top: 20,
        right: 20,
        bottom: 20,
        left: 20
      }
    }
  )
  //graph.exportPNG('a.png',{padding:'20px'});
}

function addNodeInfo(node: any) {
  state.nodeFrom.title = 'Node'
  state.nodeFrom.label = node.label
  state.nodeFrom.desc = node.attrs.label.text
  state.nodeFrom.show = true
  state.nodeFrom.id = node.id
  state.cellFrom.show = false
}

function addEdgeInfo(edge: any) {
  state.nodeFrom.show = false
  state.cellFrom.title = 'Edge'
  if (edge.labels[0]) {
    state.cellFrom.label = edge.labels[0].attrs.label.text
  } else {
    state.cellFrom.label = ''
  }
  state.cellFrom.edgeType = edge.data ? edge.data.edgeType : ''
  state.cellFrom.show = true
  state.cellFrom.id = edge.id
}
//修改文本
function changeLabel() {
  const nodes = graph.getNodes()
  nodes.forEach((node: any) => {
    if (state.nodeFrom.id == node.id) {
      node.label = state.nodeFrom.label
    }
  })
}

//修改描述
function changeDesc() {
  const nodes = graph.getNodes()
  nodes.forEach((node: any) => {
    if (state.nodeFrom.id == node.id) {
      node.attr('label/text', state.nodeFrom.desc)
    }
  })
}

//修改边文本
function changeEdgeLabel() {
  const edges = graph.getEdges()
  edges.forEach((edge: any) => {
    if (state.cellFrom.id == edge.id) {
      edge.setLabels(state.cellFrom.label)
      console.log(edge)
    }
  })
}

//修改边的类型
// function changeEdgeType() {
//   const edges = graph.getEdges()
//   edges.forEach((edge: any) => {
//     if (state.cellFrom.id == edge.id) {
//       //    console.log(state.cellFrom.edgeType);
//       if (state.cellFrom.edgeType == 'topBottom') {
//         edge.setConnector('curveConnectorTB')
//       } else {
//         edge.setConnector('curveConnectorLR')
//         //      console.log(edge);
//       }
//       edge.data.edgeType = state.cellFrom.edgeType
//     }
//   })
// }

const getNodeAttrs = (nodeType: string) => {
  let attr = {} as any
  switch (nodeType) {
    case NodeType.INPUT:
      attr = {
        image: {
          'xlink:href': new URL('@/assets/imgs/lime.png', import.meta.url).href
        },
        //左侧拖拽样式
        body: {
          fill: '#b9dec9',
          stroke: '#229453'
        },
        text: {
          text: '结束',
          fill: '#229453'
        }
      }
      break
    case NodeType.SYNC:
      attr = {
        image: {
          'xlink:href': new URL('@/assets/imgs/persimmon.png', import.meta.url).href
        },
        //左侧拖拽样式
        body: {
          fill: '#edc3ae',
          stroke: '#f9723d'
        },
        text: {
          text: '数据同步',
          fill: '#f9723d'
        }
      }
      break
    case NodeType.OUTPUT:
      attr = {
        image: {
          'xlink:href': new URL('@/assets/imgs/rice.png', import.meta.url).href
        },
        //左侧拖拽样式
        body: {
          fill: '#EFF4FF',
          stroke: '#5F95FF'
        },
        text: {
          text: '开始',
          fill: '#5F95FF'
        }
      }
      break
  }
  return attr
}

//加载初始节点
function getData() {
  let cells = [] as any
  const location = state.data
  location.nodes.map((node) => {
    let attr = getNodeAttrs(node.data.type)
    if (node.data.desc) {
      attr.label = { text: node.data.desc }
    }
    if (node.data.name) {
      let temp = attr.text
      if (temp) {
        temp.text = node.data.name
      }
    }
    cells.push(
      graph.addNode({
        id: node.id,
        x: node.position.x,
        y: node.position.y,
        shape: 'custom-node',
        attrs: attr,
        ports: getPortsByType(node.data.type as NodeType, node.id),
        data: node.data
      })
    )
  })
  location.edges.map((edge) => {
    cells.push(
      graph.addEdge({
        id: edge.id,
        source: edge.source,
        target: edge.target,
        zIndex: edge.zIndex,
        shape: 'processing-curve',
        //  connector: { name: 'curveConnector' },
        labels: edge.labels,
        attrs: { line: { strokeDasharray: '5 5' } },
        data: edge.data
      })
    )
  })
  graph.resetCells(cells)
}

// 开启边的运行动画
const excuteAnimate = (edge: any) => {
  edge.attr({
    line: {
      stroke: '#3471F9'
    }
  })
  edge.attr('line/strokeDasharray', 5)
  edge.attr('line/style/animation', 'running-line 30s infinite linear')
}

// 显示边状态
const showEdgeStatus = () => {
  state.edgeStatusList.forEach((item) => {
    const edge = graph.getCellById(item.id)
    if (item.status == 'success') {
      edge.attr('line/strokeDasharray', 0)
      edge.attr('line/stroke', '#52c41a')
    } else if ('error' == item.status) {
      edge.attr('line/stroke', '#ff4d4f')
    } else if ('executing' == item.status) {
      excuteAnimate(edge)
    }
  })
}

// 显示添加按钮菜单
function showNodeTool(e: any, _view: any) {
  //  console.log(view);
  state.showMenu = true
  nextTick(() => {
    nodeMenu.value.style.top = e.offsetY + 60 + 'px'
    nodeMenu.value.style.left = e.offsetX + 210 + 'px'
  })
}

// 点击添加节点按钮
function addNodeTool(item: any) {
  //  console.log(item);
  createDownstream(item.type)
  state.showMenu = false
}

/**
 * 根据起点初始下游节点的位置信息
 * @param node 起始节点
 * @param graph
 * @returns
 */
const getDownstreamNodePosition = (node: Node, graph: Graph, dx = 250, dy = 100) => {
  // 找出画布中以该起始节点为起点的相关边的终点id集合
  const downstreamNodeIdList: string[] = []
  graph.getEdges().forEach((edge) => {
    const originEdge = edge.toJSON()?.data
    console.log(node)
    if (originEdge.source === node.id) {
      downstreamNodeIdList.push(originEdge.target)
    }
  })
  // 获取起点的位置信息
  const position = node.getPosition()
  let minX = Infinity
  let maxY = -Infinity
  graph.getNodes().forEach((graphNode) => {
    if (downstreamNodeIdList.indexOf(graphNode.id) > -1) {
      const nodePosition = graphNode.getPosition()
      // 找到所有节点中最左侧的节点的x坐标
      if (nodePosition.x < minX) {
        minX = nodePosition.x
      }
      // 找到所有节点中最x下方的节点的y坐标
      if (nodePosition.y > maxY) {
        maxY = nodePosition.y
      }
    }
  })

  return {
    x: minX !== Infinity ? minX : position.x + dx,
    y: maxY !== -Infinity ? maxY + dy : position.y
  }
}

// 创建下游的节点和边
const createDownstream = (type: NodeType) => {
  //  console.log(graph.getSelectedCells());
  const cells = graph.getSelectedCells()
  if (cells.length == 1) {
    const node = cells[0]
    //console.log(node,"node");
    if (graph) {
      // 获取下游节点的初始位置信息
      const position = getDownstreamNodePosition(node, graph)
      // 创建下游节点
      const newNode = createNode(type, graph, position)
      const source = node.id
      const target = newNode.id
      // 创建该节点出发到下游节点的边
      createEdge(source, target, graph)
    }
  } else {
    ElMessage({
      message: '请选择一个节点',
      type: 'warning'
    })
  }
}

const createNode = (type: NodeType, graph: Graph, position?: Position): Node => {
  let newNode = {} as Node
  const typeName = state.PROCESSING_TYPE_LIST?.find((item) => item.type === type)?.name
  const id = StringExt.uuid()
  const node = {
    id,
    shape: 'custom-node',
    x: position?.x,
    y: position?.y,
    ports: getPortsByType(type, id),
    data: {
      name: `${typeName}`,
      type
    },
    attrs: getNodeAttrs(type)
  }
  newNode = graph.addNode(node)
  return newNode
}

const createEdge = (source: string, target: string, graph: Graph) => {
  const edge = {
    id: StringExt.uuid(),
    shape: 'processing-curve',
    source: {
      cell: source
      // port: `${source}-out`,
    },
    target: {
      cell: target
      //  port: `${target}-in`,
    },
    zIndex: -1,
    data: {
      source,
      target
    },
    attrs: { line: { strokeDasharray: '5 5' } }
  }
  // console.log(edge);
  if (graph) {
    graph.addEdge(edge)
  }
}

onMounted(() => {
  init()
  // graph.fromJSON(state.data);
  getData()
  showEdgeStatus()
})

onUnmounted(() => {
  graph.dispose()
})
</script>

<style lang="less" scoped>
.content-main {
  display: flex;
  width: 100%;
  flex-direction: column;
  height: calc(100vh - 85px - 40px);
  background-color: #ffffff;
  position: relative;

  .tool-container {
    padding: 8px;
    display: flex;
    align-items: center;
    color: rgba(0, 0, 0, 0.45);

    .command {
      display: inline-block;
      width: 27px;
      height: 27px;
      margin: 0 6px;
      padding-top: 6px;
      text-align: center;
      cursor: pointer;
    }
  }
}
.content-container {
  position: relative;
  width: 100%;
  height: 100%;
  .content {
    width: 100%;
    height: 100%;
    position: relative;

    min-width: 400px;
    min-height: 600px;
    display: flex;
    border: 1px solid #dfe3e8;
    flex-direction: row;
    //   flex-wrap: wrap;
    flex: 1 1;

    .stencil {
      width: 250px;
      height: 100%;
      border-right: 1px solid #dfe3e8;
      position: relative;

      :deep(.x6-widget-stencil) {
        background-color: #fff;
      }
      :deep(.x6-widget-stencil-title) {
        background-color: #fff;
      }
      :deep(.x6-widget-stencil-group-title) {
        background-color: #fff !important;
      }
    }
    .graph-content {
      width: calc(100% - 180px);
      height: 100%;
    }

    .editor-sidebar {
      display: flex;
      flex-direction: column;
      border-left: 1px solid #e6f7ff;
      background: #fafafa;
      z-index: 9;

      .el-card {
        border: none;
      }
      .edit-panel {
        flex: 1 1;
        background-color: #fff;
      }

      :deep(.x6-widget-minimap-viewport) {
        border: 1px solid #8f8f8f;
      }

      :deep(.x6-widget-minimap-viewport-zoom) {
        border: 1px solid #8f8f8f;
      }
    }
  }
}

:deep(.x6-widget-transform) {
  margin: -1px 0 0 -1px;
  padding: 0px;
  border: 1px solid #239edd;
}
:deep(.x6-widget-transform > div) {
  border: 1px solid #239edd;
}
:deep(.x6-widget-transform > div:hover) {
  background-color: #3dafe4;
}
:deep(.x6-widget-transform-active-handle) {
  background-color: #3dafe4;
}
:deep(.x6-widget-transform-resize) {
  border-radius: 0;
}
:deep(.x6-widget-selection-inner) {
  border: 1px solid #239edd;
}
:deep(.x6-widget-selection-box) {
  opacity: 0;
}

.topic-image {
  visibility: hidden;
  cursor: pointer;
}
.x6-node:hover .topic-image {
  visibility: visible;
}
.x6-node-selected rect {
  stroke-width: 2px;
}
.node-menu {
  position: absolute;
  box-shadow: var(--el-box-shadow-light);
  background: var(--el-bg-color-overlay);
  border: 1px solid var(--el-border-color-light);
  padding: 5px 0px;

  .menu-item {
    display: flex;
    align-items: center;
    white-space: nowrap;
    list-style: none;
    line-height: 22px;
    padding: 5px 16px;
    margin: 0;
    font-size: var(--el-font-size-base);
    color: var(--el-text-color-regular);
    cursor: pointer;
    outline: none;
    box-sizing: border-box;
  }

  .menu-item .el-image {
    margin-right: 5px;
  }

  .menu-item:hover {
    background-color: var(--el-color-primary-light-9);
    color: var(--el-color-primary);
  }
}
</style>


显示流程图

在这里插入图片描述

<template>
  <div class="content-main">
    <div class="content-container" id="">
      <div class="content">
        <div class="graph-content" id="graphContainer" ref="graphContainer"></div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { Graph, Path, Edge } from '@antv/x6'
import { ref, onMounted, reactive } from 'vue'
import '@/styles/animation.less'

const graphContainer = ref()

let graph: any = null

const state = reactive({
  data: {
    nodes: [
      {
        id: 'ac51fb2f-2753-4852-8239-53672a29bb14',
        x: -340,
        y: -160,
        ports: [
          {
            id: 'ac51fb2f-2753-4852-8239-53672a29bb14_out',
            group: 'out'
          }
        ],
        data: {
          name: '数据输入_1',
          type: 'OUTPUT',
          checkStatus: 'sucess'
        },
        attrs: {
          body: {
            fill: '#EFF4FF',
            stroke: '#5F95FF'
          },
          image: {
            'xlink:href': 'http://localhost:20002/src/assets/imgs/rice.png'
          },
          label: {
            text: '春望'
          },
          text: {
            fill: '#5F95FF',
            text: '开始'
          }
        }
      },
      {
        id: '81004c2f-0413-4cc6-8622-127004b3befa',
        x: -340,
        y: -10,
        ports: [
          {
            id: '81004c2f-0413-4cc6-8622-127004b3befa_in',
            group: 'in'
          },
          {
            id: '81004c2f-0413-4cc6-8622-127004b3befa_out',
            group: 'out'
          }
        ],
        data: {
          name: '数据输入_1',
          type: 'SYAN',
          checkStatus: 'sucess'
        },
        attrs: {
          body: {
            fill: '#edc3ae',
            stroke: '#f9723d'
          },
          image: {
            'xlink:href': 'http://localhost:20002/src/assets/imgs/persimmon.png'
          },
          label: {
            text: '国破山河在'
          },
          text: {
            fill: '#f9723d',
            text: '数据同步'
          }
        }
      },
      {
        id: '7505da25-1308-4d7a-98fd-e6d5c917d35d',
        x: -140,
        y: 180,
        ports: [
          {
            id: '7505da25-1308-4d7a-98fd-e6d5c917d35d_in',
            group: 'in'
          }
        ],
        data: {
          name: '数据输入_1',
          type: 'INPUT',
          checkStatus: 'sucess'
        },
        attrs: {
          body: {
            fill: '#b9dec9',
            stroke: '#229453'
          },
          image: {
            'xlink:href': 'http://localhost:20002/src/assets/imgs/lime.png'
          },
          label: {
            text: '城春草木胜'
          },
          text: {
            fill: '#229453',
            text: '结束'
          }
        }
      }
    ],
    edges: [
      {
        attrs: { line: { strokeDasharray: '5 5' } },
        connector: { name: 'curveConnector' },
        id: '6eea5dc9-4e15-4e78-959f-ee13ec59d11c',
        shape: 'data-processing-curve',
        source: { cell: 'ac51fb2f-2753-4852-8239-53672a29bb14', port: '_out' },
        target: { cell: '81004c2f-0413-4cc6-8622-127004b3befa', port: '_in' },
        zIndex: -1
      },
      {
        attrs: { line: { strokeDasharray: '5 5' } },
        connector: { name: 'curveConnector' },
        id: '8cbce713-54be-4c07-8efa-59c505f74ad7',
        labels: ['下半句'],
        shape: 'data-processing-curve',
        source: { cell: '81004c2f-0413-4cc6-8622-127004b3befa', port: '_out' },
        target: { cell: '7505da25-1308-4d7a-98fd-e6d5c917d35d', port: '_in' }
      }
    ]
  },
  // 节点状态列表
  nodeStatusList: [
    {
      id: 'ac51fb2f-2753-4852-8239-53672a29bb14',
      status: 'success'
    },
    {
      id: '81004c2f-0413-4cc6-8622-127004b3befa',
      status: 'success'
    }
  ],

  // 边状态列表
  edgeStatusList: [
    {
      id: '6eea5dc9-4e15-4e78-959f-ee13ec59d11c',
      status: 'success'
    },
    {
      id: '8cbce713-54be-4c07-8efa-59c505f74ad7',
      status: 'executing'
    }
  ]
})

// const { data } = toRefs(state)

// // 节点类型
// enum NodeType {
//   INPUT = 'INPUT', // 数据输入
//   FILTER = 'FILTER', // 数据过滤
//   JOIN = 'JOIN', // 数据连接
//   UNION = 'UNION', // 数据合并
//   AGG = 'AGG', // 数据聚合
//   OUTPUT = 'OUTPUT' // 数据输出
// }

function init() {
  graph = new Graph({
    container: graphContainer.value,
    interacting: function () {
      return { nodeMovable: false }
    },
    grid: true,
    panning: {
      enabled: false,
      eventTypes: ['leftMouseDown', 'mouseWheel']
    },
    mousewheel: {
      enabled: true,
      modifiers: 'ctrl',
      factor: 1.1,
      maxScale: 1.5,
      minScale: 0.5
    },
    highlighting: {
      magnetAdsorbed: {
        name: 'stroke',
        args: {
          attrs: {
            fill: '#fff',
            stroke: '#31d0c6',
            strokeWidth: 4
          }
        }
      }
    },
    connecting: {
      snap: true,
      allowBlank: false,
      allowLoop: false,
      highlight: true,
      sourceAnchor: {
        name: 'bottom',
        args: {
          dx: 0
        }
      },
      targetAnchor: {
        name: 'top',
        args: {
          dx: 0
        }
      },
      createEdge() {
        return graph.createEdge({
          shape: 'data-processing-curve',
          attrs: {
            line: {
              strokeDasharray: '5 5'
            }
          },
          zIndex: -1
        })
      },
      // 连接桩校验
      validateConnection({ sourceMagnet, targetMagnet }) {
        // 只能从输出链接桩创建连接
        if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
          return false
        }
        // 只能连接到输入链接桩
        if (!targetMagnet || targetMagnet.getAttribute('port-group') === 'out') {
          return false
        }
        return true
      }
    }
  })
  graph.centerContent()

  // #region 初始化图形
  const ports = {
    groups: {
      in: {
        position: 'top',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      out: {
        position: 'bottom',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#31d0c6',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      left: {
        position: 'left',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      },
      right: {
        position: 'right',
        attrs: {
          circle: {
            r: 4,
            magnet: true,
            stroke: '#5F95FF',
            strokeWidth: 1,
            fill: '#fff',
            style: {
              visibility: 'hidden'
            }
          }
        }
      }
    }
    // items: [
    //   {
    //     id: state.currentCode + '_in',
    //     group: 'top',
    //   },
    //   {
    //     id: state.currentCode + '_out',
    //     group: 'out',
    //   }
    // ],
  }

  Graph.registerNode(
    'custom-node',
    {
      inherit: 'rect',
      width: 140,
      height: 76,
      attrs: {
        body: {
          strokeWidth: 1
        },
        image: {
          width: 16,
          height: 16,
          x: 12,
          y: 6
        },
        text: {
          refX: 40,
          refY: 15,
          fontSize: 15,
          'text-anchor': 'start'
        },
        label: {
          text: 'Please nominate this node',
          refX: 10,
          refY: 30,
          fontSize: 12,
          fill: 'rgba(0,0,0,0.6)',
          'text-anchor': 'start',
          textWrap: {
            width: -10, // 宽度减少 10px
            height: '70%', // 高度为参照元素高度的一半
            ellipsis: true, // 文本超出显示范围时,自动添加省略号
            breakWord: true // 是否截断单词
          }
        }
      },
      markup: [
        {
          tagName: 'rect',
          selector: 'body'
        },
        {
          tagName: 'image',
          selector: 'image'
        },
        {
          tagName: 'text',
          selector: 'text'
        },
        {
          tagName: 'text',
          selector: 'label'
        }
      ],
      data: {},
      relation: {},
      ports: { ...ports }
    },
    true
  )

  // 注册连线
  Graph.registerConnector(
    'curveConnector',
    (s, e) => {
      const offset = 4
      const deltaY = Math.abs(e.y - s.y)
      const control = Math.floor((deltaY / 3) * 2)

      const v1 = { x: s.x, y: s.y + offset + control }
      const v2 = { x: e.x, y: e.y - offset - control }

      return Path.normalize(
        `M ${s.x} ${s.y}
         L ${s.x} ${s.y + offset}
         C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
         L ${e.x} ${e.y}
        `
      )
    },
    true
  )
}

Edge.config({
  markup: [
    {
      tagName: 'path',
      selector: 'wrap',
      attrs: {
        fill: 'none',
        cursor: 'pointer',
        stroke: 'transparent',
        strokeLinecap: 'round'
      }
    },
    {
      tagName: 'path',
      selector: 'line',
      attrs: {
        fill: 'none',
        pointerEvents: 'none'
      }
    }
  ],
  connector: { name: 'curveConnector' },
  attrs: {
    wrap: {
      connection: true,
      strokeWidth: 10,
      strokeLinejoin: 'round'
    },
    line: {
      connection: true,
      stroke: '#A2B1C3',
      strokeWidth: 1,
      targetMarker: {
        name: 'classic',
        size: 6
      }
    }
  }
})

Graph.registerEdge('data-processing-curve', Edge, true)

function getData() {
  let cells = [] as any
  const location = state.data
  location.nodes.map((node) => {
    cells.push(
      graph.addNode({
        id: node.id,
        x: node.x,
        y: node.y,
        shape: 'custom-node',
        attrs: node.attrs,
        ports: node.ports,
        data: node.data
      })
    )
  })
  location.edges.map((edge) => {
    cells.push(
      graph.addEdge({
        id: edge.id,
        source: edge.source,
        target: edge.target,
        zIndex: edge.zIndex,
        shape: 'data-processing-curve',
        connector: { name: 'curveConnector' },
        labels: edge.labels,
        attrs: edge.attrs
      })
    )
  })
  graph.resetCells(cells)
}

// 开启边的运行动画
const excuteAnimate = (edge: any) => {
  edge.attr({
    line: {
      stroke: '#3471F9'
    }
  })
  edge.attr('line/strokeDasharray', 5)
  edge.attr('line/style/animation', 'running-line 30s infinite linear')
}

// 显示边状态
const showEdgeStatus = () => {
  state.edgeStatusList.forEach((item) => {
    const edge = graph.getCellById(item.id)
    if (item.status == 'success') {
      edge.attr('line/strokeDasharray', 0)
      edge.attr('line/stroke', '#52c41a')
    } else if ('error' == item.status) {
      edge.attr('line/stroke', '#ff4d4f')
    } else if ('executing' == item.status) {
      excuteAnimate(edge)
    }
  })
}

onMounted(() => {
  init()
  // graph.fromJSON(state.data);
  getData()
  showEdgeStatus()
})
</script>

<style lang="less" scoped>
.content-main {
  display: flex;
  width: 100%;
  flex-direction: column;
  height: calc(100vh - 85px - 40px);
  background-color: #ffffff;
  position: relative;
}
.content-container {
  position: relative;
  width: 100%;
  height: 100%;
  .content {
    width: 100%;
    height: 100%;
    position: relative;

    min-width: 400px;
    min-height: 600px;
    display: flex;
    border: 1px solid #dfe3e8;
    flex-direction: row;
    //   flex-wrap: wrap;
    flex: 1 1;

    .graph-content {
      width: calc(100%);
      height: 100%;
    }
  }
}

:deep(.x6-widget-transform) {
  margin: -1px 0 0 -1px;
  padding: 0px;
  border: 1px solid #239edd;
}
:deep(.x6-widget-transform > div) {
  border: 1px solid #239edd;
}
:deep(.x6-widget-transform > div:hover) {
  background-color: #3dafe4;
}
:deep(.x6-widget-transform-active-handle) {
  background-color: #3dafe4;
}
:deep(.x6-widget-transform-resize) {
  border-radius: 0;
}
:deep(.x6-widget-selection-inner) {
  border: 1px solid #239edd;
}
:deep(.x6-widget-selection-box) {
  opacity: 0;
}

.topic-image {
  visibility: hidden;
  cursor: pointer;
}
.x6-node:hover .topic-image {
  visibility: visible;
}
.x6-node-selected rect {
  stroke-width: 2px;
}
</style>

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

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

相关文章

kibana新增查看更新删除es中的数据

登录kibana&#xff0c;打开开发工具 写入数据 PUT test20240311/person/1 {"name": "张三","mobile":"13011111111" } 查询数据 GET /test20240311/person/_search {"query": {"term": {"mobile": {…

类和对象练习题

第1题 import java.util.Scanner; public class Homework01{public static void main(String[] args){//创建一个double类型的数组double array[]new double[6];//创建一个输入对象Scanner inputnew Scanner(System.in);//提示信息&#xff1a;对数组元素进行赋值System.out.pr…

docker-swarm集群管理命令

为什么选择swarm集群&#xff1f; 灵魂疑问&#xff1a;同样是集群&#xff0c;为什么选择docker swarm&#xff0c;而不不选择k8s或者k3s&#xff1f; 我的需求场景&#xff1a;不想直接用docker或者java -jar直接跑&#xff0c;修改前是使用java -jar方式&#xff0c;这两种…

爬虫练习:获取某网站的房价信息

一、相关网站 二、相关代码 import requests from lxml import etree import csv with open(房天下数据.csv, w, newline, encodingutf-8) as csvfile:fieldnames [名称, 地点,价格,总价,联系电话]writer csv.DictWriter(csvfile, fieldnamesfieldnames)writer.writeheader…

【UE5】非持枪站姿移动混合空间

项目资源文末百度网盘自取 创建角色在非持枪状态且站立移动的动画混合空间 在Character文件夹中创建文件夹&#xff0c;命名为BlendSpace 所有混合空间文件都放到这个文件夹中 在BlendSpace文件夹中单击右键&#xff0c;选择动画(Animation)中的混合空间(BlendSpace) 选择SK…

爬虫技术之正则提取静态页面数据

第一天 简单示例 在爬虫过程中&#xff0c;我们获取到了页面之后&#xff0c;通常需要做的就是解析数据&#xff0c;将数据持久化到数据库为我所用。如何又快又准确得提取有效数据&#xff1f;这是一门技术&#xff0c;看了我的博客之前可能略有难度&#xff0c;但各位大师看…

OpenvSwitch VXLAN 隧道实验

OpenvSwitch VXLAN 隧道实验 最近在了解 openstack 网络&#xff0c;下面基于ubuntu虚拟机安装OpenvSwitch&#xff0c;测试vxlan的基本配置。 节点信息&#xff1a; 主机名IP地址OS网卡node1192.168.95.11Ubuntu 22.04ens33node2192.168.95.12Ubuntu 22.04ens33 网卡信息&…

Win10系统使用IIS服务搭建WebDAV网站结合内网穿透公网访问本地文件

文章目录 推荐1. 安装IIS必要WebDav组件2. 客户端测试3. cpolar内网穿透3.1 打开Web-UI管理界面3.2 创建隧道3.3 查看在线隧道列表3.4 浏览器访问测试 4. 安装Raidrive客户端4.1 连接WebDav服务器4.2 连接成功4.2 连接成功总结&#xff1a; 推荐 前些天发现了一个巨牛的人工智能…

【单调栈】代码随想录算法训练营第五十九天 |503.下一个更大元素II, 42. 接雨水 (待补充)

503.下一个更大元素II 1、题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 2、文章讲解&#xff1a;代码随想录 3、题目&#xff1a; 给定一个循环数组&#xff08;最后一个元素的下一个元素是数组的第一个元素&#xff09;&#xff0c;输出每个元素的下一个…

寄存器(内存访问)

文章目录 寄存器&#xff08;内存访问&#xff09;1 内存中字的存储2 DS和[address]3 字的传送4 mov、add、sub指令5 数据段6 栈7 CPU提供的栈机制8 栈顶超界的问题9 push、pop指令10 栈段 寄存器&#xff08;内存访问&#xff09; 1 内存中字的存储 CPU中&#xff0c;用16位寄…

部署私有KMS服务器,并设置自动激活Windows和office

介绍 vlmcsd是一个KMS激活服务器的模拟器&#xff0c;可以在Windows Server之外的平台上部署自己的KMS服务器。它是一个开源项目&#xff0c;由Wind4开发&#xff0c;目前在Linux上运行&#xff08;包括Android、FreeBSD、Solaris、Minix、Mac OS、iOS和Windows等&#xff09;…

[MYSQL数据库]- 索引

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 目录 一、认识索…

22-Java状态模式 ( State Pattern )

Java状态模式 摘要实现范例 状态模式&#xff08;State Pattern&#xff09;中类的行为是基于它的状态改变的 在状态模式中&#xff0c;我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象 状态模式属于行为型模式 摘要 1. 意图 允许对象在内部…

深入解析HashMap:结构与哈希函数揭秘一

文章目录 一、HashMap的基本结构1.数组与链表的结构1.1 数组1.2 链表 2.红黑树的简单介绍3.Node节点的组成 二、HashMap的哈希函数1.hashCode()方法的作用2.位运算与哈希值的计算3.扰动函数的作用 思考&#xff1a;为什么HashMap源码中使用位运算 在Java编程语言中&#xff0c;…

『 Linux 』进程替换( Process replacement ) 及 简单Shell的实现(万字)

文章目录 &#x1f984; 进程替换&#x1f9a9; execl()函数&#x1f9a9; execlp()函数&#x1f9a9; execle()函数&#x1f9a9; execv()函数&#x1f9a9; execvp()函数&#x1f9a9; execvpe()函数&#x1f9a9; execve()函数 &#x1f984; 简单Shell命令行解释器的实现&a…

区块链基础知识(上):区块链基本原理、加密哈希、公钥加密

目录 基本原理 加密哈希&#xff1a; 公钥加密&#xff1a; 希望有人向你发送只有你才能打开的加密文档/消息时使用 PKC 希望向其他人发送加密文档/消息并证明它确实由你发送时使用 PKC 使用 PKC 和加密哈希对文档/消息进行数字签名 交易哈希链使用数字签名转让数字资产所…

Docker进阶:深入了解 Dockerfile

Docker进阶&#xff1a;深入了解 Dockerfile 一、Dockerfile 概述二、Dockerfile 优点三、Dockerfile 编写规则四、Dockerfile 中常用的指令1、FROM2、LABEL3、RUN4、CMD5、ENTRYPOINT6、COPY7、ADD8、WORKDIR9、 ENV10、EXPOSE11、VOLUME12、USER13、注释14、ONBUILD 命令15、…

算法刷题Day9 | 28. 实现 strStr()、459.重复的子字符串、字符串总结

目录 0 引言1 实现 strStr()1.1 我的解题1.2 KMP算法解题 2 重复的子字符串2.1 暴力求解2.2 KMP求解法 3 字符串总结 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;算法刷题Day8 | 28. 实现 strStr()、45…

画图实战-Python实现某产品全年销量数据多种样式可视化

画图实战-Python实现某产品全年销量数据多种样式可视化 学习心得Matplotlib说明什么是Matplotlib&#xff1f;Matplotlib特性Matplotlib安装 产品订单量-折线图某产品全年订单量数据数据提取和分析绘制折线图 产品订单&销售额-条形图某产品全年订单&销售额数据绘制条形…

【嵌入式学习】C++day03.14

一、思维导图 二、练习 成员函数版本实现算术运算符的重载 全局函数版本实现算术运算符的重载 #include <iostream>using namespace std;class Num {friend const Num operator-(const Num &L,const Num &R); private:int a;int b; public://运算符重载const …