vue3中使用Antv G6渲染树形结构并支持节点增删改

写在前面

在一些管理系统中,会对组织架构、级联数据等做一些管理,你会怎么实现呢?在经过调研很多插件之后决定使用 Antv G6 实现,文档也比较清晰,看看怎么实现吧,先来看看效果图。点击在线体验

效果图
实现的功能有:

  1. 增加节点
  2. 删除节点
  3. 编辑节点
  4. 展开收起

具体实现

  1. 先在项目中安装 antv g6
npm install --save @antv/g6
  1. vue 文件创建容器渲染
  • 渲染的容器
<div id="container" class="one-tree"></div>
  • 渲染方法和初始化树图
import G6 from '@antv/g6'const state = reactive({treeData: {id: 'root',sname: 'root',name: uniqueId(),children: [],},graph: null,
})function renderMap(data: any[], graph: Graph): void {G6.registerNode('icon-node',{options: {size: [60, 20],stroke: '#73D13D',fill: '#fff'},draw(cfg: any, group: any) {const styles = (this as any).getShapeStyle(cfg)const { labelCfg = {} } = cfgconst w = cfg.size[0]const h = cfg.size[1]const keyShape = group.addShape('rect', {attrs: {...styles,cursor: 'pointer',x: 0,y: 0,width: w, // 200,height: h, // 60fill: cfg.style.fill || '#fff'},name: 'node-rect',draggable: true})// 动态增加和删除元素group.addShape('text', {attrs: {x: 131,y: 20,r: 6,stroke: '#707070',cursor: 'pointer',opacity: 1,fontFamily: 'iconfont',textAlign: 'center',textBaseline: 'middle',text: '\ue658',fontSize: 16},name: 'add-item'})// 删除icon,根元素不能删除if (cfg.id !== 'root') {group.addShape('text', {attrs: {x: 110,y: 20,r: 6,fontFamily: 'iconfont',textAlign: 'center',textBaseline: 'middle',text: '\ue74b',fontSize: 14,stroke: '#909399',cursor: 'pointer',opacity: 0},name: 'remove-item'})}if (cfg.sname) {group.addShape('text', {attrs: {...labelCfg.style,text: fittingString(cfg.sname, 110, 12),textAlign: 'left',x: 10,y: 25}})}// 展开收起if (cfg.children && cfg.children.length > 0) {group.addShape('circle', {attrs: {width: 24,height: 24,x: 154,y: 20,r: 12,cursor: 'pointer',lineWidth: 1,fill: !cfg.collapsed ? '#9e9e9e' : '#2196f3',opacity: 1,text: 1},name: 'collapse-icon'})group.addShape('text', {attrs: {...labelCfg.style,text: cfg.children.length,textAlign: 'left',x: 150,y: 25,fill: '#ffffff',fontWeight: 500,cursor: 'pointer'},name: 'collapse-icon'})}return keyShape},setState(name, value, item) {const group = item?.getContainer()if (name === 'collapsed') {const marker = item?.get('group').find((ele: any) => ele.get('name') === 'collapse-icon')const icon = value ? G6.Marker.expand : G6.Marker.collapsemarker.attr('symbol', icon)}if (name === 'selected') {const nodeRect = group?.find(function (e) {return e.get('name') === 'node-rect'})if (value) {nodeRect?.attr({stroke: '#2196f3',lineWidth: 2})}}if (name === 'hover') {const addMarker = group?.find(function (e) {return e.get('name') === 'add-item'})const reduceMarker = group?.find(function (e) {return e.get('name') === 'remove-item'})if (value) {addMarker?.attr({opacity: 1})reduceMarker?.attr({opacity: 1})}}},update: undefined},'rect')graph.data(data)graph.render()mouseenterNode(graph)mouseLeaveNode(graph)collapseNode(graph)
}function initGraph(graphWrapId: string): Graph {const width = (document.getElementById(graphWrapId) as HTMLElement).clientWidth || 1000const height = (document.getElementById(graphWrapId) as HTMLElement).clientHeight || 1000const graph = new G6.TreeGraph({container: graphWrapId,width,height,linkCenter: true,animate: false,fitView: false, // 自动调整节点位置和缩放,使得节点适应画布大小modes: {default: ['scroll-canvas'],edit: ['click-select']},defaultNode: {type: 'icon-node',size: [120, 40],style: defaultNodeStyle,labelCfg: defaultLabelCfg},defaultEdge: {type: 'cubic-vertical'},comboStateStyles,layout: defaultLayout})return graph
}
  • 事件处理
/*** @description:树型图的事件绑定*/// 展开收起子节点
function collapseNode(graph: Graph): void {// 展开和收起子节点graph.on('node:click', (e: any) => {if (e.target.get('name') === 'collapse-icon') {e.item.getModel().collapsed = !e.item.getModel().collapsedgraph.setItemState(e.item, 'collapsed', e.item.getModel().collapsed)graph.layout()}})
}// 鼠标滑入
function mouseenterNode(graph: Graph): void {graph.on('node:mouseover', (evt: any) => {const { item, target } = evtif (item._cfg.id === 'root') returnconst canHoverName = ['node-rect', 'remove-item']if (!canHoverName.includes(target.get('name'))) return// 显示iconconst deleteItem = item.get('group').find(function (el: any) {return el.cfg.name === 'remove-item'})deleteItem.attr('opacity', 1)if (item._cfg && item._cfg.keyShape) {item._cfg.keyShape.attr('stroke', '#2196f3')}graph.setItemState(item, 'active', true)})
}// 鼠标离开
function mouseLeaveNode(graph: Graph): void {graph.on('node:mouseout', (evt: any) => {const { item, target } = evtconst canHoverName = ['node-rect', 'remove-item']if (item._cfg.id === 'root') returnif (!canHoverName.includes(target.get('name'))) return// 隐藏iconconst deleteItem = item.get('group').find(function (el: any) {return el.cfg.name === 'remove-item'})deleteItem.attr('opacity', 0)if (item._cfg && item._cfg.keyShape) {item._cfg.keyShape.attr('stroke', '#fff')}graph.setItemState(item, 'active', false)})
}/*** @description 文本超长显示*/
const fittingString = (str: string, maxWidth: number, fontSize: number): string => {const ellipsis = '...'const ellipsisLength = Util.getTextSize(ellipsis, fontSize)[0]let currentWidth = 0let res = strconst pattern = new RegExp('[\u4E00-\u9FA5]+')str.split('').forEach((letter, i) => {if (currentWidth > maxWidth - ellipsisLength) returnif (pattern.test(letter)) {currentWidth += fontSize} else {currentWidth += Util.getLetterWidth(letter, fontSize)}if (currentWidth > maxWidth - ellipsisLength) {res = `${str.substr(0, i)}${ellipsis}`}})return res
}
  • 节点的增加、删除、编辑时间
const addEvent = (graph: any) => {graph.on('node:click', (evt: any) => {const { item, target } = evtconst name = target.get('name')// 增加元素const model = item.getModel()if (name === 'add-item') {state.editType = 'add'// 如果收起需要展开if (model.collapsed) model.collapsed = false// 没有子级的时候设置空数组if (!model.children) model.children = []const id = uniqueId()model.children.push({id,name: 1,sname: '',parentId: model.id,})graph.updateChild(model, model.id)const curTarget = graph.findDataById(id)const canvasXY = graph.getCanvasByPoint(curTarget.x, curTarget.y)state.editOne = curTargetstate.input = curTarget.snamesetTimeout(() => {state.showInput = truenextTick(() => {inputRref.value.focus()})}, 200)// 更改输入框的位置state.inputStyle = {left: `${canvasXY.x}px`,top: `${canvasXY.y}px`,}}// 删除节点if (name === 'remove-item') {graph.removeChild(model.id)// 查找当前的父id,更新其子元素的长度graph.updateItem(model.parentId, {})}// 编辑if (name === 'node-rect') {const curTarget = graph.findDataById(item._cfg.id)const canvasXY = graph.getCanvasByPoint(curTarget.x, curTarget.y)state.editOne = evt.itemstate.input = curTarget.snamestate.showInput = truestate.editType = 'edit'nextTick(() => {inputRref.value.focus()})state.inputStyle = {left: `${canvasXY.x}px`,top: `${canvasXY.y}px`,}}})// 画布滚动、拖动时,不能编辑节点名称graph.on('dragstart', () => {state.showInput = false})graph.on('wheel', () => {state.showInput = false})
}
  • dom 节点渲染后渲染树图
onMounted(() => {nextTick(() => {state.graph = initGraph('container')state.graph.clear()addEvent(state.graph)renderMap(state.treeData, state.graph)})
})

相关链接

  1. 源码链接
  2. Antv G6 官网
  3. 参考文章

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

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

相关文章

受限玻尔兹曼机浅析

受限玻尔兹曼机&#xff08;Restricted Boltzmann Machine&#xff0c;简称RBM&#xff09;是一种特殊的随机生成神经网络&#xff0c;能够学习并发现数据的复杂规则分布。以下是关于受限玻尔兹曼机算法的详细介绍&#xff1a; ⭐️ 定义与起源 定义&#xff1a; 受限玻尔兹…

深入剖析Tomcat(十四) Server、Service 组件:如何启停Tomcat服务?

通过前面文章的学习&#xff0c;我们已经了解了连接器&#xff0c;四大容器是如何配合工作的&#xff0c;在源码中提供的示例也都是“一个连接器”“一个顶层容器”的结构。并且启动方式是分别启动连接器和容器&#xff0c;类似下面代码 connector.setContainer(engine); try …

小程序的运行机制、更新机制、生命周期介绍保姆级教程全解

一、小程序运行机制 1. 小程序冷启动 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热启动- 冷启动&#xff1a;如果用户首次打开&#xff0c;或小程序销毁后被用户再次打开&#xff0c;此时小程序需要重新加载启动- 热启动&#xff1a;如果用户已经打…

HSP_12章 Python面向对象编程oop_多态

文章目录 P128 多态问题的引出P129 多态细节和使用1. 多态介绍&特别说明2. 多态的好处3. 特别说明: Python多态的特点4. 使用多态的机制来解决主人喂食物的问题 P128 多态问题的引出 先看一个问题 # 说明: 先试用传统的方式完成 class Food:name Nonedef __init__(self,…

4.Android逆向协议-详解二次打包失败解决方案

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;微尘网校 上一个内容&#xff1a;3.Android逆向协议-APP反反编译及回编译 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.…

#笔记# 写给自己用的小爬虫

最近完成了一个文旅行业信息聚合的小应用&#xff0c;实现仅从一个入口了解全行业的信息动态&#xff0c;不用一个一个翻看各网站&#xff0c;节省了不少检索时间。 一、基本思路 明确数据来源。基于前述目标&#xff0c;确定数据源为文化和旅游部管理部门官网&#xff0c;比…

【办公类-21-18】20240701 养老护理员初级选择题488,制作PyQt5图形界面GUI

背景需求&#xff1a; 6月16日育婴师高级考完了。运气好&#xff0c;抽到的是”护理患腹泻的幼儿”&#xff0c;“晨检与家长沟通”&#xff0c;“4个月婴儿喂蛋黄”&#xff0c;“21个月食谱”&#xff0c;都是我背过的题目&#xff08;没有抽到感统&#xff09; 于是一放假&…

【C语言】解决C语言报错:Invalid Pointer

文章目录 简介什么是Invalid PointerInvalid Pointer的常见原因如何检测和调试Invalid Pointer解决Invalid Pointer的最佳实践详细实例解析示例1&#xff1a;未初始化的指针示例2&#xff1a;已释放的指针示例3&#xff1a;返回局部变量的指针示例4&#xff1a;野指针 进一步阅…

《化学工程与装备》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《化学工程与装备》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的第一批认定学术期刊。 问&#xff1a;《化学工程与装备》级别&#xff1f; 答&#xff1a;省级。主管单位&#xff1a;福建石油化工集团有限责任公司 …

昇思25天学习打卡营第6天|网络构建

网络构建 概念模型模型参数 概念 神经网络模型是由神经网络层和Tensor操作构成的&#xff0c;mindspore.nn提供了常见神经网络层的实现&#xff0c;在MindSpore中&#xff0c;Cell类是构建所有网络的基类&#xff0c;也是网络的基本单元。一个神经网络模型表示为一个Cell&…

技术革新:如何用数据中台实现数字化转型

作为程序员&#xff0c;我们总是对技术如何改变企业运作充满好奇。今天&#xff0c;我们将深入探讨森马集团如何利用数据中台技术&#xff0c;实现从传统数据分析到数字化转型的华丽转身。 1. 技术背景&#xff1a;森马集团的数字化挑战 森马集团&#xff0c;一个在服饰行业占…

天津惠灵顿:从心,致逐梦康桥|在这所天津国际学校从容不迫中走近梦想

在刚刚落下帷幕的申请季中&#xff0c;来自惠灵顿天津校区的Herman&#xff0c;陆续收到了剑桥大学、帝国理工学院、纽约大学、瓦萨学院等10余封录取通知书。面对纷至沓来的名校肯定&#xff0c;经历了短暂的尘埃落定的喜悦&#xff0c;Herman很快恢复了往日里的泰然自若。在他…

chunkers/maxent_ne_chunker/english_ace_multiclass.pickle 找不到

首先在这个nltk_data &#xff1a; NLTK Data官方下的数据集&#xff0c;找不到english_ace_multiclass.pic 说明缺少这个文件 : 那么在 nlp/resources/chunkers/maxent_ne_chunker/english_ace_multiclass.pickle at master teropa/nlp (github.com) 下载那两个文件 : 然…

基于SpringBoot的CSGO赛事管理系统

您好&#xff01;我是专注于计算机技术研究的码农小野。如果您对CSGO赛事管理系统感兴趣或有相关开发需求&#xff0c;欢迎随时联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot框架&#xff0c;Java技术 工具&#xff1a;Eclipse&a…

迈阿密色主题学科 HTML5静态导航源码

源码介绍 迈阿密色主题学科 HTML5静态导航源码&#xff0c;源码直接上传可用&#xff0c;有技术的可以拿去写个后端搜索调用百度接口&#xff0c;也可用于做引导页下面加你网址添加一个A标签就行了&#xff0c;很简单&#xff0c;需要的朋友就拿去吧 界面预览 源码下载 迈阿…

专题五:Spring源码之初始化容器上下文

上一篇我们通过如下一段基础代码作为切入点&#xff0c;最终找到核心的处理是refresh方法&#xff0c;从今天开始正式进入refresh方法的解读。 public class Main {public static void main(String[] args) {ApplicationContext context new ClassPathXmlApplicationContext(…

鸿蒙本地签名不匹配问题

连接鸿蒙手机运行项目报如下错误 这是由于本地签名和鸿蒙设备签名不匹配导致的&#xff0c;需要注释掉如下代码&#xff0c;选择file project 自动签名 勾选auto选项&#xff0c;会在build-profile.json5中生成一个签名&#xff0c;然后运行就ok了~

创建一个Django用户认证系统

目录 1、Django2、Django用户认证系统User 模型&#xff1a;Authentication 视图&#xff1a;认证后端 (Authentication Backends)&#xff1a;Form 类&#xff1a;中间件 (Middleware)&#xff1a;权限和组 (Permissions and Groups)&#xff1a; 3、创建一个django用户认证系…

MNIST手写字体识别(算法基础)

快教程 10分钟入门神经网络 PyTorch 手写数字识别 慢教程 【深度学习Pytorch入门】 简单回归问题-1 梯度下降算法 梯度下降算法 l o s s x 2 ∗ s i n ( x ) loss x^2 * sin(x) lossx2∗sin(x) 求导得&#xff1a; f ‘ ( x ) 2 x s i n x x 2 c o s x f^(x)2xsinx x^…

Cesium大屏-vue3注册全局组件

1.需求 说明&#xff1a;产品经理要求开发人员在地图大屏上面随意放置组件&#xff0c;并且需要通过数据库更改其组件大小&#xff0c;位置等&#xff1b;适用于大屏组件中场站视角、任意位置标题等。 2.实现 2.1GlobalComponents.vue 说明&#xff1a;containerList可以通…