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

最近公司有这样的业务,要实现一个类似流程图的编辑器,可以拖拉拽之类的,网上寻找了一番,最终决定使用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"><divv-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"><divv-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"><divv-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 = nodenode.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 = edgeedge.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 = nullthis.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.mzph.cn/bicheng/30665.shtml

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

相关文章

React 通信:深层传递(Props、Context、Children Jsx)

在之前的文章 探讨&#xff1a;围绕 props 阐述 React 通信 中总结了关于“父子”组件传值&#xff0c;但是当需要在组件树中深层传递参数以及需要在组件间复用相同的参数时&#xff0c;传递 props 就会变得很麻烦。 实际案例&#xff1a; 下述展示有两种状态&#xff1a;① 详…

数据治理的七大核心技术 全面了解数据治理必读篇

在当今的数字化时代&#xff0c;数据已成为企业最宝贵的资产之一&#xff0c;其价值不仅体现在数据量的巨大&#xff0c;更在于数据的深度和宽度。随着大数据、云计算、物联网&#xff08;IoT&#xff09;和人工智能&#xff08;AI&#xff09;等技术的不断进步&#xff0c;企业…

Jenkins 发测试邮件报错 553 Mail from must equal authorized user

Jenkins 发测试邮件报错 553 Mail from must equal authorized user 报错信息报错原因解决办法 报错信息 org.eclipse.angus.mail.smtp.SMTPSenderFailedException: 553 Mail from must equal authorized user at org.eclipse.angus.mail.smtp.SMTPTransport.mailFrom(SMTPTra…

微信小程序端在线客服源码系统 聊天记录实时保存 带完整的安装代码包以及搭建教程

系统概述 在当今数字化时代&#xff0c;客户服务的质量和效率成为企业竞争的关键因素之一。微信小程序作为一种便捷的应用形式&#xff0c;为在线客服提供了广阔的平台。而具备聊天记录实时保存功能的微信小程序端在线客服源码系统&#xff0c;则能够更好地满足企业与客户之间…

【人机交互 复习】第8章 交互设计模型与理论

一、引文 1.模型&#xff1a; 有的人成功了&#xff0c;他把这一路的经验中可以供其他人参考的部分总结了出来&#xff0c;然后让别人套用。 2.本章模型 &#xff08;1&#xff09;计算用户完成任务的时间&#xff1a;KLM &#xff08;2&#xff09;描述交互过程中系统状态的变…

linux端口被占用 关闭端口

系列文章目录 文章目录 系列文章目录一、linux端口被占用 关闭端口1.参考链接2.具体命令 二、【linux关闭进程命令】fuser -k 和 kill -9 的区别1.参考链接2.具体命令 一、linux端口被占用 关闭端口 1.参考链接 linux端口被占用 关闭端口 2.具体命令 1.查看端口是否被占用 …

麒麟移动运行环境(KMRE)——国内首个开源的商用移固融合“Android生态兼容环境”正式开源

近日&#xff0c;由麒麟软件研发的KMRE&#xff08;Kylin Mobile Runtime Environment&#xff0c;麒麟移动运行环境&#xff09;在openKylin&#xff08;开放麒麟&#xff09;社区正式发布&#xff0c;为Linux桌面操作系统产品提供了高效的Android运行环境解决方案。这也是国内…

Nature 苏浩团队发表创新人工智能“仿真中学习”框架,实现外骨骼的智能性和通用性

北京时间2024年6月12日23时&#xff0c;美国北卡罗来纳州立大学与北卡罗来纳大学教堂山分校的苏浩团队在《自然》&#xff08;Nature&#xff09;上发表了一篇关于机器人和人工智能算法相结合服务人类的突破性研究论文&#xff0c;标题为“Experiment-free Exoskeleton Assista…

Java语法糖写法

一、函数式接口 1、Function函数式接口&#xff1a;有一个输入参数&#xff0c;有一个输出 2、断定型接口&#xff1a;有一个输入参数&#xff0c;返回值只能是布尔值&#xff01; 3、Consumer 消费型接口&#xff1a;只有输入&#xff0c;没有返回值 4、Supplier供给型接口&a…

git知识域

知识体系 FAQ git clone与git pull区别 git clone&#xff1a;从无到有&#xff0c;包含分支信息&#xff0c;日志信息。 git pull&#xff1a;在已有项目基础上操作&#xff0c;针对特定分支。

【MySQL】事务二

事务二 1.数据库并发的场景2.读-写2.1 3个记录隐藏字段2.2 undo日志2.3 模拟 MVCC2.4 Read View2.5 RR 与 RC的本质区别 3.读-读4.写-写 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励&#xff0c;我…

技术支持与开发助手:Kompas AI的革新力量

一、引言 随着技术发展的迅猛进步&#xff0c;技术开发的高效需求日益增加。开发人员面临着更复杂的项目、更紧迫的时间表以及不断提高的质量标准。在这种背景下&#xff0c;能够提供智能支持的工具变得尤为重要。Kompas AI 正是在这种需求下应运而生的。它通过人工智能技术&a…

【轨迹规划论文整理(1)】UAV轨迹规划的开山之作Minimum Snap Trajectory

【轨迹规划论文整理(1)】UAV轨迹规划的开山之作Minimum Snap Trajectory Generation and Control for Quadrotors 本系列主要是对精读的一些关于无人机、无人车的轨迹搜索论文的整理&#xff0c;包括了论文所拓展的其他一些算法的改进思路。 这是本系列的第一篇文章&#xff0…

Acrel-2000ES储能能量管理系统方案

应用场合 电池厂商、储能集成商等厂家所生产的储能一体柜能集成箱

CTF-pwn-虚拟化-【d3ctf-2021-d3dev】

文章目录 参考流程附件检查启动信息逆向分析漏洞查看设备配置信息exp 参考 https://x1ng.top/2021/11/26/qemu-pwn/ https://bbs.kanxue.com/thread-275216.htm#msg_header_h1_0 https://xz.aliyun.com/t/6562?time__1311n4%2BxnD0DRDBAi%3DGkDgiDlhjmYh2xuCllx7whD&alic…

1-函数极限与连续

1 2 平方项没有考虑到&#xff08;其正负&#xff09;

scratch编程03-反弹球

这篇文章和上一篇文章《scratch3编程02-使用克隆来编写小游戏》类似&#xff08;已经完全掌握了克隆的可以忽略这篇文章&#xff09;&#xff0c;两篇文章都使用到了克隆来编写一个小游戏&#xff0c;这篇文章与上篇文章不同的是&#xff0c;本体在进行克隆操作时&#xff0c;不…

游戏遇到攻击有什么办法能解决?

随着网络技术的飞速发展&#xff0c;游戏行业在迎来繁荣的同时&#xff0c;也面临着日益严峻的网络威胁。黑客攻击、数据泄露、DDoS攻击等安全事件频发&#xff0c;给游戏服务器带来了极大的挑战。面对愈演愈烈的网络威胁&#xff0c;寻找一个能解决游戏行业攻击问题的安全解决…

Python酷库之旅-比翼双飞情侣库(17)

目录 一、xlwt库的由来 1、背景和需求 2、项目启动 3、功能特点 4、版本兼容性 5、与其他库的关系 6、示例和应用 7、发展历史 二、xlwt库优缺点 1、优点 1-1、简单易用 1-2、功能丰富 1-3、兼容旧版Excel 1-4、社区支持 1-5、稳定性 2、缺点 2-1、不支持.xls…

在Ubuntu中创建Ruby on Rails项目并搭建数据库

新建Rails项目 先安装bundle Ruby gem依赖项工具&#xff1a; sudo apt install bundle 安装Node.js: sudo apt install nodejs 安装npm 包管理器&#xff1a; sudo apt install npm 安装yarn JavaScript包管理工具&#xff1a; sudo apt install yarn 安装webpacker: …