前言
依稀记得很久之前,用 ant-G6 开发了公司业务系统中的血缘视图。但是 ant-G6 却不能使用 当前流行的框架来实现自定义节点,这很大程度上限制了开发的可行性。最初我使用 提供的 rect,circle,image 等来实现业务效果。后来对 Vue 的了解上,发现并实践出了以下方法。
核心解决方案
本质上是使用 vue 的 render 渲染函数 和 h 函数,将vue的组件渲染为html,最终截取出来作为 G6 的节点使用。
自定义节点组件
xml
// Node.vue
<template>
<div
:class="[$style['node'], isCenter ? $style['is-center'] : '']"
role="button"
>
<ElIcon :class="$style['icon']" class="yg-icon mx-4">
<component :is="icons['ai-search']"></component>
</ElIcon>
<span :class="$style['title']">{{ title || '' }}</span>
</div>
</template>
<script lang="ts" setup>
// 自己封装导出图标,修改后使用
import icons from './icons';
//
import { ElIcon } from '@element-plus';
const props = withDefaults(
defineProps<{ isCenter?: boolean; title?: string }>(),
{
isCenter: true,
title: '',
}
);
</script>
<style lang="scss" module>
@use '@insight/ui/scss/config' as *;
@use '@insight/ui/scss/mixins' as *;
.node {
height: 40px;
width: 180px;
display: flex;
align-items: center;
user-select: none;
// background-color: var(--el-color-primary-light-5);
background-color: #e4f0fa;
color: var(--el-content-text-color-default);
&.is-center {
// background-color: var(--el-color-primary-light-3);
background-color: rgb(22, 135, 232);
color: var(--el-color-white);
}
&::before {
content: '';
width: 2px;
height: 100%;
background-color: var(--el-color-primary);
}
}
.icon {
color: var(--el-color-primary);
}
.title {
display: inline-block;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
Vue 组件转化为Html的 hooks
ini
// index.ts
/**
* Vue组件转换为Html字符串 hooks
**/
const useComponentHtmlString = () => {
const $iconContainer = document.createElement('div');
const getCustomNodeHtml = (
nodeProps?: Partial<InstanceType<typeof Node>>
) => {
render(vueH(Node, nodeProps), $iconContainer);
return $iconContainer.innerHTML;
};
return {
getCustomNodeHtml,
};
};
使用
php
registerNodeimport G6, {
type GraphData,
type NodeConfig,
type ComboConfig,
Graph,
type INode,
type IEdge,
type Item,
} from '@antv/g6';
const { getCustomNodeHtml } = useComponentHtmlString();
自定义节点方法
G6.registerNode(
'card-node',
{
draw: function drawShape(cfg, group) {
// cfg 参数中包含节点的配置信息
const rect = cfg.rect as Rect;
const { width: w, height: h } = cfg.rect as Rect;
const isField = rect.isField || false;
const isCenter = rect.isCenter || false;
const displayName = cfg.displayName as string;
group.addShape('dom', {
zIndex: 9,
attrs: {
height: 40,
width: 180,
x: 0,
y: 0,
opacity: 1,
html: getCustomNodeHtml({ title: displayName, isCenter: isCenter }),
cursor: 'pointer',
},
draggable: true,
name: 'card-node',
});
group.addShape('rect', {
zIndex: 1,
attrs: {
x: 0,
y: 0,
height: 40,
width: 180,
fill: 'transparent',
opacity: 1,
stroke: '',
},
name: 'rect-shape-border',
});
const shape = group.addShape('rect', {
zIndex: 1,
attrs: {
height: 40,
width: 180,
x: 0,
y: 0,
fill: 'transparent',
opacity: 1,
},
name: 'card-node-header-drag',
draggable: true,
});
// more ...
return shape
}
}
)
注意点
- 虽然使用渲染函数可以实现自定义节点,和复杂的交互功能,但是一些节点的交互和自定义状态需要配合 ant-G6 的 nodeStateStyles 等等参数使用。
- 在 registerNode 的 draw 方法中,第一个参数 cfg 也包含了自定义节点配置的相关信息,可以基于 节点的dom 和rect 等布局框架实现自定义效果。