style: 修复样式

This commit is contained in:
TsMask
2024-11-23 15:51:31 +08:00
parent cb510d9fdb
commit 729d5518e8
24 changed files with 3038 additions and 346 deletions

View File

@@ -0,0 +1,284 @@
<script setup lang="ts">
import * as echarts from 'echarts/core';
import {
TitleComponent,
TitleComponentOption,
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
} from 'echarts/components';
import {
PieChart,
PieSeriesOption,
BarChart,
BarSeriesOption,
} from 'echarts/charts';
import { LabelLayout } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { markRaw, onMounted, ref } from 'vue';
import { origGet, top3Sel } from '@/api/faultManage/actAlarm';
import useI18n from '@/hooks/useI18n';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
const { t } = useI18n();
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
LegendComponent,
PieChart,
BarChart,
CanvasRenderer,
LabelLayout,
]);
type EChartsOption = echarts.ComposeOption<
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| PieSeriesOption
| BarSeriesOption
>;
/**图DOM节点实例对象 */
const alarmTypeBar = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const alarmTypeBarChart = ref<any>(null);
/**告警类型数据 */
const alarmTypeType = ref<any>([
{
value: 0,
name: t('views.index.Critical'),
},
{
value: 0,
name: t('views.index.Major'),
},
{
value: 0,
name: t('views.index.Minor'),
},
{
value: 0,
name: t('views.index.Warning'),
},
// {
// value: 0,
// name: t('views.index.Event'),
// },
]);
/**告警类型Top数据 */
const alarmTypeTypeTop = ref<any>([
{ name: 'AMF', value: 0 },
{ name: 'UDM', value: 0 },
{ name: 'SMF', value: 0 },
]);
//
function initPicture() {
Promise.allSettled([origGet(), top3Sel()])
.then(resArr => {
if (resArr[0].status === 'fulfilled') {
const res0 = resArr[0].value;
if (res0.code === RESULT_CODE_SUCCESS && Array.isArray(res0.data)) {
for (const item of res0.data) {
let index = 0;
switch (item.name) {
case 'Critical':
index = 0;
break;
case 'Major':
index = 1;
break;
case 'Minor':
index = 2;
break;
case 'Warning':
index = 3;
break;
// case 'Event':
// index = 4;
// break;
}
alarmTypeType.value[index].value = Number(item.value);
}
}
}
if (resArr[1].status === 'fulfilled') {
const res1 = resArr[1].value;
if (res1.code === RESULT_CODE_SUCCESS && Array.isArray(res1.data)) {
alarmTypeTypeTop.value = alarmTypeTypeTop.value
.concat(res1.data)
.sort((a: any, b: any) => {
return b.value - a.value;
})
.slice(0, 3);
}
}
})
.then(() => {
const optionData: EChartsOption = {
title: [
{
show: false,
},
{
text: t('views.dashboard.overview.alarmTypeBar.topTitle'),
textStyle: {
color: '#fff',
fontSize: '14',
fontWeight: 400,
},
top: '50%',
left: '0%',
},
],
tooltip: {
trigger: 'item',
formatter: '{b} : {c}',
},
legend: {
orient: 'vertical',
right: '2%',
top: '12%',
data: alarmTypeType.value.map((item: any) => item.name), //label数组
textStyle: {
color: '#A7D6F4', // 设置图例文字颜色
},
},
grid: [
{
top: '60%',
left: '15%',
right: '25%',
bottom: '10%',
},
],
series: [
//饼图:
{
type: 'pie',
radius: '35%',
color: ['#f5222d', '#fa8c16', '#fadb14', '#1677ff', '#13c2c2'],
label: {
show: true,
position: 'inner',
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)',
},
},
//柱状
{
type: 'bar',
barWidth: 12, // 柱子宽度
barCategoryGap: '30%', // 控制同一系列的柱间距离
label: {
show: true,
position: 'right', // 位置
color: '#A7D6F4', //淡蓝色
fontSize: 14,
distance: 14, // label与柱子距离
formatter: '{c}',
},
itemStyle: {
borderRadius: [0, 20, 20, 0], // 圆角(左上、右上、右下、左下)
color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
{ offset: 0, color: '#f0f5ff' },
{ offset: 0.5, color: '#adc6ff' },
{ offset: 1, color: '#2f54eb' },
]), // 渐变
},
data: alarmTypeTypeTop.value,
},
],
// 柱状图设置
xAxis: [
{
splitLine: {
show: false,
},
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,
},
},
],
};
fnDesign(alarmTypeBar.value, optionData);
});
}
function fnDesign(container: HTMLElement | undefined, option: any) {
if (!container) return;
alarmTypeBarChart.value = markRaw(echarts.init(container, 'light'));
option && alarmTypeBarChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (alarmTypeBarChart.value) {
alarmTypeBarChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
onMounted(() => {
initPicture();
});
</script>
<template>
<div ref="alarmTypeBar" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,368 @@
<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 { CanvasRenderer } from 'echarts/renderers';
import { graphNodeClickID, graphNodeState } from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
import { markRaw } from 'vue';
const { t } = useI18n();
echarts.use([GridComponent, BarChart, PictorialBarChart, CanvasRenderer]);
type EChartsOption = echarts.ComposeOption<
GridComponentOption | BarSeriesOption | PictorialBarSeriesOption
>;
/**图DOM节点实例对象 */
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,
},
]);
// 数据总数
const total = 100;
/**图数据 */
const optionData: EChartsOption = {
xAxis: {
max: total,
splitLine: {
show: false,
},
axisLine: {
show: false,
},
axisLabel: {
show: false,
},
axisTick: {
show: false,
},
},
grid: {
top: '1%', // 设置条形图的边距
bottom: '12%',
left: '25%',
right: '25%',
},
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' },
]);
},
},
label: {
show: true,
position: 'left',
formatter: '{b}: ',
fontSize: 15,
color: '#fff',
},
data: category.value,
z: 1,
animationEasing: 'elasticOut',
},
{
// 分隔
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',
},
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}%} `;
}
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',
},
{
name: '外框',
type: 'bar',
barGap: '-120%', // 设置外框粗细
data: [total, total, total],
barWidth: 14,
itemStyle: {
color: 'transparent', // 填充色
borderColor: '#0a3ca0', // 边框色
borderWidth: 1, // 边框宽度
borderRadius: 1, //圆角半径
},
label: {
// 标签显示位置
show: false,
position: 'top', // insideTop 或者横向的 insideLeft
},
z: 0,
},
],
};
/**图数据渲染 */
function handleRanderChart(
container: HTMLElement | undefined,
option: EChartsOption
) {
if (!container) return;
neResourcesChart.value = markRaw(echarts.init(container, 'light'));
option && neResourcesChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (neResourcesChart.value) {
neResourcesChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
function fnChangeData(data: any[], itemID: string) {
let info = data.find((item: any) => item.id === itemID);
if (!info || !info.neState.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.neState.cpu) {
nfCpuUsage = info.neState.cpu.nfCpuUsage;
const nfCpu = +(info.neState.cpu.nfCpuUsage / 100);
nfCpuUsage = +nfCpu.toFixed(2);
if (nfCpuUsage > 100) {
nfCpuUsage = 100;
}
sysCpuUsage = info.neState.cpu.sysCpuUsage;
let sysCpu = +(info.neState.cpu.sysCpuUsage / 100);
sysCpuUsage = +sysCpu.toFixed(2);
if (sysCpuUsage > 100) {
sysCpuUsage = 100;
}
}
let sysMemUsage = 0;
if (info.neState.mem) {
const men = info.neState.mem.sysMemUsage;
sysMemUsage = +(men / 100).toFixed(2);
if (sysMemUsage > 100) {
sysMemUsage = 100;
}
}
let sysDiskUsage = 0;
if (info.neState.disk && Array.isArray(info.neState.disk.partitionInfo)) {
let disks: any[] = info.neState.disk.partitionInfo;
disks = disks.sort((a, b) => +b.used - +a.used);
if (disks.length > 0) {
const { total, used } = disks[0];
sysDiskUsage = +((used / total) * 100).toFixed(2);
}
}
category.value[0].value = sysDiskUsage;
category.value[1].value = sysMemUsage;
category.value[2].value = sysCpuUsage;
category.value[3].value = nfCpuUsage;
neResourcesChart.value.setOption({
series: [
{
data: category.value,
},
{
data: category.value,
},
{
data: category.value,
},
{
data: category.value,
},
],
});
}
watch(
graphNodeState,
v => {
fnChangeData(v, graphNodeClickID.value);
},
{
deep: true,
}
);
watch(graphNodeClickID, v => {
fnChangeData(graphNodeState.value, 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);
});
});
</script>
<template>
<div ref="neResourcesDom" class="chart"></div>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,267 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { listAllNeInfo } from '@/api/ne/neInfo';
import { message } from 'ant-design-vue/es';
import { getGraphData } from '@/api/monitor/topology';
import { Graph, GraphData, Tooltip } from '@antv/g6';
import { parseBasePath } from '@/plugins/file-static-url';
import { edgeLineAnimateState } from '@/views/monitor/topologyBuild/hooks/registerEdge';
import { nodeImageAnimateState } from '@/views/monitor/topologyBuild/hooks/registerNode';
import {
graphG6,
graphState,
graphNodeClickID,
notNeNodes,
} from '../../hooks/useTopology';
import useI18n from '@/hooks/useI18n';
const { t } = useI18n();
/**图DOM节点实例对象 */
const graphG6Dom = ref<HTMLElement | undefined>(undefined);
/**图节点展示 */
const graphNodeTooltip = new Tooltip({
offsetX: 20,
offsetY: 20,
getContent(evt) {
if (!evt) return t('views.monitor.topologyBuild.graphNotInfo');
const { id, label, neState }: any = evt.item?.getModel();
if (notNeNodes.includes(id)) {
return `<div><span>${label || id}</span></div>`;
}
if (!neState) {
return `<div><span>${label || id}</span></div>`;
}
return `
<div
style="
display: flex;
flex-direction: column;
width: 200px;
"
>
<div><strong>${t('views.monitor.topology.state')}</strong><span>
${
neState.online
? t('views.monitor.topology.normalcy')
: t('views.monitor.topology.exceptions')
}
</span></div>
<div><strong>${t('views.monitor.topology.refreshTime')}</strong><span>
${neState.refreshTime ?? '--'}
</span></div>
<div>========================</div>
<div><strong>ID</strong><span>${neState.neId}</span></div>
<div><strong>${t('views.monitor.topology.name')}</strong><span>
${neState.neName ?? '--'}
</span></div>
<div><strong>IP</strong><span>${neState.neIP}</span></div>
<div><strong>${t('views.monitor.topology.version')}</strong><span>
${neState.version ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.serialNum')}</strong><span>
${neState.sn ?? '--'}
</span></div>
<div><strong>${t('views.monitor.topology.expiryDate')}</strong><span>
${neState.expire ?? '--'}
</span></div>
</div>
`;
},
itemTypes: ['node'],
});
/**图绑定事件 */
function fnGraphEvent(graph: Graph) {
// 节点点击
graph.on('node:click', evt => {
// 获得鼠标当前目标节点
const node = evt.item?.getModel();
if (node && node.id && !notNeNodes.includes(node.id)) {
graphNodeClickID.value = node.id;
}
});
}
/**图数据渲染 */
function handleRanderGraph(
container: HTMLElement | undefined,
data: GraphData
) {
if (!container) return;
const { clientHeight, clientWidth } = container;
edgeLineAnimateState();
nodeImageAnimateState();
const graph = new Graph({
container: container,
width: clientWidth,
height: clientHeight - 36,
fitCenter: true,
fitView: true,
fitViewPadding: [20],
autoPaint: true,
modes: {
default: ['drag-canvas', 'zoom-canvas'],
},
groupByTypes: false,
nodeStateStyles: {
selected: {
fill: 'transparent',
},
},
plugins: [graphNodeTooltip],
animate: true, // 是否使用动画过度,默认为 false
animateCfg: {
duration: 500, // Number一次动画的时长
easing: 'linearEasing', // String动画函数
},
});
graph.data(data);
graph.render();
fnGraphEvent(graph);
graphG6.value = graph;
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(function (entries) {
// 当元素大小发生变化时触发回调函数
entries.forEach(function (entry) {
if (!graphG6.value) {
return;
}
graphG6.value.changeSize(
entry.contentRect.width,
entry.contentRect.height - 30
);
graphG6.value.fitCenter();
});
});
// 监听元素大小变化
observer.observe(container);
return graph;
}
/**
* 获取图组数据渲染到画布
* @param reload 是否重载数据
*/
function fnGraphDataLoad(reload: boolean = false) {
Promise.all([
getGraphData(graphState.group),
listAllNeInfo({
bandStatus: false,
}),
])
.then(resArr => {
const graphRes = resArr[0];
const neRes = resArr[1];
if (
graphRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(graphRes.data.nodes) &&
graphRes.data.nodes.length > 0 &&
neRes.code === RESULT_CODE_SUCCESS &&
Array.isArray(neRes.data) &&
neRes.data.length > 0
) {
return {
graphData: graphRes.data,
neList: neRes.data,
};
} else {
message.warning({
content: t('views.monitor.topology.noData'),
duration: 5,
});
}
})
.then(res => {
if (!res) return;
const { combos, edges, nodes } = res.graphData;
// 节点过滤
const nf: Record<string, any>[] = nodes.filter(
(node: Record<string, any>) => {
Reflect.set(node, 'neState', { online: false });
// 图片路径处理
if (node.img) node.img = parseBasePath(node.img);
if (node.icon.show && node.icon?.img) {
node.icon.img = parseBasePath(node.icon.img);
}
// 遍历是否有网元数据
const nodeID: string = node.id;
const hasNe = res.neList.some(ne => {
Reflect.set(node, 'neInfo', ne.neType === nodeID ? ne : {});
return ne.neType === nodeID;
});
if (hasNe) {
return true;
}
if (notNeNodes.includes(nodeID)) {
return true;
}
return false;
}
);
// 边过滤
const ef: Record<string, any>[] = edges.filter(
(edge: Record<string, any>) => {
const edgeSource: string = edge.source;
const edgeTarget: string = edge.target;
const hasNeS = nf.some(n => n.id === edgeSource);
const hasNeT = nf.some(n => n.id === edgeTarget);
// console.log(hasNeS, edgeSource, hasNeT, edgeTarget);
if (hasNeS && hasNeT) {
return true;
}
if (hasNeS && notNeNodes.includes(edgeTarget)) {
return true;
}
if (hasNeT && notNeNodes.includes(edgeSource)) {
return true;
}
return false;
}
);
// 分组过滤
combos.forEach((combo: Record<string, any>) => {
const comboChildren: Record<string, any>[] = combo.children;
combo.children = comboChildren.filter(c => nf.some(n => n.id === c.id));
return combo;
});
// 图数据
graphState.data = { combos, edges: ef, nodes: nf };
})
.finally(() => {
if (graphState.data.length < 0) return;
// 重载数据
if (reload) {
graphG6.value.read(graphState.data);
} else {
handleRanderGraph(graphG6Dom.value, graphState.data);
}
});
}
onMounted(() => {
fnGraphDataLoad(false);
});
</script>
<template>
<div ref="graphG6Dom" class="chart"></div>
</template>
<style lang="less" scoped>
.chart {
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,290 @@
<script setup lang="ts">
import { onMounted, ref, watch } from 'vue';
import { listKPIData } from '@/api/perfManage/goldTarget';
import * as echarts from 'echarts/core';
import {
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
LegendComponent,
LegendComponentOption,
} from 'echarts/components';
import { LineChart, LineSeriesOption } from 'echarts/charts';
import { UniversalTransition } from 'echarts/features';
import { CanvasRenderer } from 'echarts/renderers';
import { markRaw } from 'vue';
import useI18n from '@/hooks/useI18n';
import { parseDateToStr } from '@/utils/date-utils';
import { RESULT_CODE_SUCCESS } from '@/constants/result-constants';
import { upfFlowData, upfFlowParse } from '../../hooks/useUPFTotalFlow';
const { t } = useI18n();
echarts.use([
TooltipComponent,
GridComponent,
LegendComponent,
LineChart,
CanvasRenderer,
UniversalTransition,
]);
type EChartsOption = echarts.ComposeOption<
| TooltipComponentOption
| GridComponentOption
| LegendComponentOption
| LineSeriesOption
>;
/**图DOM节点实例对象 */
const upfFlow = ref<HTMLElement | undefined>(undefined);
/**图实例对象 */
const upfFlowChart = ref<any>(null);
function fnDesign(container: HTMLElement | undefined, option: EChartsOption) {
if (!container) {
return;
}
if (!upfFlowChart.value) {
upfFlowChart.value = markRaw(echarts.init(container, 'light'));
}
option && upfFlowChart.value.setOption(option);
// 创建 ResizeObserver 实例
var observer = new ResizeObserver(entries => {
if (upfFlowChart.value) {
upfFlowChart.value.resize();
}
});
// 监听元素大小变化
observer.observe(container);
}
//渲染速率图
function handleRanderChart() {
const { lineXTime, lineYUp, lineYDown } = upfFlowData.value;
var yAxisSeries: any = [
{
name: t('views.dashboard.overview.upfFlow.up'),
type: 'line',
color: 'rgba(250, 219, 20)',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(250, 219, 20, .5)',
},
{
offset: 1,
color: 'rgba(250, 219, 20, 0.5)',
},
]),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
symbol: 'circle',
symbolSize: 5,
formatter: '{b}',
data: lineYUp,
},
{
name: t('views.dashboard.overview.upfFlow.down'),
type: 'line',
color: 'rgba(92, 123, 217)',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(92, 123, 217, .5)',
},
{
offset: 1,
color: 'rgba(92, 123, 217, 0.5)',
},
]),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10,
},
symbol: 'circle',
symbolSize: 5,
formatter: '{b}',
data: lineYDown,
},
];
const optionData: EChartsOption = {
tooltip: {
show: true, //是否显示提示框组件
trigger: 'axis',
//formatter:'{a0}:{c0}<br>{a1}:{c1}'
formatter: function (param: any) {
var tip = '';
if (param !== null && param.length > 0) {
tip += param[0].name + '<br />';
for (var i = 0; i < param.length; i++) {
tip +=
param[i].marker +
param[i].seriesName +
': ' +
param[i].value +
'<br />';
}
}
return tip;
},
},
legend: {
data: yAxisSeries.map((s: any) => s.name),
textStyle: {
fontSize: 12,
color: 'rgb(0,253,255,0.6)',
},
left: 'center', // 设置图例居中
},
grid: {
top: '14%',
left: '4%',
right: '4%',
bottom: '12%',
containLabel: true,
},
xAxis: {
type: 'category',
boundaryGap: false,
data: lineXTime,
axisLabel: {
formatter: function (params: any) {
return params.split(' ')[1];
},
fontSize: 14,
},
axisLine: {
lineStyle: {
color: 'rgb(0,253,255,0.6)',
},
},
},
yAxis: [
{
name: '(Mbps)',
nameTextStyle: {
fontSize: 12, // 设置文字距离x轴的距离
padding: [0, -10, 0, 0], // 设置名称在x轴方向上的偏移
},
type: 'value',
// splitNumber: 4,
min: 0,
//max: 300,
axisLabel: {
formatter: '{value}',
},
splitLine: {
lineStyle: {
color: 'rgb(23,255,243,0.3)',
},
},
axisLine: {
lineStyle: {
color: 'rgb(0,253,255,0.6)',
},
},
},
],
series: yAxisSeries,
};
fnDesign(upfFlow.value, optionData);
}
/**查询初始UPF数据 */
function fnGetInitData() {
// 查询5分钟前的
const nowDate = new Date().getTime();
listKPIData({
neType: 'UPF',
neId: '001',
startTime: nowDate - 5 * 60 * 1000,
endTime: nowDate,
interval: 5, // 5秒
sortField: 'timeGroup',
sortOrder: 'asc',
})
.then(res => {
if (res.code === RESULT_CODE_SUCCESS && Array.isArray(res.data)) {
for (const item of res.data) {
upfFlowParse(item);
}
}
})
.finally(() => {
handleRanderChart();
});
}
watch(
() => upfFlowData.value,
v => {
if (upfFlowChart.value == null) return;
upfFlowChart.value.setOption({
xAxis: {
data: v.lineXTime,
},
series: [
{
data: v.lineYUp,
},
{
data: v.lineYDown,
},
],
});
},
{
deep: true,
}
);
onMounted(() => {
fnGetInitData();
// setInterval(() => {
// upfFlowData.value.lineXTime.push(parseDateToStr(new Date()));
// const upN3 = parseSizeFromKbs(+145452, 5);
// upfFlowData.value.lineYUp.push(upN3[0]);
// const downN6 = parseSizeFromKbs(+232343, 5);
// upfFlowData.value.lineYDown.push(downN6[0]);
// upfFlowChart.value.setOption({
// xAxis: {
// data: upfFlowData.value.lineXTime,
// },
// series: [
// {
// data: upfFlowData.value.lineYUp,
// },
// {
// data: upfFlowData.value.lineYDown,
// },
// ],
// });
// }, 5000);
});
</script>
<template>
<div ref="upfFlow" class="chart-container"></div>
</template>
<style lang="less" scoped>
.chart-container {
/* 设置图表容器大小和位置 */
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,370 @@
<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[];
} = reactive({
cdrSipCode: [],
cdrCallType: [],
ueAauthCode: [],
ueEventType: [],
ueEventCmState: [],
});
onMounted(() => {
// 初始字典数据
Promise.allSettled([
getDict('cdr_sip_code'),
getDict('cdr_call_type'),
getDict('ue_auth_code'),
getDict('ue_event_type'),
getDict('ue_event_cm_state'),
]).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;
}
});
});
</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>
{{ t('views.dashboard.overview.userActivity.time') }}:&nbsp;
<span :title="parseDateToStr(item.data.releaseTime * 1000)">
{{ parseDateToStr(item.data.releaseTime * 1000) }}
</span>
</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.result') }}:&nbsp;
<span v-if="item.data.callType !== 'sms'">
<DictTag
:options="dict.cdrSipCode"
: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"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'amf_ue'"
>
<div class="card-ue-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventType" :value="item.type" />
</span>
</div>
<div>
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span
v-if="item.type === 'auth-result'"
:title="item.data.authTime"
>
{{ item.data.authTime }}
</span>
<span v-if="item.type === 'detach'" :title="item.data.detachTime">
{{ item.data.detachTime }}
</span>
<span v-if="item.type === 'cm-state'" :title="item.data.changeTime">
{{ item.data.changeTime }}
</span>
</div>
</div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
GNB ID: <span>{{ item.data.gNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.authCode" />
</span>
</div>
<div v-if="item.type === 'detach'">
{{ t('views.dashboard.overview.userActivity.result') }}:
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
</div>
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.status" />
</span>
</div>
</div>
<!-- UE事件MME -->
<div
class="card-ue"
:class="{ active: item.eId === eventId }"
v-if="item.eType === 'mme_ue'"
>
<div class="card-ue-item">
<div>
{{ t('views.dashboard.overview.userActivity.type') }}:&nbsp;
<span v-if="item.type === 'cm-state'">
{{
dict.ueEventType
.find(s => s.value === item.type)
?.label.replace('CM', 'ECM')
}}
</span>
<span v-else>
<DictTag :options="dict.ueEventType" :value="item.type" />
</span>
</div>
<div>
IMSI: <span :title="item.data.imsi">{{ item.data.imsi }}</span>
</div>
<div>
{{ t('views.dashboard.overview.userActivity.time') }}:
<span :title="item.data.timestamp">
{{ parseDateToStr(+item.data.timestamp * 1000) }}
</span>
</div>
</div>
<div class="card-ue-w33" v-if="item.type === 'auth-result'">
<div>
ENB ID: <span>{{ item.data.eNBID }}</span>
</div>
<div>
Cell ID: <span>{{ item.data.cellID }}</span>
</div>
<div>
TAC ID: <span>{{ item.data.tacID }}</span>
</div>
</div>
<div v-if="item.type === 'auth-result'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueAauthCode" :value="item.data.result" />
</span>
</div>
<div v-if="item.type === 'detach'">
{{ t('views.dashboard.overview.userActivity.result') }}:
<span>{{ t('views.dashboard.overview.userActivity.resultOK') }}</span>
</div>
<div class="card-ue-w33" v-if="item.type === 'cm-state'">
{{ t('views.dashboard.overview.userActivity.result') }}:&nbsp;
<span>
<DictTag :options="dict.ueEventCmState" :value="item.data.result" />
</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>