G6文档
自定义节点
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='负责人'><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 的 htmlhtml: str,},draggable: true,});},},"single-node"
);
在pc端,自定义的节点,绑定的点击事件起作用,但是移动端模式,不会起作用;
解决方法:
文档中也说明了,节点的选中事件,需要将Mode
切换到edit
模式。graph.setMode("edit");
如:
graph.setMode("edit");
graph.on("nodeselectchange", (e) => {// 当前操作的 itemalert("node");console.log(e.target);// 当前操作后,所有被选中的 items 集合console.log(e.selectedItems);// 当前操作时选中(true)还是取消选中(false)console.log(e.select);
});
此处,直接用自定节点的事件了。
全部代码
<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的配置项
G6.registerNode("icon-node",{options: {size: [60, 20], // 宽高stroke: "#91d5ff", // 变颜色fill: "#fff", // 填充色},// draw是绘制后的附加操作-节点的配置项 图形分组,节点中图形对象的容器draw(cfg, group) {// 获取节点的配置const styles = this.getShapeStyle(cfg);// 解构赋值const { labelCfg = {} } = cfg;const w = styles.width;const h = styles.height;// 向分组中添加新的图形 图形 配置 rect矩形 xy 代表左上角坐标 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;},// 更新节点后的操作,一般同 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='负责人'> <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 的 htmlhtml: str,},draggable: true,});},},"single-node"
);// 绘制层级之间的连接线
G6.registerEdge("flow-line", {// 绘制后的附加操作draw(cfg, group) {// 边两端与起始节点和结束节点的交点;const startPoint = cfg.startPoint;const endPoint = cfg.endPoint;// 边的配置const { style } = cfg;const shape = group.addShape("path", {attrs: {stroke: style.stroke, // 边框的样式endArrow: style.endArrow, // 结束箭头// 路径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;},
});// 默认连接边线的颜色 末尾箭头
const defaultEdgeStyle = {stroke: "#ccc",
};// 默认布局
// compactBox 紧凑树布局
// 从根节点开始,同一深度的节点在同一层,并且布局时会将节点大小考虑进去。
const defaultLayout = {type: "compactBox", // 布局类型树direction: "TB", // TB 根节点在上,往下布局getId: function getId(d) {// 节点 id 的回调函数return d.id;},getHeight: function getHeight() {// 节点高度的回调函数return 16;},getWidth: function getWidth() {// 节点宽度的回调函数return 16;},getVGap: function getVGap(d) {// 节点纵向间距的回调函数if (d.parId === "0") return 70;return 80;},getHGap: function getHGap(d) {// 节点横向间距的回调函数if (d.parId === "0") return 100;return 150;},
};
let graph;export default {name: "Home",props: {treeListData: {type: Array,default: () => [],},options: {type: Object,default: () => {return {};},},},emits: ["handleSelected"],data() {return {listData: [],selectedId: "", // 选中的节点IdinitOptions: {isFitView: true, // 是否默认适应全局isFitCenter: true, // 是否居中isHiddenRoot: 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 图表的载体-实例化graph = new G6.TreeGraph({container: "container", // 图的 DOM 容器width,height,linkCenter: true, // 指定边是否连入节点的中心modes: {// default 模式中包含点击选中节点行为和拖拽画布行为;default: [{// 这个是可以展开可以收起type: "collapse-expand",onChange: function onChange(item, collapsed) {const data = item.get("model");data.collapsed = collapsed;return true;},},"drag-canvas","zoom-canvas","click-select",],edit: ["click-select"],},// 默认状态下节点的配置defaultNode: {type: "dom-node", // 'icon-node',size: [250, 60],},// 默认状态下边线的配置,defaultEdge: {type: "flow-line",style: defaultEdgeStyle,},// 布局配置项layout: defaultLayout,renderer: "svg",});graph.data([...this.listData][0]);graph.render();// 让画布内容适应视口。if (this.initOptions.isFitView) {graph.fitView();}if (this.initOptions.isFitCenter) {graph.fitCenter();}if (!this.initOptions.isHiddenRoot) {// 是否要移除根节点const item = graph.findById([...this.listData][0].id);graph.removeItem(item);}// 改变视口的缩放比例,在当前画布比例下缩放,是相对比例。graph.zoom(1);graph.setMode("edit");},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.nodeClick = function (id) {const item = graph.findById(id);setSelectFalse(_this.listData);item._cfg.model.isSelected = true;graph.changeData(_this.listData[0]);graph.refresh();graph.fitCenter();};window.handleDetail = (id) => {const item = graph.findById(id);if (item?._cfg?.parent) {_this.$emit("handleSelected", id);}};this.G6init();},},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: 40px;height: 40px;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;}}
}
::v-deep g g g:not(:first-child) foreignObject {font-size: 14px;
}
foreignObject {overflow: initial !important;
}
</style>