vue結(jié)合g6實(shí)現(xiàn)樹(shù)級(jí)結(jié)構(gòu)(compactBox?緊湊樹(shù))
最近公司項(xiàng)目需求有用到樹(shù)級(jí)結(jié)構(gòu),所以就上網(wǎng)上找了點(diǎn)資料,本文就實(shí)現(xiàn)vue結(jié)合g6實(shí)現(xiàn)樹(shù)級(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ì)起作用;
解決方法:
文檔中也說(shuō)明了,節(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)擊事件沖突問(wè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 緊湊樹(shù)布局
// 從根節(jié)點(diǎn)開(kāi)始,同一深度的節(jié)點(diǎn)在同一層,并且布局時(shí)會(huì)將節(jié)點(diǎn)大小考慮進(jìn)去。
const defaultLayout = {
type: "compactBox", // 布局類(lèi)型樹(shù)
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;
// 這里開(kāi)始識(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)行為和拖拽畫(huà)布行為;
default: [
{
type: "zoom-canvas",
enableOptimize: true, //開(kāi)啟性能優(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();
// 讓畫(huà)布內(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)前畫(huà)布比例下縮放,是相對(duì)比例。
graph.zoom(1);
},
async init() {
let _this = this;
if (graph) {
// 如果原來(lái)有畫(huà)布,需要先清除
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 未開(kāi)始、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)樹(shù)級(jí)結(jié)構(gòu)(compactBox 緊湊樹(shù))的文章就介紹到這了,更多相關(guān)vue g6 樹(shù)級(jí)結(jié)構(gòu) 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3中使用reactive時(shí),后端有返回?cái)?shù)據(jù)但dom沒(méi)有更新的解決
這篇文章主要介紹了Vue3中使用reactive時(shí),后端有返回?cái)?shù)據(jù)但dom沒(méi)有更新的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Vue數(shù)據(jù)變化后頁(yè)面更新詳細(xì)介紹
這篇文章主要介紹了Vue在數(shù)據(jù)發(fā)生變化后是如何更新頁(yè)面的,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-10-10
如何利用Echarts根據(jù)經(jīng)緯度給地圖畫(huà)點(diǎn)畫(huà)線
最近工作中遇到了一個(gè)需求,需要利用echarts在地圖上面標(biāo)記點(diǎn)位,所下面這篇文章主要給大家介紹了關(guān)于如何利用Echarts根據(jù)經(jīng)緯度給地圖畫(huà)點(diǎn)畫(huà)線的相關(guān)資料,需要的朋友可以參考下2022-05-05
vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法
下面小編就為大家分享一篇vue.js移動(dòng)數(shù)組位置,同時(shí)更新視圖的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
如何在Vue單頁(yè)面中進(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ù)、頁(yè)面錯(cuò)誤數(shù)據(jù)、console捕獲等。這里我們只講解業(yè)務(wù)數(shù)據(jù)的埋點(diǎn)。2021-05-05
Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問(wèn)題的實(shí)現(xiàn)
這篇文章主要介紹了Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問(wèn)題的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Vue 實(shí)現(xiàn)簡(jiǎn)易多行滾動(dòng)"彈幕"效果
這篇文章主要介紹了Vue 實(shí)現(xiàn)簡(jiǎn)易多行滾動(dòng)“彈幕”效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-01-01
vue+element樹(shù)形選擇器組件封裝和使用方式
這篇文章主要介紹了vue+element樹(shù)形選擇器組件封裝和使用方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2020-04-04

