使用Vue+Antv-X6实现一个输送线可视化编辑器(支持拖拽、自定义连线、自定义节点等)

news2025/1/18 6:21:50

最近公司有这样的业务,要实现一个类似流程图的编辑器,可以拖拉拽之类的,网上寻找了一番,最终决定使用Antv-X6这个图形引擎,非常强大,文档多看几遍也就能上手使用了。感觉还不错就写个使用心得期望能帮助到同样需要的猿猿吧

Antv-X6文档地址

Antv-X6国内官网

不多废话,上实现效果

  • 支持拖拽放置图案,支持连线
    在这里插入图片描述
  • 支持编辑修改图案label
    在这里插入图片描述
  • 支持修改连线的label(这个label支持自定义改变图形宽度,就是label长短会自动改变图案宽度)
    在这里插入图片描述
  • 支持点击删除图案
    在这里插入图片描述
  • 支持点击删除路径
    在这里插入图片描述
  • 支持CV复制粘贴
    在这里插入图片描述
  • 支持更多操作、例如清空画布、上一步、下一步历史操作、导出图片
    在这里插入图片描述

项目环境介绍

技术栈

  • vue: 2.6.13
  • element-ui: 2.13.0

X6各个依赖版本如下

 "@antv/x6": "^2.18.1",  // 核心
 "@antv/x6-plugin-clipboard": "^2.1.6", // 复制粘贴插件
 "@antv/x6-plugin-dnd": "^2.1.1", // 拖拽插件(我没用到,测试了一下)
 "@antv/x6-plugin-export": "^2.1.6", // 导出插件
 "@antv/x6-plugin-history": "^2.2.4", // 历史记录插件
 "@antv/x6-plugin-keyboard": "^2.2.3", // 键盘快捷键插件
 "@antv/x6-plugin-selection": "^2.2.2", // 框选插件
 "@antv/x6-plugin-snapline": "^2.1.7", // 对齐线插件
 "@antv/x6-plugin-stencil": "^2.1.5", // 快捷工具插件(我没用到,自定义程度不高)

开始编码

整体结构

  • 布局采用左右布局,左侧是拖拽源,右侧是放置图形区域
  • 布局很简单的,一个外部容器设置相对定位,然后左侧容器宽300px,右侧容器动态计算宽calc(100%-300px),顶部操作栏绝对定位
    在这里插入图片描述

template代码

<template>
  <div class="visual_container">
    <div class="toolbar">
      <el-tooltip class="item" effect="dark" content="清空画布" placement="top-start">
        <el-button type="danger" icon="el-icon-delete" circle @click="clearCanvas" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="全屏" placement="top-start">
        <el-button icon="el-icon-full-screen" circle @click="fullscreenHandler" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="回退一步" placement="top-start">
        <el-button icon="el-icon-refresh-left" circle :disabled="isUndo" @click="undoHandler" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="前进一步" placement="top-start">
        <el-button icon="el-icon-refresh-right" circle :disabled="isCando" @click="candoHandler" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="暂存当前画布" placement="top-start">
        <el-button icon="el-icon-paperclip" circle @click="cacheCanvas" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="导出为图片" placement="top-start">
        <el-button icon="el-icon-camera" circle @click="exportCanvasToPng" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="上传当前配置至服务器" placement="top-start">
        <el-button icon="el-icon-upload" circle @click="saveHandler" />
      </el-tooltip>
    </div>
    <div id="toolbox" ref="toolBoxRef">
      <div class="row">
        <div class="row_label">输送线图形</div>
        <div class="row_content">
          <div
            v-for="item in moduleList"
            :key="item.id"
            class="item"
            draggable="true"
            @dragend="handleDragEnd($event, item)"
          >
            <img :src="item.icon" alt="" srcset="" />
            <span>{{ item.name }}</span>
          </div>
        </div>
      </div>
      <div class="row">
        <div class="row_label">基本图形</div>
        <div class="row_content">
          <div
            v-for="item in moduleList2"
            :key="item.id"
            class="item"
            draggable="true"
            @dragend="handleDragEnd($event, item)"
          >
            <img :src="item.icon" alt="" srcset="" />
            <span>{{ item.name }}</span>
          </div>
        </div>
      </div>
      <div class="row">
        <div class="row_label">个性化图形</div>
        <div class="row_content">
          <div
            v-for="item in moduleList3"
            :key="item.id"
            class="item"
            draggable="true"
            @dragend="handleDragEnd($event, item)"
          >
            <img :src="item.icon" alt="" srcset="" />
            <span>{{ item.name }}</span>
          </div>
        </div>
      </div>
    </div>
    <div id="container" ref="containerRef" />
    <!-- <div id="attrbox">
      属性栏,开发中
      <br />
    </div> -->
  </div>
</template>

script代码

<script>
import { Graph } from '@antv/x6'
import { Snapline } from '@antv/x6-plugin-snapline'
import { Selection } from '@antv/x6-plugin-selection'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Export } from '@antv/x6-plugin-export'
import { History } from '@antv/x6-plugin-history'
import { mapGetters } from 'vuex'
import moment from 'moment'
export default {
  name: 'VisualizationLines',
  data() {
    return {
      graph: null,
      curSelectNode: null, // 当前选中的节点
      curSelectEdge: null, // 当前选中的边
      isCando: true,
      isUndo: true,
      moduleList: [
        {
          id: 1,
          name: '穿梭车',
          icon: require('@/assets/images/穿梭车.png')
        },
        {
          id: 2,
          name: '堆垛机',
          icon: require('@/assets/images/堆垛机.png')
        },
        {
          id: 3,
          name: '货架',
          icon: require('@/assets/images/货架.png')
        },
        {
          id: 4,
          name: '托盘',
          icon: require('@/assets/images/托盘.png')
        },
        {
          id: 5,
          name: '扫码枪',
          icon: require('@/assets/images/扫码枪.png')
        },
        {
          id: 6,
          name: '提升机',
          icon: require('@/assets/images/提升机.png')
        },
        {
          id: 7,
          name: '工人',
          icon: require('@/assets/images/工人.png')
        },
        {
          id: 8,
          name: 'AGV',
          icon: require('@/assets/images/AGV.png')
        }
      ],
      moduleList2: [
        {
          id: 1,
          name: '正方形',
          icon: require('@/assets/images/正方形.png')
        },
        {
          id: 2,
          name: '长方形',
          icon: require('@/assets/images/长方形.png')
        },
        {
          id: 3,
          name: '圆形',
          icon: require('@/assets/images/圆形.png')
        },
        {
          id: 4,
          name: '梯形',
          icon: require('@/assets/images/梯形.png')
        },
        {
          id: 5,
          name: '三角形',
          icon: require('@/assets/images/三角形.png')
        }
      ],
      moduleList3: [
        {
          id: 1,
          name: '风扇',
          icon: require('@/assets/images/风扇.png')
        },
        {
          id: 2,
          name: '扳手',
          icon: require('@/assets/images/扳手.png')
        },
        {
          id: 3,
          name: '齿轮',
          icon: require('@/assets/images/齿轮.png')
        },
        {
          id: 4,
          name: '时效',
          icon: require('@/assets/images/时效.png')
        },
        {
          id: 5,
          name: '禁止',
          icon: require('@/assets/images/禁止.png')
        },
        {
          id: 6,
          name: '易碎品',
          icon: require('@/assets/images/易碎品.png')
        },
        {
          id: 7,
          name: '防水',
          icon: require('@/assets/images/防水.png')
        },
        {
          id: 8,
          name: '火焰',
          icon: require('@/assets/images/火焰.png')
        },
        {
          id: 9,
          name: '叉车',
          icon: require('@/assets/images/叉车.png')
        },
        {
          id: 10,
          name: '手机',
          icon: require('@/assets/images/手机.png')
        },
        {
          id: 11,
          name: '电池',
          icon: require('@/assets/images/电池.png')
        }
      ],
      cacheKey: 'X6_GRAPH_CACHE'
    }
  },
  computed: {
    ...mapGetters(['sidebar'])
  },
  mounted() {
  	// 初始化graph实例以及一些配置
    this.initGraph()
    // 初始化对应的一些插件
    this.initPluging()
    // 注册事件
    this.initEvent()
    // 如果本地存在值那么直接读取本地的内容进行回显
    const cache = localStorage.getItem(this.cacheKey)
    if (cache && this.graph) {
      this.graph.fromJSON(JSON.parse(cache))
    }
  },
  methods: {
    initGraph() {
      // 自定义边的样式并注册
      Graph.registerEdge(
        'dag-edge',
        {
          inherit: 'edge',
          connector: { name: 'smooth' },
          attrs: {
            line: {
              stroke: '#5F95FF',
              strokeDasharray: 5,
              strokeWidth: 3,
              targetMarker: 'classic', // 经典箭头样式
              // 动画效果
              style: {
                animation: 'ant-line 30s infinite linear'
              }
            }
          }
        },
        true
      )
      this.graph = new Graph({
        container: document.getElementById('container'),
        autoResize: true, // 自适应布局
        background: {
          color: '#F2F7FA'
        },
        panning: true, // 允许拖拽画面
        mousewheel: true, // 允许缩放
        snapline: true, // 对齐线
        // 配置连线规则
        connecting: {
          snap: true, // 自动吸附
          allowBlank: false, // 是否允许连接到画布空白位置的点
          allowMulti: true, // 是否允许在相同的起始节点和终止之间创建多条边
          allowLoop: true, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点
          highlight: true, // 拖动边时,是否高亮显示所有可用的节点
          highlighting: {
            magnetAdsorbed: {
              name: 'stroke',
              args: {
                attrs: {
                  fill: '#5F95FF',
                  stroke: '#5F95FF'
                }
              }
            }
          },
          createEdge: () =>
            this.graph.createEdge({
              shape: 'dag-edge',
              attrs: {
                line: {
                  strokeDasharray: '5 5'
                }
              },
              zIndex: -1
            }),
          validateConnection: ({ sourceMagnet, targetMagnet }) => {
            const sourceParentId = sourceMagnet && sourceMagnet.parentNode.parentNode.getAttribute('data-cell-id')
            const targetParentId = targetMagnet && targetMagnet.parentNode.parentNode.getAttribute('data-cell-id')
            if (sourceParentId === targetParentId) {
              return false
            }
            return true
          }
        },
        grid: {
          visible: true,
          type: 'doubleMesh',
          args: [
            {
              color: '#eee', // 主网格线颜色
              thickness: 1 // 主网格线宽度
            },
            {
              color: '#ddd', // 次网格线颜色
              thickness: 1, // 次网格线宽度
              factor: 4 // 主次网格线间隔
            }
          ]
        }
      })
    },
    // 初始化插件
    initPluging() {
      // 对齐线
      this.graph.use(
        new Snapline({
          enabled: true
        })
      )
      // 框选
      this.graph.use(
        new Selection({
          enabled: true,
          showNodeSelectionBox: false,
          rubberband: true, // 是否启用移动框选,这个会和拉动画布冲突
          eventTypes: ['mouseWheelDown']
        })
      )
      // 复制粘贴
      this.graph.use(
        new Clipboard({
          enabled: true
        })
      )
      // 快捷键
      this.graph.use(
        new Keyboard({
          enabled: true
        })
      )

      // 绑定cv键
      this.graph.bindKey('ctrl+c', () => {
        const cells = this.graph.getSelectedCells()
        if (cells.length) {
          this.graph.copy(cells)
        }
        return false
      })

      this.graph.bindKey('ctrl+v', () => {
        if (!this.graph.isClipboardEmpty()) {
          const cells = this.graph.paste({ offset: 32 })
          this.graph.cleanSelection()
          this.graph.select(cells)
        }
        return false
      })
      // 导出功能
      this.graph.use(new Export())
      // 历史记录
      this.graph.use(
        new History({
          enabled: true
        })
      )
    },
    handleDragEnd(e, item) {
      console.log(e, item)
      // TODO:这里还要判断左侧导航是否折叠,如果是那还要动态计算一次
      this.graph.addNode({
        x: this.sidebar.opened ? e.pageX - 300 - 260 : e.pageX - 300,
        y: e.pageY - 100,
        id: new Date().getTime(),
        width: 200,
        height: 60,
        attrs: {
          body: {
            stroke: '#5F95FF',
            strokeWidth: 1,
            strokeDasharray: 5,
            fill: 'rgba(95,149,255,0.05)',
            refWidth: 1,
            refHeight: 1
          },
          image: {
            'xlink:href': require(`@/assets/images/${item.name}.png`),
            width: 60,
            height: 60,
            x: 10,
            y: 0
          },
          title: {
            text: item.name,
            refX: 80,
            refY: 30,
            fill: 'rgba(0,0,0,0.85)',
            fontSize: 20,
            fontWeight: 600,
            'text-anchor': 'start'
          }
        },
        // 连接桩配置
        ports: {
          groups: {
            // 上顶点
            top: {
              position: 'top'
            },
            // 右顶点
            right: {
              position: 'right'
            },
            // 下顶点
            bottom: {
              position: 'bottom'
            },
            // 左顶点
            left: {
              position: 'left'
            }
          },
          items: [
            {
              group: 'top',
              id: 'top',
              attrs: {
                circle: {
                  r: 6,
                  magnet: true,
                  stroke: '#5F95FF',
                  strokeWidth: 2
                }
              }
            },
            {
              group: 'right',
              id: 'right',
              attrs: {
                circle: {
                  r: 6,
                  magnet: true,
                  stroke: '#5F95FF',
                  strokeWidth: 2
                }
              }
            },
            {
              group: 'bottom',
              id: 'bottom',
              attrs: {
                circle: {
                  r: 6,
                  magnet: true,
                  stroke: '#5F95FF',
                  strokeWidth: 2
                }
              }
            },
            {
              group: 'left',
              id: 'left',
              attrs: {
                circle: {
                  r: 6,
                  magnet: true,
                  stroke: '#5F95FF',
                  strokeWidth: 2
                }
              }
            }
          ]
        },
        markup: [
          {
            tagName: 'rect',
            selector: 'body'
          },
          {
            tagName: 'image',
            selector: 'image'
          },
          {
            tagName: 'text',
            selector: 'title'
          }
        ]
      })
      // this.graph.centerContent()
    },
    // 注册事件
    initEvent() {
      // 节点点击事件
      this.graph.on('node:click', ({ e, x, y, node, view }) => {
        // 判断是否有选中过节点
        if (this.curSelectNode) {
          // 移除选中状态
          this.curSelectNode.removeTools()
          // 判断两次选中节点是否相同
          if (this.curSelectNode !== node) {
            node.addTools([
              {
                name: 'button-remove',
                args: {
                  x: '100%',
                  y: 0,
                  offset: {
                    x: 0,
                    y: 0
                  }
                }
              }
            ])
            this.curSelectNode = node
          } else {
            this.curSelectNode = null
          }
        } else {
          this.curSelectNode = node
          node.addTools([
            {
              name: 'button-remove',
              args: {
                x: '100%',
                y: 0,
                offset: {
                  x: 0,
                  y: 0
                }
              }
            }
          ])
        }
      })
      // 节点双击事件
      this.graph.on('node:dblclick', ({ e, x, y, node, view }) => {
        // 编辑器容器父节点
        const visualParentNode = document.querySelector('.visual_container')
        // 创建一个文本框
        const textField = document.createElement('input')
        textField.type = 'text'
        // 设置绝对定位,是相对于这个编辑器的父元素
        textField.style.position = 'absolute'
        textField.style.left = x + 200 + 'px'
        textField.style.top = y + 10 + 'px'
        // 给输入框添加一个类
        textField.classList.add('customer_visual_input')
        // 将原本的label填入输入框
        textField.value = node.attrs.title.text
        // 设置占位符
        textField.placeholder = '请输入'
        // 将内容添加到容器父节点,让他们共享坐标系
        visualParentNode.appendChild(textField)
        // 自动聚焦
        textField.focus()
        // 监听失去焦点事件
        textField.addEventListener('blur', () => {
          if (!textField.value) {
            this.$message.error('标签名不能为空')
            return
          } else {
          	// 修改节点的label文字
            node.attr('title/text', textField.value)
            // 修改节点的大小,根据里面的文字自动调整
            node.prop('size', {
              width: textField.value.length <= 0 ? 200 : textField.value.length * 20 + 100,
              height: 60
            })
            // 移除dom元素
            visualParentNode.removeChild(textField)
          }
        })
      })
      // 边点击事件
      this.graph.on('edge:click', ({ e, x, y, edge, view }) => {
        if (this.curSelectEdge) {
          // 移除选中状态
          this.curSelectEdge.removeTools()
          this.curSelectEdge = null
        } else {
          this.curSelectEdge = edge
          edge.addTools([
            {
              name: 'button-remove',
              args: {
                x: x,
                y: y,
                offset: {
                  x: 0,
                  y: 0
                }
              }
            }
          ])
          edge.setAttrs({
            line: {
              stroke: '#409EFF'
            }
          })
          edge.zIndex = 99 // 保证当前悬停的线在最上层,不会被遮挡
        }
      })
      // 边双击
      this.graph.on('edge:dblclick', ({ e, x, y, edge, view }) => {
        // 编辑器容器父节点
        const visualParentNode = document.querySelector('.visual_container')
        // 创建一个文本框
        const textField = document.createElement('input')
        textField.type = 'text'
        // 设置绝对定位,是相对于这个编辑器的父元素
        textField.style.position = 'absolute'
        textField.style.left = x + 200 + 'px'
        textField.style.top = y + 10 + 'px'
        // 给输入框添加一个类
        textField.classList.add('customer_visual_input')
        // 设置占位符
        textField.placeholder = '请输入'
        // 如果已经存在标签了,那么将原本的内容写入输入框
        const labels = edge.getLabels()
        if (labels.length > 0) {
          console.log(labels[0].attrs.text.text)
          textField.value = labels[0].attrs.text.text
        }
        // 将内容添加到容器父节点,让他们共享坐标系
        visualParentNode.appendChild(textField)
        // 自动聚焦
        textField.focus()
        // 监听失去焦点事件
        textField.addEventListener('blur', () => {
          if (!textField.value) {
            // 如果没有输入内容那就删除
            edge.removeLabelAt(0)
          }
          edge.appendLabel({
            attrs: {
              text: {
                text: textField.value
              }
            }
          })
          // 移除dom元素
          visualParentNode.removeChild(textField)
        })
      })
      // 空白画布点击事件
      this.graph.on('blank:click', () => {
        // 移除选中元素的删除图标
        this.curSelectNode && this.curSelectNode.removeTools()
        this.curSelectEdge && this.curSelectEdge.removeTools()
        // 同时移除选中对象
        this.curSelectNode = null
        this.curSelectEdge = null
      })
      // 历史记录变更的时候
      this.graph.on('history:change', () => {
        this.isCando = !this.graph.canRedo()
        this.isUndo = !this.graph.canUndo()
      })
    },
    fullscreenHandler() {
      document.querySelector('#container').requestFullscreen()
    },
    // 清空画布内容
    clearCanvas() {
      if (this.graph) {
        const nodes = this.graph.getNodes()
        if (nodes.length <= 0) {
          this.$message.error('当前画布没有任何内容')
          return
        }
        this.$confirm('此操作将清空画布内容以及所有历史记录,无法还原, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        })
          .then(() => {
            this.graph.clearCells()
            localStorage.removeItem(this.cacheKey)
          })
          .catch(() => {
            this.$message({
              type: 'info',
              message: '已取消删除'
            })
          })
      }
    },
    // 回退
    undoHandler() {
      this.graph.undo()
    },
    // 前进
    candoHandler() {
      this.graph.redo()
    },
    // 暂存当前画布内容
    cacheCanvas() {
      if (this.graph) {
        const nodes = this.graph.getNodes()
        if (nodes.length <= 0) {
          this.$message.error('当前画布没有任何内容')
          return
        }
        const cache = this.graph.toJSON()
        localStorage.setItem(this.cacheKey, JSON.stringify(cache))
        this.$message.success('暂存成功,刷新浏览器或者关闭浏览器再重新打开会还原当前画布内容')
      }
    },
    // 导出图片
    exportCanvasToPng() {
      if (this.graph) {
        const nodes = this.graph.getNodes()
        if (nodes.length <= 0) {
          this.$message.error('当前画布没有任何内容')
          return
        }
        this.graph.exportPNG(`${moment().format('YYYY-MM-DD')}画布图`, {
          width: 1920,
          height: 1080
        })
      }
    },
    // 上传至服务器
    saveHandler() {
      if (this.graph) {
        const nodes = this.graph.getNodes()
        if (nodes.length <= 0) {
          this.$message.error('当前画布没有任何内容')
          return
        }
        this.$message.warning('功能开发中')
      }
    }
  }
}
</script>

style代码

<style lang="scss" scoped>
.visual_container {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: row;
  position: relative;
  .toolbar {
    position: absolute;
    top: 0;
    left: 260px;
    height: 46px;
    line-height: 46px;
    padding-left: 10px;
    width: 100%;
    background-color: white;
    z-index: 2001;
    box-sizing: border-box;
  }
  #toolbox {
    width: 300px;
    height: 100%;
    box-sizing: border-box;
    overflow-y: auto;
    .row {
      .row_label {
        font-size: 16px;
        background-color: azure;
        padding: 10px;
        position: sticky;
        top: 0;
        left: 0;
        font-weight: 600;
      }
      .row_content {
        display: flex;
        row-gap: 20px;
        column-gap: 28px;
        flex-direction: row;
        flex-wrap: wrap;
        align-items: center;
        justify-content: flex-start;
        align-content: flex-start;
        padding: 0 5px;
        .item {
          width: 60px;
          height: 80px;
          display: flex;
          justify-content: center;
          align-items: center;
          flex-direction: column;
          cursor: move;
          img {
            width: 100%;
            height: 80px;
          }
        }
      }
    }
  }
  #container {
    width: calc(100% - 300px);
    height: 100%;
  }
  /* #attrbox {
    width: 300px;
    height: 100%;
    padding: 10px;
    box-sizing: border-box;
  } */
}
.customer_svg {
  cursor: move;
  width: 60px;
  height: 60px;
}
</style>

特殊CSS,里面的边动画

最好写在全局的index.css文件中

// 边动画效果
@keyframes ant-line {
  to {
    stroke-dashoffset: -1000;
  }
}
// 输送线可视化输入框样式(这个元素是动态添加的)
.customer_visual_input {
  width: 200px;
  height: 40px;
  outline: none;
  border: 1px solid rgb(168, 198, 252);
  border-radius: 3px;
  &:focus {
    border: 2px solid #5f95ff;
  }
}

静态资源文件

全部从阿里图标库下载,大小是64*64,颜色是绿色
在这里插入图片描述

代码注释都非常详细,耐心看绝对能看懂并运用起来的,最主要是要熟悉官网的文档,里面多看几遍多试几次就会发现写的还挺全面的,特别是事件那一部分。另外那些演示示例里面包含的代码其实也是文档的一部分,告诉你怎么用这个x6的

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

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

相关文章

2000年 - 2022年 Fama-French三因子模型数据+代码

Fama-French三因子模型是由著名经济学家尤金法玛&#xff08;Eugene Fama&#xff09;和肯尼斯法兰奇&#xff08;Kenneth French&#xff09;提出的&#xff0c;旨在改进资本资产定价模型&#xff08;CAPM&#xff09;&#xff0c;更全面地解释资产收益率的变化。该模型认为&a…

论文笔记:Spatial-Temporal Interval Aware Sequential POI Recommendation

ICDE 2022 1 intro 1.1 背景 空间&#xff08;Spatial&#xff09;和时间&#xff08;Temporal&#xff09;信息是序列 POI 推荐中两个重要且相辅相成的因素。 空间因素&#xff08;如地理距离间隔&#xff09;可以在用户的历史轨迹呈现空间分簇现象时&#xff0c;细粒度刻画…

主流中间件--Redis

NOSQL 什么是NOSQL NoSQL(NoSQL Not Only SQL )&#xff0c;意即“不仅仅是SQL”&#xff0c;它泛指非关系型的数据库。 关系型数据库&#xff1a;以关系(由行和列组成的二维表)模型建模的数据库。简单理解&#xff1a;有表的就是关系型数据库。 NOSQL分类 Redis 什么是Redi…

L02_并发编程知识图谱

这些知识点你都掌握了吗&#xff1f;大家可以对着问题看下自己掌握程度如何&#xff1f;对于没掌握的知识点&#xff0c;大家自行网上搜索&#xff0c;都会有对应答案&#xff0c;本文不做知识点详细说明&#xff0c;只做简要文字或图示引导。 并发理论 并发编程Bug源头 为了…

反射机制详解

✅作者简介&#xff1a;大家好&#xff0c;我是Leo&#xff0c;热爱Java后端开发者&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;Leo的博客 &#x1f49e;当前专栏&#xff1a;Java从入门到精通 ✨特色专栏&#xff…

读书笔记之智能商业

智能商业 本书试图勾勒的是未来商业的大蓝图&#xff0c;目前中国市场变化速度非常之快&#xff0c;作者用了一个词来形容现在市场发展的特殊性&#xff1a;三浪叠加 1.0浪&#xff1a;传统的零售2.0浪&#xff1a;国美、苏宁为代表的综合商城模式3.0浪&#xff1a;以淘宝为代…

STM32学习之一:什么是STM32

目录 1.什么是STM32 2.STM32命名规则 3.STM32外设资源 4. STM32的系统架构 5. 从0到1搭建一个STM32工程 学习stm32已经很久了&#xff0c;因为种种原因&#xff0c;也有很久一段时间没接触过stm32了。等我捡起来的时候&#xff0c;发现很多都已经忘记了&#xff0c;重新捡…

【mysql】常用操作:维护用户/开启远程/忘记密码/常用命令

一、维护用户 1.1 创建用户 -- 语法 > CREATE USER [username][host] IDENTIFIED BY [password];-- 例子&#xff1a; -- 添加用户user007&#xff0c;密码123456&#xff0c;并且只能在本地可以登录 > CREATE USER user007localhost IDENTIFIED BY 123456; -- 添加用户…

宇哥强调!7月份必须开始强化阶段(附规划)

七月开始强化不晚&#xff0c;但是要开始了&#xff01; 张宇老师说&#xff1a;七月&#xff0c;也就是暑假是大家学习的最佳时机&#xff0c;这个时候要大量的做题&#xff01; 如果你是三月份开始备考的&#xff0c;那么到了七月份&#xff0c;基础应该复习的差不多了&…

wget:unable to resolve host address ...(已解决)

写在前面&#xff1a; 最近在学习Linux命令&#xff0c;此问题是在使用wget安装rar时出现的问题&#xff0c;记录一下解决的过程。仅供参考&#xff0c;若有不当的地方&#xff0c;恳请指正。如果对你有帮助&#xff0c;欢迎点赞&#xff0c;关注&#xff0c;收藏&#xff0c;…

基于YOLOv5+pyqt5的跌倒检测系统(含pyqt页面、训练好的模型)

简介 跌倒是老年人和身体不便者常见的意外事故&#xff0c;及时检测和处理跌倒事件对于保障他们的安全至关重要。为了提高对跌倒事件的监控效率&#xff0c;我们开发了一种基于YOLOv5目标检测模型的跌倒检测系统。本报告将详细介绍该系统的实际应用与实现&#xff0c;包括系统…

OpenCv形态学(一)

目录 形态学转换 结构元素 腐蚀 膨胀 开运算 闭运算 形态学梯度 顶帽 黑帽 图像轮廓 查找轮廓 绘制轮廓 形态学转换 形态变换是一些基于图像形状的简单操作。通常在二值图像上执行。它需要两个输入&#xff0c;一个是我们的原始图像&#xff0c;第二个是决定操作性…

Nginx Proxy Manager反向代理Jackett

1 说明 最近折腾nas&#xff0c;发现npm反向代理Jackett后出现无法访问的问题&#xff0c;是因为外网访问jackett (例如https://domain.com:7373/jackett/UI/Dashboard)时&#xff0c;url会被重定向到https://domain.com/jackett/UI/Login?ReturnUrl%2Fjackett%2FUI%2FDashbo…

基于matlab的K-means聚类图像分割

1 原理 K-means聚类算法在图像分割中的应用是基于一种无监督的学习方法&#xff0c;它将图像中的像素点或特征区域划分为K个不同的簇或类别。以下是K-means聚类算法用于图像分割的原理&#xff0c;包括步骤和公式&#xff1a; 1.1 原理概述 选择簇的数量(K)&#xff1a; 首先…

《数字图像处理与机器视觉》案例一(库尔勒香梨果梗提取和测量)

一、引言 果梗是判断水果新鲜程度的重要标志&#xff0c;对水果的贮藏和保鲜也具有重要的参考价值。库尔勒香梨分级标准中对果梗有明确要求&#xff0c;要求果梗完整&#xff0c;但由于库尔勒香梨果梗颜色与果实接近&#xff0c;用传统的简单阈值分割方法难以提取。因此&#…

双指针算法专题(移动零 复写零 快乐数)

目录 前言 1. 移动零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般思路 &#xff08;3&#xff09;双指针解法 2. 复写零 &#xff08;1&#xff09;题目及示例 &#xff08;2&#xff09;一般解法 &#xff08;3&#xff09;双指针解法 3. 快…

Kubernetes相关生态

1、Prometheus、Metrics Server与Kubernetes监控体系 简介&#xff1a; Prometheus 项目与 Kubernetes 项目一样&#xff0c;也来自于 Google 的 Borg 体系&#xff0c;它的原型系统&#xff0c;叫作 BorgMon&#xff0c;是一个几乎与 Borg 同时诞生的内部监控系统 Pro…

AG32 MCU Start Kit 开发板快速入门及 21天体验活动

AG32 IDE开发环境搭建-完整版 海振远科技 2024-6-18 AG32 MCU开发板的使用 使用准备 在使用开发板前&#xff0c;请确认已经安装好开发环境。 安装环境过程&#xff0c;请参考文档《AG32 开发环境搭建.pdf》 上电&#xff1a; 给开发板5V 供电&#xff0c;打开开关&#…

平面设计软件PS/AI/ID/CDR怎么选怎么下载(附教程)

随着设计行业的普遍化&#xff0c;平面设计软件也越来越多且功能越来越强大。平面设计软件需要在电脑上运行使用&#xff0c;来进行平面画面、平面文字的设计工作。如大家所了解的&#xff0c;Adobe Photoshop、Adobe Illustrator、CorelDRAW、Adobe InDesign是平面设计中最常用…

PostgreSQL计算 queryid 原理

数据库版本 PG 16.1 queryid 是什么 queryid 是将 sql 规范化 (normalization) 后&#xff0c;通过哈希函数计算出来的 64 位整数。 以 SELECT id, data FROM tbl_a WHERE id < 300 ORDER BY data; 这条 SQL 为例。当我们在 PG 中执行这条 sql 时&#xff0c;内核在语义…