CostPrediction/frontend/src/views/AnalysisPage.vue

366 lines
9.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="analysis-page">
<el-card class="analysis-card">
<template #header>
<div class="header-content">
<h2>特征分析</h2>
</div>
</template>
<!-- 数据集选择 -->
<div class="dataset-section">
<el-form :model="analysisForm" label-width="120px">
<el-form-item label="装备类型" required>
<el-select v-model="analysisForm.equipment_type" @change="handleEquipmentTypeChange">
<el-option label="火箭炮" value="火箭炮"></el-option>
<el-option label="巡飞弹" value="巡飞弹"></el-option>
</el-select>
</el-form-item>
<el-form-item label="选择数据集" required>
<el-select v-model="analysisForm.dataset_id" @change="handleDatasetChange">
<el-option
v-for="dataset in availableDatasets"
:key="dataset.id"
:label="dataset.name"
:value="dataset.id"
></el-option>
</el-select>
</el-form-item>
</el-form>
<!-- 数据集信息 -->
<el-descriptions v-if="selectedDataset" :column="2" border>
<el-descriptions-item label="数据集名称">{{ selectedDataset.name }}</el-descriptions-item>
<el-descriptions-item label="装备数量">{{ selectedDataset.equipment_count }}</el-descriptions-item>
<el-descriptions-item label="描述" :span="2">{{ selectedDataset.description }}</el-descriptions-item>
</el-descriptions>
</div>
<!-- 分析按钮 -->
<div class="action-section">
<el-button type="primary" @click="startAnalysis" :loading="analyzing" :disabled="!analysisForm.dataset_id">
{{ analyzing ? '分析中...' : '开始分析' }}
</el-button>
</div>
<!-- 分析结果 -->
<div v-if="analysisResult" class="result-section">
<el-divider content-position="left">分析结果</el-divider>
<!-- 特征重要性 -->
<h3>特征重要性</h3>
<div class="chart-container">
<div ref="importanceChartRef" style="width: 100%; height: 400px"></div>
</div>
<!-- 相关性分析 -->
<h3>相关性分析</h3>
<div class="chart-container">
<div ref="correlationChartRef" style="width: 100%; height: 500px"></div>
</div>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue'
import { ElMessage } from 'element-plus'
import axios from 'axios'
import { API_BASE_URL } from '@/config'
import * as echarts from 'echarts'
// 响应式数据
const analysisForm = ref({
equipment_type: '',
dataset_id: null
})
const availableDatasets = ref([])
const selectedDataset = ref(null)
const analyzing = ref(false)
const analysisResult = ref(null)
const importanceChartRef = ref(null)
const correlationChartRef = ref(null)
// 图表实例引用
const importanceChart = ref(null)
const correlationChart = ref(null)
// 监听分析结果变化
watch(() => analysisResult.value, async (newResult) => {
if (newResult) {
console.log('Analysis result updated:', newResult)
// 等待下一个 tick确保 DOM 已更新
await nextTick()
// 延迟一点时间再渲染图表
setTimeout(() => {
renderCharts()
}, 100)
}
}, { deep: true })
// 加载可用数据集
const loadDatasets = async (type) => {
try {
const response = await axios.get(`${API_BASE_URL}/datasets`, {
params: { equipment_type: type, purpose: '训练' }
})
availableDatasets.value = response.data
} catch (error) {
ElMessage.error('获取数据集列表失败')
}
}
// 处理装备类型变化
const handleEquipmentTypeChange = () => {
analysisForm.value.dataset_id = null
selectedDataset.value = null
analysisResult.value = null
loadDatasets(analysisForm.value.equipment_type)
}
// 处理数据集选择变化
const handleDatasetChange = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/datasets/${analysisForm.value.dataset_id}`)
selectedDataset.value = response.data
analysisResult.value = null
} catch (error) {
ElMessage.error('获取数据集详情失败')
}
}
// 开始分析
const startAnalysis = async () => {
if (!analysisForm.value.dataset_id) {
ElMessage.warning('请先选择数据集')
return
}
analyzing.value = true
try {
const response = await axios.post(`${API_BASE_URL}/analyze-features`, {
dataset_id: analysisForm.value.dataset_id
})
analysisResult.value = response.data
console.log('Analysis completed, result:', analysisResult.value)
} catch (error) {
ElMessage.error('特征分析失败')
console.error('Analysis error:', error)
} finally {
analyzing.value = false
}
}
// 窗口大小变化处理函数
const resizeHandler = ref(null)
// 创建 resize 处理函数
const createResizeHandler = () => {
const handler = () => {
try {
if (importanceChart.value && !importanceChart.value.isDisposed()) {
importanceChart.value.resize()
}
if (correlationChart.value && !correlationChart.value.isDisposed()) {
correlationChart.value.resize()
}
} catch (error) {
console.error('Error in resize handler:', error)
}
}
// 使用防抖包装
return debounce(handler, 200)
}
// 组件挂载时
onMounted(() => {
// 创建并保存 resize 处理函数的引用
resizeHandler.value = createResizeHandler()
window.addEventListener('resize', resizeHandler.value)
})
// 组件卸载时
onUnmounted(() => {
// 移除事件监听
if (resizeHandler.value) {
window.removeEventListener('resize', resizeHandler.value)
resizeHandler.value = null
}
// 销毁图表实例
try {
if (importanceChart.value) {
importanceChart.value.dispose()
importanceChart.value = null
}
if (correlationChart.value) {
correlationChart.value.dispose()
correlationChart.value = null
}
} catch (error) {
console.error('Error disposing charts:', error)
}
})
// 渲染图表
const renderCharts = () => {
console.log('Starting to render charts')
// 检查分析结果是否存在且包含必要数据
if (!analysisResult.value ||
!analysisResult.value.important_features ||
!analysisResult.value.correlation_analysis) {
console.log('Analysis result not ready')
return
}
// 检查DOM元素
if (!importanceChartRef.value || !correlationChartRef.value) {
console.log('Chart DOM elements not ready')
return
}
try {
// 销毁旧的图表实例
if (importanceChart.value) {
importanceChart.value.dispose()
importanceChart.value = null
}
if (correlationChart.value) {
correlationChart.value.dispose()
correlationChart.value = null
}
// 创建新的图表实例
importanceChart.value = echarts.init(importanceChartRef.value)
correlationChart.value = echarts.init(correlationChartRef.value)
// 设置图表选项
const importanceOption = {
title: { text: '特征重要性排序' },
tooltip: {},
xAxis: {
type: 'value',
name: '重要性得分'
},
yAxis: {
type: 'category',
data: analysisResult.value.important_features.map(f => f.name)
},
series: [{
name: '重要性',
type: 'bar',
data: analysisResult.value.important_features.map(f => f.importance)
}]
}
const correlationOption = {
title: { text: '特征相关性热力图' },
tooltip: {
position: 'top',
formatter: function (params) {
const value = params.data[2].toFixed(2)
const feature1 = analysisResult.value.correlation_analysis.features[params.data[0]]
const feature2 = analysisResult.value.correlation_analysis.features[params.data[1]]
return `${feature1}${feature2} 的相关性: ${value}`
}
},
grid: {
height: '50%',
top: '10%'
},
xAxis: {
type: 'category',
data: analysisResult.value.correlation_analysis.features,
splitArea: { show: true },
axisLabel: {
interval: 0,
rotate: 45
}
},
yAxis: {
type: 'category',
data: analysisResult.value.correlation_analysis.features,
splitArea: { show: true }
},
visualMap: {
min: -1,
max: 1,
calculable: true,
orient: 'horizontal',
left: 'center',
bottom: '15%',
color: ['#cc3333', '#eeeeee', '#00007f']
},
series: [{
name: '相关性',
type: 'heatmap',
data: analysisResult.value.correlation_analysis.matrix,
label: {
show: true,
formatter: function(params) {
return params.data[2].toFixed(2)
}
}
}]
}
// 设置图表选项
importanceChart.value.setOption(importanceOption)
correlationChart.value.setOption(correlationOption)
console.log('Charts rendered successfully')
} catch (error) {
console.error('Error rendering charts:', error)
}
}
// 防抖函数
function debounce(fn, delay) {
let timer = null
return function (...args) {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
</script>
<style lang="scss" scoped>
.analysis-page {
padding: 20px;
.analysis-card {
.header-content {
h2 {
margin: 0;
}
}
.dataset-section {
margin-bottom: 20px;
}
.action-section {
margin: 20px 0;
text-align: center;
}
.result-section {
h3 {
margin: 20px 0;
}
.chart-container {
margin: 20px 0;
border: 1px solid #ebeef5;
border-radius: 4px;
}
}
}
}
</style>