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

797 lines
24 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>
<GiPageLayout>
<div class="analysis-report-container">
<!-- 页面标题 -->
<div class="page-header">
<h2 class="page-title">检测分析结果报告</h2>
<p class="page-subtitle">基于计算机视觉的风机叶片净空距离与形变状态分析</p>
</div>
<!-- 筛选和操作栏 -->
<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> 查看大图
</a-button>
</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> 查看大图
</a-button>
</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>
<div class="report-section">
<h6>检测结论</h6>
<div class="conclusion">
<p>{{ blade.conclusion }}</p>
</div>
</div>
<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>
<!-- 图片大图预览模态框 -->
<a-modal
v-model:visible="imageModalVisible"
title="图片预览"
width="800px"
@ok="imageModalVisible = false"
@cancel="imageModalVisible = false"
>
<img :src="previewImage" alt="检测图片" style="width: 100%; border-radius: 8px;" />
</a-modal>
</div>
</GiPageLayout>
</template>
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
import { Message } from '@arco-design/web-vue'
import {
IconDownload,
IconFilePdf,
IconCheckCircle,
IconExclamationCircle,
IconCloseCircle,
IconFile,
IconEye
} from '@arco-design/web-vue/es/icon'
const filterForm = reactive({
projectId: '',
unitNumber: '',
status: '',
dateRange: []
})
const imageModalVisible = ref(false)
const previewImage = ref('')
// 示例统计数据
const overviewData = reactive({
normalCount: 12,
warningCount: 3,
errorCount: 1,
totalCount: 16
})
// 示例项目、机组、叶片检测数据
const projects = ref([
{
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
}
},
// ... 其他叶片
]
}
]
}
// ... 其他项目
])
const filteredProjects = computed(() => {
// 可根据filterForm进行过滤
return projects.value
})
function handleFilterChange() {
// 可扩展为API请求
}
function handleExportReport() {
Message.success('报告导出成功')
}
function handleBatchExport() {
Message.success('批量导出成功')
}
function handleViewImage(url: string) {
previewImage.value = url
imageModalVisible.value = true
}
function getStatusColor(status: string) {
switch (status) {
case 'normal': return 'green'
case 'warning': return 'orange'
case 'error': return 'red'
default: return 'gray'
}
}
function getStatusText(status: string) {
switch (status) {
case 'normal': return '正常'
case 'warning': return '警告'
case 'error': return '异常'
default: return '未知'
}
}
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'
}
</script>
<style scoped lang="scss">
.analysis-report-container {
padding: 20px;
height: 100vh;
overflow-y: auto;
box-sizing: border-box;
}
.page-header {
margin-bottom: 20px;
}
.page-title {
font-size: 28px;
font-weight: 600;
margin: 0 0 8px 0;
color: #1d2129;
}
.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 {
margin-bottom: 20px;
}
.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;
}
}
}
}
.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;
}
.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;
}
}
}
}
}
}
}
}
}
}
}
}
</style>