背景说明
近期检查前端工程的打包情况,发现@antv/g6占用了很大的空间:
经过前端团队内部了解,这个主要是用于项目内超算中心的关系图展示,关系图这一块也随着业务的需求使用了不同的组件来实现:echarts > Mindelixir > @antv/G6
实际使用@antv/G6
实现完的效果,我的直观感觉也不是很理想,数据量大的情况下,显示太小,而且看了下相关的代码,感觉实现过程也不够优雅。所以秉承公司精益求精的企业文化,为了减少打包体积及优化使用体验等目的,准备使用「Relation Graph
」对这一块进行大刀阔斧的改造。
经过三四天的打磨,整体效果得到了运营同事的认可,先看看实现后的效果:
本次调整后,打包体积上有较大的缩减:
优化前:(原始大小:13.69M;压缩后:4.71M;开启gzip:1.42M)
优化后:(原始大小:8.97M;压缩后:3.14M;开启gzip:957.42K)
代码实现
下面将项目中关系图相关的一些核心代码进行讲解
视图代码
<relation-graph ref="relationGraph" :options="graphOptions">
<div slot="node" slot-scope="{node}">
<el-popover
v-if="node.data.status === 'user'"
trigger="hover"
placement="right"
popper-class="account-graph-popver"
>
<div slot="reference" class="node-item flex align-center">
<div class="node-icon" :style="{color:node.data.iconColor}">
<em class="node-icon-inner" :class="node.data.nodeIconClassName" />
</div>
<span class="node-name margin-left-sm" v-text="node.text" />
</div>
<div class="popover-tips">
<p v-if="node.data.realname !== node.data.email && node.data.realname !== ''">
<span>{{ $t('userCenter.name') }}</span>: {{ node.data.realname }}
</p>
<p><span>{{ $t('common.email') }}</span>: {{ node.data.email }}</p>
<p v-if="node.data.phone">
<span>{{ $t('common.mobile') }}</span>: {{ node.data.phone }}
</p>
<div v-if="belongingType==='use' && stopClick('accountMgmt')">
<el-divider />
<p class="text-center">
<el-button slot="reference" type="text" class="padding-lr-xs" @click="unBind(node)">
<para-lang code="selfService.group.accountUnbinding" />
</el-button>
</p>
</div>
</div>
</el-popover>
<el-popover
v-else-if="node.data.status !== 'accountMessage' && stopClick('accountMgmt')"
trigger="hover"
placement="right"
popper-class="account-graph-popver"
>
<div slot="reference" class="node-item flex align-center">
<div class="node-icon" :style="{color:node.data.iconColor}">
<em class="node-icon-inner" :class="node.data.nodeIconClassName" />
</div>
<span class="node-name margin-left-sm" v-text="node.text" />
</div>
<div v-if="node.data.status === 'account'" class="popover-tips">
<p><span>{{ $t('common.SCAccount') }}</span>: {{ node.text }}</p>
<div>
<el-divider />
<p class="text-center">
<el-button v-if="belongingType==='belonging'" type="text" @click="modifyAccountRelation(node)">
修改所属关系
</el-button>
<el-button v-if="belongingType==='use'" type="text" @click="bindAccount(node)">
<para-lang code="selfService.group.accountBinding" />
</el-button>
</p>
</div>
</div>
<div v-if="node.data.status === 'ssc'" class="popover-tips">
<p><span>{{ $t('common.SCCode') }}</span>: {{ node.text }}</p>
<el-divider />
<p class="text-center">
<el-button type="text" @click="showAccountApply(node.text)">
<para-lang code="selfService.group.applicationAccount" />
</el-button>
</p>
</div>
</el-popover>
<div v-else class="node-item flex align-center">
<div class="node-icon" :style="{color:node.data.iconColor}">
<em class="node-icon-inner" :class="node.data.nodeIconClassName" />
</div>
<span class="node-name margin-left-sm" v-text="node.text" />
</div>
</div>
</relation-graph>
图标配置项
在Vue 组件的 data 里添加如下信息
graphOptions: {
'debug': envName === 'development', // 仅开发或Istio环境下为调试模式
'allowShowMiniView': true,
'allowSwitchLineShape': true,
'defaultFocusRootNode': false,
'layouts': [
{
'label': '树状-横向',
'layoutName': 'tree',
'useLayoutStyleOptions': true,
'layoutClassName': 'member-layout-tree-v',
'defaultLineColor': '#DDDDDD',
'levelDistance': '300,300,300,500',
'min_per_width': 100,
'max_per_width': 300,
'min_per_height': 40,
'max_per_height': 100,
'defaultNodeShape': 1,
'defaultLineShape': 6,
'defaultJunctionPoint': 'lr',
'defaultExpandHolderPosition': 'right',
'from': 'left',
'defaultNodeHeight': '36',
'defaultNodeColor': '#C6E5FF',
'defaultNodeBorderWidth': 1,
'defaultNodeBorderColor': '#CED4D9',
'defaultNodeFontColor': '#666666'
}, {
'label': '树状-纵向',
'layoutName': 'tree',
'useLayoutStyleOptions': true,
// 'defaultFocusRootNode': false,
'layoutClassName': 'member-layout-tree-h',
'defaultLineColor': '#DDDDDD',
// 'levelDistance': '120',
'min_per_width': 100,
'max_per_width': 400,
'min_per_height': 100,
'max_per_height': 500,
'defaultNodeShape': 1,
'defaultLineShape': 6,
'defaultJunctionPoint': 'tb',
'defaultExpandHolderPosition': 'bottom',
'from': 'top',
'defaultNodeHeight': '36',
'defaultNodeColor': '#C6E5FF',
'defaultNodeBorderWidth': 1,
'defaultNodeBorderColor': '#CED4D9',
'defaultNodeFontColor': '#666666'
},
{
'label': '中心布局',
'layoutName': 'center',
'useLayoutStyleOptions': true,
'layoutClassName': 'member-layout-center',
'levelDistance': '80',
'defaultNodeShape': 1,
'defaultLineShape': 5,
// 'defaultFocusRootNode': true,
'defaultLineColor': '#DDDDDD',
'defaultJunctionPoint': 'border',
'defaultNodeHeight': '36',
'defaultNodeColor': '#C6E5FF',
'defaultNodeBorderWidth': 1,
'defaultNodeBorderColor': '#CED4D9',
'defaultNodeFontColor': '#666666'
}
]
}
将后端响应的数据组织成 Relation Graph 组件所需的数据
在 methods 中添加如下方法
// 根据 RelationGraph 图表的数据组织形式,梳理内容
buildGraphData() {
const _this = this
const graphData = {
rootId: '',
nodes: [],
links: []
}
const nodes = graphData.nodes
const links = graphData.links
// 生成随机ID,针对数据无id属性的情况可生成一个随机ID
const buildRandomId = function() {
return 'mock-' + Date.now().toString(36) + '-' + Math.random().toString(36).substr(4)
}
var buildNodeData = function(node) {
const nodeData = {
'id': node.id,
'name': node.name,
'data': { ...node }
}
const nodeClassMap = {
'ssc': { name: '超算中心', color: node.online === 1 ? '#9de173' : '#ccc', icon: 'console-svg svg-sc-center' },
'user': { name: '用户', color: node.groupId === _this.user.userInfo.groupId ? '#C6E5FF' : '#8aabc1', iconColor: node.userId === _this.user.userInfo.group.payUserId ? '#f7c934' : '', icon: node.userId === _this.user.userInfo.group.payUserId ? 'console-svg svg-main-account' : 'console-svg svg-sub-account' },
'account': { name: '超算账号', color: node.inSameGroup ? '#da97f9' : '#c7ceff', icon: 'console-svg svg-sc-account-2' },
'accountMessage': { name: '账号信息', color: '#0f7dfa', icon: 'console-icon icon-sc-account' }
}
// nodeData.width = node.name.length * 12
nodeData.data.nodeIconClassName = nodeClassMap[node.status].icon
nodeData.data.iconColor = nodeClassMap[node.status].iconColor || '#666'
nodeData.color = nodeClassMap[node.status].color || '#C6E5FF'
if (node.status === 'accountMessage') {
nodeData.fontColor = '#fff'
}
return nodeData
}
const buildData = function(node) {
let parentNodeId = node.id
if (!parentNodeId) {
node.id = parentNodeId = buildRandomId()
}
if (nodes.length === 0) {
graphData.rootId = parentNodeId
const nodeData = buildNodeData(node)
nodes.push(nodeData)
}
if (node.children && node.children.length > 0) {
for (const childNode of node.children) {
// 若节点没有id,则给一个随机id值
if (!childNode.id) {
childNode.id = buildRandomId()
}
const childNodeData = buildNodeData(childNode)
const nodeLink = { from: parentNodeId, to: childNode.id, text: '' }
nodes.push(childNodeData)
links.push(nodeLink)
// 递归添加子节点
if (childNode.children && childNode.children.length > 0) {
buildData(childNode)
}
}
}
}
if (this.drawData && this.drawData.length > 0) {
buildData(this.drawData[0])
}
return graphData
}
获取数据并初始化组件
在 methods 中添加如下代码
initGraph() {
this.graphInited = false
const graphData = this.buildGraphData()
this.$nextTick(_ => {
this.$refs.relationGraph.setJsonData(graphData, () => {
this.graphInited = true
})
})
}
其他说明:
- layoutName 为 force 时存在一些问题,自动时会动态调整各节点的位置,导致一些交互受到影响,所以本工程没有启用自动布局
- 使用纵向树状布局时节点较多的情况,后面的节点容易覆盖在上一个节点上,找了下github里的issue,说可以对各节点进行绝对布局实现,但是工作量会比较大,运营没在这个点上纠结,咱也就不考虑优化了。
- 鼠标悬停在节点上时无法触发el-popover组件的悬停事件,经排查跟节点的 + - 折叠样式有关,该节点的宽度太宽,导致底部的节点内容无法响应鼠标事件,使用如下样式覆盖默认样式完美解决(以下样式代码包含图标组件其他元素的样式调整):
.graph-container {
::v-deep .c-mini-graph {
position: absolute;
top:0;
right:0;
margin-top:0
}
::v-deep .c-mini-toolbar {
margin-left:0;
margin-top:0;
right:0;
bottom:10px;
}
/* 调整样式,避免出现账号悬停不显示用户信息的情况 */
::v-deep .c-expand-positon-right{
width: auto;
right:0;
}
::v-deep .c-expand-positon-bottom {
width: auto;
left: 50%;
margin-left: -10px;
}
::v-deep .rel-node-shape-1 {
overflow:hidden;
padding:0;
border-radius: 6px;
}
::v-deep .node-item {
padding: 0 10px 0 0;
line-height:34px;
.node-icon {
background-color:#fff;
width: 36px;
text-align: center;
line-height: 34px;
vertical-align: middle;
em {
font-size:22px;
}
}
.node-name {
white-space:nowrap;
display: inline-block;
}
}
// 布局文字定位调整
::v-deep .c-mb-child-panel .c-mb-button .c-mb-text{
padding-top:3px;
display: block;
width: 100%;
position: relative;
margin: 0;
}
}
评论 (0)