const graph = new G6.TreeGraph({
animate: false, // 切换布局时是否使用动画过度
})
<div class="reset-fitView"
id="reset-button">
<img :src="resetViewUrl"
style="margin: 4px;">
div>
// 设置复原按钮图片默认值
export default {
data () {
return {
resetViewUrl: require('./img/original.svg'), // 复位按钮根据画布调整改变展示图片
}
},
以下这段代码结合下面,标题“还有各种鼠标事件(G6拓扑图缩放事件等)”里的,根据数据创建树状图节createNode () 方法里的页面缩放操作,可以官方文档搜viewportchange属性了解页面事件
// 监听画布缩放
let originalView = graph.getZoom()
let number = graph.getZoom()
graph.on('viewportchange', e => {
if (e.action == 'translate') { // 画布被拖动
this.resetViewUrl = require('./img/change.svg')
}
if (e.action !== 'zoom') return; // 画布未被缩放
const currentZoom = graph.getZoom();
number = currentZoom;
this.resetViewUrl = require('./img/change.svg')
if (originalView == number) {
this.resetViewUrl = require('./img/original.svg')
}
});
// 画布里数据展示复位
var resetView = document.getElementById('reset-button')
resetView.onclick = function () {
graph.render();
graph.fitView();
};
<template>
<div class="topology-diagram">
<div id="containerBox"
style="background: #F7F8FA;border-radius: 4px;">
div>
<div v-if="show"
class="img-annotation">
<div class="annotation-icon1">div>
<span class="annotation-text1">允许出入span>
<div class="annotation-icon2">div>
<span class="annotation-text2">无权限span>
<img class="annotation-icon3"
src="./img/disable.svg" />
<span class="annotation-text3">禁止出入span>
div>
<div class="operation-button"
:style="show ? 'margin-top: -48px;margin-left: 262px;':'position: absolute;right: 12px;top: 380px;'">
<img src="./img/zoom-out-button.svg"
style="margin-left: 16px;margin-top: 11px;"
id="zoom-out-button" />
<img src="./img/zoom-in-button.svg"
style="margin-left: 16px;margin-top: 11px;"
id="zoom-in-button" />
div>
<drawer :title="title"
:display.sync="display"
:inner="true"
:width="drawerWidth"
:height="drawerHeight"
:regionType="regionType"
:mask="false">
drawer>
div>
template>
<script>
import G6 from '@antv/g6'
import drawer from '@/components/drawer/drawer'
export default {
data () {
return {
title: '',
title_id: '',// 用于判断右边浮窗关闭弹出效果
drawerHeight: '557px',
display: false,
drawerWidth: '220px',
regionType: -1,
// 根据通行点类型展示不同图片、以及不同的鼠标悬浮图标
imgPath: '',
imgHoverPath: '',
imgDisablePath: '',
imgNoPermissionPath: '',
}
},
props: {
// 宽度
topologyMapWidth: {
type: String,
default: '1121'
},
// 高度H
topologyMapHeight: {
type: String,
default: '570'
},
topologyMapData: { // 拓扑图数据
type: Object,
default: () => {
return {}
}
},
// 判断是否需要鼠标数据、放大缩小点击按钮再左下还是右下、图左下注解是否展示
show: {
type: Boolean,
default: true
}
},
components: {
drawer
},
mounted () {
this.customNode()
this.createNode()
},
methods: {
// 自定义节点方法
customNode () {
// 文本超出隐藏 (字段, 最大长度, 字体大小)
const fittingString = (str, maxWidth, fontSize) => {
const ellipsis = '...';
const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
let currentWidth = 0;
let res = str;
const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
str.split('').forEach((letter, i) => {
if (currentWidth > maxWidth - ellipsisLength) return;
if (pattern.test(letter)) {
// Chinese charactors
currentWidth += fontSize;
} else {
// get the width of single letter according to the fontSize
currentWidth += G6.Util.getLetterWidth(letter, fontSize);
}
if (currentWidth > maxWidth - ellipsisLength) {
res = `${str.substr(0, i)}${ellipsis}`;
}
});
return res;
};
// 节点缩放点击事件,根据不同场所展示不同样式
const marker = (hasChildren, group, cfg, rect) => {
if (hasChildren) {
if (cfg.category == 0) {
group.addShape('image', {
attrs: {
x: rect.getBBox().width - 80,
y: rect.getBBox().height / 2 - 84.5,
cursor: 'pointer',
width: 20,
height: 20,
img: cfg.collapsed ? require('./img/close1.svg') : require('./img/open1.svg'),
},
name: 'collapse-icon0',
modelId: cfg.id,
})
} if (cfg.category == 1) {
group.addShape('image', {
attrs: {
x: rect.getBBox().width - 80,
y: rect.getBBox().height / 2 - 84.5,
cursor: 'pointer',
width: 20,
height: 20,
img: cfg.collapsed ? require('./img/close2.svg') : require('./img/open2.svg'),
},
name: 'collapse-icon1',
modelId: cfg.id,
})
} if (cfg.category == 2) {
group.addShape('image', {
attrs: {
x: rect.getBBox().width - 80,
y: rect.getBBox().height / 2 - 84.5,
cursor: 'pointer',
width: 20,
height: 20,
img: cfg.collapsed ? require('./img/close3.svg') : require('./img/open3.svg'),
},
name: 'collapse-icon2',
modelId: cfg.id,
})
} if (cfg.category == 3) {
group.addShape('image', {
attrs: {
x: rect.getBBox().width - 80,
y: rect.getBBox().height / 2 - 84.5,
cursor: 'pointer',
width: 20,
height: 20,
img: cfg.collapsed ? require('./img/close4.svg') : require('./img/open4.svg'),
},
name: 'collapse-icon3',
modelId: cfg.id,
})
}
}
}
const _this = this
G6.registerNode(
'tree-node',
{
drawShape: function drawShape (cfg, group) {
const rect = group.addShape('rect', {
attrs: {
fill: '#E9EBF5',
stroke: '#E9EBF5',
cursor: 'pointer',
x: 0,
y: 0,
width: 1,
height: 1,
radius: 8
},
name: 'rect-shape',
});
let textX = -24
let textY = 0
let iconLength = cfg.units ? cfg.units.length : 0 // 节点里的图标个数
// let iconLength = cfg.iconLength
// const content = cfg.name;
let number = 10 // 两个图标换行
let textArray = []
for (let i = 0; i < iconLength; i++) {
if (i != 0) {
if ((i + 1) % number === 1) {
textY = textY + 50 + 17
textX = -24
} else if ((i + 1) % number != 1) {
textX = textX + 41 + 30
}
}
const content = cfg.units[i].name;
textArray[i] = group.addShape('text', {
attrs: {
text: fittingString(content, 66, 11),
x: textX,
y: textY,
cursor: 'pointer',
textAlign: 'center',
textBaseline: 'middle',
fill: '#424874',
fontSize: 11,
fontWeight: 400,
fontFamily: 'PingFangSC-Regular, PingFang SC',
lineHeight: 17,
width: 66
},
name: 'text-shape' + i,
});
// 判断门禁状态 .units.permission 出入点权限 0 允许 1 禁止 为空时无权限
if (cfg.units[i].permission === '' || cfg.units[i].permission === 1) {
textArray[i].attr('fill', '#C3C5D9')
continue
}
console.log('1111111111111111111111!_this.show', !_this.show);
if (!_this.show) continue
// 判断通行点类型(只判断出入点、电梯厅)unitTypeCode 通行点类型 pass 通行点 elevator 电剃厅
textArray[i].on('mouseenter', e => {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/passHover.svg') : require('./img/elevatorHover.svg'))
textArray[i].attr('fill', '#6373FF')
})
textArray[i].on('mouseleave', e => {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/pass.svg') : require('./img/elevator.svg'))
textArray[i].attr('fill', '#424874')
})
textArray[i].on('click', e => {
// 根据门禁id判断是否重复点击同一个门禁
if (_this.title_id !== cfg.id) {
_this.display = _this.display ? !_this.display : _this.display
setTimeout(() => {
_this.title = content
_this.title_id = cfg.id
_this.regionType = cfg.category
_this.display = !_this.display
}, 500)
} else {
_this.title = content
_this.title_id = cfg.id
_this.regionType = cfg.category
_this.display = true
}
})
}
// 无门禁时
let NoIcon
if (iconLength === 0) {
NoIcon = group.addShape('text', {
attrs: {
text: fittingString('无门禁', 61, 12),
x: textX,
y: textY,
textAlign: 'center',
textBaseline: 'middle',
fill: '#424874',
fontSize: 12,
fontWeight: 400,
fontFamily: 'PingFangSC-Regular, PingFang SC',
lineHeight: 17,
width: 61
},
name: 'text-shape0',
});
}
const text = textArray[0] ? textArray[0] : NoIcon
const bbox = text.getBBox();
const hasChildren = cfg.children && cfg.children.length > 0;
let nodeHeight = iconLength === 0 ? 0 : iconLength % number > 0 ? Math.ceil(iconLength / number) : iconLength / number // 节点根据图标换行相应高度
rect.attr({
x: -66 / 2 - 20 - 28,
y: -24 - 50, // 节点框根据图标上移
width: NoIcon ? 250 : nodeHeight === 0 ? ((32 + 51) * (number - 1)) : number > iconLength ? ((32 + 45) * iconLength) : ((32 + 51) * (number - 1)),
height: NoIcon ? bbox.height + 120 : number >= iconLength ? bbox.height + 120 : 80 * nodeHeight,
});
const rectBox = rect.getBBox();
let imgX = rectBox.x + 30
let imgY = rectBox.y + 30
let imgArray = []
for (let i = 0; i < iconLength; i++) {
if (i != 0) { // 图标移动间隔距离
if ((i + 1) % number === 1) {
imgY = imgY + 32 + 35
imgX = rectBox.x + 32
} else if ((i + 1) % number != 1) {
imgX = imgX + 32 + 40
}
}
// 判断通行点类型(只判断出入点、电梯厅)unitTypeCode 通行点类型 pass 通行点 elevator 电剃厅
imgArray[i] = group.addShape('image', {
attrs: {
x: imgX,
y: imgY,
width: 32,
height: 32,
cursor: 'pointer',
img: cfg.units[i].unitTypeCode === 'pass' ? require('./img/pass.svg') : require('./img/elevator.svg'),
shadowBlur: 20,
// shadowColor: '#C7CBF6',
shadowColor: '#C6C9EA'
},
name: 'image-shape' + i,
});
textArray[i].attr({
x: imgX + 16, // 文字根据图片宽度居中
y: imgY + 32 + 12, // 32图片宽度12文字图片间距
});
// 判断门禁状态 .units.permission 出入点权限 0 允许 1 禁止 为空时无权限
if (cfg.units[i].permission === '') {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/passNoPermission.svg') : require('./img/elevatorNoPermission.svg'))
continue
}
if (cfg.units[i].permission === 1) {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/passDisable.svg') : require('./img/elevatorDisable.svg'))
continue
}
if (!_this.show) continue
// const imageBox = group.find((element) => element.get('name') === 'image-shape' + i);
imgArray[i].on('mouseenter', e => {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/passHover.svg') : require('./img/elevatorHover.svg'))
textArray[i].attr('fill', '#6373FF')
})
imgArray[i].on('mouseleave', e => {
imgArray[i].attr('img', cfg.units[i].unitTypeCode === 'pass' ? require('./img/pass.svg') : require('./img/elevator.svg'))
textArray[i].attr('fill', '#424874')
})
imgArray[i].on('click', e => {
// 根据门禁id判断是否重复点击同一个门禁
if (_this.title_id !== cfg.id) {
_this.display = _this.display ? !_this.display : _this.display
setTimeout(() => {
_this.title = cfg.units[i].name
_this.title_id = cfg.id
_this.regionType = cfg.category
_this.display = !_this.display
}, 500)
} else {
_this.title = cfg.units[i].name
_this.title_id = cfg.id
_this.regionType = cfg.category
_this.display = true
}
})
}
// 文件头****************************************************************************************
// 文件头文字
let fileText = cfg.name
const imageLeft = group.addShape('image', {
attrs: {
x: rectBox.x + 36,
y: rectBox.y - 23.5,
width: 36,
height: 24,
img: require('./img/left.svg'),
},
name: 'image-left',
});
const rectFile = group.addShape('rect', {
attrs: {
x: rectBox.x + 36 + 36,
y: rectBox.y - 23,
height: 24,
width: 36,
stroke: '#E9EBF5',
fill: '#E9EBF5',
},
name: 'rect-file',
});
const imageRight = group.addShape('image', {
attrs: {
x: rectFile.getBBox().x + rectFile.getBBox().width,
y: rectFile.getBBox().y,
width: 36,
height: 24,
img: require('./img/right.svg'),
},
name: 'image-right',
});
// 图标不固定要做判断,判断0:园区 1:楼栋 2:楼层 3:办公区*************************
let fileIconUrl = require('./img/fileIcon1.svg')
if (cfg.category == 0) {
fileIconUrl = require('./img/fileIcon1.svg')
} if (cfg.category == 1) {
fileIconUrl = require('./img/fileIcon2.svg')
} if (cfg.category == 2) {
fileIconUrl = require('./img/fileIcon3.svg')
} if (cfg.category == 3) {
fileIconUrl = require('./img/fileIcon4.svg')
}
const fileIcon = group.addShape('image', {
attrs: {
x: rectFile.getBBox().x - 8,
y: rectFile.getBBox().y + 5,
width: 20,
height: 20,
img: fileIconUrl,
},
name: 'file-icon',
});
const textFile = group.addShape('text', {
attrs: {
text: fileText,
x: fileIcon.getBBox().x + 20 + 8,
y: fileIcon.getBBox().y + 12,
textAlign: 'left',
textBaseline: 'middle',
fill: '#202340',
fontSize: 16,
fontWeight: 400,
fontFamily: 'PingFangSC-Regular, PingFang SC',
lineHeight: 22,
},
name: 'text-file',
});
// 文件文字宽度重新渲染文件头样式宽度度
rectFile.attr({
width: textFile.getBBox().width + fileIcon.getBBox().width + 8,
});
imageRight.attr({
x: rectFile.getBBox().x + rectFile.getBBox().width,
y: rectFile.getBBox().y,
});
// 节点宽度根据文件头宽度来判断
let fileWidth = rectFile.getBBox().width + 36 + 36 + 24 + 24
if (fileWidth > rectBox.width) {
rect.attr({
width: fileWidth + 36,
});
}
// 无门禁时,文字定位在节点的中心
if (NoIcon) {
NoIcon.attr({
x: (rect.getBBox().width) / 2 - 36 - 40,
});
}
// 节点后面加号位置,判断0:园区 1:楼栋 2:楼层 3:办公区
marker(hasChildren, group, cfg, rect)
// 整个节点悬浮时节点+文件头悬浮样式*****************************************************
if (_this.show) {
rect.on('mouseenter', e => {
imageLeft.attr('img', require('./img/leftHover.svg'))
imageRight.attr('img', require('./img/rightHover.svg'))
rect.attr({
stroke: '#DFE0ED',
fill: '#DFE0ED',
})
rectFile.attr({
stroke: '#DFE0ED',
fill: '#DFE0ED',
})
})
rect.on('mouseleave', e => {
imageLeft.attr('img', require('./img/left.svg'))
imageRight.attr('img', require('./img/right.svg'))
rect.attr({
fill: '#E9EBF5',
stroke: '#E9EBF5',
})
rectFile.attr({
stroke: '#E9EBF5',
fill: '#E9EBF5',
})
})
// 节点整体点击释放样式*********************************************************
rect.on('mousedown', e => {
imageLeft.attr('img', require('./img/leftSelect.svg'))
imageRight.attr('img', require('./img/rightSelect.svg'))
rect.attr({
fill: '#DEE3FA',
stroke: '#DEE3FA',
})
rectFile.attr({
fill: '#DEE3FA',
stroke: '#DEE3FA',
})
})
rect.on('mouseup', e => {
imageLeft.attr('img', require('./img/leftHover.svg'))
imageRight.attr('img', require('./img/rightHover.svg'))
rect.attr({
stroke: '#DFE0ED',
fill: '#DFE0ED',
})
rectFile.attr({
stroke: '#DFE0ED',
fill: '#DFE0ED',
})
})
// 节点整体点击事件*********************************************************
rect.on('click', e => {
_this.regionType = cfg.category // 需判断是0:园区 1:楼栋 2:楼层 3:办公区
// 根据节点id判断是否重复点击同一个门禁
if (_this.title_id !== cfg.id) {
_this.display = _this.display ? !_this.display : _this.display
setTimeout(() => {
_this.title = cfg.name
_this.title_id = cfg.id
_this.display = !_this.display
}, 500)
} else {
_this.display = true
_this.title = cfg.name
_this.title_id = cfg.id
}
})
}
return rect;
},
// 节点展开收起点击事件,判断0:园区 1:楼栋 2:楼层 3:办公区
setState (name, value, item) {
// 四个不同节点点击缩放颜色转换
if (name === 'collapse') {
const group = item.getContainer();
const collapseIcon = group.find((e) => e.get('name') === 'collapse-icon0');
if (collapseIcon) {
if (!value) {
collapseIcon.attr({
img: require('./img/open1.svg')
});
} else {
collapseIcon.attr({
img: require('./img/close1.svg')
});
}
}
const collapseIcon1 = group.find((e) => e.get('name') === 'collapse-icon1');
if (collapseIcon1) {
if (!value) {
collapseIcon1.attr({
img: require('./img/open2.svg')
});
} else {
collapseIcon1.attr({
img: require('./img/close2.svg')
});
}
}
const collapseIcon2 = group.find((e) => e.get('name') === 'collapse-icon2');
if (collapseIcon2) {
if (!value) {
collapseIcon2.attr({
img: require('./img/open3.svg')
});
} else {
collapseIcon2.attr({
img: require('./img/close3.svg')
});
}
}
const collapseIcon3 = group.find((e) => e.get('name') === 'collapse-icon3');
if (collapseIcon3) {
if (!value) {
collapseIcon3.attr({
img: require('./img/open4.svg')
});
} else {
collapseIcon3.attr({
img: require('./img/close4.svg')
});
}
}
}
},
},
'single-node',
);
},
// 根据数据创建树状图节点
createNode () {
const container = document.getElementById('containerBox');
const width = container.scrollWidth - 8;
// const width = this.topologyMapWidth;
const height = container.scrollHeight || this.topologyMapHeight;
// 实例化 minimap 插件
// const minimap = new G6.Minimap({
// container: 'minimapBox',
// size: [100, 100],
// className: 'minimap',
// type: 'default',
// });
const graph = new G6.TreeGraph({
container: 'containerBox',
// plugins: [minimap],
width,
height,
modes: {
default: [
'drag-canvas',
'zoom-canvas',
],
},
defaultNode: {
type: 'tree-node',
anchorPoints: [
[0, 0.5],
[1, 0.5],
],
},
// nodeStateStyles: {
// hover: {
// fill: '#fff',
// stroke: '#fff',
// // shadowBlur: 20,
// // shadowColor: '#5666de',
// }
// },
defaultEdge: {
type: 'flow-line',// type: 'cubic-horizontal'
style: {
stroke: '#C3C5D9',
},
},
layout: {
type: 'compactBox',
direction: 'LR',
getId: function getId (d) {
return d.id;
},
getHeight: function getHeight () {
return 260;
},
getWidth: function getWidth () {
return 260;
},
getVGap: function getVGap () {
return 120;
},
getHGap: function getHGap () {
return 380;
},
},
});
// 设置线条样式-------------------------------------------
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,
// lineWidth: 1,
path: [
['M', startPoint.x + 22, startPoint.y],
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, startPoint.y], // 三分之一处
['L', endPoint.x / 3 + (2 / 3) * startPoint.x, endPoint.y], // 三分之二处
['L', endPoint.x, endPoint.y]
],
},
});
return shape;
},
});
// 节点缩放点击事件
const handleCollapse = (e) => {
const target = e.target;
const id = target.get('modelId');
const item = graph.findById(id);
graph.setItemState(item, 'collapse', true); // 数据collapsed默认为true时,第一次点击不触发+变为-,故加上此行,
const nodeModel = item.getModel();
nodeModel.collapsed = !nodeModel.collapsed;
graph.layout();
graph.setItemState(item, 'collapse', nodeModel.collapsed);
};
graph.on('collapse-icon0:click', (e) => {
handleCollapse(e);
});
graph.on('collapse-icon1:click', (e) => {
handleCollapse(e);
});
graph.on('collapse-icon2:click', (e) => {
handleCollapse(e);
});
graph.on('collapse-icon3:click', (e) => {
handleCollapse(e);
});
// graph.on('node:mouseenter', e => {
// graph.setItemState(e.item, 'hover', true);
// })
// graph.on('node:mouseleave', e => {
// graph.setItemState(e.item, 'hover', false);
// });
// fetch('https://gw.alipayobjects.com/os/antvdemo/assets/data/modeling-methods.json')
// .then((res) => res.json())
// .then((data) => {
G6.Util.traverseTree(this.topologyMapData, function (item) {
item.id = item._id;
});
graph.data(this.topologyMapData);
graph.render();
graph.fitView();
// });
// 监听画布缩放
let number = graph.getZoom()
graph.on('viewportchange', e => {
if (e.action !== 'zoom') return;
const currentZoom = graph.getZoom();
number = currentZoom;
});
// 获取缩小放大按钮
var zoomOut = document.getElementById('zoom-out-button');
var zoomIn = document.getElementById('zoom-in-button');
// 根据目前画布的缩放比例调整缩放比例
zoomOut.onclick = function () {
number = number - 0.2
if (number < 0.2) number = 0.2
graph.zoomTo(number, { x: 300, y: 300 });
};
zoomIn.onclick = function () {
number = number + 0.2
graph.zoomTo(number, { x: 300, y: 300 });
};
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);
};
}
}
}
script>
<style>
.topology-diagram {
width: 100%;
height: 100%;
}
.topology-diagram .img-annotation {
width: 233px;
height: 36px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0px 0px 4px 0px #e2e3ee;
border-radius: 4px;
/* margin-top: -48px;
margin-left: 12px; */
display: inline-block;
position: absolute;
margin-left: 12px;
margin-top: -48px;
}
.topology-diagram .annotation-icon1 {
width: 10px;
height: 10px;
background: #6271f0;
border-radius: 2px;
margin-top: 13px;
margin-left: 12px;
display: inline-block;
}
.topology-diagram .annotation-icon2 {
width: 10px;
height: 10px;
background: #c3c5d9;
border-radius: 2px;
display: inline-block;
margin-left: 16px;
margin-top: 13px;
}
.topology-diagram .annotation-icon3 {
display: inline-block;
margin-left: 16px;
margin-top: 13px;
}
.topology-diagram .annotation-text1 {
width: 48px;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #202340;
line-height: 17px;
margin-top: 9px;
margin-left: 5px;
}
.topology-diagram .annotation-text2 {
width: 36px;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #202340;
line-height: 17px;
margin-left: 5px;
margin-top: 9px;
}
.topology-diagram .annotation-text3 {
width: 48px;
height: 17px;
font-size: 12px;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
color: #202340;
line-height: 17px;
margin-left: 5px;
margin-top: 9px;
}
.topology-diagram .operation-button {
width: 76px;
height: 36px;
background: rgba(255, 255, 255, 0.9);
box-shadow: 0px 0px 4px 0px #e2e3ee;
border-radius: 4px;
display: inline-block;
position: absolute;
}
style>