Industrial-image-management.../src/views/project-operation-platform/data-processing/clearance-detection/index.vue

797 lines
24 KiB
Vue
Raw Normal View History

2025-07-30 09:13:52 +08:00
<template>
<GiPageLayout>
2025-08-10 20:10:47 +08:00
<div class="analysis-report-container">
2025-07-30 09:13:52 +08:00
<!-- 页面标题 -->
<div class="page-header">
2025-08-10 20:10:47 +08:00
<h2 class="page-title">检测分析结果报告</h2>
<p class="page-subtitle">基于计算机视觉的风机叶片净空距离与形变状态分析</p>
2025-07-30 09:13:52 +08:00
</div>
2025-08-10 20:10:47 +08:00
<!-- 筛选和操作栏 -->
<div class="action-bar">
<div class="filter-section">
<a-form :model="filterForm" layout="inline">
<a-form-item label="项目">
<a-select
v-model="filterForm.projectId"
placeholder="选择项目"
style="width: 150px"
@change="handleFilterChange"
>
<a-option value="">全部项目</a-option>
<a-option value="project-1">风电场A区</a-option>
<a-option value="project-2">风电场B区</a-option>
<a-option value="project-3">风电场C区</a-option>
</a-select>
</a-form-item>
<a-form-item label="机组号">
<a-input
v-model="filterForm.unitNumber"
placeholder="输入机组号"
style="width: 120px"
@change="handleFilterChange"
/>
</a-form-item>
<a-form-item label="检测状态">
<a-select
v-model="filterForm.status"
placeholder="选择状态"
style="width: 120px"
@change="handleFilterChange"
>
<a-option value="">全部状态</a-option>
<a-option value="normal">正常</a-option>
<a-option value="warning">警告</a-option>
<a-option value="error">异常</a-option>
</a-select>
</a-form-item>
<a-form-item label="检测日期">
<a-date-picker
v-model="filterForm.dateRange"
type="daterange"
style="width: 200px"
@change="handleFilterChange"
/>
</a-form-item>
</a-form>
</div>
<div class="action-buttons">
<a-button @click="handleExportReport">
<template #icon><icon-download /></template>
导出报告
</a-button>
<a-button @click="handleBatchExport">
<template #icon><icon-file-pdf /></template>
批量导出
</a-button>
</div>
</div>
<!-- 统计概览 -->
<div class="overview-section">
<a-row :gutter="16">
<a-col :span="6">
<a-card class="overview-card">
<div class="overview-item">
<div class="overview-icon normal">
<icon-check-circle />
</div>
<div class="overview-content">
<div class="overview-number">{{ overviewData.normalCount }}</div>
<div class="overview-label">正常叶片</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="overview-card">
<div class="overview-item">
<div class="overview-icon warning">
<icon-exclamation-circle />
</div>
<div class="overview-content">
<div class="overview-number">{{ overviewData.warningCount }}</div>
<div class="overview-label">警告叶片</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="overview-card">
<div class="overview-item">
<div class="overview-icon error">
<icon-close-circle />
</div>
<div class="overview-content">
<div class="overview-number">{{ overviewData.errorCount }}</div>
<div class="overview-label">异常叶片</div>
</div>
</div>
</a-card>
</a-col>
<a-col :span="6">
<a-card class="overview-card">
<div class="overview-item">
<div class="overview-icon total">
<icon-file />
</div>
<div class="overview-content">
<div class="overview-number">{{ overviewData.totalCount }}</div>
<div class="overview-label">总检测数</div>
</div>
</div>
</a-card>
</a-col>
</a-row>
</div>
<!-- 检测结果列表 -->
<div class="results-section">
<div v-for="project in filteredProjects" :key="project.id" class="project-section">
<div class="project-header">
<h3 class="project-title">{{ project.name }}</h3>
<div class="project-stats">
<span class="stat-item">
<icon-file />
{{ project.totalUnits }} 个机组
</span>
<span class="stat-item">
<icon-check-circle />
{{ project.completedUnits }} 已完成
</span>
</div>
</div>
<div class="units-list">
<div v-for="unit in project.units" :key="unit.id" class="unit-result-card">
<div class="unit-header">
<div class="unit-info">
<h4 class="unit-title">{{ unit.number }}</h4>
<div class="unit-meta">
<span>检测时间{{ unit.detectionTime }}</span>
<span>检测人员{{ unit.operator }}</span>
</div>
</div>
<div class="unit-status">
<a-tag :color="getStatusColor(unit.status)">
{{ getStatusText(unit.status) }}
</a-tag>
</div>
</div>
<div class="blades-analysis">
<div v-for="(blade, index) in unit.blades" :key="blade.id" class="blade-result">
<!-- 图片区域左右并排 -->
<div class="blade-content">
<div class="result-images">
<div class="image-section">
<h6>净空距离检测</h6>
<div class="image-container large">
<img :src="blade.clearanceImage" />
<div class="image-overlay">
<a-button type="primary" size="small" @click="handleViewImage(blade.clearanceImage)">
<template #icon><icon-eye /></template> 查看大图
2025-07-30 09:13:52 +08:00
</a-button>
2025-08-10 20:10:47 +08:00
</div>
</div>
</div>
<div class="image-section">
<h6>形变状态检测</h6>
<div class="image-container large">
<img :src="blade.deformationImage" />
<div class="image-overlay">
<a-button type="primary" size="small" @click="handleViewImage(blade.deformationImage)">
<template #icon><icon-eye /></template> 查看大图
2025-07-30 09:13:52 +08:00
</a-button>
2025-08-10 20:10:47 +08:00
</div>
</div>
</div>
</div>
<!-- 右侧结果报告 -->
<div class="result-report">
<div class="blade-topbar">
<a-tag :color="getStatusColor(blade.status)" size="small">
叶片{{ index + 1 }} · {{ getStatusText(blade.status) }}
</a-tag>
</div>
<div class="report-section">
<h6>净空距离测量</h6>
<div class="measurement-data">
<div class="data-item">
<span class="label">最小净空距离</span>
<span class="value">{{ blade.clearanceDistance.min }}m</span>
</div>
<div class="data-item">
<span class="label">平均净空距离</span>
<span class="value">{{ blade.clearanceDistance.avg }}m</span>
</div>
<div class="data-item">
<span class="label">安全阈值</span>
<span class="value">{{ blade.clearanceDistance.threshold }}m</span>
</div>
<div class="data-item">
<span class="label">安全系数</span>
<span class="value" :class="getSafetyClass(blade.clearanceDistance.safetyFactor)">
{{ blade.clearanceDistance.safetyFactor }}
</span>
</div>
</div>
</div>
<div class="report-section">
<h6>形变状态分析</h6>
<div class="deformation-data">
<div class="data-item">
<span class="label">最大形变量</span>
<span class="value">{{ blade.deformation.max }}mm</span>
</div>
<div class="data-item">
<span class="label">平均形变量</span>
<span class="value">{{ blade.deformation.avg }}mm</span>
</div>
<div class="data-item">
<span class="label">形变位置</span>
<span class="value">{{ blade.deformation.location }}</span>
</div>
<div class="data-item">
<span class="label">形变等级</span>
<a-tag :color="getDeformationColor(blade.deformation.level)" size="small">
{{ getDeformationText(blade.deformation.level) }}
</a-tag>
</div>
</div>
</div>
2025-07-30 09:13:52 +08:00
2025-08-10 20:10:47 +08:00
<div class="report-section">
<h6>检测结论</h6>
<div class="conclusion">
<p>{{ blade.conclusion }}</p>
</div>
</div>
2025-07-30 09:13:52 +08:00
2025-08-10 20:10:47 +08:00
<div class="report-section">
<h6>建议措施</h6>
<div class="recommendations">
<ul>
<li v-for="(rec, recIndex) in blade.recommendations" :key="recIndex">
{{ rec }}
</li>
</ul>
</div>
</div>
<div class="report-section">
<h6>检测参数</h6>
<div class="detection-params">
<div class="param-item">
<span class="label">风速</span>
<span class="value">{{ blade.params.windSpeed }}m/s</span>
</div>
<div class="param-item">
<span class="label">转速</span>
<span class="value">{{ blade.params.rpm }}rpm</span>
</div>
<div class="param-item">
<span class="label">温度</span>
<span class="value">{{ blade.params.temperature }}°C</span>
</div>
<div class="param-item">
<span class="label">湿度</span>
<span class="value">{{ blade.params.humidity }}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图片大图预览模态框 -->
2025-07-30 09:13:52 +08:00
<a-modal
2025-08-10 20:10:47 +08:00
v-model:visible="imageModalVisible"
title="图片预览"
2025-07-30 09:13:52 +08:00
width="800px"
2025-08-10 20:10:47 +08:00
@ok="imageModalVisible = false"
@cancel="imageModalVisible = false"
2025-07-30 09:13:52 +08:00
>
2025-08-10 20:10:47 +08:00
<img :src="previewImage" alt="检测图片" style="width: 100%; border-radius: 8px;" />
2025-07-30 09:13:52 +08:00
</a-modal>
</div>
</GiPageLayout>
</template>
<script setup lang="ts">
2025-08-10 20:10:47 +08:00
import { ref, reactive, computed } from 'vue'
2025-07-30 09:13:52 +08:00
import { Message } from '@arco-design/web-vue'
2025-08-10 20:10:47 +08:00
import {
IconDownload,
IconFilePdf,
IconCheckCircle,
IconExclamationCircle,
IconCloseCircle,
IconFile,
IconEye
} from '@arco-design/web-vue/es/icon'
const filterForm = reactive({
projectId: '',
unitNumber: '',
status: '',
dateRange: []
})
2025-07-30 09:13:52 +08:00
2025-08-10 20:10:47 +08:00
const imageModalVisible = ref(false)
const previewImage = ref('')
2025-07-30 09:13:52 +08:00
2025-08-10 20:10:47 +08:00
// 示例统计数据
const overviewData = reactive({
normalCount: 12,
warningCount: 3,
errorCount: 1,
totalCount: 16
2025-07-30 09:13:52 +08:00
})
2025-08-10 20:10:47 +08:00
// 示例项目、机组、叶片检测数据
const projects = ref([
2025-07-30 09:13:52 +08:00
{
2025-08-10 20:10:47 +08:00
id: 'project-1',
name: '风电场A区',
totalUnits: 2,
completedUnits: 2,
units: [
{
id: 'A-001',
number: 'A-001',
detectionTime: '2023-11-05 08:00',
operator: '张三',
status: 'normal',
blades: [
{
id: 'A-001-blade-1',
status: 'normal',
clearanceImage: '/images/blade1_clearance.jpg',
deformationImage: '/images/blade1_deform.jpg',
clearanceDistance: {
min: 12.5,
avg: 13.2,
threshold: 10.0,
safetyFactor: 1.25
},
deformation: {
max: 2.1,
avg: 1.5,
location: '叶尖',
level: 'normal'
},
conclusion: '叶片净空距离充足,形变在正常范围,无需特殊处理。',
recommendations: [
'定期检测叶片净空与形变',
'关注极端天气下的叶片状态'
],
params: {
windSpeed: 8.2,
rpm: 15,
temperature: 15.3,
humidity: 60
}
},
{
id: 'A-001-blade-2',
status: 'warning',
clearanceImage: '/images/blade2_clearance.jpg',
deformationImage: '/images/blade2_deform.jpg',
clearanceDistance: {
min: 10.1,
avg: 10.5,
threshold: 10.0,
safetyFactor: 1.01
},
deformation: {
max: 4.5,
avg: 3.2,
location: '中部',
level: 'warning'
},
conclusion: '叶片净空距离接近安全阈值,形变略大,建议加强监测。',
recommendations: [
'缩短检测周期',
'必要时进行维护'
],
params: {
windSpeed: 8.2,
rpm: 15,
temperature: 15.3,
humidity: 60
}
},
{
id: 'A-001-blade-3',
status: 'normal',
clearanceImage: '/images/blade3_clearance.jpg',
deformationImage: '/images/blade3_deform.jpg',
clearanceDistance: {
min: 12.0,
avg: 12.8,
threshold: 10.0,
safetyFactor: 1.20
},
deformation: {
max: 2.0,
avg: 1.4,
location: '根部',
level: 'normal'
},
conclusion: '叶片状态良好。',
recommendations: [
'保持常规巡检'
],
params: {
windSpeed: 8.2,
rpm: 15,
temperature: 15.3,
humidity: 60
}
}
]
},
{
id: 'A-002',
number: 'A-002',
detectionTime: '2023-11-05 12:00',
operator: '李四',
status: 'error',
blades: [
{
id: 'A-002-blade-1',
status: 'error',
clearanceImage: '/images/blade1_clearance_error.jpg',
deformationImage: '/images/blade1_deform_error.jpg',
clearanceDistance: {
min: 8.5,
avg: 9.0,
threshold: 10.0,
safetyFactor: 0.85
},
deformation: {
max: 8.2,
avg: 6.5,
location: '叶尖',
level: 'error'
},
conclusion: '叶片净空距离低于安全阈值,形变严重,存在安全隐患。',
recommendations: [
'立即停机检查',
'联系技术人员处理'
],
params: {
windSpeed: 10.5,
rpm: 16,
temperature: 18.1,
humidity: 65
}
},
// ... 其他叶片
]
}
]
}
2025-08-10 20:10:47 +08:00
// ... 其他项目
2025-07-30 09:13:52 +08:00
])
2025-08-10 20:10:47 +08:00
const filteredProjects = computed(() => {
// 可根据filterForm进行过滤
return projects.value
2025-07-30 09:13:52 +08:00
})
2025-08-10 20:10:47 +08:00
function handleFilterChange() {
// 可扩展为API请求
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function handleExportReport() {
Message.success('报告导出成功')
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function handleBatchExport() {
Message.success('批量导出成功')
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function handleViewImage(url: string) {
previewImage.value = url
imageModalVisible.value = true
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function getStatusColor(status: string) {
switch (status) {
case 'normal': return 'green'
case 'warning': return 'orange'
case 'error': return 'red'
default: return 'gray'
}
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function getStatusText(status: string) {
switch (status) {
case 'normal': return '正常'
case 'warning': return '警告'
case 'error': return '异常'
default: return '未知'
}
2025-07-30 09:13:52 +08:00
}
2025-08-10 20:10:47 +08:00
function getDeformationColor(level: string) {
switch (level) {
case 'normal': return 'green'
case 'warning': return 'orange'
case 'error': return 'red'
default: return 'gray'
}
}
function getDeformationText(level: string) {
switch (level) {
case 'normal': return '正常'
case 'warning': return '警告'
case 'error': return '异常'
default: return '未知'
}
}
function getSafetyClass(factor: number) {
if (factor >= 1.2) return 'safe'
if (factor >= 1.0) return 'warning'
return 'danger'
2025-07-30 09:13:52 +08:00
}
</script>
<style scoped lang="scss">
2025-08-10 20:10:47 +08:00
.analysis-report-container {
2025-07-30 09:13:52 +08:00
padding: 20px;
2025-08-10 20:10:47 +08:00
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
2025-07-30 09:13:52 +08:00
}
.page-header {
margin-bottom: 20px;
}
.page-title {
2025-08-10 20:10:47 +08:00
font-size: 28px;
2025-07-30 09:13:52 +08:00
font-weight: 600;
2025-08-10 20:10:47 +08:00
margin: 0 0 8px 0;
2025-07-30 09:13:52 +08:00
color: #1d2129;
}
2025-08-10 20:10:47 +08:00
.page-subtitle {
font-size: 14px;
color: #86909c;
margin: 0;
}
.action-bar {
display: flex;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 20px;
.filter-section {
flex: 1;
}
.action-buttons {
display: flex;
gap: 8px;
}
}
.overview-section {
2025-07-30 09:13:52 +08:00
margin-bottom: 20px;
}
2025-08-10 20:10:47 +08:00
.overview-card {
.overview-item {
display: flex;
align-items: center;
padding: 16px;
.overview-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 16px;
font-size: 24px;
color: white;
&.normal { background-color: #00b42a; }
&.warning { background-color: #ff7d00; }
&.error { background-color: #f53f3f; }
&.total { background-color: #165dff; }
}
.overview-content {
.overview-number {
font-size: 24px;
font-weight: 600;
color: #1d2129;
line-height: 1;
}
.overview-label {
font-size: 14px;
color: #86909c;
margin-top: 4px;
}
2025-07-30 09:13:52 +08:00
}
}
}
2025-08-10 20:10:47 +08:00
.results-section {
.project-section {
margin-bottom: 32px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px #f0f1f2;
padding: 20px;
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
.project-title {
font-size: 20px;
font-weight: 600;
}
.project-stats {
display: flex;
gap: 16px;
.stat-item {
display: flex;
align-items: center;
gap: 4px;
color: #86909c;
}
}
}
.units-list {
.unit-result-card {
background: #fafbfc;
border-radius: 8px;
padding: 16px;
margin-bottom: 20px;
.unit-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.unit-info {
.unit-title {
font-size: 16px;
font-weight: 600;
}
.unit-meta {
font-size: 12px;
color: #86909c;
margin-top: 2px;
span {
margin-right: 16px;
}
}
}
}
.blades-analysis {
display: flex;
flex-direction: column;
gap: 24px;
.blade-result {
display: flex;
gap: 32px;
background: #fff;
border-radius: 6px;
box-shadow: 0 1px 4px #f0f1f2;
padding: 16px;
.blade-header {
flex: 0 0 80px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
.blade-title {
font-size: 15px;
font-weight: 600;
}
}
.blade-content {
display: flex;
gap: 24px;
width: 100%;
.result-images {
display: flex;
flex-direction: column;
gap: 16px;
.image-section {
h6 {
font-size: 13px;
color: #4e5969;
margin-bottom: 4px;
}
.blade-topbar {
margin-bottom: 4px;
}
2025-07-30 09:13:52 +08:00
2025-08-10 20:10:47 +08:00
.image-container.large {
width: 220px; // 比之前 160px 大
height: 140px;
}
.image-container {
position: relative;
width: 160px;
height: 100px;
border-radius: 6px;
overflow: hidden;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.image-overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.2);
opacity: 0;
transition: opacity 0.2s;
&:hover {
opacity: 1;
}
}
}
}
}
.result-report {
flex: 1;
display: flex;
flex-direction: column;
gap: 12px;
.report-section {
h6 {
font-size: 13px;
color: #165dff;
margin-bottom: 4px;
}
.measurement-data, .deformation-data, .detection-params {
display: flex;
flex-wrap: wrap;
gap: 16px;
.data-item, .param-item {
font-size: 13px;
color: #4e5969;
.label {
color: #86909c;
}
.value.safe { color: #00b42a; }
.value.warning { color: #ff7d00; }
.value.danger { color: #f53f3f; }
}
}
.conclusion {
p {
color: #1d2129;
font-weight: 500;
margin: 0;
}
}
.recommendations {
ul {
margin: 0;
padding-left: 20px;
li {
color: #4e5969;
margin-bottom: 4px;
}
}
}
}
}
}
}
}
}
}
2025-07-30 09:13:52 +08:00
}
}
2025-08-10 20:10:47 +08:00
</style>