Merge branch 'main' of http://192.168.2.166:3180/OMC/ems_frontend_vue3
This commit is contained in:
@@ -13,13 +13,41 @@ export function generateColorHEX(): string {
|
|||||||
* @returns rgb(24 144 255) / rgba(0,0,0,.85)
|
* @returns rgb(24 144 255) / rgba(0,0,0,.85)
|
||||||
*/
|
*/
|
||||||
export function generateColorRGBA(hasAlpha: boolean = false) {
|
export function generateColorRGBA(hasAlpha: boolean = false) {
|
||||||
const red = Math.floor(Math.random() * 256);
|
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
||||||
const green = Math.floor(Math.random() * 256);
|
|
||||||
const blue = Math.floor(Math.random() * 256);
|
let red: number;
|
||||||
|
let green: number;
|
||||||
|
let blue: number;
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
// 暗色模式下生成较亮的颜色
|
||||||
|
red = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||||
|
green = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||||
|
blue = Math.floor(Math.random() * 156) + 100; // 100-255
|
||||||
|
|
||||||
|
// 确保至少有一个通道的值较高,使颜色更明亮
|
||||||
|
const brightChannel = Math.floor(Math.random() * 3);
|
||||||
|
switch (brightChannel) {
|
||||||
|
case 0:
|
||||||
|
red = Math.min(255, red + 50);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
green = Math.min(255, green + 50);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
blue = Math.min(255, blue + 50);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 亮色模式下生成正常的颜色
|
||||||
|
red = Math.floor(Math.random() * 256); // 0-255
|
||||||
|
green = Math.floor(Math.random() * 256); // 0-255
|
||||||
|
blue = Math.floor(Math.random() * 256); // 0-255
|
||||||
|
}
|
||||||
|
|
||||||
if (hasAlpha) {
|
if (hasAlpha) {
|
||||||
const alpha = Math.floor(Math.random() * 100);
|
const alpha = Math.floor(Math.random() * 100);
|
||||||
return `rgb(${red}, ${green}, ${blue}, 0.${alpha})`;
|
return `rgba(${red}, ${green}, ${blue}, 0.${alpha})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `rgb(${red}, ${green}, ${blue})`;
|
return `rgb(${red}, ${green}, ${blue})`;
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { OptionsType, WS } from '@/plugins/ws-websocket';
|
|||||||
import { generateColorRGBA } from '@/utils/generate-utils';
|
import { generateColorRGBA } from '@/utils/generate-utils';
|
||||||
import { LineOutlined } from '@ant-design/icons-vue';
|
import { LineOutlined } from '@ant-design/icons-vue';
|
||||||
import { TableColumnType } from 'ant-design-vue';
|
import { TableColumnType } from 'ant-design-vue';
|
||||||
|
import { viewTransitionTheme } from 'antdv-pro-layout';
|
||||||
const { t, currentLocale } = useI18n();
|
const { t, currentLocale } = useI18n();
|
||||||
//定义KPI接口
|
//定义KPI接口
|
||||||
interface KPIBase{
|
interface KPIBase{
|
||||||
@@ -69,8 +70,8 @@ const ranges = ref([
|
|||||||
])
|
])
|
||||||
//日期范围响应式变量
|
//日期范围响应式变量
|
||||||
const dateRange = ref<[string, string]>([
|
const dateRange = ref<[string, string]>([
|
||||||
dayjs().startOf('hour').valueOf().toString(),
|
dayjs().subtract(1, 'hour').startOf('hour').valueOf().toString(), // 上一小时开始
|
||||||
dayjs().endOf('hour').valueOf().toString(),
|
dayjs().startOf('hour').add(1, 'hour').valueOf().toString(), // 当前小时结束
|
||||||
]);
|
]);
|
||||||
//实时数据状态
|
//实时数据状态
|
||||||
const isRealtime = ref(false);
|
const isRealtime = ref(false);
|
||||||
@@ -246,6 +247,7 @@ const fetchChartData = async () => {
|
|||||||
|
|
||||||
chartData.value = processChartData(allData);
|
chartData.value = processChartData(allData);
|
||||||
updateChart();
|
updateChart();
|
||||||
|
updateKpiStats();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to fetch chart data:', error);
|
console.error('Failed to fetch chart data:', error);
|
||||||
message.error(t('common.getInfoFail'));
|
message.error(t('common.getInfoFail'));
|
||||||
@@ -265,6 +267,147 @@ const getSeriesConfig = () => ({
|
|||||||
smooth:0.6,
|
smooth:0.6,
|
||||||
showSymbol: true,
|
showSymbol: true,
|
||||||
});
|
});
|
||||||
|
// 添加一个函数来获取当前主题下的网格线颜色
|
||||||
|
const getSplitLineColor = () => {
|
||||||
|
return document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#333333'
|
||||||
|
: '#E8E8E8'; // 亮色模式返回 undefined,使用默认颜色
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加主题变化的观察器
|
||||||
|
const themeObserver = new MutationObserver(() => {
|
||||||
|
if (chart) {
|
||||||
|
//清空颜色缓存
|
||||||
|
kpiColors.clear();
|
||||||
|
// 获取当前的主题色
|
||||||
|
const splitLineColor = getSplitLineColor();
|
||||||
|
|
||||||
|
// 重新设置完整的图表配置并触发重新渲染
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
// 先生成所有新的颜色并存储
|
||||||
|
selectedKPIs.value.forEach(kpiId => {
|
||||||
|
const color = generateColorRGBA();
|
||||||
|
kpiColors.set(kpiId, color);
|
||||||
|
});
|
||||||
|
// 使用存储的颜色更新图表系列
|
||||||
|
const series = selectedKPIs.value.map(kpiId => {
|
||||||
|
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||||
|
if (!kpi) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: kpi.title,
|
||||||
|
type: 'line',
|
||||||
|
data: chartData.value.map(item => item[kpiId] || 0),
|
||||||
|
itemStyle: { color: kpiColors.get(kpiId) },
|
||||||
|
...getSeriesConfig(),
|
||||||
|
};
|
||||||
|
}).filter(Boolean);
|
||||||
|
const option = {
|
||||||
|
title: {
|
||||||
|
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
||||||
|
left: 'center',
|
||||||
|
textStyle: {
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
// 保持现有的 xAxis 配置
|
||||||
|
type: 'category',
|
||||||
|
boundaryGap: false,
|
||||||
|
data: chartData.value.map(item =>
|
||||||
|
dayjs(Number(item.date)).format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
),
|
||||||
|
axisLabel: {
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: splitLineColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
},
|
||||||
|
splitNumber: 5,
|
||||||
|
scale: true,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: splitLineColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
show: false,
|
||||||
|
selected: Object.fromEntries(
|
||||||
|
selectedKPIs.value.map(kpiId => [
|
||||||
|
kpiColumns.value.find(col => col.kpiId === kpiId)?.title || kpiId,
|
||||||
|
selectedRow.value ? kpiId === selectedRow.value : true
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
position: function (point: number[], params: any, dom: HTMLElement, rect: any, size: { viewSize: number[], contentSize: number[] }) {
|
||||||
|
const [x, y] = point;
|
||||||
|
const [viewWidth] = size.viewSize;
|
||||||
|
const [tooltipWidth, tooltipHeight] = size.contentSize;
|
||||||
|
|
||||||
|
// 距离右侧的距离
|
||||||
|
const rightSpace = viewWidth - x;
|
||||||
|
|
||||||
|
// 计算垂直方向的居中位置
|
||||||
|
// 将 tooltip 的中心点对齐到鼠标位置
|
||||||
|
const verticalOffset = -tooltipHeight / 2;
|
||||||
|
|
||||||
|
// 如果右侧空间不足以显示tooltip(假设需要20px的安全距离)
|
||||||
|
if (rightSpace < tooltipWidth + 20) {
|
||||||
|
// 向左显示,垂直居中
|
||||||
|
return [x - tooltipWidth - 10, y + verticalOffset];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 默认向右显示,垂直居中
|
||||||
|
return [x + 10, y + verticalOffset];
|
||||||
|
},
|
||||||
|
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#555'
|
||||||
|
: '#ddd',
|
||||||
|
textStyle: {
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
},
|
||||||
|
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);'
|
||||||
|
},
|
||||||
|
// 重新设置系列数据
|
||||||
|
series
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用新的配置更新图表
|
||||||
|
chart!.setOption(option, true);
|
||||||
|
// 强制重新渲染
|
||||||
|
chart!.resize();
|
||||||
|
// 更新统计数据
|
||||||
|
nextTick(() => {
|
||||||
|
updateKpiStats();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const updateChart = () => {
|
const updateChart = () => {
|
||||||
if (!chart || !kpiColumns.value.length) return;
|
if (!chart || !kpiColumns.value.length) return;
|
||||||
@@ -278,14 +421,6 @@ const updateChart = () => {
|
|||||||
const color = kpiColors.get(kpiId)||generateColorRGBA();
|
const color = kpiColors.get(kpiId)||generateColorRGBA();
|
||||||
kpiColors.set(kpiId, color);
|
kpiColors.set(kpiId, color);
|
||||||
|
|
||||||
// 获取数据数组
|
|
||||||
const data = chartData.value.map((item, index) => {
|
|
||||||
const value = item[kpiId] || 0;
|
|
||||||
// 如果是最后一个数据点,添加特殊样式
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
name: kpi.title,
|
name: kpi.title,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@@ -299,6 +434,12 @@ const updateChart = () => {
|
|||||||
title: {
|
title: {
|
||||||
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
text: t('views.perfManage.kpiOverView.kpiChartTitle'),
|
||||||
left: 'center',
|
left: 'center',
|
||||||
|
// 添加文字颜色配置,根据主题切换
|
||||||
|
textStyle: {
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@@ -323,6 +464,18 @@ const updateChart = () => {
|
|||||||
// 默认向右显示,垂直居中
|
// 默认向右显示,垂直居中
|
||||||
return [x + 10, y + verticalOffset];
|
return [x + 10, y + verticalOffset];
|
||||||
},
|
},
|
||||||
|
backgroundColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? 'rgba(48, 48, 48, 0.8)'
|
||||||
|
: 'rgba(255, 255, 255, 0.9)',
|
||||||
|
borderColor: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#555'
|
||||||
|
: '#ddd',
|
||||||
|
textStyle: {
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
},
|
||||||
|
extraCssText: 'box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);'
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: selectedKPIs.value.map(kpiId =>
|
data: selectedKPIs.value.map(kpiId =>
|
||||||
@@ -356,11 +509,16 @@ const updateChart = () => {
|
|||||||
xAxis: {
|
xAxis: {
|
||||||
// 指定x轴类型为类目轴,适用于离散的类目数据
|
// 指定x轴类型为类目轴,适用于离散的类目数据
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
axisLabel: {
|
||||||
|
formatter: '{value}',
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
|
},
|
||||||
splitLine: {
|
splitLine: {
|
||||||
show: true,
|
show: true,
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
// 使用深浅的间隔色
|
color: getSplitLineColor()
|
||||||
color: '#aaa',
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//控制坐标轴两边留白
|
//控制坐标轴两边留白
|
||||||
@@ -402,11 +560,20 @@ const updateChart = () => {
|
|||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
formatter: '{value}',
|
formatter: '{value}',
|
||||||
|
color: document.documentElement.getAttribute('data-theme') === 'dark'
|
||||||
|
? '#CACADA'
|
||||||
|
: '#333'
|
||||||
},
|
},
|
||||||
// 添加自<E58AA0><E887AA><EFBFBD>计算的分割段数
|
// 添加自<E58AA0><E887AA><EFBFBD>计算的分割段数
|
||||||
splitNumber: 5,
|
splitNumber: 5,
|
||||||
// 添加自动计算的最小/最大值范围
|
// 添加自动计算的最小/最大值范围
|
||||||
scale: true,
|
scale: true,
|
||||||
|
splitLine: {
|
||||||
|
show: true,
|
||||||
|
lineStyle: {
|
||||||
|
color: getSplitLineColor()
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
series: series, //配置数据
|
series: series, //配置数据
|
||||||
};
|
};
|
||||||
@@ -435,6 +602,8 @@ const updateChart = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//钩子函数
|
//钩子函数
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -457,6 +626,11 @@ onMounted(async () => {
|
|||||||
} else {
|
} else {
|
||||||
console.error('Chart container not found');
|
console.error('Chart container not found');
|
||||||
}
|
}
|
||||||
|
// 添加主题观察器
|
||||||
|
themeObserver.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['data-theme']
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to initialize:', error);
|
console.error('Failed to initialize:', error);
|
||||||
message.error(t('common.initFail'));
|
message.error(t('common.initFail'));
|
||||||
@@ -525,6 +699,8 @@ onUnmounted(() => {
|
|||||||
chart.dispose();
|
chart.dispose();
|
||||||
chart = null;
|
chart = null;
|
||||||
}
|
}
|
||||||
|
// 断开主题观察器
|
||||||
|
themeObserver.disconnect();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 实时数据更新图表数据方法
|
// 实时数据更新图表数据方法
|
||||||
@@ -573,10 +749,16 @@ interface KPIStats {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 添加计算属性,用于计算每个指标的最大值和最小值
|
// 添加计算属性,用于计算每个指标的最大值和最小值
|
||||||
const kpiStats = computed((): KPIStats[] => {
|
// 将 kpiStats 从计算属性改为响应式引用
|
||||||
if (!chartData.value.length || !kpiColumns.value.length) return [];
|
const kpiStats = ref<KPIStats[]>([]);
|
||||||
|
|
||||||
return selectedKPIs.value.map(kpiId => {
|
// 添加一个计算函数来更新统计数据
|
||||||
|
const updateKpiStats = () => {
|
||||||
|
if (!chartData.value.length || !kpiColumns.value.length) {
|
||||||
|
kpiStats.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
kpiStats.value = selectedKPIs.value.map(kpiId => {
|
||||||
// 找到对应的KPI标题
|
// 找到对应的KPI标题
|
||||||
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
const kpi = kpiColumns.value.find(col => col.kpiId === kpiId);
|
||||||
if (!kpi) return null;
|
if (!kpi) return null;
|
||||||
@@ -597,7 +779,8 @@ const kpiStats = computed((): KPIStats[] => {
|
|||||||
avg:avg
|
avg:avg
|
||||||
};
|
};
|
||||||
}).filter((item): item is KPIStats => item !== null);
|
}).filter((item): item is KPIStats => item !== null);
|
||||||
});
|
};
|
||||||
|
|
||||||
|
|
||||||
// 添加表格列定义
|
// 添加表格列定义
|
||||||
const statsColumns: TableColumnType<KPIStats>[] = [
|
const statsColumns: TableColumnType<KPIStats>[] = [
|
||||||
@@ -606,9 +789,12 @@ const statsColumns: TableColumnType<KPIStats>[] = [
|
|||||||
key: 'icon',
|
key: 'icon',
|
||||||
width: 50,
|
width: 50,
|
||||||
customRender: ({ record }: { record: KPIStats }) => {
|
customRender: ({ record }: { record: KPIStats }) => {
|
||||||
|
// 获取当前主题下的颜色
|
||||||
|
// 直接使用 kpiColors 中存储的颜色
|
||||||
|
const color = kpiColors.get(record.kpiId);
|
||||||
return h(LineOutlined, {
|
return h(LineOutlined, {
|
||||||
style: {
|
style: {
|
||||||
color: kpiColors.get(record.kpiId) || '#000', // 使用与折线图相同的颜色
|
color: color || '#000', // 使用与折线图相同的颜色
|
||||||
fontSize: '30px', // 增大图标尺寸到30px
|
fontSize: '30px', // 增大图标尺寸到30px
|
||||||
fontWeight: 'bold', // 加粗
|
fontWeight: 'bold', // 加粗
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user