You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

242 lines
7.7 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<div v-loading="loading" class="app-container vibration-board-page">
<VibrationBoardFilter
:selection-label="queryForm.selectionLabel"
:time-range="daterangeRecordTime"
:sampling-interval="queryForm.samplingInterval"
@update:time-range="setTimeRange"
@update:sampling-interval="setSamplingInterval"
@query="handleQuery"
/>
<el-row :gutter="16" class="mt-4 vibration-grid">
<el-col :xs="24" :lg="7" :xl="6">
<VibrationBoardDeviceTree :loading="treeLoading" :tree-data="monitorTreeOptions" :tree-props="treeProps" @node-click="handleTreeNodeClick" />
</el-col>
<el-col :xs="24" :lg="17" :xl="18">
<el-alert type="info" :closable="false" show-icon class="section-tip" :title="bandTip" />
<el-card shadow="never" class="chart-card">
<template #header>
<div class="card-header">
<div class="card-title-group">
<div class="card-title">位移流向图</div>
<div class="card-subtitle">观察设备样本在不同风险带之间的迁移方向,识别整体恶化路径和主要流量通道。</div>
</div>
<HelpButton title="位移流向图" :content="ADVANCED_HELP" />
</div>
</template>
<div ref="sankeyChartRef" class="chart-container-xl" />
</el-card>
<el-row :gutter="16" class="mt-4 vibration-grid">
<el-col :xs="24" :xl="12">
<el-card shadow="never" class="chart-card">
<template #header>
<div class="card-header">
<div class="card-title-group">
<div class="card-title">矩形树图</div>
<div class="card-subtitle">面积看数值规模颜色看风险带适合快速定位高值高影响设备</div>
</div>
<HelpButton title="矩形树图" :content="ADVANCED_HELP" />
</div>
</template>
<div ref="treemapChartRef" class="chart-container-lg" />
</el-card>
</el-col>
</el-row>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { getDisplacementAdvancedData, type DisplacementAdvancedPageVO } from '@/api/ems/report/displacementBoard';
import HelpButton from '../components/HelpButton.vue';
import VibrationBoardFilter from '../components/VibrationBoardFilter.vue';
import VibrationBoardDeviceTree from '../components/VibrationBoardDeviceTree.vue';
import { ADVANCED_HELP } from '../components/helpContent';
import { useChartResize } from '../components/useChartResize';
import { useVibrationBoardQueryState } from '../components/useVibrationBoardQueryState';
import { toNumber } from '../components/vibrationBoardShared';
defineOptions({ name: 'VibrationBoardAdvanced' });
const sankeyChartRef = ref<HTMLElement>();
const treemapChartRef = ref<HTMLElement>();
// 高级页已收口为“桑基图 + 矩形树图”,
// 平行坐标图在位移专属语义下无意义,不再保留 parallelChartRef/解析逻辑。
const { getChart } = useChartResize(sankeyChartRef, treemapChartRef);
const {
loading,
treeLoading,
monitorTreeOptions,
treeProps,
daterangeRecordTime,
queryForm,
loadTree,
handleTreeNodeClick,
setTimeRange,
setSamplingInterval,
buildQuery
} = useVibrationBoardQueryState();
const advancedData = ref<DisplacementAdvancedPageVO>({
sankeyNodes: [],
sankeyLinks: [],
treemapItems: []
});
const riskBandColorMap: Record<string, string> = {
高位: '#ef4444',
关注: '#f59e0b',
平稳: '#10b981'
};
const bandTip = computed(() => {
const lowBandUpper = advancedData.value.lowBandUpper;
const focusBandUpper = advancedData.value.focusBandUpper;
if (lowBandUpper === undefined || focusBandUpper === undefined) {
return '高级分析会根据当前位移指标的风险分带,对设备群做迁移与占比分析。';
}
return `当前风险分带建议:平稳 ≤ ${lowBandUpper},关注区 ≤ ${focusBandUpper},其上视为高位。`;
});
const getNodeColor = (name?: string) => {
if (!name) {
return '#5b8ff9';
}
if (name.includes('高')) {
return riskBandColorMap['高位'];
}
if (name.includes('关注') || name.includes('预警')) {
return riskBandColorMap['关注'];
}
return riskBandColorMap['平稳'];
};
const renderCharts = () => {
const sankeyChart = getChart(sankeyChartRef);
const treemapChart = getChart(treemapChartRef);
const sankeyNodes = advancedData.value.sankeyNodes || [];
const sankeyLinks = advancedData.value.sankeyLinks || [];
const treemapItems = advancedData.value.treemapItems || [];
if (sankeyChart) {
if (!sankeyNodes.length || !sankeyLinks.length) {
sankeyChart.clear();
} else {
sankeyChart.setOption(
{
tooltip: {
trigger: 'item',
formatter: (params: any) => {
if (params.dataType === 'edge') {
return `${params.data.source}${params.data.target}<br/>流量: <b>${params.data.value}</b>`;
}
return `${params.name}`;
}
},
series: [
{
type: 'sankey',
left: 16,
right: 24,
top: 24,
bottom: 16,
nodeGap: 26,
emphasis: { focus: 'adjacency' },
data: sankeyNodes.map((item) => ({
name: item.name,
itemStyle: { color: getNodeColor(item.name) }
})),
links: sankeyLinks.map((item) => ({
source: item.source,
target: item.target,
value: toNumber(item.value)
})),
lineStyle: {
color: 'gradient',
curveness: 0.52,
opacity: 0.5
},
label: { color: '#334155', fontSize: 12 }
}
]
},
true
);
}
}
if (treemapChart) {
if (!treemapItems.length) {
treemapChart.clear();
} else {
treemapChart.setOption(
{
tooltip: {
formatter: (params: any) =>
`${params.name}<br/>值: <b>${Number(params.value).toFixed(2)}</b><br/>风险带: <b>${params.data.levelTag || '--'}</b>`
},
series: [
{
type: 'treemap',
roam: false,
breadcrumb: { show: false },
visibleMin: 8,
data: treemapItems.map((item) => ({
name: item.name,
value: toNumber(item.value),
levelTag: item.levelTag,
itemStyle: {
color: riskBandColorMap[item.levelTag || ''] || '#5b8ff9',
borderColor: '#fff',
borderWidth: 2,
gapWidth: 2
}
})),
upperLabel: { show: false },
label: {
show: true,
color: '#fff',
formatter: (params: any) => `${params.name}\n${Number(params.value).toFixed(1)}`
}
}
]
},
true
);
}
}
// 高级页仅展示桑基图与矩形树图,不再维护平行坐标图。
};
const handleQuery = async () => {
loading.value = true;
try {
const { data } = await getDisplacementAdvancedData(buildQuery());
advancedData.value = data || {
sankeyNodes: [],
sankeyLinks: [],
treemapItems: []
};
await nextTick();
renderCharts();
} finally {
loading.value = false;
}
};
onMounted(async () => {
await loadTree();
await handleQuery();
});
</script>
<style scoped lang="scss">
@import '../components/vibrationBoardReport.scss';
</style>