左屏服务器运行流程图

This commit is contained in:
ruyinjuan 2025-04-15 11:05:09 +08:00
parent 38f6673fac
commit e5dc139a6b
20 changed files with 2503 additions and 0 deletions

View File

@ -11,6 +11,8 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@antv/x6": "^2.18.1",
"@antv/x6-vue-shape": "^2.1.2",
"@turf/turf": "^7.1.0",
"@visactor/vtable": "^1.13.2",
"@visactor/vtable-gantt": "^1.13.2",
@ -24,6 +26,7 @@
"chroma-js": "^3.1.2",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"elkjs": "^0.10.0",
"es-toolkit": "^1.32.0",
"lodash": "^4.17.21",
"maplibre-gl": "^5.0.1",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

1512
public/json/topology.json Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -46,6 +46,11 @@ const router = createRouter({
name: 'Test',
component: () => import('@/views/Test/index.vue'),
},
{
path: '/TopologyMap',
name: 'TopologyMap',
component: () => import('@/views/TopologyMap/index.vue'),
},
],
})

View File

@ -0,0 +1,97 @@
<template>
<div class="dialog" :style="{ width: width + 'px' }" v-if="id.indexOf('cpu') != -1">
<span class="arrow-bottom" :style="{ left: (width - 17) / 2 + 'px' }"></span>
<p>CPU{{ cpu }}</p>
<p>内存{{ neicun }}</p>
<p>硬盘{{ yingpan }}</p>
</div>
<div class="dialog" :style="{ width: width + 'px' }" v-if="id.indexOf('yp2') != -1">
<span class="arrow-left" :style="{ top: (height - 17) / 2 + 'px' }"></span>
<p>数据{{ shuju }}</p>
<p>CPU{{ cpu }}</p>
<p>内存{{ neicun }}</p>
<p>硬盘{{ yingpan }}</p>
</div>
<div class="dialog" :style="{ width: width + 'px' }" v-if="id.indexOf('yp1') != -1">
<span class="arrow-bottom" :style="{ left: (width - 17) / 2 + 'px' }"></span>
<p>数据{{ shuju }}</p>
<p>CPU{{ cpu }}</p>
<p>内存{{ neicun }}</p>
<p>硬盘{{ yingpan }}</p>
</div>
</template>
<script lang='ts'>
import { defineComponent, toRefs, reactive, onMounted } from 'vue'
// import { useRoute, useRouter } from 'vue-router';
export default defineComponent({
name: '',
inject: ['getNode'],
components: {},
setup() {
// const route = useRoute();
// const router = useRouter();
const state = reactive({
id: "",
"width": 70,
"height": 55,
"cpu": "80%",
"neicun": "70%",
"yingpan": "50%",
"shuju": "128G/520G"
})
const getNode: Function | undefined = inject('getNode');
onMounted(() => {
const node = getNode();
let info = node.getData();
if (node) {
console.log(info, 'info')
state.id = info.id;
state.width = info.width;
state.height = info.height;
state.shuju = info.shuju;
state.cpu = info.cpu;
state.neicun = info.neicun;
state.yingpan = info.yingpan;
}
})
return { ...toRefs(state) };
}
});
</script>
<style scoped lang='scss'>
.dialog {
background: #001232;
border: 0.4px solid rgba(22, 238, 243, 1);
box-shadow: inset 0px 0px 13px 0px rgba(22, 238, 243, 1);
border-radius: 4px 4px 4px 0px 0px 0px 4px;
border-radius: 4px;
position: relative;
padding: 3px 5px;
z-index: 999;
.arrow-bottom {
position: absolute;
display: inline-block;
width: 17px;
height: 8px;
bottom: -7px;
background: url("./images/topology/arrow-bottom.png") no-repeat;
}
.arrow-left {
position: absolute;
display: inline-block;
width: 8px;
height: 17px;
left: -7px;
background: url("./images/topology/arrow-left.png") no-repeat;
}
p {
color: #16EEF3;
font-size: 12px;
line-height: 16px;
}
}
</style>

View File

@ -0,0 +1,886 @@
<template>
<div class="topology-map">
<div class="top">
<div class="left"><span>互联网</span></div>
<div class="center"><span>办公网</span></div>
<div class="right"><span>高密网</span></div>
</div>
<div class="content">
<div id="container"></div>
<TeleportContainer />
</div>
<div class="bottom">
<div class="left">
<i class="triangle"></i>
<i class="rectangle"></i>
<div class="b-con">
<div class="b-left yingpan">
<span>硬盘空间</span>
</div>
<div class="b-right">
<div class="box">
<p class="text">服务器1</p>
<p class="line"><i class="jd" :style="{ 'width': 127 / 1942 * 100 + '%' }"></i></p>
<p class="text">1942T<span class="yy">已用127T</span></p>
</div>
<div class="box">
<p class="text">服务器2</p>
<p class="line"><i class="jd" :style="{ 'width': 127 / 1942 * 100 + '%' }"></i></p>
<p class="text">1942T<span class="yy">已用127T</span></p>
</div>
</div>
</div>
</div>
<div class="center">
<i class="triangle"></i>
<i class="rectangle"></i>
<div class="b-con">
<div class="b-left neicun">
<span>内存</span>
</div>
<div class="b-right">
<div class="box">
<p class="text">服务器1</p>
<p class="line"><i class="jd" :style="{ 'width': 27 / 194 * 100 + '%' }"></i></p>
<p class="text">194T<span class="yy">已用27T</span></p>
</div>
<div class="box">
<p class="text">服务器2</p>
<p class="line"><i class="jd" :style="{ 'width': 89 / 123 * 100 + '%' }"></i></p>
<p class="text">123T<span class="yy">已用89T</span></p>
</div>
</div>
</div>
</div>
<div class="right">
<i class="triangle"></i>
<i class="rectangle"></i>
<div class="b-con">
<div class="b-left cpu">
<span>CPU</span>
</div>
<div class="b-right chart-box">
<div ref="chart1Ref" class="chart"></div>
<div ref="chart2Ref" class="chart"></div>
</div>
</div>
</div>
</div>
</div>
</template>
<script lang='ts'>
import { defineComponent, toRefs, reactive, onMounted } from 'vue'
import axios from 'axios'
import DialogNode from './components/dialogNode.vue';
import { Graph, Cell } from '@antv/x6'
import { register, getTeleport } from '@antv/x6-vue-shape'
import * as echarts from 'echarts';
Graph.registerNode('elk-node',
{
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body',
},
],
attrs: {
body: {
fill: 'rgba(255,255,255,0.02)',
stroke: 'rgba(142,255,253,0.25)',
strokeWidth: 1,
}
},
},
true,
)
Graph.registerNode('rect-node',
{
inherit: 'rect',
attrs: {
body: {
fill: 'none',
stroke: 'none',
strokeWidth: 1,
},
},
tools: [
{
name: 'boundary',
args: {
padding: 5,
attrs: {
fill: 'none',
stroke: 'rgba(142,248,255,0.25)',
strokeWidth: 1,
},
},
},
],
},
true,
)
Graph.registerNode('node-a',
{
width: 48,
height: 39,
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
body: {
fill: 'rgba(255,255,255,0)',
strokeWidth: 0
},
img: {
'xlink:href': "./images/topology/a.png", //
width: 48,
height: 39,
},
},
},
true,
)
Graph.registerNode('node-cpu',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
// {
// tagName: 'rect',
// selector: 'bg',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
],
attrs: {
img: {
'xlink:href': "./images/topology/cpu.png", //
width: 74,
height: 86,
},
// bg: {
// stroke: '#5F95FF',
// strokeWidth: 1,
// fill: 'rgba(0,0,0,0.5)',
// refWidth: -10,
// refHeight: -20,
// rx: 5,
// ry: 5,
// refX: 5,
// refY: -80,
// },
// label: {
// refX: 20,
// refY: -70,
// fontSize: 10,
// fill: '#fff',
// },
},
},
true,
)
Graph.registerNode('node-fs',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
img: {
'xlink:href': "./images/topology/fs.png", //
width: 48,
height: 86,
}
},
},
true,
)
Graph.registerNode('node-fwq',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
img: {
'xlink:href': "./images/topology/fwq.png", //
width: 36,
height: 86,
},
},
},
true,
)
Graph.registerNode('node-yp',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
img: {
'xlink:href': "./images/topology/yp.png", //
width: 48,
height: 86,
},
},
},
true,
)
Graph.registerNode('node-com',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
img: {
'xlink:href': "./images/topology/com.png", //
width: 64,
height: 52,
},
},
},
true,
)
Graph.registerNode('node-zhuji',
{
inherit: 'rect',
markup: [
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
img: {
'xlink:href': "./images/topology/zhuji.png", //
width: 53,
height: 81,
},
},
},
true,
)
Graph.registerNode('node-xinhao',
{
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
body: {
fill: 'rgba(255,255,255,0)',
strokeWidth: 0,
},
img: {
'xlink:href': "./images/topology/xinhao.png", //
width: 27,
height: 28,
},
},
},
true,
)
Graph.registerNode('node-hezi',
{
inherit: 'rect',
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
body: {
fill: 'rgba(255,255,255,0)',
strokeWidth: 0
},
img: {
'xlink:href': "./images/topology/hezi.png", //
width: 55,
height: 56,
},
},
},
true,
)
Graph.registerEdge('elk-edge',
{
inherit: 'edge',
markup: [
{
tagName: 'path',
selector: 'p1',
},
{
tagName: 'image',
selector: 'img',
},
],
attrs: {
p1: {
connection: true,
fill: 'none',
stroke: '#16EEF3',
strokeWidth: 1,
targetMarker: {
name: 'block',
width: 6,
height: 6,
},
},
img: {
'xlink:href': "./images/topology/duankai.png", //
width: 16,
height: 16,
r: 10,
y: -8,
atConnectionRatio: 0.2,
},
},
// attrs: {
// line: {
// stroke: '#16EEF3',
// strokeWidth: 1,
// targetMarker: {
// name: 'block',
// width: 6,
// height: 6,
// },
// },
// },
zIndex: 1,
},
true,
)
register({
shape: 'node-dialog',
component: DialogNode,
zIndex: 15,
})
interface Position {
x: number
y: number
}
let graph: Graph;
const TeleportContainer = getTeleport()
export default defineComponent({
name: '',
components: { TeleportContainer },
setup() {
// const route = useRoute();
// const router = useRouter();
// const elk = new ELK()
const portIdToNodeIdMap: Record<string, string> = {}
const cells: Cell[] = []
const chart1Ref = ref(null);
const chart2Ref = ref(null);
const state = reactive({
json: [],
})
onMounted(async () => {
graph = new Graph({
container: document.getElementById('container'),
width: 1795,
height: 700,
// background: {
// color: 'rgba(255, 255, 255, 1)',
// },
router: { name: 'manhattan' },
connector: { name: 'rounded' },
// interacting: {
// nodeMovable: false,
// edgeMovable: false,
// },
translating: {
restrict(view) {
if (view) {
const cell = view.cell
if (cell.isNode()) {
const parent = cell.getParent()
if (parent) {
return parent.getBBox()
}
}
}
return null
},
},
})
await getData().then(data => {
addChildren(data.children || [])
addEdges(data.edges || [])
graph.zoomTo(1)
// graph.centerContent()
})
getCharts();
})
const addChildren = (children, pos?: Position) => {
let leftParentNode = graph.addNode({
shape: 'elk-node',
id: "left",
label: "互联网",
"x": 0,
"y": 0,
size: {
"width": 347,
"height": 666,
},
zIndex: 1,
})
let centerParentNode = graph.addNode({
shape: 'elk-node',
id: "center",
label: "办公网",
"x": 366,
"y": 0,
size: {
"width": 595,
"height": 666,
},
zIndex: 1,
})
let rightParentNode = graph.addNode({
shape: 'elk-node',
id: "right",
label: "高密网",
"x": 981,
"y": 0,
size: {
"width": 814,
"height": 666,
},
zIndex: 1,
})
children.forEach((child) => {
const position = {
x: (child.x || 0) + (pos ? pos.x : 0),
y: (child.y || 0) + (pos ? pos.y : 0),
}
let label: string = ''
if (typeof child.labels === 'string') {
label = child.labels
} else if (Array.isArray(child.labels) && child.labels[0]) {
label = child.labels[0].text
}
let type = "elk-node"
if (child.type && child.type === 'a') {
type = 'node-a'
} else if (child.type && child.type === 'cpu') {
type = 'node-cpu'
} else if (child.type && child.type === 'fwq') {
type = 'node-fwq'
} else if (child.type && child.type === 'yp') {
type = 'node-yp'
} else if (child.type && child.type === 'com') {
type = 'node-com'
} else if (child.type && child.type === 'rect') {
type = 'rect-node'
} else if (child.type && child.type === 'zhuji') {
type = 'node-zhuji'
} else if (child.type && child.type === 'xinhao') {
type = 'node-xinhao'
} else if (child.type && child.type === 'hezi') {
type = 'node-hezi'
}
const node = graph.addNode({
shape: type,
id: child.id,
position,
label,
body: {
width: child.width || 0,
height: child.height || 0,
},
size: {
width: child.width || 0,
height: child.height || 0,
},
zIndex: 2,
})
if (child.infos) {
console.log(child.infos)
const dialog = graph.createNode({
shape: 'node-dialog',
data: {
id: child.id,
...child.infos
},
x: child.infos.x || 0,
y: child.infos.y || 0,
zIndex: 3
})
node.addChild(dialog)
}
if (child.parentId === 'left') {
leftParentNode.addChild(node)
} else if (child.parentId === 'center') {
centerParentNode.addChild(node)
} else if (child.parentId === 'right') {
rightParentNode.addChild(node)
}
})
cells.push(leftParentNode)
cells.push(centerParentNode)
cells.push(rightParentNode)
}
const addEdges = (edges) => {
edges.forEach((edge) => {
let edgeline = {}
let markup = []
if (edge.isTrue !== undefined && edge.isTrue === false) {
markup = [
{
tagName: 'path',
selector: 'p1',
},
{
tagName: 'image',
selector: 'img',
},
]
} else {
markup = [
{
tagName: 'path',
selector: 'p1',
}
]
}
if (edge.vertices) {
edgeline = {
shape: 'elk-edge',
source: edge.sources,
target: edge.targets,
vertices: edge.vertices,
markup: markup,
}
} else {
edgeline = {
shape: 'elk-edge',
source: edge.sources,
target: edge.targets,
router: {
name: 'er',
args: {
offset: 'center'
}
},
markup: markup,
}
}
graph.addEdge(edgeline)
})
}
const getData = () => {
return axios.get("./json/topology.json").then(res => {
if (res.status === 200) {
return Promise.resolve(res.data)
} else {
console.log('获取TLE失败')
return Promise.reject(res.statusText)
}
})
}
const getCharts = () => {
const chart1 = echarts.init(chart1Ref.value);
const chart2 = echarts.init(chart2Ref.value);
let option1 = getOption('服务器1', 46)
let option2 = getOption('服务器2', 23)
//
chart1.setOption(option1);
chart2.setOption(option2);
}
const getOption = (title, num) => {
//
const option = {
title: {
text: title,
left: 'center',
textStyle:
{
color: "#fff",
fontSize: 14
}
},
series: [
{
type: 'pie',
radius: ['45%', '60%'], //
center: ['50%', '60%'],
data: [
{//
value: 100 - num, name: '未完成值',
itemStyle: { color: 'rgba(22, 238, 243,0.3)' },
label: {
show: false,
},
},
{ //
value: num, name: '完成值',
itemStyle: { color: '#16EEF3' },
label: {
show: true, //
position: 'center',
fontSize: 16,
formatter: '{d}%',
color: '#fff',
},
},
],
labelLine: {
show: false, // 线
},
emphasis: {
scale: false, //
},
},
],
};
return option;
}
return { ...toRefs(state), chart1Ref, chart2Ref };
}
});
</script>
<style scoped lang='scss'>
.topology-map {
height: 100%;
background-color: #082857;
overflow: auto;
.top {
width: 1795px;
display: flex;
padding-top: 52px;
margin: 0 auto 30px;
.left {
width: 347px;
}
.center {
width: 595px;
margin-left: 20px;
}
.right {
width: 814px;
margin-left: 20px;
}
.left,
.center,
.right {
text-align: center;
span {
display: inline-block;
width: 262px;
height: 32px;
text-align: center;
line-height: 32px;
color: #fff;
background: url("@/assets/image/topology/topology_bg.png") no-repeat center center;
}
}
}
.content {
display: block;
min-width: 1800px;
// height: 666px;
// margin: 0 auto;
#container {
margin: 0 auto;
}
}
.bottom {
display: flex;
justify-content: space-evenly;
.left,
.center,
.right {
width: 477px;
height: 160px;
// border-top: 4px solid rgba(22, 238, 243, 0.6);
// border-bottom: 4px solid rgba(22, 238, 243, 0.3);
position: relative;
overflow-y: hidden;
.b-con {
display: flex;
.b-left {
width: 140px;
height: 140px;
span {
font-size: 16px;
color: #16EEF3;
text-align: center;
display: block;
margin-top: 35px;
}
&.yingpan {
background: url("@/assets/image/topology/ypkj.png") no-repeat center bottom;
}
&.neicun {
background: url("@/assets/image/topology/neicun.png") no-repeat center bottom;
}
&.cpu {
height: 135px;
background: url("@/assets/image/topology/cpuicon.png") no-repeat center bottom;
}
}
.b-right {
flex: 1;
margin-right: 40px;
.box {
margin-top: 17px;
.text {
font-size: 14px;
line-height: 22px;
color: #fff;
.yy {
color: #F5A623;
margin-left: 10px;
}
}
.line {
display: block;
height: 5px;
background: rgba(22, 238, 243, 0.5);
border-radius: 5px;
margin: 2px 0;
.jd {
display: block;
width: 0;
height: 5px;
background: #16EEF3;
border-radius: 5px;
}
}
}
&.chart-box {
display: flex;
margin-top: 30px;
.chart {
width: 50%;
height: 110px;
}
}
}
}
}
.triangle {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 4px;
background: rgba(22, 238, 243, 0.6);
}
.triangle::after {
content: "";
position: absolute;
top: -4px;
right: 0;
width: 0;
height: 0;
border-right: 22px solid rgba(22, 238, 243, 1);
border-bottom: 22px solid transparent;
}
.rectangle {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 4px;
background-color: rgba(22, 238, 243, 1);
}
.rectangle:after {
content: "";
position: absolute;
right: 0;
top: 0;
width: 0;
height: 0;
border-top: 0 solid transparent;
border-right: 50px solid #149eb5;
border-bottom: 25px solid transparent;
}
}
}
</style>