vue結(jié)合g6實(shí)現(xiàn)樹級(jí)結(jié)構(gòu)(compactBox?緊湊樹)
最近公司項(xiàng)目需求有用到樹級(jí)結(jié)構(gòu),所以就上網(wǎng)上找了點(diǎn)資料,本文就實(shí)現(xiàn)vue結(jié)合g6實(shí)現(xiàn)樹級(jí)結(jié)構(gòu),具體如下:
自定義節(jié)點(diǎn)
G6.registerNode( "dom-node", { draw: (cfg, group) => { let str = ` <div class='item-box catalog-node ${ cfg.isSelected ? "is-selected" : "" } ${cfg.status}-box' οnclick='handleDetail("${cfg.id}")' id="${ cfg.id }" style="width: ${cfg.size[0] - 5}px;"> ${ cfg.status ? `<span class='status ${cfg.status}'>${getLabel( ISSUE_STATUS, cfg.status )}</span>` : "" } ${ cfg?.manager?.name ? `<p class=''><span class="title-txt avatar-img" title='負(fù)責(zé)人'> <img src="${cfg?.manager?.avatar}" /> </span>${cfg.manager.name} </p>` : "" } <div class='title' οnclick='handleDetail("${cfg.id}")'><span ${ cfg.typeName === "Bug" ? `class='tipText'` : "" }>${cfg.title}</span> </div> </div> `; return group.addShape("dom", { attrs: { width: cfg.size[0], height: nodeHeight(cfg), // 傳入 DOM 的 html html: str, }, draggable: true, }); }, }, "single-node" );
在pc端,自定義的節(jié)點(diǎn),綁定的點(diǎn)擊事件起作用,但是移動(dòng)端模式,不會(huì)起作用;
解決方法:
文檔中也說明了,節(jié)點(diǎn)的選中事件,需要將Mode
切換到edit
模式。graph.setMode("edit");
(模式可自定義)
如:
graph.setMode("edit"); graph.on("nodeselectchange", (e) => { // 當(dāng)前操作的 item alert("node"); console.log(e.target); // 當(dāng)前操作后,所有被選中的 items 集合 console.log(e.selectedItems); // 當(dāng)前操作時(shí)選中(true)還是取消選中(false) console.log(e.select); });
此處,直接用自定節(jié)點(diǎn)的事件了。
以上只是解決了移動(dòng)端不能觸發(fā)點(diǎn)擊事件,但是移動(dòng)端存在拖動(dòng)canvas與點(diǎn)擊事件沖突問題,所以為了PC端與移動(dòng)端都能起效果,做了以下處理。
- 設(shè)置兩個(gè)模式,default: [“drag-node”, “drag-canvas”, “click-select”]; edit: [‘click-select’]。edit 模式還需要添加個(gè)自定義的Behavior(G6.registerBehavior(“behavior-name”, {})),自定義鼠標(biāo)拖動(dòng)事件;
- 所以,先判斷是否是移動(dòng)端,如果是移動(dòng)端,setMode(‘edit’);
全部代碼
<template> <div id="container"></div> </template> <script> // 引入antv-G6 import G6 from "@antv/g6"; import { ISSUE_STATUS } from "@/utils/constant"; import { getLabel } from "@/utils"; // G6的配置項(xiàng) G6.registerNode( "icon-node", { options: { size: [60, 20], // 寬高 stroke: "#91d5ff", // 變顏色 fill: "#fff", // 填充色 }, // draw是繪制后的附加操作-節(jié)點(diǎn)的配置項(xiàng) 圖形分組,節(jié)點(diǎn)中圖形對(duì)象的容器 draw(cfg, group) { // 獲取節(jié)點(diǎn)的配置 const styles = this.getShapeStyle(cfg); // 解構(gòu)賦值 const { labelCfg = {} } = cfg; const w = styles.width; const h = styles.height; // 向分組中添加新的圖形 圖形 配置 rect矩形 xy 代表左上角坐標(biāo) w h是寬高 const keyShape = group.addShape("rect", { attrs: { ...styles, x: -w / 2, y: -h / 2, }, }); // 文本文字的配置 if (cfg.title) { group.addShape("text", { attrs: { ...labelCfg.style, text: cfg.title, x: 50 - w / 2, y: 25 - h / 2, }, }); } return keyShape; }, // 更新節(jié)點(diǎn)后的操作,一般同 afterDraw 配合使用 update: undefined, }, "rect" ); const nodeHeight = (obj) => { // if (obj.depth == 0) { // return 100; // } const l = ["manager", "title"]; const arr = l.filter((item) => { return obj[item]; }); return arr.length * 25 + 50; }; G6.registerNode( "dom-node", { draw: (cfg, group) => { let str = ` <div class='item-box catalog-node ${ cfg.isSelected ? "is-selected" : "" } ${cfg.status}-box' οnclick='handleDetail("${cfg.id}")' id="${ cfg.id }" style="width: ${cfg.size[0] - 5}px;"> ${ cfg.status ? `<span class='status ${cfg.status}'>${getLabel( ISSUE_STATUS, cfg.status )}</span>` : "" } ${ cfg?.manager?.name ? `<p class=''><span class="title-txt avatar-img" title='負(fù)責(zé)人'> <img src="${cfg?.manager?.avatar}" /> </span>${cfg.manager.name} </p>` : "" } <div class='title' οnclick='handleDetail("${cfg.id}")'><span ${ cfg.typeName === "Bug" ? `class='tipText'` : "" }>${cfg.title}</span> </div> </div> `; return group.addShape("dom", { attrs: { width: cfg.size[0], height: nodeHeight(cfg), // 傳入 DOM 的 html html: str, }, draggable: true, }); }, }, "single-node" ); // 繪制層級(jí)之間的連接線 G6.registerEdge("flow-line", { // 繪制后的附加操作 draw(cfg, group) { // 邊兩端與起始節(jié)點(diǎn)和結(jié)束節(jié)點(diǎn)的交點(diǎn); const startPoint = cfg.startPoint; const endPoint = cfg.endPoint; // 邊的配置 const { style } = cfg; const shape = group.addShape("path", { attrs: { stroke: style.stroke, // 邊框的樣式 endArrow: style.endArrow, // 結(jié)束箭頭 // 路徑 path: [ ["M", startPoint.x, startPoint.y], ["L", startPoint.x, (startPoint.y + endPoint.y) / 2], ["L", endPoint.x, (startPoint.y + endPoint.y) / 2], ["L", endPoint.x, endPoint.y], ], }, }); return shape; }, }); // 默認(rèn)連接邊線的顏色 末尾箭頭 const defaultEdgeStyle = { stroke: "#ccc", }; // 默認(rèn)布局 // compactBox 緊湊樹布局 // 從根節(jié)點(diǎn)開始,同一深度的節(jié)點(diǎn)在同一層,并且布局時(shí)會(huì)將節(jié)點(diǎn)大小考慮進(jìn)去。 const defaultLayout = { type: "compactBox", // 布局類型樹 direction: "TB", // TB 根節(jié)點(diǎn)在上,往下布局 getId: function getId(d) { // 節(jié)點(diǎn) id 的回調(diào)函數(shù) return d.id; }, getHeight: function getHeight() { // 節(jié)點(diǎn)高度的回調(diào)函數(shù) return 16; }, getWidth: function getWidth() { // 節(jié)點(diǎn)寬度的回調(diào)函數(shù) return 16; }, getVGap: function getVGap(d) { // 節(jié)點(diǎn)縱向間距的回調(diào)函數(shù) if (d.parId === "0") return 70; return 80; }, getHGap: function getHGap(d) { // 節(jié)點(diǎn)橫向間距的回調(diào)函數(shù) if (d.parId === "0") return 100; return 150; }, }; // 自定義拖動(dòng)事件 G6.registerBehavior("finger-drag-canvas", { dragging: false, offset: 0, getEvents() { return { touchstart: "onDragStart", touchmove: "onDrag", touchend: "onDragEnd", }; }, onDragStart(e) { const self = this; self.dragging = false; self.offset = 0; const clientX = +e.clientX; const clientY = +e.clientY; this.origin = { x: clientX, y: clientY, }; }, onDrag(e) { const { graph } = this; if (!this.dragging) { this.dragging = true; } this.updateViewport(e); }, onDragEnd(evt) { const edges = graph.getEdges(); const nodes = graph.getNodes(); const node = evt.item; const point = { x: evt.x, y: evt.y }; // this.updateViewport(e); this.dragging = false; // 這里開始識(shí)別點(diǎn)擊事件 if (this.offset < 30) { // 觸發(fā)點(diǎn)擊事件(或者依靠e.target,e.type去做相應(yīng)的業(yè)務(wù)操作) console.log(evt, evt.type); } console.log(evt, evt.type); this.updateViewport(evt); }, updateViewport(e) { const { origin } = this; const clientX = +e.clientX; const clientY = +e.clientY; if (isNaN(clientX) || isNaN(clientY)) { return; } let dx = clientX - origin.x; let dy = clientY - origin.y; if (this.get("direction") === "x") { dy = 0; } else if (this.get("direction") === "y") { dx = 0; } this.origin = { x: clientX, y: clientY, }; const width = graph.get("width"); const height = graph.get("height"); const graphCanvasBBox = graph.get("canvas").getCanvasBBox(); if ( (graphCanvasBBox.minX <= width && graphCanvasBBox.minX + dx > width) || (graphCanvasBBox.maxX >= 0 && graphCanvasBBox.maxX + dx < 0) ) { dx = 0; } if ( (graphCanvasBBox.minY <= height && graphCanvasBBox.minY + dy > height) || (graphCanvasBBox.maxY >= 0 && graphCanvasBBox.maxY + dy < 0) ) { dy = 0; } if (dx === 0 && dy === 0) return; // 增加拖動(dòng)距離統(tǒng)計(jì) this.offset += Math.abs(dx) + Math.abs(dy); graph.translate(dx, dy); }, }); let graph; export default { name: "Home", props: { treeListData: { type: Array, default: () => [], }, options: { type: Object, default: () => { return {}; }, }, }, emits: ["handleSelected"], data() { return { listData: [], selectedId: "", // 選中的節(jié)點(diǎn)Id initOptions: { isFitView: true, // 是否默認(rèn)適應(yīng)全局 isFitCenter: true, // 是否居中 isHiddenRoot: true, // 是否顯示根元素 }, flag: false, // 如果是移動(dòng)端,true }; }, methods: { G6init() { if (typeof window !== "undefined") { window.onresize = () => { if (!graph || graph.get("destroyed")) return; if (!container || !container.scrollWidth || !container.scrollHeight) return; graph.changeSize(container.scrollWidth, container.scrollHeight); }; } // 獲取容器 const container = document.getElementById("container"); // 獲取容器的寬高 const width = container.scrollWidth; const height = container.scrollHeight - 30 || 500; // Graph 是 G6 圖表的載體-實(shí)例化 graph = new G6.TreeGraph({ container: "container", // 圖的 DOM 容器 width, height, linkCenter: true, // 指定邊是否連入節(jié)點(diǎn)的中心 modes: { // default 模式中包含點(diǎn)擊選中節(jié)點(diǎn)行為和拖拽畫布行為; default: [ { type: "zoom-canvas", enableOptimize: true, //開啟性能優(yōu)化 }, "drag-node", "drag-canvas", // "zoom-canvas", "click-select", ], edit: ["click-select"], }, // 默認(rèn)狀態(tài)下節(jié)點(diǎn)的配置 defaultNode: { type: "dom-node", // 'icon-node', size: [250, 60], }, // 默認(rèn)狀態(tài)下邊線的配置, defaultEdge: { type: "flow-line", style: defaultEdgeStyle, }, // 布局配置項(xiàng) layout: defaultLayout, renderer: "svg", }); graph.data([...this.listData][0]); graph.render(); // 讓畫布內(nèi)容適應(yīng)視口。 if (this.initOptions.isFitView) { graph.fitView(); } if (this.initOptions.isFitCenter) { graph.fitCenter(); } if (!this.initOptions.isHiddenRoot) { // 是否要移除根節(jié)點(diǎn) const item = graph.findById([...this.listData][0].id); graph.removeItem(item); } // 改變視口的縮放比例,在當(dāng)前畫布比例下縮放,是相對(duì)比例。 graph.zoom(1); }, async init() { let _this = this; if (graph) { // 如果原來有畫布,需要先清除 graph.destroy(); } this.initOptions = Object.assign(this.initOptions, this.options); this.listData = [...this.treeListData]; function setSelectFalse(obj) { obj.forEach((element) => { element.isSelected = false; if (element.children) { setSelectFalse(element.children); } }); } window.handleDetail = (id) => { const item = graph.findById(id); if (item?._cfg?.parent) { _this.$emit("handleSelected", id); } }; this.G6init(); this.isMobile(); }, isMobile() { // 判斷是否是移動(dòng)端 this.flag = navigator.userAgent.match( /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i ); if (this.flag) { graph.setMode("edit"); graph.addBehaviors("finger-drag-canvas", "edit"); } }, }, beforeDestroy() { console.log("推出"); }, }; </script> <style lang="scss" scoped> @import "@/assets/styles/common.scss"; #container { height: 100%; width: 100%; border: 1px solid #efefef; ::v-deep .title { font-size: 15px; display: block; // text-align: center; position: relative; margin: 10px 0; padding-left: 15px; color: #1199ff; cursor: pointer; } ::v-deep .item-box { background-color: #fff; border-radius: 5px; padding: 5px; // height: 100%; border: 1px solid; position: relative; p { margin-bottom: 2px; display: flex; align-items: center; color: #333; } &.is-selected { border: 1px solid #1199ff; } .tipText { color: red; } .logs { height: 70px; overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3; } .title-txt { display: inline-block; width: 80px; color: rgb(169, 169, 169); } .avatar-img { width: 35px; height: 35px; margin-right: 15px; img { width: 100%; height: 100%; border-radius: 100%; } } .status { position: absolute; right: 15px; top: 15px; border: 1px solid; padding: 0 5px; font-size: 12px; border-radius: 4px; // Wait 未開始、Doing進(jìn)行中、Pause暫停、Verify待驗(yàn)證、Done已完成、Cancel已取消 } } } ::v-deep g g g:not(:first-child) foreignObject { font-size: 14px; } foreignObject { overflow: initial !important; } </style>
到此這篇關(guān)于vue結(jié)合g6實(shí)現(xiàn)樹級(jí)結(jié)構(gòu)(compactBox 緊湊樹)的文章就介紹到這了,更多相關(guān)vue g6 樹級(jí)結(jié)構(gòu) 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3中使用reactive時(shí),后端有返回?cái)?shù)據(jù)但dom沒有更新的解決
這篇文章主要介紹了Vue3中使用reactive時(shí),后端有返回?cái)?shù)據(jù)但dom沒有更新的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03如何利用Echarts根據(jù)經(jīng)緯度給地圖畫點(diǎn)畫線
最近工作中遇到了一個(gè)需求,需要利用echarts在地圖上面標(biāo)記點(diǎn)位,所下面這篇文章主要給大家介紹了關(guān)于如何利用Echarts根據(jù)經(jīng)緯度給地圖畫點(diǎn)畫線的相關(guān)資料,需要的朋友可以參考下2022-05-05vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法
下面小編就為大家分享一篇vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-03-03如何在Vue單頁面中進(jìn)行業(yè)務(wù)數(shù)據(jù)的上報(bào)
為什么要在標(biāo)題里加上一個(gè)業(yè)務(wù)數(shù)據(jù)的上報(bào)呢,因?yàn)樵谠蹅兦岸隧?xiàng)目中,可上報(bào)的數(shù)據(jù)維度太多,比如還有性能數(shù)據(jù)、頁面錯(cuò)誤數(shù)據(jù)、console捕獲等。這里我們只講解業(yè)務(wù)數(shù)據(jù)的埋點(diǎn)。2021-05-05Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問題的實(shí)現(xiàn)
這篇文章主要介紹了Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問題的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05Vue 實(shí)現(xiàn)簡(jiǎn)易多行滾動(dòng)"彈幕"效果
這篇文章主要介紹了Vue 實(shí)現(xiàn)簡(jiǎn)易多行滾動(dòng)“彈幕”效果,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01