feat: 基站状态页面及拓扑展示页面功能实现

This commit is contained in:
TsMask
2025-01-03 21:09:16 +08:00
parent 35a7ed5b35
commit fe82336937
8 changed files with 1370 additions and 23 deletions

28
public/svg/base4G.svg Normal file
View File

@@ -0,0 +1,28 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
</g>
</g>
<g>
<title>Layer 1</title>
<g stroke="null" id="svg_15">
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
</g>
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#B5D6FB">4G</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

28
public/svg/base5G.svg Normal file
View File

@@ -0,0 +1,28 @@
<svg width="1024" height="1024" xmlns="http://www.w3.org/2000/svg">
<!-- Created with Method Draw - http://github.com/duopixel/Method-Draw/ -->
<g>
<title>background</title>
<rect fill="none" id="canvas_background" height="1026" width="1026" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="1" x="1" height="768" width="1024"/>
</g>
</g>
<g>
<title>Layer 1</title>
<g stroke="null" id="svg_15">
<path stroke="null" id="svg_4" fill="#B5D6FB" d="m512.094844,961.632039c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-405.588164,-251.575028c-2.275921,-1.419037 -3.698372,-4.054392 -3.698372,-6.892467l0,-90.007504c0,-2.838074 1.422451,-5.473429 3.698372,-6.892467l405.588164,-255.426701c1.137961,-0.709519 2.465582,-1.114958 3.793202,-1.114958s2.655242,0.405439 3.793202,1.114958l405.493334,255.426701c2.275921,1.419037 3.698372,4.054392 3.698372,6.892467l0,90.007504c0,2.838074 -1.422451,5.473429 -3.698372,6.892467l-405.588164,251.575028c-1.137961,0.709519 -2.465582,1.114958 -3.698372,1.114958z"/>
<path stroke="null" id="svg_5" fill="#0276F7" d="m512.094844,356.615382l405.398504,255.426701l0,90.007504l-66.096551,40.94936l-339.301952,210.625668l-339.491613,-210.625668l-66.096551,-40.94936l0,-90.007504l405.588164,-255.426701m0,-16.014849c-2.655242,0 -5.215653,0.709519 -7.586405,2.229916l-405.588164,255.426701c-4.551843,2.838074 -7.396745,8.108784 -7.396745,13.784933l0,90.007504c0,5.676149 2.844902,10.946859 7.491575,13.886293l66.096551,41.05072l339.491613,210.625668c2.275921,1.419037 4.931163,2.128556 7.491575,2.128556s5.215653,-0.709519 7.491575,-2.128556l339.301952,-210.625668l66.096551,-40.94936c4.646673,-2.838074 7.491575,-8.108784 7.491575,-13.886293l0,-90.007504c0,-5.676149 -2.844902,-10.946859 -7.396745,-13.784933l-405.398504,-255.426701c-2.370751,-1.520397 -5.025993,-2.331275 -7.586405,-2.331275z"/>
<path stroke="null" id="svg_6" fill="#FFFFFF" d="m106.50668,612.042083l405.493334,253.298145l405.493334,-253.298145l-405.398504,-255.426701l-405.588164,255.426701z"/>
<path stroke="null" id="svg_7" fill="#D4E4FC" d="m501.473877,64.192353l-254.9032,498.487506l263.343075,161.162085l266.662127,-162.074323l-275.102002,-497.575268z"/>
<path stroke="null" id="svg_8" fill="#0276F7" d="m229.975417,602.311542c-1.232791,0 -2.465582,-0.304079 -3.698372,-1.013598c-3.603542,-2.128556 -4.931163,-6.993826 -2.844902,-10.845499l279.653845,-532.13896c1.327621,-2.533995 3.793202,-4.054392 6.543274,-4.054392c2.655242,0 5.120823,1.520397 6.543274,4.054392l284.395348,532.13896c2.086261,3.851672 0.75864,8.716943 -2.750072,10.946859c-3.603542,2.128556 -8.155385,0.810878 -10.241646,-2.939434l-277.852074,-519.874424l-273.205401,519.671704c-1.422451,2.635355 -3.982862,4.054392 -6.543274,4.054392z"/>
<path stroke="null" id="svg_9" fill="#0276F7" d="m509.913752,755.567562c-4.172523,0 -7.491575,-3.547593 -7.491575,-8.007424l0,-666.744777c0,-4.459831 3.319052,-8.007424 7.491575,-8.007424s7.491575,3.547593 7.491575,8.007424l0,666.846137c0,4.358471 -3.413882,7.906065 -7.491575,7.906065z"/>
<path stroke="null" id="svg_10" fill="#0276F7" d="m509.913752,731.849369c-1.327621,0 -2.560412,-0.405439 -3.698372,-1.013598l-263.343075,-161.162085c-3.603542,-2.229916 -4.836333,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.939434l259.549873,158.83081l262.963755,-159.844408c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-266.662127,162.074323c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598z"/>
<path stroke="null" id="svg_11" fill="#0276F7" d="m509.913752,579.708306c-1.327621,0 -2.560412,-0.405439 -3.793202,-1.114958l-201.988026,-125.686154c-3.603542,-2.229916 -4.741503,-7.095186 -2.750072,-10.946859c2.086261,-3.851672 6.638104,-5.16935 10.241646,-2.838074l198.289654,123.354879l201.798366,-122.138561c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.845499l-205.496739,124.469837c-1.137961,0.709519 -2.465582,1.013598 -3.698372,1.013598zm-2.465582,-157.513132c-1.232791,0 -2.370751,-0.304079 -3.508712,-0.912238l-140.917468,-79.668804c-3.698372,-2.128556 -5.025993,-6.892467 -3.129392,-10.845499c1.896601,-3.953032 6.448444,-5.37207 10.146816,-3.344873l137.503586,77.742968l143.00373,-79.871524c3.698372,-2.027196 8.155385,-0.506799 10.146816,3.344873c1.896601,3.953032 0.47415,8.716943 -3.129392,10.845499l-146.512442,81.79736c-1.232791,0.608159 -2.370751,0.912238 -3.603542,0.912238zm2.465582,-148.49211c-1.232791,0 -2.465582,-0.304079 -3.508712,-0.912238l-82.312492,-47.436387c-3.603542,-2.128556 -5.025993,-6.993826 -3.034562,-10.845499c1.991431,-3.953032 6.543274,-5.27071 10.146816,-3.243514l78.708949,45.409191l78.329629,-47.537747c3.603542,-2.229916 8.155385,-0.810878 10.241646,3.040794c2.086261,3.851672 0.75864,8.716943 -2.844902,10.946859l-81.933171,49.666303c-1.232791,0.608159 -2.560412,0.912238 -3.793202,0.912238z"/>
<path stroke="null" id="svg_12" fill="#0276F7" d="m509.913752,579.708306l-0.28449,0l-263.248245,-9.021022c-4.172523,-0.10136 -7.396745,-3.851672 -7.207085,-8.210144c0.09483,-4.459831 4.077693,-7.703345 7.681235,-7.703345l263.343075,9.021022c4.172523,0.10136 7.396745,3.851672 7.207085,8.210144c-0.18966,4.257112 -3.508712,7.703345 -7.491575,7.703345zm0,152.141063c-1.612111,0 -3.224222,-0.608159 -4.646673,-1.723117c-3.224222,-2.736715 -3.793202,-7.804705 -1.232791,-11.250938l205.496739,-276.610899c2.560412,-3.446233 7.301915,-4.054392 10.526137,-1.317677c3.224222,2.736715 3.793202,7.804705 1.232791,11.250938l-205.496739,276.610899c-1.517281,2.027196 -3.698372,3.040794 -5.879464,3.040794z"/>
<path stroke="null" id="svg_13" fill="#0276F7" d="m509.913752,579.708306c-1.422451,0 -2.750072,-0.405439 -4.077693,-1.216318c-3.508712,-2.432635 -4.457013,-7.297906 -2.275921,-11.048218l144.14169,-239.310492c2.275921,-3.750313 6.922594,-4.763911 10.336476,-2.432635c3.508712,2.432635 4.457013,7.297906 2.275921,11.048218l-144.14169,239.310492c-1.422451,2.331275 -3.793202,3.648953 -6.258784,3.648953zm-2.465582,-157.513132c-1.043131,0 -2.086261,-0.20272 -3.129392,-0.709519c-3.793202,-1.824476 -5.405313,-6.588387 -3.698372,-10.642779l84.398753,-198.158413c1.706941,-4.054392 6.069124,-5.777509 9.957156,-3.953032c3.793202,1.824476 5.405313,6.588387 3.698372,10.642779l-84.303923,198.158413c-1.327621,2.939434 -4.077693,4.662551 -6.922594,4.662551z"/>
<path stroke="null" id="svg_14" fill="#0276F7" d="m591.846924,375.062866c-2.750072,0 -5.405313,-1.621757 -6.732934,-4.459831c-1.801771,-3.953032 -0.28449,-8.716943 3.413882,-10.642779l129.253371,-67.302908l-365.759539,-178.089172l20.862613,208.091673l133.994874,-64.262114c3.698372,-1.824476 8.155385,0 9.862326,4.054392c1.706941,4.054392 0,8.716943 -3.793202,10.541419l-143.38305,68.823305c-2.181091,1.013598 -4.646673,0.912238 -6.827764,-0.405439c-2.086261,-1.317677 -3.413882,-3.547593 -3.698372,-6.081588l-23.328195,-233.026185c-0.28449,-2.838074 0.853471,-5.676149 3.034562,-7.297906c2.181091,-1.621757 5.025993,-2.027196 7.491575,-0.810878l392.217126,190.961867c2.655242,1.317677 4.362183,4.054392 4.362183,7.196546c0,3.142154 -1.612111,5.980228 -4.172523,7.297906l-143.57271,74.600814c-1.043131,0.608159 -2.181091,0.810878 -3.224222,0.810878zm-283.921198,78.959286c-3.603542,0 -6.827764,-2.838074 -7.396745,-6.791107c-0.56898,-4.358471 2.181091,-8.412864 6.258784,-9.122382l199.617275,-31.826978c4.077693,-0.608159 7.870895,2.331275 8.534705,6.689747c0.56898,4.358471 -2.181091,8.412864 -6.258784,9.122382l-199.617275,31.826978c-0.47415,0.10136 -0.853471,0.10136 -1.137961,0.10136z"/>
</g>
<text stroke="null" font-style="italic" transform="matrix(6.577099502228161,0,0,7.449448263868419,-1073.2057632249744,-908.8606073938396) " xml:space="preserve" text-anchor="start" font-family="Arvo, sans-serif" font-size="24" id="svg_16" y="177.898525" x="178.621382" stroke-width="0" fill="#D4E4FC">5G</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -41,3 +41,90 @@ export function exportAMFDataUE(data: Record<string, any>) {
timeout: 60_000, timeout: 60_000,
}); });
} }
/**
* AMF-接入基站信息列表
* @param query 查询参数 neId=001&id=1
* @returns object
*/
export function listAMFNblist(query: Record<string, any>) {
return request({
url: '/neData/amf/nb/list',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* AMF-接入基站状态信息列表
* @param query 查询参数 neId=001&state=1
* @returns object
*/
export function listAMFNbStatelist(query: Record<string, any>) {
return request({
url: '/neData/amf/nb/list-cfg',
method: 'get',
params: query,
timeout: 60_000,
});
}
/**
* AMF-接入基站状态信息新增
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function addAMFNbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'post',
data: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* AMF-接入基站状态信息修改
* @param neId 网元ID
* @param data 数据 { "index": 1, "name": "Gnb", "address": "192.168.8.1", "position": "Area-B" }
* @returns object
*/
export function editAMFNbState(neId: string, data: Record<string, any>) {
return request({
url: `/ne/config/data`,
method: 'put',
data: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
paramData: data,
loc: `${data.index}`,
},
});
}
/**
* AMF-接入基站状态信息删除
* @param neId 网元ID
* @param index 数据index
* @returns object
*/
export function delAMFNbState(neId: string, index: string | number) {
return request({
url: `/ne/config/data`,
method: 'delete',
params: {
neType: 'AMF',
neId: neId,
paramName: 'gnbList',
loc: `${index}`,
},
});
}

View File

@@ -130,26 +130,17 @@ export function edgeLineAnimateState() {
// circle-move 圆点沿边运动 // circle-move 圆点沿边运动
if (name === 'circle-move') { if (name === 'circle-move') {
let back1 = group.find( const backArr = group.findAll((ele: any) =>
(ele: any) => ele.get('name') === 'circle-stroke1' ele.get('name').startsWith('circle-stroke')
); );
if (back1) {
back1.remove();
back1.destroy();
}
let back2 = group.find(
(ele: any) => ele.get('name') === 'circle-stroke2'
);
if (back2) {
back2.remove();
back2.destroy();
}
if (value) { if (value) {
if (backArr.length > 0) return;
// 第一个矩形边 // 第一个矩形边
const fillColor = typeof value === 'string' ? value : '#1890ff'; const fillColor = typeof value === 'string' ? value : '#1890ff';
// 边缘路径的起始位置 // 边缘路径的起始位置
const startPoint = keyShape.getPoint(0); const startPoint = keyShape.getPoint(0);
back1 = group.addShape('circle', { const back1 = group.addShape('circle', {
attrs: { attrs: {
x: startPoint.x, x: startPoint.x,
y: startPoint.y, y: startPoint.y,
@@ -159,6 +150,7 @@ export function edgeLineAnimateState() {
// 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性 // 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性
name: 'circle-stroke1', name: 'circle-stroke1',
}); });
back1.show();
back1.animate( back1.animate(
(ratio: any) => { (ratio: any) => {
// 每帧中的操作。比率范围从 0 到 1表示动画的进度。返回修改后的配置 // 每帧中的操作。比率范围从 0 到 1表示动画的进度。返回修改后的配置
@@ -177,7 +169,7 @@ export function edgeLineAnimateState() {
); );
// 第二个矩形边 // 第二个矩形边
const endPoint = keyShape.getPoint(1); const endPoint = keyShape.getPoint(1);
back2 = group.addShape('circle', { const back2 = group.addShape('circle', {
zIndex: -2, zIndex: -2,
attrs: { attrs: {
x: endPoint.x, x: endPoint.x,
@@ -188,7 +180,7 @@ export function edgeLineAnimateState() {
// 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性 // 在 G6 3.3 及之后的版本中,必须指定 name可以是任意字符串但需要在同一个自定义元素类型中保持唯一性
name: 'circle-stroke2', name: 'circle-stroke2',
}); });
back2.show();
back2.animate( back2.animate(
(ratio: any) => { (ratio: any) => {
// 每帧中的操作。比率范围从 0 到 1表示动画的进度。返回修改后的配置 // 每帧中的操作。比率范围从 0 到 1表示动画的进度。返回修改后的配置
@@ -205,6 +197,14 @@ export function edgeLineAnimateState() {
duration: 2 * 1000, // 执行一次的持续时间 duration: 2 * 1000, // 执行一次的持续时间
} }
); );
} else {
if (backArr.length <= 0) return;
backArr.forEach((ele: any) => {
ele.hide();
ele.remove();
ele.destroy();
});
backArr.length = 0;
} }
return; return;
} }
@@ -212,11 +212,7 @@ export function edgeLineAnimateState() {
// line-dash 虚线运动 // line-dash 虚线运动
if (name === 'line-dash') { if (name === 'line-dash') {
if (value) { if (value) {
keyShape.stopAnimate(); if (keyShape.cfg.animating) return;
keyShape.attr({
lineDash: null,
lineDashOffset: null,
});
let index = 0; let index = 0;
keyShape.animate( keyShape.animate(
() => { () => {
@@ -234,6 +230,12 @@ export function edgeLineAnimateState() {
duration: 3000, // 执行一次的持续时间 duration: 3000, // 执行一次的持续时间
} }
); );
} else {
keyShape.stopAnimate();
keyShape.attr({
lineDash: null,
lineDashOffset: null,
});
} }
return; return;
} }
@@ -246,6 +248,7 @@ export function edgeLineAnimateState() {
back.remove(); back.remove();
back.destroy(); back.destroy();
} }
const { path, stroke, lineWidth } = keyShape.attr(); const { path, stroke, lineWidth } = keyShape.attr();
back = group.addShape('path', { back = group.addShape('path', {
attrs: { attrs: {

View File

@@ -300,12 +300,23 @@ export function nodeImageAnimateState() {
ele.get('name').startsWith('circle-shape') ele.get('name').startsWith('circle-shape')
); );
if (value) { if (value) {
if (Array.isArray(backArr) && backArr.length === 3) return; const fillColor = typeof value === 'string' ? value : '#f5222d';
// 移除
if (Array.isArray(backArr) && backArr.length >= 3) {
for (const back of backArr) {
back.stopAnimate();
back.hide();
back.remove();
back.destroy();
}
backArr.length = 0;
}
// 根据大小确定半径
const size = Array.isArray(model?.size) ? model?.size : [40, 40]; const size = Array.isArray(model?.size) ? model?.size : [40, 40];
const x = size[0] / 2; const x = size[0] / 2;
const y = -size[1] / 2; const y = -size[1] / 2;
const r = 3; const r = 3;
const fillColor = typeof value === 'string' ? value : '#f5222d';
// 第一个背景圆 // 第一个背景圆
const back1 = group.addShape('circle', { const back1 = group.addShape('circle', {

View File

@@ -0,0 +1,593 @@
<script setup lang="ts">
import { reactive, ref, onMounted, toRaw } from 'vue';
import { Form, message, Modal } from 'ant-design-vue';
import { SizeType } from 'ant-design-vue/es/config-provider';
import { ColumnsType } from 'ant-design-vue/es/table';
import { ProModal } from 'antdv-pro-modal';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import {
addAMFNbState,
delAMFNbState,
editAMFNbState,
listAMFNbStatelist,
} from '@/api/neData/amf';
const { t } = useI18n();
import { useRoute } from 'vue-router';
const route = useRoute();
const nbState = ref<DictType[]>([
{
value: 'ON',
label: 'Online',
tagType: 'green',
tagClass: '',
},
{
value: 'OFF',
label: 'Offline',
tagType: 'red',
tagClass: '',
},
]);
/**网元参数 */
let neCascaderOptions = ref<Record<string, any>[]>([]);
/**网元数据 */
let neTypeAndId = ref<string[]>([]);
/**查询参数 */
let queryParams = reactive({
/**网元ID */
neId: '',
/**IMSI */
state: undefined,
});
/**查询参数重置 */
function fnQueryReset() {
queryParams = Object.assign(queryParams, {
state: undefined,
});
fnGetList();
}
/**表格状态类型 */
type TabeStateType = {
/**加载等待 */
loading: boolean;
/**紧凑型 */
size: SizeType;
/**记录数据 */
data: Record<string, any>[];
/**勾选记录 */
selectedRowKeys: (string | number)[];
};
/**表格状态 */
let tableState: TabeStateType = reactive({
loading: false,
size: 'small',
data: [],
selectedRowKeys: [],
});
/**表格字段列 */
let tableColumns = ref<ColumnsType>([
{
title: 'Index',
dataIndex: 'index',
align: 'left',
width: 80,
},
{
title: 'Name',
dataIndex: 'name',
align: 'left',
width: 150,
ellipsis: true,
},
{
title: 'Position',
dataIndex: 'position',
align: 'left',
width: 150,
ellipsis: true,
},
{
title: 'Address',
dataIndex: 'address',
align: 'left',
width: 100,
},
{
title: 'State',
dataIndex: 'state',
key: 'state',
align: 'left',
width: 80,
},
{
title: 'Time',
align: 'left',
width: 150,
customRender(opt) {
const record = opt.value;
if (record.state === 'OFF') {
return record.offTime;
}
return record.onTime;
},
},
]);
/**表格分页器参数 */
let tablePagination = {
/**当前页数 */
current: 1,
/**每页条数 */
pageSize: 20,
/**默认的每页条数 */
defaultPageSize: 20,
/**指定每页可以显示多少条 */
pageSizeOptions: ['10', '20', '50', '100'],
/**只有一页时是否隐藏分页器 */
hideOnSinglePage: true,
/**是否可以快速跳转至某页 */
showQuickJumper: true,
/**是否可以改变 pageSize */
showSizeChanger: true,
/**数据总数 */
total: 0,
showTotal: (total: number) => t('common.tablePaginationTotal', { total }),
onChange: (page: number, pageSize: number) => {
tablePagination.current = page;
tablePagination.pageSize = pageSize;
},
};
/**表格多选 */
function fnTableSelectedRowKeys(keys: (string | number)[]) {
tableState.selectedRowKeys = keys;
}
/**
* 记录删除
* @param index ID
*/
function fnRecordDelete(index: string) {
const [neType, neId] = neTypeAndId.value;
if (!neId) return;
let msg = `Delete index as:${index}`;
if (index === '0') {
msg = `Remove the index checkbox:${tableState.selectedRowKeys.length}`;
}
Modal.confirm({
title: t('common.tipTitle'),
content: msg,
onOk() {
const reqArr = [];
if (index === '0') {
if (tableState.selectedRowKeys.length <= 0) {
return;
}
for (const v of tableState.selectedRowKeys) {
if (neType === 'MME') {
// reqArr.push(delAMFNbState(neId, v));
}
if (neType === 'AMF') {
reqArr.push(delAMFNbState(neId, v));
}
}
} else {
if (neType === 'MME') {
// reqArr.push(delAMFNbState(neId, index));
}
if (neType === 'AMF') {
reqArr.push(delAMFNbState(neId, index));
}
}
if (reqArr.length <= 0) return;
Promise.all(reqArr).then(res => {
const resArr = res.filter(
(item: any) => item.code !== RESULT_CODE_SUCCESS
);
if (resArr.length <= 0) {
message.success({
content: `${t('common.operateOk')}`,
duration: 3,
});
fnGetList();
} else {
message.error({
content: `${t('common.operateErr')}`,
duration: 3,
});
}
});
},
});
}
/**查询列表 */
function fnGetList() {
if (tableState.loading) return;
tableState.loading = true;
const [neType, neId] = neTypeAndId.value;
queryParams.neId = neId;
let req = null;
// if (neType === 'MME') {
// req = listAMFNbStatelist(toRaw(queryParams));
// }
if (neType === 'AMF') {
req = listAMFNbStatelist(toRaw(queryParams));
}
if (req === null) {
tableState.data = [];
tableState.loading = false;
return;
}
req.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
// 取消勾选
if (tableState.selectedRowKeys.length > 0) {
tableState.selectedRowKeys = [];
}
tableState.data = res.data.filter((item: any) => {
// 状态过滤
if (queryParams.state) {
return item.state === queryParams.state;
}
return true;
});
} else {
tableState.data = [];
}
tableState.loading = false;
});
}
/**对话框对象信息状态类型 */
type ModalStateType = {
/**新增框或修改框是否显示 */
openByEdit: boolean;
/**标题 */
title: string;
/**表单数据 */
from: Record<string, any>;
/**确定按钮 loading */
confirmLoading: boolean;
};
/**对话框对象信息状态 */
let modalState: ModalStateType = reactive({
openByEdit: false,
title: 'NB Config List',
from: {
index: undefined,
address: '',
name: '',
position: '',
},
confirmLoading: false,
});
/**对话框内表单属性和校验规则 */
const modalStateFrom = Form.useForm(
modalState.from,
reactive({
address: [{ required: true, message: `text content length 0~64` }],
name: [{ required: true, message: `text content length 0~64` }],
position: [
{
required: true,
message: `location description. Prohibition of spaces, length of text content 0-64`,
},
],
})
);
/**
* 对话框弹出显示为 新增或者修改
* @param noticeId 网元id, 不传为新增
*/
function fnModalVisibleByEdit(edit?: string | number) {
if (!edit) {
modalStateFrom.resetFields(); //重置表单
modalState.title = 'Add Radio Info';
modalState.openByEdit = true;
// 获取最大index
if (tableState.data.length <= 0) {
modalState.from.index = 1;
} else {
const last = tableState.data[tableState.data.length - 1];
modalState.from.index = last.index + 1;
}
}
// 编辑
if (edit === '0') {
const row = tableState.data.find((row: any) => {
return row.index === tableState.selectedRowKeys[0];
});
modalStateFrom.resetFields(); //重置表单
Object.assign(modalState.from, row);
modalState.title = 'Edit Radio Info';
modalState.openByEdit = true;
}
}
/**
* 对话框弹出确认执行函数
* 进行表达规则校验
*/
function fnModalOk() {
const neID = queryParams.neId;
if (!neID) return;
const from = JSON.parse(JSON.stringify(modalState.from));
modalStateFrom
.validate()
.then(e => {
modalState.confirmLoading = true;
const hide = message.loading(t('common.loading'), 0);
let result: any = modalState.title.startsWith('Edit')
? editAMFNbState(neID, from)
: addAMFNbState(neID, from);
result
.then((res: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
message.success({
content: t('common.operateOk'),
duration: 3,
});
fnModalCancel();
fnGetList();
} else {
message.error({
content: t('common.operateErr'),
duration: 3,
});
}
})
.finally(() => {
hide();
modalState.confirmLoading = false;
});
})
.catch(e => {
message.error(t('common.errorFields', { num: e.errorFields.length }), 3);
});
}
/**
* 对话框弹出关闭执行函数
* 进行表达规则校验
*/
function fnModalCancel() {
modalState.openByEdit = false;
modalStateFrom.resetFields();
}
onMounted(() => {
// 获取网元网元列表
useNeInfoStore()
.fnNelist()
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
if (res.data.length > 0) {
let arr: Record<string, any>[] = [];
useNeInfoStore().neCascaderOptions.forEach(item => {
if (['AMF', 'MME'].includes(item.value)) {
arr.push(JSON.parse(JSON.stringify(item)));
}
});
neCascaderOptions.value = arr;
// 无查询参数neType时 默认选择AMF
const queryNeType = (route.query.neType as string) || 'AMF';
const item = arr.find(s => s.value === queryNeType);
if (item && item.children) {
const info = item.children[0];
neTypeAndId.value = [info.neType, info.neId];
} else {
const info = neCascaderOptions.value[0].children[0];
neTypeAndId.value = [info.neType, info.neId];
}
}
} else {
message.warning({
content: t('common.noData'),
duration: 2,
});
}
})
.finally(() => {
// 获取列表数据
fnGetList();
});
});
</script>
<template>
<div>
<a-card
:bordered="false"
:body-style="{ marginBottom: '24px', paddingBottom: 0 }"
>
<!-- 表格搜索栏 -->
<a-form :model="queryParams" name="queryParams" layout="horizontal">
<a-row :gutter="16">
<a-col :lg="6" :md="12" :xs="24">
<a-form-item :label="t('views.ne.common.neType')" name="neType ">
<a-cascader
v-model:value="neTypeAndId"
:options="neCascaderOptions"
:allow-clear="false"
:placeholder="t('common.selectPlease')"
@change="fnGetList"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item label="State" name="state">
<a-select
v-model:value="queryParams.state"
:options="nbState"
:placeholder="t('common.selectPlease')"
/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :xs="24">
<a-form-item>
<a-space :size="8">
<a-button type="primary" @click.prevent="fnGetList()">
<template #icon><SearchOutlined /></template>
{{ t('common.search') }}
</a-button>
<a-button type="default" @click.prevent="fnQueryReset">
<template #icon><ClearOutlined /></template>
{{ t('common.reset') }}
</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-card>
<a-card :bordered="false" :body-style="{ padding: '0px' }">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center">
<a-button type="primary" @click.prevent="fnModalVisibleByEdit()">
<template #icon>
<PlusOutlined />
</template>
{{ t('common.addText') }}
</a-button>
<a-button
type="default"
:disabled="tableState.selectedRowKeys.length != 1"
:loading="modalState.confirmLoading"
@click.prevent="fnModalVisibleByEdit('0')"
>
<template #icon><FormOutlined /></template>
{{ t('common.editText') }}
</a-button>
<a-button
type="default"
danger
:disabled="tableState.selectedRowKeys.length <= 0"
:loading="modalState.confirmLoading"
@click.prevent="fnRecordDelete('0')"
>
<template #icon><DeleteOutlined /></template>
{{ t('common.deleteText') }}
</a-button>
</a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-space :size="8" align="center">
<a-tooltip>
<template #title>{{ t('common.reloadText') }}</template>
<a-button type="text" @click.prevent="fnGetList()">
<template #icon><ReloadOutlined /></template>
</a-button>
</a-tooltip>
</a-space>
</template>
<!-- 表格列表 -->
<a-table
class="table"
row-key="index"
:columns="tableColumns"
:loading="tableState.loading"
:data-source="tableState.data"
:size="tableState.size"
:pagination="tablePagination"
:scroll="{ x: tableColumns.length * 120 }"
:row-selection="{
type: 'checkbox',
selectedRowKeys: tableState.selectedRowKeys,
onChange: fnTableSelectedRowKeys,
}"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'state'">
<DictTag
:options="nbState"
:value="record.state"
value-default="OFF"
/>
</template>
</template>
</a-table>
</a-card>
<!-- 新增框或修改框 -->
<ProModal
:drag="true"
:width="500"
:destroyOnClose="true"
:keyboard="false"
:mask-closable="false"
:open="modalState.openByEdit"
:title="modalState.title"
:confirm-loading="modalState.confirmLoading"
@ok="fnModalOk"
@cancel="fnModalCancel"
>
<a-form
name="modalStateFrom"
layout="horizontal"
:label-col="{ span: 6 }"
:labelWrap="true"
>
<a-form-item
label="Name"
name="name"
v-bind="modalStateFrom.validateInfos.name"
>
<a-input
v-model:value="modalState.from.name"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
<a-form-item
label="Address"
name="address"
v-bind="modalStateFrom.validateInfos.address"
>
<a-input
v-model:value="modalState.from.address"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
<a-form-item
label="Position"
name="position"
v-bind="modalStateFrom.validateInfos.position"
>
<a-input
v-model:value="modalState.from.position"
allow-clear
:maxlength="64"
>
</a-input>
</a-form-item>
</a-form>
</ProModal>
</div>
</template>
<style lang="less" scoped>
.table :deep(.ant-pagination) {
padding: 0 24px;
}
</style>

View File

@@ -0,0 +1,557 @@
<script setup lang="ts">
import { reactive, onMounted, ref, onBeforeUnmount, useTemplateRef } from 'vue';
import { Graph, GraphData, Menu, Tooltip } from '@antv/g6';
import { listAMFNbStatelist } from '@/api/neData/amf';
import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
import useNeInfoStore from '@/store/modules/neinfo';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { stateNeInfo } from '@/api/ne/neInfo';
import { parseDateToStr } from '@/utils/date-utils';
import { useFullscreen } from '@vueuse/core';
const { t } = useI18n();
/**图DOM节点实例对象 */
const graphG6Dom = useTemplateRef('graphG6Dom');
/**图数据 */
const graphData = reactive<Record<string, any>>({
nodes: [
{
id: 'omc',
label: 'OMC',
img: parseBasePath('/svg/service_db.svg'),
},
{
id: 'amf1',
label: 'amf1',
img: parseBasePath('/svg/service.svg'),
},
{
id: 'amf2',
label: 'amf2',
img: parseBasePath('/svg/service.svg'),
},
{
id: 'base1',
label: 'base1',
img: parseBasePath('/svg/base.svg'),
},
{
id: 'base2',
label: 'base2',
img: parseBasePath('/svg/base.svg'),
},
],
edges: [
{
source: 'omc',
target: 'amf1',
},
{
source: 'omc',
target: 'amf2',
},
{
source: 'amf1',
target: 'base1',
},
{
source: 'amf2',
target: 'base1',
},
{
source: 'amf2',
target: 'base2',
},
],
});
/**图实例对象 */
const graphG6 = ref<any>(null);
/**图节点右击菜单 */
const graphNodeMenu = new Menu({
offsetX: 6,
offseY: 10,
itemTypes: ['node'],
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, nType, nInfo }: any = evt.item?.getModel();
return `
<div
style="
display: flex;
flex-direction: column;
width: 140px;
"
>
<span>
${t('views.monitor.topology.name')}:
${label ?? '--'}
</span>
</div>
`;
},
});
/**图节点展示 */
const graphNodeTooltip = new Tooltip({
offsetX: 10,
offsetY: 20,
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, nType, nInfo }: any = evt.item?.getModel();
if (['GNB', 'ENB'].includes(nType)) {
return `
<div
style="
display: flex;
flex-direction: column;
width: 228px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
nInfo.state === 'ON'
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>OnTime</strong><span>
${nInfo.onTime ?? '--'}
</span></div>
<div><strong>OffTime</strong><span>
${nInfo.offTime ?? '--'}
</span></div>
<div>===========================</div>
<div><strong>ID</strong><span>${nInfo.index}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span>
${nInfo.name ?? '--'}
</span></div>
<div><strong>Address</strong><span>${nInfo.address}</span></div>
<div><strong>Position</strong><span style="word-wrap: break-word;">
${nInfo.position}
</span></div>
</div>
`;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 200px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
nInfo.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${nInfo.refreshTime ?? '--'}
</span></div>
<div>========================</div>
<div><strong>ID</strong><span>${nInfo.neId}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span>
${nInfo.neName ?? '--'}
</span></div>
<div><strong>IP</strong><span>${nInfo.neIP}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span>
${nInfo.version ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.serialNum')}</strong><span>
${nInfo.sn ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${nInfo.expire ?? '--'}
</span></div>
</div>
`;
},
itemTypes: ['node'],
});
/**注册自定义边或节点 */
function registerEdgeNode() {
// 边
edgeLineAnimateState();
// 节点
nodeImageAnimateState();
}
/**图事件 */
function graphEvent(graph: Graph) {
graph.on('edge:mouseenter', evt => {
const { item } = evt;
if (!item) return;
graph.setItemState(item, 'circle-move', '#b5d6fb');
});
graph.on('edge:mouseleave', evt => {
const { item } = evt;
if (!item) return;
graph.setItemState(item, 'circle-move', false);
graph.setItemState(item, 'circle-move:#b5d6fb', false);
});
}
/**图数据渲染 */
function handleRanderGraph(container: HTMLElement | null, data: GraphData) {
if (!container) return;
const { clientHeight, clientWidth } = container;
// 注册自定义边或节点
registerEdgeNode();
const graph = new Graph({
container: container,
width: clientWidth,
height: clientHeight,
fitCenter: false,
fitView: true,
fitViewPadding: [40, 40, 40, 40],
modes: {
// default: ['drag-canvas', 'drag-node', 'zoom-canvas'],
default: [
'zoom-canvas',
'drag-canvas',
'drag-node',
{
type: 'drag-combo',
onlyChangeComboSize: true, // 不改变层级关系
},
{
type: 'collapse-expand-combo',
trigger: 'dblclick',
relayout: true, // 收缩展开后,不重新布局
},
],
},
groupByTypes: false,
plugins: [graphNodeMenu, graphNodeTooltip],
layout: {
type: 'dagre',
rankdir: 'BT', // 布局的方向TB从上到下BT从下到上LR从左到右RL从右到左
align: 'UL', // 节点对齐方式 UL、UR、DL、DR
controlPoints: true,
nodesep: 20,
ranksep: 40,
},
animate: true,
defaultNode: {
type: 'image-animate-state',
labelCfg: {
offset: 8,
position: 'bottom',
style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
},
size: 48,
img: parseBasePath('/svg/cloud.svg'),
width: 48,
height: 48,
},
defaultEdge: {
type: 'line-animate-state',
labelCfg: {
autoRotate: true,
refY: 10,
refX: 40,
},
style: {
stroke: '#fafafa',
lineWidth: 1.5,
},
},
defaultCombo: {
labelCfg: {
offset: 16,
position: 'bottom',
style: { fill: '#ffffff', fontSize: 14, fontWeight: 500 },
},
style: {
stroke: '#BDEFDB',
fill: '#BDEFDB',
opacity: 0.25,
},
collapsedSubstituteIcon: {
show: true,
img: parseBasePath('/svg/service.svg'),
width: 48,
height: 48,
},
},
});
graph.data(data);
graph.render();
graphEvent(graph);
// 创建 ResizeObserver 实例
const observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graph) {
return;
}
graph.changeSize(entry.contentRect.width, entry.contentRect.height);
graph.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
/**
* 获取图组数据渲染到画布
*/
async function fnGraphDataLoad() {
// 加载基础网元
await useNeInfoStore().fnNelist();
const dataNe = await fnGraphDataBase();
Object.assign(graphData, dataNe);
graphG6.value = handleRanderGraph(graphG6Dom.value, dataNe);
// 添加基站
const dataNb = await fnGraphDataNb(dataNe);
Object.assign(graphData, dataNb);
// graphG6.value.clear();
graphG6.value.read(dataNb);
// 添加状态
interval.value = true;
repeatFn();
}
/**图数据网元构建 */
async function fnGraphDataBase() {
const data: GraphData = {
nodes: [],
edges: [],
};
// 添加基础网元
for (const item of useNeInfoStore().getNeSelectOtions) {
if ('OMC' === item.value) {
if (item.children?.length === 0) continue;
// 是否存在OMC保证唯一
const hasOMC = data.nodes?.findIndex(v => v.neType === 'OMC');
if (hasOMC !== -1) continue;
// 根网元
const omcInfo = item.children[0];
const node = {
id: 'OMC',
label: omcInfo.neName,
img: parseBasePath('/svg/service.svg'),
nInfo: { online: false, neId: omcInfo.neId, neType: omcInfo.neType },
nType: 'OMC',
};
// 添加OMC节点
data.nodes?.push(node);
continue;
}
// if (['AMF', 'MME'].includes(item.value)) {
if (['AMF'].includes(item.value)) {
if (item.children?.length === 0) continue;
for (const child of item.children) {
const id = `${child.neType}_${child.neId}`;
const node = {
id: id,
label: child.neName,
img: parseBasePath('/svg/service.svg'),
nInfo: { online: false, neId: child.neId, neType: child.neType },
nType: item.value,
};
// 添加节点
data.nodes?.push(node);
data.edges?.push({
source: 'OMC',
target: id,
});
}
item.children.forEach((v: any) => {});
continue;
}
}
return data;
}
/**图数据基站构建 */
async function fnGraphDataNb(data: GraphData) {
const arr = data.nodes?.filter((v: any) => ['AMF', 'MME'].includes(v.nType));
if (arr === undefined || arr.length === 0) return data;
for (const item of arr) {
if (item.nType === 'AMF') {
const neId = (item.nInfo as any).neId;
const res = await listAMFNbStatelist({ neId });
if (res.code !== RESULT_CODE_SUCCESS || !Array.isArray(res.data)) {
continue;
}
for (const nb of res.data) {
const id = `${item.id}_${nb.index}`;
data.nodes?.push({
id: id,
label: `${nb.name}`,
img: parseBasePath('/svg/base5G.svg'),
nInfo: nb,
nType: 'GNB',
});
data.edges?.push({
source: item.id,
target: id,
});
}
}
if (item.nType === 'MME') {
}
}
return data;
}
/**
* 图状态构建
* @param reload 是否重载状态
*/
async function fnGraphState(reload: boolean = false) {
// 节点状态
if (!Array.isArray(graphData.nodes)) return;
const onc = graphData.nodes.find((v: any) => v.nType === 'OMC');
if (onc) {
const { id, nInfo } = onc as any;
if (!nInfo) return;
const res = await stateNeInfo(nInfo.neType, nInfo.neId);
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(nInfo, res.data, {
refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
});
}
const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
}
graphData.nodes
.filter((v: any) => ['AMF', 'MME'].includes(v.nType))
.forEach(async (v: any) => {
const { id, nInfo } = v;
if (!nInfo) return;
const res = await stateNeInfo(nInfo.neType, nInfo.neId);
if (res.code === RESULT_CODE_SUCCESS) {
Object.assign(nInfo, res.data, {
refreshTime: parseDateToStr(res.data.refreshTime, 'HH:mm:ss'),
});
}
const stateColor = nInfo.online ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
// 重载时更新下级基站状态
if (reload && nInfo.neType === 'AMF') {
const res = await listAMFNbStatelist({ neId: nInfo.neId });
if (res.code == RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const nb of res.data) {
const nbItem = graphData.nodes.find(
(v: any) => v.id === `${id}_${nb.index}`
);
if (nbItem) {
Object.assign(nbItem.nInfo, nb);
const stateColor = nb.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(
nbItem.id,
'top-right-dot',
stateColor
);
}
}
}
}
if (reload && nInfo.neType === 'MME') {
}
});
if (reload) {
await new Promise(resolve => setTimeout(resolve, 15_000));
return;
}
// 非重载时使用初始获取的状态
graphData.nodes
.filter((v: any) => ['GNB', 'ENB'].includes(v.nType))
.forEach(async (v: any) => {
const { id, nInfo } = v;
if (!nInfo) return;
const stateColor = nInfo.state === 'ON' ? '#52c41a' : '#f5222d'; // 状态颜色
graphG6.value.setItemState(id, 'top-right-dot', stateColor);
});
}
/**递归调度器 */
const interval = ref<boolean>(false);
/**递归刷新图状态 */
function repeatFn(reload: boolean = false) {
if (!interval.value) {
return;
}
fnGraphState(reload)
.finally(() => {
repeatFn(true); // 递归调用自己
})
.catch(error => {
console.error(error);
});
}
const viewportDom = ref<HTMLElement | null>(null);
const { isFullscreen, toggle } = useFullscreen(viewportDom);
function fullscreen() {
toggle();
if (!graphG6Dom.value) return;
if (isFullscreen.value) {
graphG6Dom.value.style.height = 'calc(100vh - 300px)';
} else {
graphG6Dom.value.style.height = '100vh';
}
const { clientHeight, clientWidth } = graphG6Dom.value;
graphG6.value.changeSize(clientHeight, clientWidth);
graphG6.value.fitView(40);
}
onMounted(() => {
fnGraphDataLoad();
});
onBeforeUnmount(() => {
interval.value = false;
});
</script>
<template>
<a-card :bordered="false" :body-style="{ padding: '0' }" ref="viewportDom">
<!-- 插槽-卡片左侧侧 -->
<template #title>
<a-space :size="8" align="center"> Radio State Graph </a-space>
</template>
<!-- 插槽-卡片右侧 -->
<template #extra>
<a-button type="default" @click.prevent="fullscreen()">
<template #icon>
<FullscreenExitOutlined v-if="isFullscreen" />
<FullscreenOutlined v-else />
</template>
Full Screen
</a-button>
</template>
<div ref="graphG6Dom" class="chart"></div>
</a-card>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: calc(100vh - 300px);
background-color: rgb(43, 47, 51);
}
</style>

View File

@@ -0,0 +1,40 @@
<script setup lang="ts">
import {
onMounted,
type Component,
defineAsyncComponent,
shallowRef,
} from 'vue';
import { PageContainer } from 'antdv-pro-layout';
const defineComponent = shallowRef<Component | null>(null);
function fnSwitch(name: string) {
if (name === 'topology') {
defineComponent.value = defineAsyncComponent(
() => import('@/views/ne-data/base-station/components/topology.vue')
);
}
if (name === 'list') {
defineComponent.value = defineAsyncComponent(
() => import('@/views/ne-data/base-station/components/list.vue')
);
}
}
onMounted(() => {
fnSwitch('topology');
});
</script>
<template>
<PageContainer>
<template #extra>
<a-button @click="fnSwitch('list')">List</a-button>
<a-button type="primary" @click="fnSwitch('topology')">Topology</a-button>
</template>
<component :is="defineComponent" />
</PageContainer>
</template>
<style scoped></style>