净空形变监测相关原型
This commit is contained in:
parent
7728cfd8aa
commit
8f7591589b
File diff suppressed because it is too large
Load Diff
|
@ -1,281 +1,409 @@
|
|||
<template>
|
||||
<GiPageLayout>
|
||||
<div class="deformation-detection-container">
|
||||
<!-- 页面标题 -->
|
||||
<div class="raw-data-container">
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">形变检测</h2>
|
||||
<div class="page-title">原始数据管理</div>
|
||||
<div class="page-subtitle">管理和分析原始视频数据</div>
|
||||
</div>
|
||||
|
||||
<!-- 查询表单 -->
|
||||
<a-card class="search-card" :bordered="false">
|
||||
<a-form :model="searchForm" layout="inline">
|
||||
<a-form-item label="开始日期">
|
||||
<a-date-picker
|
||||
v-model="searchForm.startDate"
|
||||
style="width: 200px"
|
||||
format="YYYY-MM-DD"
|
||||
:placeholder="'选择开始日期'"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="至">
|
||||
<a-date-picker
|
||||
v-model="searchForm.endDate"
|
||||
style="width: 200px"
|
||||
format="YYYY-MM-DD"
|
||||
:placeholder="'选择结束日期'"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleSearch">
|
||||
<template #icon><icon-search /></template>
|
||||
查询
|
||||
<div class="action-bar">
|
||||
<div class="action-buttons">
|
||||
<a-button type="primary" @click="showUploadModal = true">
|
||||
<template #icon>
|
||||
<IconUpload />
|
||||
</template>
|
||||
上传视频
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button @click="handleExport">
|
||||
<template #icon><icon-download /></template>
|
||||
<a-button type="primary" @click="handleBatchAnalysis">
|
||||
<template #icon>
|
||||
<IconPlayCircle />
|
||||
</template>
|
||||
批量分析
|
||||
</a-button>
|
||||
<a-button type="primary" @click="handleExportData">
|
||||
<template #icon>
|
||||
<IconDownload />
|
||||
</template>
|
||||
导出数据
|
||||
</a-button>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<a-form :model="filterForm" layout="inline">
|
||||
<a-form-item label="项目">
|
||||
<a-select v-model="filterForm.projectId" placeholder="请选择项目">
|
||||
<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="请输入机组号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="状态">
|
||||
<a-select v-model="filterForm.status" placeholder="请选择状态">
|
||||
<a-option value="completed">已完成</a-option>
|
||||
<a-option value="pending">待分析</a-option>
|
||||
<a-option value="analyzing">分析中</a-option>
|
||||
<a-option value="failed">失败</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="handleFilterChange">查询</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-card>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<a-card class="table-card" :bordered="false">
|
||||
<a-table
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="pagination"
|
||||
@page-change="handlePageChange"
|
||||
@page-size-change="handlePageSizeChange"
|
||||
>
|
||||
<!-- 状态列 -->
|
||||
<template #status="{ record }">
|
||||
<a-tag :color="getStatusColor(record.status)">
|
||||
{{ getStatusText(record.status) }}
|
||||
</a-tag>
|
||||
</template>
|
||||
|
||||
<!-- 操作列 -->
|
||||
<template #action="{ record }">
|
||||
<a-link @click="handleViewDetail(record)">详情</a-link>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
|
||||
<!-- 详情模态框 -->
|
||||
<a-modal
|
||||
v-model:visible="detailModalVisible"
|
||||
title="形变检测详情"
|
||||
width="800px"
|
||||
@ok="detailModalVisible = false"
|
||||
@cancel="detailModalVisible = false"
|
||||
>
|
||||
<div v-if="selectedRecord" class="detail-content">
|
||||
<a-descriptions :column="2" bordered>
|
||||
<a-descriptions-item label="日期">
|
||||
{{ selectedRecord.date }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="机组编号">
|
||||
{{ selectedRecord.unitNumber }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="X方向形变量">
|
||||
{{ selectedRecord.xDeformation }}mm
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Y方向形变量">
|
||||
{{ selectedRecord.yDeformation }}mm
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="Z方向形变量">
|
||||
{{ selectedRecord.zDeformation }}mm
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(selectedRecord.status)">
|
||||
{{ getStatusText(selectedRecord.status) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="检测时间">
|
||||
{{ selectedRecord.detectionTime }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="备注">
|
||||
{{ selectedRecord.remark || '-' }}
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project-sections">
|
||||
<div v-for="project in filteredProjects" :key="project.id" class="project-section">
|
||||
<div class="project-header">
|
||||
<div class="project-title">{{ project.name }}</div>
|
||||
<div class="project-stats">
|
||||
<div class="stat-item">
|
||||
<IconVideoCamera />
|
||||
{{ project.totalVideos }} 个视频
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<IconCheckCircle />
|
||||
{{ project.completedCount }} 个已完成
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<IconClockCircle />
|
||||
{{ project.pendingCount }} 个待分析
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="units-grid">
|
||||
<div v-for="unit in project.units" :key="unit.id" class="unit-card">
|
||||
<div class="unit-header">
|
||||
<div class="unit-title">{{ unit.number }}</div>
|
||||
<div class="unit-actions">
|
||||
<a-button type="primary" size="small" @click="handleViewUnitVideos(unit)">查看全部</a-button>
|
||||
<a-button type="primary" size="small" @click="handleAnalyzeUnit(unit)">{{
|
||||
getAnalysisButtonText(unit.status)
|
||||
}}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="videos-list">
|
||||
<div v-for="video in unit.videos" :key="video.id" class="video-item">
|
||||
<div class="video-thumbnail">
|
||||
<img :src="video.thumbnail" alt="Video Thumbnail" />
|
||||
<div class="video-overlay" @click="handlePlayVideo(video)">
|
||||
<IconPlayArrowFill style="font-size: 24px; color: #fff;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="video-info">
|
||||
<div class="video-name">{{ video.name }}</div>
|
||||
<div class="video-meta">
|
||||
<span>{{ video.duration }}</span>
|
||||
<span>{{ video.angle }}°</span>
|
||||
</div>
|
||||
<div class="video-status">
|
||||
<a-tag :color="getStatusColor(video.status)">{{ getStatusText(video.status) }}</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="analysis-progress">
|
||||
<div class="progress-info">
|
||||
<span>分析进度</span>
|
||||
<span>{{ unit.progress }}%</span>
|
||||
</div>
|
||||
<a-progress :percent="unit.progress" :show-text="false" status="normal" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 视频播放模态框 -->
|
||||
<a-modal v-model:visible="videoModalVisible" title="原始视频播放" width="900px" @ok="videoModalVisible = false"
|
||||
@cancel="videoModalVisible = false">
|
||||
<video v-if="selectedVideo" :src="selectedVideo.url" controls
|
||||
style="width: 100%; height: 480px; border-radius: 8px; background: #000;"></video>
|
||||
<div v-if="selectedVideo" class="video-meta-info">
|
||||
<p>项目:{{ selectedVideo.projectName }}</p>
|
||||
<p>机组号:{{ selectedVideo.unitNumber }}</p>
|
||||
<p>采集人:{{ selectedVideo.collector }}</p>
|
||||
<p>风速:{{ selectedVideo.windSpeed }} m/s</p>
|
||||
<p>转速:{{ selectedVideo.rpm }} rpm</p>
|
||||
<p>采集时间:{{ selectedVideo.time }}</p>
|
||||
<p>角度:{{ selectedVideo.angle }}°</p>
|
||||
</div>
|
||||
</a-modal>
|
||||
|
||||
<!-- 上传视频模态框 -->
|
||||
<a-modal v-model:visible="showUploadModal" title="上传原始视频" width="600px" @ok="handleUpload"
|
||||
@cancel="showUploadModal = false">
|
||||
<a-form :model="uploadForm" layout="vertical">
|
||||
<a-form-item label="项目" required>
|
||||
<a-select v-model="uploadForm.projectId" placeholder="请选择项目">
|
||||
<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="机组号" required>
|
||||
<a-input v-model="uploadForm.unitNumber" placeholder="请输入机组号" />
|
||||
</a-form-item>
|
||||
<a-form-item label="采集人" required>
|
||||
<a-input v-model="uploadForm.collector" placeholder="请输入采集人姓名" />
|
||||
</a-form-item>
|
||||
<a-form-item label="风速 (m/s)">
|
||||
<a-input-number v-model="uploadForm.windSpeed" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="转速 (rpm)">
|
||||
<a-input-number v-model="uploadForm.rpm" :min="0" />
|
||||
</a-form-item>
|
||||
<a-form-item label="采集时间" required>
|
||||
<a-date-picker v-model="uploadForm.time" show-time format="YYYY-MM-DD HH:mm" style="width: 100%;" />
|
||||
</a-form-item>
|
||||
<a-form-item label="视频文件(可多选,建议3个角度)" required>
|
||||
<a-upload v-model:file-list="uploadForm.fileList" :multiple="true" :limit="3" accept="video/*"
|
||||
:auto-upload="false" list-type="picture-card">
|
||||
<template #upload-button>
|
||||
<a-button>选择视频</a-button>
|
||||
</template>
|
||||
</a-upload>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-modal>
|
||||
</div>
|
||||
</GiPageLayout>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, computed } from 'vue'
|
||||
import { Message } from '@arco-design/web-vue'
|
||||
import { IconSearch, IconDownload } from '@arco-design/web-vue/es/icon'
|
||||
import type { TableColumnData } from '@arco-design/web-vue'
|
||||
import {
|
||||
IconUpload,
|
||||
IconPlayCircle,
|
||||
IconDownload,
|
||||
IconVideoCamera,
|
||||
IconCheckCircle,
|
||||
IconClockCircle,
|
||||
IconPlayArrowFill
|
||||
} from '@arco-design/web-vue/es/icon'
|
||||
|
||||
defineOptions({ name: 'DeformationDetection' })
|
||||
const showUploadModal = ref(false)
|
||||
const videoModalVisible = ref(false)
|
||||
const selectedVideo = ref<any>(null)
|
||||
|
||||
// 搜索表单
|
||||
const searchForm = reactive({
|
||||
startDate: '',
|
||||
endDate: ''
|
||||
const filterForm = reactive({
|
||||
projectId: '',
|
||||
unitNumber: '',
|
||||
status: ''
|
||||
})
|
||||
|
||||
// 表格数据
|
||||
const tableData = ref([
|
||||
{
|
||||
id: 1,
|
||||
date: '2023-11-05',
|
||||
unitNumber: 'A-001',
|
||||
xDeformation: 2.1,
|
||||
yDeformation: 1.5,
|
||||
zDeformation: 0.8,
|
||||
status: 'normal',
|
||||
detectionTime: '2023-11-05 14:30:00',
|
||||
remark: '正常范围内'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
date: '2023-11-06',
|
||||
unitNumber: 'A-001',
|
||||
xDeformation: 2.3,
|
||||
yDeformation: 1.6,
|
||||
zDeformation: 0.9,
|
||||
status: 'normal',
|
||||
detectionTime: '2023-11-06 14:30:00',
|
||||
remark: '正常范围内'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
date: '2023-11-07',
|
||||
unitNumber: 'A-001',
|
||||
xDeformation: 3.5,
|
||||
yDeformation: 2.8,
|
||||
zDeformation: 1.2,
|
||||
status: 'warning',
|
||||
detectionTime: '2023-11-07 14:30:00',
|
||||
remark: '超出正常范围,需要关注'
|
||||
}
|
||||
])
|
||||
const uploadForm = reactive({
|
||||
projectId: '',
|
||||
unitNumber: '',
|
||||
collector: '',
|
||||
windSpeed: null,
|
||||
rpm: null,
|
||||
time: '',
|
||||
fileList: []
|
||||
})
|
||||
|
||||
// 表格配置
|
||||
const columns: TableColumnData[] = [
|
||||
// 示例数据结构
|
||||
const projects = ref([
|
||||
{
|
||||
title: '日期',
|
||||
dataIndex: 'date',
|
||||
width: 120
|
||||
id: 'project-1',
|
||||
name: '风电场A区',
|
||||
totalVideos: 6,
|
||||
completedCount: 4,
|
||||
pendingCount: 2,
|
||||
units: [
|
||||
{
|
||||
id: 'A-001',
|
||||
number: 'A-001',
|
||||
status: 'completed',
|
||||
progress: 100,
|
||||
videos: [
|
||||
{
|
||||
id: 'v1',
|
||||
name: 'A-001-正面',
|
||||
url: '/videos/A-001-front.mp4',
|
||||
thumbnail: '/images/A-001-front.jpg',
|
||||
angle: 0,
|
||||
duration: '00:30',
|
||||
status: 'completed',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-001',
|
||||
collector: '张三',
|
||||
windSpeed: 8.2,
|
||||
rpm: 15,
|
||||
time: '2023-11-05 08:00'
|
||||
},
|
||||
{
|
||||
title: '机组编号',
|
||||
dataIndex: 'unitNumber',
|
||||
width: 100
|
||||
id: 'v2',
|
||||
name: 'A-001-侧面',
|
||||
url: '/videos/A-001-side.mp4',
|
||||
thumbnail: '/images/A-001-side.jpg',
|
||||
angle: 90,
|
||||
duration: '00:30',
|
||||
status: 'completed',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-001',
|
||||
collector: '张三',
|
||||
windSpeed: 8.2,
|
||||
rpm: 15,
|
||||
time: '2023-11-05 08:00'
|
||||
},
|
||||
{
|
||||
title: 'X方向形变量(mm)',
|
||||
dataIndex: 'xDeformation',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: 'Y方向形变量(mm)',
|
||||
dataIndex: 'yDeformation',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: 'Z方向形变量(mm)',
|
||||
dataIndex: 'zDeformation',
|
||||
width: 140
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
width: 100
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
slotName: 'action',
|
||||
width: 100,
|
||||
fixed: 'right'
|
||||
id: 'v3',
|
||||
name: 'A-001-背面',
|
||||
url: '/videos/A-001-back.mp4',
|
||||
thumbnail: '/images/A-001-back.jpg',
|
||||
angle: 180,
|
||||
duration: '00:30',
|
||||
status: 'pending',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-001',
|
||||
collector: '张三',
|
||||
windSpeed: 8.2,
|
||||
rpm: 15,
|
||||
time: '2023-11-05 08:00'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'A-002',
|
||||
number: 'A-002',
|
||||
status: 'analyzing',
|
||||
progress: 60,
|
||||
videos: [
|
||||
{
|
||||
id: 'v4',
|
||||
name: 'A-002-正面',
|
||||
url: '/videos/A-002-front.mp4',
|
||||
thumbnail: '/images/A-002-front.jpg',
|
||||
angle: 0,
|
||||
duration: '00:28',
|
||||
status: 'analyzing',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-002',
|
||||
collector: '李四',
|
||||
windSpeed: 7.9,
|
||||
rpm: 14,
|
||||
time: '2023-11-05 12:00'
|
||||
},
|
||||
{
|
||||
id: 'v5',
|
||||
name: 'A-002-侧面',
|
||||
url: '/videos/A-002-side.mp4',
|
||||
thumbnail: '/images/A-002-side.jpg',
|
||||
angle: 90,
|
||||
duration: '00:28',
|
||||
status: 'pending',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-002',
|
||||
collector: '李四',
|
||||
windSpeed: 7.9,
|
||||
rpm: 14,
|
||||
time: '2023-11-05 12:00'
|
||||
},
|
||||
{
|
||||
id: 'v6',
|
||||
name: 'A-002-背面',
|
||||
url: '/videos/A-002-back.mp4',
|
||||
thumbnail: '/images/A-002-back.jpg',
|
||||
angle: 180,
|
||||
duration: '00:28',
|
||||
status: 'pending',
|
||||
projectName: '风电场A区',
|
||||
unitNumber: 'A-002',
|
||||
collector: '李四',
|
||||
windSpeed: 7.9,
|
||||
rpm: 14,
|
||||
time: '2023-11-05 12:00'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
// ... 其他项目
|
||||
])
|
||||
|
||||
// 分页配置
|
||||
const pagination = reactive({
|
||||
current: 1,
|
||||
pageSize: 10,
|
||||
total: 100,
|
||||
showTotal: true,
|
||||
showPageSize: true
|
||||
const filteredProjects = computed(() => {
|
||||
// 按筛选条件过滤
|
||||
return projects.value
|
||||
.filter(p => !filterForm.projectId || p.id === filterForm.projectId)
|
||||
.map(project => ({
|
||||
...project,
|
||||
units: project.units
|
||||
.filter(u => !filterForm.unitNumber || u.number === filterForm.unitNumber)
|
||||
.map(unit => ({
|
||||
...unit,
|
||||
videos: unit.videos.filter(v => !filterForm.status || v.status === filterForm.status)
|
||||
}))
|
||||
.filter(u => u.videos.length > 0)
|
||||
}))
|
||||
.filter(p => p.units.length > 0)
|
||||
})
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
// 详情模态框
|
||||
const detailModalVisible = ref(false)
|
||||
const selectedRecord = ref<any>(null)
|
||||
|
||||
// 状态颜色映射
|
||||
const getStatusColor = (status: string) => {
|
||||
const colorMap = {
|
||||
normal: 'green',
|
||||
warning: 'orange',
|
||||
error: 'red'
|
||||
}
|
||||
return colorMap[status] || 'gray'
|
||||
function handleFilterChange() {
|
||||
// 这里可以加API请求
|
||||
}
|
||||
|
||||
// 状态文本映射
|
||||
const getStatusText = (status: string) => {
|
||||
const textMap = {
|
||||
normal: '正常',
|
||||
warning: '警告',
|
||||
error: '异常'
|
||||
}
|
||||
return textMap[status] || '未知'
|
||||
function handlePlayVideo(video: any) {
|
||||
selectedVideo.value = video
|
||||
videoModalVisible.value = true
|
||||
}
|
||||
|
||||
// 查询处理
|
||||
const handleSearch = () => {
|
||||
loading.value = true
|
||||
|
||||
// 模拟API调用
|
||||
setTimeout(() => {
|
||||
Message.success('查询成功')
|
||||
loading.value = false
|
||||
}, 1000)
|
||||
function handleViewUnitVideos(unit: any) {
|
||||
// 可扩展为弹窗或跳转页面,展示该机组所有视频
|
||||
Message.info(`查看机组 ${unit.number} 的所有视频`)
|
||||
}
|
||||
|
||||
// 导出数据
|
||||
const handleExport = () => {
|
||||
Message.success('导出成功')
|
||||
function handleAnalyzeUnit(unit: any) {
|
||||
// 触发分析流程
|
||||
Message.success(`已提交机组 ${unit.number} 的分析任务`)
|
||||
// 实际应调用API并刷新状态
|
||||
}
|
||||
|
||||
// 分页变化
|
||||
const handlePageChange = (page: number) => {
|
||||
pagination.current = page
|
||||
handleSearch()
|
||||
function getStatusColor(status: string) {
|
||||
switch (status) {
|
||||
case 'completed': return 'green'
|
||||
case 'pending': return 'gray'
|
||||
case 'analyzing': return 'blue'
|
||||
case 'failed': return 'red'
|
||||
default: return 'gray'
|
||||
}
|
||||
}
|
||||
function getStatusText(status: string) {
|
||||
switch (status) {
|
||||
case 'completed': return '已完成'
|
||||
case 'pending': return '待分析'
|
||||
case 'analyzing': return '分析中'
|
||||
case 'failed': return '失败'
|
||||
default: return '未知'
|
||||
}
|
||||
}
|
||||
function getAnalysisButtonText(status: string) {
|
||||
switch (status) {
|
||||
case 'completed': return '重新分析'
|
||||
case 'pending': return '分析'
|
||||
case 'analyzing': return '分析中...'
|
||||
case 'failed': return '重新分析'
|
||||
default: return '分析'
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageSizeChange = (pageSize: number) => {
|
||||
pagination.pageSize = pageSize
|
||||
pagination.current = 1
|
||||
handleSearch()
|
||||
function handleBatchAnalysis() {
|
||||
Message.success('批量分析任务已提交')
|
||||
}
|
||||
|
||||
// 查看详情
|
||||
const handleViewDetail = (record: any) => {
|
||||
selectedRecord.value = record
|
||||
detailModalVisible.value = true
|
||||
function handleExportData() {
|
||||
Message.success('数据导出成功')
|
||||
}
|
||||
function handleUpload() {
|
||||
Message.success('上传成功')
|
||||
showUploadModal.value = false
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
handleSearch()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.deformation-detection-container {
|
||||
.raw-data-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
@ -284,28 +412,179 @@ onMounted(() => {
|
|||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
margin: 0 0 8px 0;
|
||||
color: #1d2129;
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 20px;
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
color: #86909c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table-card {
|
||||
.arco-table {
|
||||
.arco-table-th {
|
||||
background-color: #f7f8fa;
|
||||
.action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.filter-section {
|
||||
margin-left: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-sections {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
.arco-descriptions {
|
||||
.units-grid {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.unit-card {
|
||||
background: #fafbfc;
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
width: 360px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
.unit-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.unit-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.unit-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.videos-list {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
|
||||
.video-item {
|
||||
width: 100px;
|
||||
|
||||
.video-thumbnail {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.video-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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-info {
|
||||
margin-top: 4px;
|
||||
|
||||
.video-name {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #1d2129;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.video-meta {
|
||||
font-size: 11px;
|
||||
color: #86909c;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.video-status {
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-progress {
|
||||
margin-top: 8px;
|
||||
|
||||
.progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #86909c;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-meta-info {
|
||||
margin-top: 16px;
|
||||
font-size: 13px;
|
||||
color: #4e5969;
|
||||
|
||||
p {
|
||||
margin: 2px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,17 +1,797 @@
|
|||
<template>
|
||||
<div class="WideAngleVideo-container">
|
||||
<a-card title="广角视频" :bordered="false">
|
||||
<a-empty description="广角视频模块正在建设中..." />
|
||||
</a-card>
|
||||
<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">
|
||||
defineOptions({ name: 'WideAngleVideo' })
|
||||
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">
|
||||
.WideAngleVideo-container {
|
||||
.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>
|
Loading…
Reference in New Issue