修改综合仪表盘样式

This commit is contained in:
lai
2025-07-04 17:07:53 +08:00
parent edd3865094
commit 1a10a5a9d1
13 changed files with 912 additions and 539 deletions

View File

@@ -29,6 +29,7 @@
"crypto-js": "4.2.0",
"dayjs": "1.11.13",
"echarts": "5.6.0",
"echarts-liquidfill": "^3.1.0",
"file-saver": "2.0.5",
"grid-layout-plus": "1.0.6",
"intl-tel-input": "25.2.0",

View File

@@ -361,10 +361,13 @@ export default {
imsUeNum: "VoNR/VoLTE",
smfUeNum: "Data Sessions",
gnbBase: "Online gNodeB",
gnbSumBase: "Total gNodeB",
enbBase: "Online eNodeB",
enbSumBase: "Total eNodeB",
gnbUeNum:'5G Active Users',
enbUeNum:'4G Active Users',
baseTitle:'Online Information',
nodeBInfo: 'NodeB Information',
},
upfFlow:{
title: "UPF Throughput",
@@ -394,6 +397,7 @@ export default {
},
userActivity: {
title: "User Activity",
imsTitle: "IMS Activity",
type: "Type",
duration: "Duration",
caller: "Caller",
@@ -668,7 +672,7 @@ export default {
toIpPleace: "Please input the remote backup server IP address",
toPort: "Service Port",
username: "UserName",
usernamePleace: 'Please enter the service login username',
usernamePleace: 'Please enter the service login username',
password: "Password",
dir: "Save Dir",
dirPleace: 'Please enter the service address target file directory',

View File

@@ -361,10 +361,13 @@ export default {
imsUeNum: "IMS 会话数",
smfUeNum: "Data 会话数",
gnbBase: "5G 基站数",
gnbSumBase: "5G 基站总数",
gnbUeNum:'5G 用户数',
enbBase: "4G 基站数",
enbSumBase: "4G 基站总数",
enbUeNum:'4G 用户数',
baseTitle:'在线信息',
nodeBInfo: '基站信息',
},
upfFlow:{
title: "用户面吞吐量",
@@ -394,6 +397,7 @@ export default {
},
userActivity: {
title: "用户活动",
imsTitle: "IMS 活动",
type: "类型",
duration: "时长",
caller: "主叫",
@@ -668,7 +672,7 @@ export default {
toIpPleace: "请输入远程备份服务器 IP 地址",
toPort: "服务端口",
username: "登录用户名",
usernamePleace: '请输入服务登录用户名',
usernamePleace: '请输入服务登录用户名',
password: "登录密码",
dir: "保存目录",
dirPleace: '请输入服务地址目标文件目录',

View File

@@ -129,79 +129,123 @@ function initPicture() {
const optionData: EChartsOption = {
title: [
{
show: false,
},
{
text: t('views.dashboard.overview.alarmTypeBar.topTitle'),
text: 'Top3',
left: 'center',
top: '36%',
textStyle: {
color: '#fff',
fontSize: '14',
fontWeight: 400,
fontSize: 16,
fontWeight: 'bold',
},
top: '50%',
left: '0%',
},
],
grid: [
{ // 主图
top: '5%',
left: '20%',
right: '10%',
height: '35%'
},
{ // Top3
top: '50%',
left: '20%',
right: '10%',
height: '30%'
}
],
tooltip: {
trigger: 'item',
axisPointer: { type: 'shadow' },
formatter: '{b} : {c}',
},
legend: {
orient: 'vertical',
right: '2%',
top: '12%',
data: alarmTypeType.value.map((item: any) => item.name), //label数组
textStyle: {
color: '#A7D6F4', // 设置图例文字颜色
},
show: false
},
grid: [
xAxis: [
{
top: '60%',
left: '15%',
right: '25%',
bottom: '10%',
type: 'value',
gridIndex: 0,
show: false,
},
{
type: 'value',
gridIndex: 1,
show: false,
}
],
yAxis: [
{
type: 'category',
gridIndex: 0,
data: alarmTypeType.value.map((item: any) => item.name),
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
axisLine: { show: false },
axisTick: { show: false },
inverse: true
},
{
type: 'category',
gridIndex: 1,
data: alarmTypeTypeTop.value.map((item: any) => item.name),
axisLabel: { color: '#fff', fontSize: 14,fontWeight: 'bold' },
axisLine: { show: false },
axisTick: { show: false },
inverse: true
}
],
series: [
//饼图:
// 四类型告警横向柱状图
{
type: 'pie',
radius: '35%',
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
type: 'bar',
xAxisIndex: 0,
yAxisIndex: 0,
barWidth: 18,
itemStyle: {
borderRadius: [0, 8, 8, 0],
color: function (params: any) {
// 渐变色
const colorArr = [
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f5222d' },
{ offset: 1, color: '#fa8c16' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fa8c16' },
{ offset: 1, color: '#fadb14' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fadb14' },
{ offset: 1, color: '#1677ff' }
]),
new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#1677ff' },
{ offset: 1, color: '#00fcff' }
])
];
return colorArr[params.dataIndex] || colorArr[3];
}
},
label: {
show: true,
position: 'inner',
position: 'right',
color: '#fff', //淡蓝色
fontWeight: 'bold',
fontSize: 16,
formatter: (params: any) => {
if (!params.value) return '';
return `${params.value}`;
},
},
labelLine: {
show: false,
},
center: ['35%', '25%'],
data: alarmTypeType.value,
zlevel: 2, // 设置zlevel为1使得柱状图在下层显示
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
data: alarmTypeType.value.map((item: any) => item.value),
zlevel: 2
},
//柱状
// Top3横向柱状
{
name: 'Top3',
type: 'bar',
barWidth: 12, // 柱子宽度
barCategoryGap: '30%', // 控制同一系列的柱间距离
label: {
show: true,
position: 'right', // 位置
color: '#A7D6F4', //淡蓝色
fontSize: 14,
distance: 14, // label与柱子距离
formatter: '{c}',
},
xAxisIndex: 1,
yAxisIndex: 1,
barWidth: 18,
itemStyle: {
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
@@ -210,42 +254,19 @@ function initPicture() {
{ offset: 1, color: '#2f54eb' },
]), // 渐变
},
data: alarmTypeTypeTop.value,
},
],
// 柱状图设置
xAxis: [
{
splitLine: {
show: false,
label: {
show: true,
position: 'right',
color: '#fff', //淡蓝色
fontWeight: 'bold',
fontSize: 16,
formatter: '{c}'
},
type: 'value',
show: false,
},
],
yAxis: [
{
splitLine: {
show: false,
},
axisLine: {
//y轴
show: false,
},
type: 'category',
axisTick: {
show: false,
},
inverse: true,
data: alarmTypeTypeTop.value.map((item: any) => item.name),
axisLabel: {
color: '#A7D6F4',
fontSize: 14,
},
},
],
data: alarmTypeTypeTop.value.map((item: any) => item.value),
zlevel: 1
}
]
};
fnDesign(alarmTypeBar.value, optionData);
});
}

View File

@@ -0,0 +1,268 @@
<script setup lang="ts">
import { parseDuration, parseDateToStr } from '@/utils/date-utils';
import { eventData, eventId } from '../../hooks/useUserActivity';
import useI18n from '@/hooks/useI18n';
import useDictStore from '@/store/modules/dict';
import { onMounted, reactive } from 'vue';
const { t } = useI18n();
const { getDict } = useDictStore();
/**字典数据 */
let dict: {
/**CDR SIP响应代码类别类型 */
cdrSipCode: DictType[];
/**CDR 呼叫类型 */
cdrCallType: DictType[];
/**UE 事件认证代码类型 */
ueAauthCode: DictType[];
/**UE 事件类型 */
ueEventType: DictType[];
/**UE 事件CM状态 */
ueEventCmState: DictType[];
/**CDR SIP响应代码类别类型 */
cdrSipCodeCause: DictType[];
} = reactive({
cdrSipCode: [],
cdrCallType: [],
ueAauthCode: [],
ueEventType: [],
ueEventCmState: [],
cdrSipCodeCause: [],
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('cdr_sip_code'),
getDict('cdr_call_type'),
getDict('ue_auth_code'),
getDict('ue_event_type'),
getDict('ue_event_cm_state'),
getDict('cdr_sip_code_cause'),
]).then(resArr => {
if (resArr[0].status === 'fulfilled') {
dict.cdrSipCode = resArr[0].value;
}
if (resArr[1].status === 'fulfilled') {
dict.cdrCallType = resArr[1].value;
}
if (resArr[2].status === 'fulfilled') {
dict.ueAauthCode = resArr[2].value;
}
if (resArr[3].status === 'fulfilled') {
dict.ueEventType = resArr[3].value;
}
if (resArr[4].status === 'fulfilled') {
dict.ueEventCmState = resArr[4].value;
}
if (resArr[5].status === 'fulfilled') {
dict.cdrSipCodeCause = resArr[5].value;
}
});
});
</script>
<template>
<div class="activty">
<template v-for="item in eventData" :key="item.eId">
<!-- CDR事件IMS -->
<div class="card-cdr" :class="{ active: item.eId === eventId }" v-if="item.eType === 'ims_cdr'">
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span>
<DictTag :options="dict.cdrCallType" :value="item.data.callType" />
</span>
</div>
<div></div>
<div></div>
</div>
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.caller') }}:
<span :title="item.data.callerParty">
{{ item.data.callerParty }}
</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.called') }}:
<span :title="item.data.calledParty">
{{ item.data.calledParty }}
</span>
</div>
<div v-if="item.data.callType !== 'sms'">
{{ t('views.dashboard.overview.userActivity.duration') }}:&nbsp;
<span>{{ parseDuration(item.data.callDuration) }}</span>
</div>
<div v-else></div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="item.data.releaseTime">
{{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: parseDateToStr(item.data.releaseTime)
}}
</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'">
<DictTag :options="dict.cdrSipCode" :value="item.data.cause" value-default="0" />&nbsp;&nbsp;
<DictTag :options="dict.cdrSipCodeCause" :value="item.data.cause" value-default="0" />
</span>
<span v-else>
{{ t('views.dashboard.overview.userActivity.resultOK') }}
</span>
</div>
</div>
</template>
</div>
</template>
<style lang="less" scoped>
.activty {
overflow-x: hidden;
overflow-y: auto;
height: 94%;
color: #61a8ff;
font-size: 0.75rem;
& .card-ue {
border: 1px #61a8ff solid;
border-radius: 4px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.3rem;
line-height: 1rem;
& span {
color: #68d8fe;
}
&-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
&>div {
width: 50%;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
&-w33 {
display: flex;
flex-direction: row;
justify-content: flex-start;
&>div {
width: 33%;
}
}
}
& .card-cdr {
border: 1px #61a8ff solid;
border-radius: 4px;
padding: 0.2rem 0.5rem;
margin-bottom: 0.3rem;
line-height: 1rem;
& span {
color: #68d8fe;
}
&-item {
display: flex;
flex-direction: row;
justify-content: flex-start;
&>div {
flex: 1;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
& .active {
color: #faad14;
border: 1px #faad14 solid;
animation: backInRight 0.3s alternate;
& span {
color: #faad14;
}
}
/* 兼容当行显示字内容 */
@media (max-width: 1720px) {
& .card-cdr {
&-item {
display: block;
&>div {
width: 100%;
}
}
}
& .card-ue {
&-item {
display: block;
&>div {
width: 100%;
}
}
}
}
/* 修改滚动条的样式 */
&::-webkit-scrollbar {
width: 8px;
/* 设置滚动条宽度 */
}
&::-webkit-scrollbar-track {
background-color: #101129;
/* 设置滚动条轨道背景颜色 */
}
&::-webkit-scrollbar-thumb {
background-color: #28293f;
/* 设置滚动条滑块颜色 */
}
&::-webkit-scrollbar-thumb:hover {
background-color: #68d8fe;
/* 设置鼠标悬停时滚动条滑块颜色 */
}
@keyframes backInRight {
0% {
opacity: 0.7;
-webkit-transform: translateX(2000px) scale(0.7);
transform: translateX(2000px) scale(0.7);
}
80% {
opacity: 0.7;
-webkit-transform: translateX(0) scale(0.7);
transform: translateX(0) scale(0.7);
}
to {
opacity: 1;
-webkit-transform: scale(1);
transform: scale(1);
}
}
}
</style>

View File

@@ -1,24 +1,19 @@
<script setup lang="ts">
import { onMounted, ref, nextTick, watch } from 'vue';
import * as echarts from 'echarts/core';
import { GridComponent, GridComponentOption } from 'echarts/components';
import {
BarChart,
BarSeriesOption,
PictorialBarChart,
PictorialBarSeriesOption,
} from 'echarts/charts';
import { GridComponent, GridComponentOption, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
import { markRaw } from 'vue';
// 引入液体填充图表
import 'echarts-liquidfill';
const { t } = useI18n();
echarts.use([GridComponent, BarChart, PictorialBarChart, CanvasRenderer]);
echarts.use([GridComponent, TooltipComponent, CanvasRenderer]);
type EChartsOption = echarts.ComposeOption<
GridComponentOption | BarSeriesOption | PictorialBarSeriesOption
>;
type EChartsOption = echarts.ComposeOption<GridComponentOption>;
/**图DOM节点实例对象 */
const neResourcesDom = ref<HTMLElement | undefined>(undefined);
@@ -26,203 +21,172 @@ const neResourcesDom = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const neResourcesChart = ref<any>(null);
// 类别
const category = ref<any>([
{
name: t('views.dashboard.overview.resources.sysDisk'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.sysMem'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.sysCpu'),
value: 1,
},
{
name: t('views.dashboard.overview.resources.neCpu'),
value: 1,
},
]);
// 当前选中的网元ID
const currentNeId = ref('');
// 数据总数
const total = 100;
// 资源数据
const resourceData = ref({
neCpu: 1,
sysCpu: 1,
sysMem: 1,
sysDisk: 1
});
// 获取颜色
function getColorByValue(value: number) {
if (value >= 70) {
return ['#f5222d', '#ff7875']; // 红色
} else if (value >= 30) {
return ['#2f54eb', '#597ef7']; // 蓝色
} else {
return ['#52c41a', '#95de64']; // 绿色
}
}
/**图数据 */
const optionData: EChartsOption = {
xAxis: {
max: total,
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
const optionData: any = {
tooltip: {
trigger: 'item',
formatter: '{b}: {c}%'
},
grid: {
top: '1%', // 设置条形图的边距
bottom: '12%',
left: '25%',
right: '25%',
top: '10%',
bottom: '5%',
left: '5%',
right: '5%',
containLabel: true
},
yAxis: [
{
type: 'category',
inverse: false,
data: category.value,
axisLine: {
show: false,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
},
},
],
series: [
{
// 内
type: 'bar',
barWidth: 10,
legendHoverLink: false,
silent: true,
itemStyle: {
color: function (params) {
// 红色
if (params.value && +params.value >= 70) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#fff1f0' },
{ offset: 0.5, color: '#ffa39e' },
{ offset: 1, color: '#f5222d' },
]);
}
// 蓝色
if (params.value && +params.value >= 30) {
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]);
}
// 绿色
return new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f6ffed' },
{ offset: 0.5, color: '#b7eb8f' },
{ offset: 1, color: '#52c41a' },
]);
},
type: 'liquidFill',
radius: '50%',
center: ['15%', '35%'],
data: [resourceData.value.neCpu / 100],
name: t('views.dashboard.overview.resources.neCpu'),
color: getColorByValue(resourceData.value.neCpu),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
position: 'left',
formatter: '{b}: ',
fontSize: 15,
color: '#fff',
},
data: category.value,
z: 1,
animationEasing: 'elasticOut',
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
{
// 分隔
type: 'pictorialBar',
itemStyle: {
color: '#0a3ca0',
},
symbolRepeat: 'fixed',
symbolMargin: 6,
symbol: 'rect',
symbolClip: true,
symbolSize: [1, 12],
symbolPosition: 'start',
symbolOffset: [0, -1],
symbolBoundingData: total,
data: category.value,
z: 2,
animationEasing: 'elasticOut',
},
{
// 外边框
type: 'pictorialBar',
symbol: 'rect',
symbolBoundingData: total,
itemStyle: {
color: 'transparent',
type: 'liquidFill',
radius: '50%',
center: ['85%', '35%'],
data: [resourceData.value.sysCpu / 100],
name: t('views.dashboard.overview.resources.sysCpu'),
color: getColorByValue(resourceData.value.sysCpu),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
formatter: params => {
var text = `{a| ${params.value}%} `;
if (params.value && +params.value >= 70) {
text = `{c| ${params.value}%} `;
} else if (params.value && +params.value >= 30) {
text = `{b| ${params.value}%} `;
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
return text;
},
rich: {
a: {
color: '#52c41a', // 绿
fontSize: 16,
},
b: {
color: '#2f54eb', // 蓝
fontSize: 16,
},
c: {
color: '#f5222d', // 红
fontSize: 16,
},
f: {
color: '#ffffff', // 默认
fontSize: 16,
},
},
position: 'right',
distance: 0, // 向右偏移位置
show: true,
}
},
data: category.value,
z: 0,
animationEasing: 'elasticOut',
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
{
name: '外框',
type: 'bar',
barGap: '-120%', // 设置外框粗细
data: [total, total, total],
barWidth: 14,
itemStyle: {
color: 'transparent', // 填充色
borderColor: '#0a3ca0', // 边框色
borderWidth: 1, // 边框宽度
borderRadius: 1, //圆角半径
type: 'liquidFill',
radius: '50%',
center: ['35%', '65%'],
data: [resourceData.value.sysMem / 100],
name: t('views.dashboard.overview.resources.sysMem'),
color: getColorByValue(resourceData.value.sysMem),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
// 标签显示位置
show: false,
position: 'top', // insideTop 或者横向的 insideLeft
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
z: 0,
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
},
],
{
type: 'liquidFill',
radius: '50%',
center: ['65%', '65%'],
data: [resourceData.value.sysDisk / 100],
name: t('views.dashboard.overview.resources.sysDisk'),
color: getColorByValue(resourceData.value.sysDisk),
backgroundStyle: {
color: 'rgba(10, 60, 160, 0.1)'
},
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
},
textStyle: {
fontSize: 12,
color: '#fff'
}
}
},
outline: {
show: true,
borderDistance: 2,
itemStyle: {
borderColor: '#0a3ca0',
borderWidth: 1
}
}
}
]
};
/**图数据渲染 */
function handleRanderChart(
container: HTMLElement | undefined,
option: EChartsOption
option: any
) {
if (!container) return;
neResourcesChart.value = markRaw(echarts.init(container, 'light'));
neResourcesChart.value = markRaw(echarts.init(container));
option && neResourcesChart.value.setOption(option);
// 创建 ResizeObserver 实例
@@ -236,20 +200,13 @@ function handleRanderChart(
}
function fnChangeData(data: any[], itemID: string) {
const neType=itemID.split('_')[0];
const neID=itemID.split('_')[1];
let info = data.find((item: any) => item.id === neType);
const neType = itemID.split('_')[0];
const neID = itemID.split('_')[1];
currentNeId.value = neID;
let info = data.find((item: any) => item.id === neType);
if (!info || !info.neStateMap[neID]?.online) return;
// if (!info.neState.online) {
// info = data.find((item: any) => item.id === itemID);
// graphNodeClickID.value = itemID;
// }
// console.log(info.id);
// console.log(info.neState.cpu.nfCpuUsage);
// console.log(info.neState.cpu.sysCpuUsage);
// console.log(info.neState.mem);
// console.log(info.neState.disk);
let sysCpuUsage = 0;
let nfCpuUsage = 0;
if (info.neStateMap[neID].cpu) {
@@ -287,25 +244,61 @@ function fnChangeData(data: any[], itemID: string) {
}
}
category.value[0].value = sysDiskUsage;
category.value[1].value = sysMemUsage;
category.value[2].value = sysCpuUsage;
category.value[3].value = nfCpuUsage;
resourceData.value = {
neCpu: nfCpuUsage,
sysCpu: sysCpuUsage,
sysMem: sysMemUsage,
sysDisk: sysDiskUsage
};
// 更新图表数据
neResourcesChart.value.setOption({
series: [
{
data: category.value,
data: [resourceData.value.neCpu / 100],
color: getColorByValue(resourceData.value.neCpu),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.neCpu')}\n${resourceData.value.neCpu}%`;
}
}
}
},
{
data: category.value,
data: [resourceData.value.sysCpu / 100],
color: getColorByValue(resourceData.value.sysCpu),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysCpu')}\n${resourceData.value.sysCpu}%`;
}
}
}
},
{
data: category.value,
data: [resourceData.value.sysMem / 100],
color: getColorByValue(resourceData.value.sysMem),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysMem')}\n${resourceData.value.sysMem}%`;
}
}
}
},
{
data: category.value,
},
],
data: [resourceData.value.sysDisk / 100],
color: getColorByValue(resourceData.value.sysDisk),
label: {
normal: {
formatter: () => {
return `${t('views.dashboard.overview.resources.sysDisk')}\n${resourceData.value.sysDisk}%`;
}
}
}
}
]
});
}
@@ -324,35 +317,6 @@ watch(graphNodeClickID, v => {
});
onMounted(() => {
// setInterval(function () {
// var ndata = [
// {
// name: '系统内存',
// value: Math.round(Math.random() * 100),
// },
// {
// name: '系统CPU',
// value: Math.round(Math.random() * 100),
// },
// {
// name: '网元CPU',
// value: Math.round(Math.random() * 100),
// },
// ];
// neResourcesChart.value.setOption({
// series: [
// {
// data: ndata,
// },
// {
// data: ndata,
// },
// {
// data: ndata,
// },
// ],
// });
// }, 2000);
nextTick(() => {
handleRanderChart(neResourcesDom.value, optionData);
});
@@ -360,12 +324,29 @@ onMounted(() => {
</script>
<template>
<div ref="neResourcesDom" class="chart"></div>
<div class="resource-panel">
<div ref="neResourcesDom" class="chart"></div>
</div>
</template>
<style lang="less" scoped>
.chart {
.resource-panel {
width: 100%;
height: 100%;
margin-top: -32px;
display: flex;
flex-direction: column;
.panel-title {
font-size: 14px;
color: #00fcff;
padding: 8px;
border-bottom: 1px solid rgba(10, 115, 255, 0.3);
}
.chart {
flex: 1;
width: 100%;
}
}
</style>

View File

@@ -188,7 +188,7 @@ function handleRanderGraph(
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
entry.contentRect.height - 20
);
graphG6.value.fitCenter();
});

View File

@@ -65,73 +65,6 @@ onMounted(() => {
<template>
<div class="activty">
<template v-for="item in eventData" :key="item.eId">
<!-- CDR事件IMS -->
<div
class="card-cdr"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'ims_cdr'"
>
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span>
<DictTag
:options="dict.cdrCallType"
:value="item.data.callType"
/>
</span>
</div>
<div></div>
<div></div>
</div>
<div class="card-cdr-item">
<div>
{{ t('views.dashboard.overview.userActivity.caller') }}:
<span :title="item.data.callerParty">
{{ item.data.callerParty }}
</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.called') }}:
<span :title="item.data.calledParty">
{{ item.data.calledParty }}
</span>
</div>
<div v-if="item.data.callType !== 'sms'">
{{ t('views.dashboard.overview.userActivity.duration') }}:&nbsp;
<span>{{ parseDuration(item.data.callDuration) }}</span>
</div>
<div v-else></div>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="item.data.releaseTime">
{{
typeof item.data.releaseTime === 'number'
? parseDateToStr(+item.data.releaseTime * 1000)
: parseDateToStr(item.data.releaseTime)
}}
</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
:value="item.data.cause"
value-default="0"
/>&nbsp;&nbsp;
<DictTag
:options="dict.cdrSipCodeCause"
:value="item.data.cause"
value-default="0"
/>
</span>
<span v-else>
{{ t('views.dashboard.overview.userActivity.resultOK') }}
</span>
</div>
</div>
<!-- UE事件AMF -->
<div
class="card-ue"

View File

@@ -8,11 +8,15 @@
display: flex;
padding: 5rem 0.833rem 0;
line-height: 1.15;
background-color: #101129;
background-image: url(../images/bj.png);
height: 100vh;
margin-bottom: -20px;
background-size:80% 80%;
background-attachment:fixed;
-webkit-background-size: cover;
}
.column {
flex: 3;
position: relative;
@@ -23,8 +27,7 @@
/* 边框 */
.panel {
box-sizing: border-box;
border: 2px solid red;
border-image: url(../images/border.png) 51 38 21 132;
border: 2px solid rgba(252, 252, 252, 0);
border-width: 2.125rem 1.583rem 0.875rem 5.5rem;
position: relative;
margin-bottom: 0.833rem;
@@ -44,9 +47,65 @@
color: #fff;
}
.leftright {
width: 100%;
min-height: 2.5rem;
background: url(../images/title.png) no-repeat center center;
background-size: 100%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.leftright .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1rem;
padding-left: 0;
}
.centerStyle {
width: 100%;
min-height: 2.5rem;
background: url(../images/title.png) no-repeat center center;
background-size: 90%;
color: #4c9bfd;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
padding: 0.5rem 1.2rem;
border-radius: 0.7rem 0.7rem 0 0;
margin: 0;
box-sizing: border-box;
text-shadow: 0 1px 4px #000a;
flex-wrap: nowrap;
/* 保证内容不换行且居中 */
}
.centerStyle .title {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
font-size: 1rem;
padding-left: 0;
}
/* 总览标题 */
.brand {
background-image: url(../images/brand.png);
background-image: url(../images/newBrand.png);
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
@@ -87,7 +146,7 @@
/* 网络拓扑 */
.topology {
/* min-height: 27.8rem; */
height: 56.4%;
height: 46.4%;
flex: 1;
}
.topology .inner h3 {
@@ -162,7 +221,7 @@
/* 概览区域 衍生基站信息 */
.skim.base {
height: 12%;
height: 11.25%;
}
.skim.base .inner .data {
@@ -190,6 +249,65 @@
margin-top: 1rem;
}
/* 流量统计 */
.upfFlowTotal1 {
/* min-height: 7.5rem; */
height: 14.4%;
}
.upfFlowTotal1 .inner h3 {
display: flex;
justify-content: space-between;
}
.upfFlowTotal1 .inner h3 .filter {
display: flex;
}
.upfFlowTotal1 .inner h3 .filter span {
display: block;
height: 0.75rem;
line-height: 1;
padding: 0 0.75rem;
color: #1950c4;
font-size: 0.75rem;
border-right: 0.083rem solid #00f2f1;
cursor: pointer;
}
.upfFlowTotal1 .inner h3 .filter span:first-child {
padding-left: 0;
}
.upfFlowTotal1 .inner h3 .filter span:last-child {
border-right: none;
}
.upfFlowTotal1 .inner h3 .filter span.active {
color: #fff;
font-size: 0.833rem;
}
.upfFlowTotal1 .inner .chart {
width: 100%;
height: 100%;
margin-top: 0.1rem;
}
.upfFlowTotal1 .inner .chart .data {
display: flex;
flex-direction: column;
justify-content: space-around;
height: 60%;
}
.upfFlowTotal1 .inner .chart .data .item {
display: flex;
justify-content: space-between;
align-items: baseline;
}
.upfFlowTotal1 .inner .chart .data .item h4 {
font-size: 1.467rem;
color: #fff;
margin-bottom: 0;
}
.upfFlowTotal1 .inner .chart .data .item span {
color: #4c9bfd;
font-size: 0.867rem;
}
/* 流量统计 */
.upfFlowTotal {
/* min-height: 7.5rem; */
@@ -251,7 +369,7 @@
/* 资源情况 */
.resources {
/* min-height: 18rem; */
height: 34.4%;
height: 24.4%;
}
.resources .inner .chart {
width: 100%;
@@ -259,16 +377,15 @@
margin-top: 1rem;
}
/* 告警统计 */
.alarmType {
/* min-height: 25rem; */
height: 46%;
flex: 1;
height: 35%;
}
.alarmType .inner .chart {
width: 100%;
height: 100%;
margin-top: 1rem;
}
/* 跳转鼠标悬浮 */

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -7,6 +7,7 @@ import useI18n from '@/hooks/useI18n';
import Topology from './components/Topology/index.vue';
import NeResources from './components/NeResources/index.vue';
import UserActivity from './components/UserActivity/index.vue';
import IMSActivity from './components/IMSActivity/index.vue';
import AlarnTypeBar from './components/AlarnTypeBar/index.vue';
import UPFFlow from './components/UPFFlow/index.vue';
import { listUDMSub } from '@/api/neData/udm_sub';
@@ -29,6 +30,11 @@ import { useRouter } from 'vue-router';
import useNeInfoStore from '@/store/modules/neinfo';
import { message } from 'ant-design-vue';
import { upfWhoId } from './hooks/useWS';
import {
listAMFNbStatelist,
} from '@/api/neData/amf';
import { listMMENbStatelist } from '@/api/neData/mme';
const neInfoStore = useNeInfoStore();
const router = useRouter();
@@ -52,8 +58,11 @@ type SkimStateType = {
enbNum: number;
/**4G在线用户数量 */
enbUeNum: number;
/**5G用户总数量 */
gNbSumNum: number;
/**4G用户总数量 */
eNbSumNum: number;
};
/**概览状态信息 */
let skimState: SkimStateType = reactive({
udmSubNum: 0,
@@ -63,6 +72,9 @@ let skimState: SkimStateType = reactive({
gnbUeNum: 0,
enbNum: 0,
enbUeNum: 0,
gNbSumNum: 0,
eNbSumNum: 0,
});
/**网元参数 */
@@ -108,6 +120,10 @@ function fnGetNeState() {
/**获取概览信息 */
async function fnGetSkim() {
let tempGnbSumNum = 0;
let tempEnbSumNum = 0;
const neHandlers = new Map([
// [
// 'UDM',
@@ -139,13 +155,19 @@ async function fnGetSkim() {
'AMF',
{
request: (neId: string) => listBase5G({ neType: 'AMF', neId }),
process: (res: any) => {
process: async (res: any, neId: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.gnbNum += res.total;
skimState.gnbUeNum += res.rows.reduce(
(sum: number, item: any) => sum + item.ueNum,
0
);
const amfNbRes = await listAMFNbStatelist({ neId });
if (amfNbRes.code === RESULT_CODE_SUCCESS && Array.isArray(amfNbRes.data)) {
// skimState.gNbSumNum += amfNbRes.data.length;
tempGnbSumNum += amfNbRes.data.length;
}
}
},
},
@@ -154,13 +176,20 @@ async function fnGetSkim() {
'MME',
{
request: (neId: string) => listBase5G({ neType: 'MME', neId }),
process: (res: any) => {
process: async (res: any, neId: any) => {
if (res.code === RESULT_CODE_SUCCESS) {
skimState.enbNum += res.total;
skimState.enbUeNum += res.rows.reduce(
(sum: number, item: any) => sum + item.ueNum,
0
);
const mmeNbRes = await listMMENbStatelist({ neId });
if (mmeNbRes.code === RESULT_CODE_SUCCESS && Array.isArray(mmeNbRes.data)) {
// skimState.eNbSumNum += mmeNbRes.data.length;
tempEnbSumNum += mmeNbRes.data.length;
}
}
},
},
@@ -176,6 +205,7 @@ async function fnGetSkim() {
? {
promise: handler.request(child.neId),
process: handler.process,
neId: child.neId, // 这里加上neId
}
: null;
})
@@ -194,14 +224,21 @@ async function fnGetSkim() {
enbNum: 0,
enbUeNum: 0,
});
results.forEach((result, index) => {
const processPromises = results.map((result, index) => {
const req = requests[index];
if (result.status === 'fulfilled') {
requests[index].process(result.value);
return req.process(result.value, req.neId);
} else {
requests[index].process(0);
return req.process(0, req.neId);
}
});
// 等待所有 process 执行完再赋值gNbSumNum等
await Promise.all(processPromises);
skimState.gNbSumNum = tempGnbSumNum;
skimState.eNbSumNum = tempEnbSumNum;
// UDM
listUDMSub({ neId: udmNeId.value, pageNum: 1, pageSize: 1 }).then(res => {
if (res.code === RESULT_CODE_SUCCESS) {
@@ -382,12 +419,14 @@ onBeforeUnmount(() => {
</div>
<div class="column">
<!--概览-->
<div class="skim panel">
<div class="inner">
<h3>
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.userTitle') }}
<h3 class="leftright">
<span class="title">
<IdcardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.skim.userTitle') }}
</span>
</h3>
<div class="data">
<div class="item toRouter" :title="t('views.dashboard.overview.toRouter')">
@@ -434,13 +473,103 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
<!--告警统计-->
<div class="alarmType panel">
<div class="inner">
<h3 class="toRouter leftright" :title="t('views.dashboard.overview.toRouter')">
<span class="title" @click="fnToRouter('HistoryAlarm_2097')">
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
</span>
</h3>
<div class="chart">
<AlarnTypeBar />
</div>
</div>
</div>
<!-- 用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3 class="leftright">
<span class="title">
<WhatsAppOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.userActivity.title') }}
</span>
</h3>
<div class="chart">
<UserActivity />
</div>
</div>
</div>
</div>
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
<!-- 实时流量 -->
<div class="upfFlow panel">
<div class="inner">
<h3 class="toRouter centerStyle" :title="t('views.dashboard.overview.toRouter')">
<span class="title">
<div @click="fnToRouter('GoldTarget_2104')">
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{
t('views.dashboard.overview.upfFlow.title')
}}
</div>&nbsp;&nbsp;&nbsp;&nbsp;
<a-select v-model:value="upfWhoId" :options="neOtions" :get-Popup-Container="getPopupContainer"
class="toDeep" style="color: #fff;" @change="fnSelectNe" />
</span>
</h3>
<div class="chart">
<UPFFlow />
</div>
</div>
</div>
<!-- 网络拓扑 -->
<div class="topology panel">
<div class="inner">
<h3 class="toRouter centerStyle" @click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')">
<span class="title">
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.topology.title') }}
</span>
</h3>
<div class="chart">
<Topology />
</div>
</div>
</div>
</div>
<div class="column">
<!-- 基站信息 -->
<div class="skim panel base" v-perms:has="['dashboard:overview:gnbBase']">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 5G
{{ t('views.dashboard.overview.skim.baseTitle') }}
<h3 class="leftright">
<span class="title">
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{t('views.dashboard.overview.skim.nodeBInfo')}}
</span>
</h3>
<div class="data">
<div class="data" style="margin-top: 20px;">
<div class="item toRouter" @click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')">
<div style="align-items: flex-start">
<img :src="svgBase" style="width: 18px; margin-right: 8px; height: 2rem" />
{{ skimState.gNbSumNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.gnbSumBase') }}</span>
</div>
<div class="item toRouter" @click="fnToRouter('BaseStation_2096', { neType: 'AMF' })"
:title="t('views.dashboard.overview.toRouter')">
<div style="align-items: flex-start">
@@ -460,13 +589,20 @@ onBeforeUnmount(() => {
</div>
</div>
</div>
<div class="skim panel base" v-perms:has="['dashboard:overview:enbBase']">
<div class="inner">
<h3>
<GlobalOutlined style="color: #68d8fe" />&nbsp;&nbsp; 4G
{{ t('views.dashboard.overview.skim.baseTitle') }}
</h3>
<div class="data">
<div class="data" style="margin-top: 40px;">
<div class="item toRouter" @click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')">
<div style="align-items: flex-start">
<img :src="svgBase" style="width: 18px; margin-right: 8px; height: 2rem" />
{{ skimState.eNbSumNum }}
</div>
<span>{{ t('views.dashboard.overview.skim.enbSumBase') }}</span>
</div>
<div class="item toRouter" @click="fnToRouter('BaseStation_2096', { neType: 'MME' })"
:title="t('views.dashboard.overview.toRouter')">
<div style="align-items: flex-start">
@@ -487,143 +623,51 @@ onBeforeUnmount(() => {
</div>
</div>
<!-- 用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3>
<WhatsAppOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.userActivity.title') }}
</h3>
<div class="chart">
<UserActivity />
</div>
</div>
</div>
</div>
<div class="column" style="flex: 4; margin: 1.333rem 0.833rem 0">
<!-- 实时流量 -->
<div class="upfFlow panel">
<div class="inner">
<h3 class="toRouter" :title="t('views.dashboard.overview.toRouter')"
style="display: flex; align-items: center">
<AreaChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
<span @click="fnToRouter('GoldTarget_2104')">{{
t('views.dashboard.overview.upfFlow.title')
}}</span>
<a-select v-model:value="upfWhoId" :options="neOtions" :get-Popup-Container="getPopupContainer"
class="toDeep" style="width: 100px; color: #fff; margin-left: auto" @change="fnSelectNe" />
</h3>
<div class="chart">
<UPFFlow />
</div>
</div>
</div>
<!-- 网络拓扑 -->
<div class="topology panel">
<div class="inner">
<h3 class="toRouter" @click="fnToRouter('TopologyArchitecture_2128')"
:title="t('views.dashboard.overview.toRouter')">
<span>
<ApartmentOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.topology.title') }}
</span>
<span>
{{ t('views.dashboard.overview.topology.normal') }}:
<span class="normal"> {{ graphNodeStateNum[0] }} </span>
{{ t('views.dashboard.overview.topology.abnormal') }}:
<span class="abnormal"> {{ graphNodeStateNum[1] }} </span>
</span>
</h3>
<div class="chart">
<Topology />
</div>
</div>
</div>
</div>
<div class="column">
<!-- 流量统计 -->
<div class="upfFlowTotal panel">
<div class="inner">
<h3>
<span>
<SwapOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.upfFlowTotal.title') }}
</span>
<!-- 筛选 -->
<div class="filter">
<span :data-key="v" :class="{ active: upfTFActive === v }" v-for="v in ['0', '7', '30']" :key="v" @click="
() => {
upfTFActive = v;
}
">
{{
v === '0'
? '24' + t('common.units.hour')
: v + t('common.units.day')
}}
</span>
</div>
</h3>
<div class="chart">
<!-- 数据 -->
<div class="data">
<div class="item">
<span>
<ArrowUpOutlined style="color: #597ef7" />
{{ t('views.dashboard.overview.upfFlowTotal.up') }}
</span>
<h4>{{ upfTotalFlow[upfTFActive].upFrom }}</h4>
</div>
<div class="item">
<span>
<ArrowDownOutlined style="color: #52c41a" />
{{ t('views.dashboard.overview.upfFlowTotal.down') }}
</span>
<h4>{{ upfTotalFlow[upfTFActive].downFrom }}</h4>
</div>
</div>
</div>
</div>
</div>
<!-- 告警统计 -->
<div class="alarmType panel">
<div class="inner">
<h3 class="toRouter" @click="fnToRouter('HistoryAlarm_2097')" :title="t('views.dashboard.overview.toRouter')">
<PieChartOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.alarmTypeBar.alarmSum') }}
</h3>
<div class="chart">
<AlarnTypeBar />
</div>
</div>
</div>
<!-- 资源情况 -->
<div class="resources panel">
<div class="inner">
<h3 style="display: flex; align-items: center;">
<DashboardOutlined style="color: #68d8fe" />&nbsp;&nbsp;
{{ t('views.dashboard.overview.resources.title') }}
<a-dropdown :trigger="['click']" :get-Popup-Container="getPopupContainer">
<div class="toDeep-text">
{{ graphNodeClickID }}
<DownOutlined style="margin-left: 12px; font-size: 12px" />
</div>
<template #overlay>
<a-menu @click="fnSelectNeRe">
<a-menu-item v-for="v in onlineOtions" :key="v.value">
{{ v.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<h3 class="resources leftright">
<span class="title">
<DashboardOutlined style="color: #68d8fe;font-size: 20px;" />&nbsp;&nbsp;
<div style="margin-left: -3px"> {{ t('views.dashboard.overview.resources.title') }}</div>
<a-dropdown :trigger="['click']" :get-Popup-Container="getPopupContainer">
<div class="toDeep-text">
{{ graphNodeClickID }}
<DownOutlined style="margin-left: -2px; font-size: 12px" />
</div>
<template #overlay>
<a-menu @click="fnSelectNeRe">
<a-menu-item v-for="v in onlineOtions" :key="v.value">
{{ v.label }}
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
</span>
</h3>
<div class="chart">
<NeResources />
</div>
</div>
</div>
<!-- IMS用户行为 -->
<div class="userActivity panel">
<div class="inner">
<h3 class="leftright">
<span class="title">
<WhatsAppOutlined style="color: #68d8fe;font-size: 20px;" />&nbsp;&nbsp; {{
t('views.dashboard.overview.userActivity.imsTitle') }}
</span>
</h3>
<div class="chart">
<IMSActivity />
</div>
</div>
</div>
</div>
</div>
</template>
@@ -636,16 +680,16 @@ onBeforeUnmount(() => {
}
.toDeep :deep(.ant-select-selector) {
background-color: #101129;
background-color: #050F23;
border: none;
}
.toDeep :deep(.ant-select-arrow) {
color: #fff;
color: #4c9bfd;
}
.toDeep :deep(.ant-select-selection-item) {
color: #fff;
color: #4c9bfd;
}
.toDeep-text {