366 lines
9.9 KiB
Vue
366 lines
9.9 KiB
Vue
<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> |