净空前端,在原数据管理右边标签页,后端需同步配置才有用
This commit is contained in:
parent
7c455f59eb
commit
eba5e68e2f
|
@ -0,0 +1,55 @@
|
||||||
|
import http from '@/utils/http'
|
||||||
|
|
||||||
|
/* 分页查询 */
|
||||||
|
export function getVideoPage(params: {
|
||||||
|
pageNo: number
|
||||||
|
pageSize: number
|
||||||
|
projectId: string
|
||||||
|
turbineId?: string
|
||||||
|
}) {
|
||||||
|
return http.get('/video-monitor/page', params )
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 单文件上传 */
|
||||||
|
export function uploadSingleVideo(
|
||||||
|
projectId: string,
|
||||||
|
turbineId: string,
|
||||||
|
type: string,
|
||||||
|
file: File
|
||||||
|
) {
|
||||||
|
const fd = new FormData()
|
||||||
|
fd.append('file', file)
|
||||||
|
return http.post(
|
||||||
|
`/video-monitor/${projectId}/upload?turbineId=${turbineId}&type=${type}`,
|
||||||
|
fd,
|
||||||
|
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 批量上传 */
|
||||||
|
export function uploadBatchVideo(
|
||||||
|
projectId: string,
|
||||||
|
turbineId: string,
|
||||||
|
type: string,
|
||||||
|
files: File[]
|
||||||
|
) {
|
||||||
|
const fd = new FormData()
|
||||||
|
files.forEach(f => fd.append('files', f))
|
||||||
|
return http.post(
|
||||||
|
`/video-monitor/${projectId}/upload-batch?turbineId=${turbineId}&type=${type}`,
|
||||||
|
fd,
|
||||||
|
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 删除 */
|
||||||
|
export function deleteVideo(videoId: string) {
|
||||||
|
return http.del(`/video-monitor/${videoId}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下载 */
|
||||||
|
export function downloadVideo(videoId: string) {
|
||||||
|
return http
|
||||||
|
.get(`/video-monitor/download/${videoId}`, { responseType: 'blob' })
|
||||||
|
.then(blob => URL.createObjectURL(blob))
|
||||||
|
}
|
|
@ -1,407 +1,311 @@
|
||||||
<template>
|
<template>
|
||||||
<GiPageLayout>
|
<GiPageLayout>
|
||||||
<div class="raw-data-container">
|
<div class="raw-data-container">
|
||||||
<!-- <div class="page-header">
|
<!-- 顶部按钮 -->
|
||||||
<div class="page-title">原始数据管理</div>
|
|
||||||
<div class="page-subtitle">管理和分析原始视频数据</div>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
<div class="action-bar">
|
<div class="action-bar">
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<a-button type="primary" @click="showUploadModal = true">
|
<a-button type="primary" @click="openUploadModal">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<IconUpload />
|
<IconUpload />
|
||||||
</template>
|
</template>
|
||||||
上传视频
|
上传视频
|
||||||
</a-button>
|
</a-button>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="project-sections">
|
<!-- 筛选 -->
|
||||||
<div v-for="project in filteredProjects" :key="project.id" class="project-section">
|
<div class="filter-section">
|
||||||
<div class="project-header">
|
<a-form :model="filterForm" layout="inline">
|
||||||
<div class="project-title">{{ project.name }}</div>
|
<a-form-item label="项目" required>
|
||||||
<div class="project-stats">
|
<a-select v-model="filterForm.projectId" placeholder="请选择项目" :options="projectOptions" allow-clear
|
||||||
<div class="stat-item">
|
@change="" />
|
||||||
<IconVideoCamera />
|
</a-form-item>
|
||||||
{{ project.totalVideos }} 个视频
|
<a-form-item label="机组">
|
||||||
</div>
|
<a-select v-model="filterForm.turbineId" placeholder="请选择机组" :options="turbineOptions" allow-clear
|
||||||
<div class="stat-item">
|
:disabled="!filterForm.projectId" />
|
||||||
<IconCheckCircle />
|
</a-form-item>
|
||||||
{{ project.completedCount }} 个已完成
|
<a-form-item>
|
||||||
</div>
|
<a-button type="primary" @click="handleQuery">查询</a-button>
|
||||||
<div class="stat-item">
|
</a-form-item>
|
||||||
<IconClockCircle />
|
</a-form>
|
||||||
{{ 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="active" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 视频播放模态框 -->
|
<!-- 列表 -->
|
||||||
<a-modal v-model:visible="videoModalVisible" title="原始视频播放" width="900px" @ok="videoModalVisible = false"
|
<a-table :columns="columns" :data="tableData" :pagination="pagination" :loading="loading"
|
||||||
@cancel="videoModalVisible = false">
|
:scroll="{ y: 'calc(100vh - 380px)' }">
|
||||||
<video v-if="selectedVideo" :src="selectedVideo.url" controls
|
<template #type="{ record }">
|
||||||
style="width: 100%; height: 480px; border-radius: 8px; background: #000;"></video>
|
<a-tag>{{ record.type === 'clearance' ? '净空' : '形变' }}</a-tag>
|
||||||
<div v-if="selectedVideo" class="video-meta-info">
|
</template>
|
||||||
<p>项目:{{ selectedVideo.projectName }}</p>
|
<template #status="{ record }">
|
||||||
<p>机组号:{{ selectedVideo.unitNumber }}</p>
|
<a-tag :color="record.preTreatment ? 'green' : 'red'">
|
||||||
<p>采集人:{{ selectedVideo.collector }}</p>
|
{{ record.preTreatment ? '已处理' : '未处理' }}
|
||||||
<p>风速:{{ selectedVideo.windSpeed }} m/s</p>
|
</a-tag>
|
||||||
<p>转速:{{ selectedVideo.rpm }} rpm</p>
|
</template>
|
||||||
<p>采集时间:{{ selectedVideo.time }}</p>
|
<template #action="{ record }">
|
||||||
<p>角度:{{ selectedVideo.angle }}°</p>
|
<a-space>
|
||||||
</div>
|
<a-button size="mini" @click="handlePreview(record)">预览</a-button>
|
||||||
</a-modal>
|
<a-button size="mini" @click="handleDownload(record)">下载</a-button>
|
||||||
|
<a-popconfirm content="确认删除?" @ok="handleDelete(record)">
|
||||||
|
<a-button size="mini" status="danger">删除</a-button>
|
||||||
|
</a-popconfirm>
|
||||||
|
</a-space>
|
||||||
|
|
||||||
<!-- 上传视频模态框 -->
|
</template>
|
||||||
<a-modal v-model:visible="showUploadModal" title="上传原始视频" width="600px" @ok="handleUpload"
|
</a-table>
|
||||||
|
|
||||||
|
<!-- 上传弹窗 -->
|
||||||
|
<a-modal v-model:visible="showUploadModal" title="上传原始视频" width="600px" :ok-loading="uploading" @ok="handleUpload"
|
||||||
@cancel="showUploadModal = false">
|
@cancel="showUploadModal = false">
|
||||||
<a-form :model="uploadForm" layout="vertical">
|
<a-form :model="uploadForm" layout="vertical">
|
||||||
<a-form-item label="项目" required>
|
<a-form-item label="项目" required>
|
||||||
<a-select v-model="uploadForm.projectId" placeholder="请选择项目">
|
<a-select v-model="uploadForm.projectId" placeholder="请选择项目" :options="projectOptions" allow-clear
|
||||||
<a-option value="project-1">风电场A区</a-option>
|
@change="onProjectChangeUpload" />
|
||||||
<a-option value="project-2">风电场B区</a-option>
|
|
||||||
<a-option value="project-3">风电场C区</a-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="机组号" required>
|
<a-form-item label="机组">
|
||||||
<a-input v-model="uploadForm.unitNumber" placeholder="请输入机组号" />
|
<a-select v-model="uploadForm.turbineId" placeholder="请选择机组" :options="turbineOptionsUpload" allow-clear
|
||||||
|
:disabled="!uploadForm.projectId" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="采集人" required>
|
<a-form-item label="类型" required>
|
||||||
<a-input v-model="uploadForm.collector" placeholder="请输入采集人姓名" />
|
<a-select v-model="uploadForm.type" placeholder="请选择类型" :options="typeOptions" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="风速 (m/s)">
|
<a-form-item label="视频文件" required>
|
||||||
<a-input-number v-model="uploadForm.windSpeed" :min="0" />
|
<a-upload v-model:file-list="uploadForm.fileList" :multiple="uploadMode === 'batch'"
|
||||||
|
:limit="uploadMode === 'batch' ? 10 : 1" accept="video/*" :auto-upload="false" list-type="picture-card" />
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
<a-form-item label="转速 (rpm)">
|
<a-form-item>
|
||||||
<a-input-number v-model="uploadForm.rpm" :min="0" />
|
<a-radio-group v-model="uploadMode" type="button">
|
||||||
</a-form-item>
|
<a-radio value="single">单文件</a-radio>
|
||||||
<a-form-item label="采集时间" required>
|
<a-radio value="batch">批量</a-radio>
|
||||||
<a-date-picker v-model="uploadForm.time" show-time format="YYYY-MM-DD HH:mm" style="width: 100%;" />
|
</a-radio-group>
|
||||||
</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-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
</a-modal>
|
</a-modal>
|
||||||
</div>
|
</div>
|
||||||
</GiPageLayout>
|
</GiPageLayout>
|
||||||
|
<!-- 视频预览弹窗 -->
|
||||||
|
<a-modal v-model:visible="previewVisible" title="视频预览" width="800px" :footer="false" @cancel="previewVisible = false">
|
||||||
|
<a-tabs v-model:active-key="activePreviewTab" @change="activePreviewTab = $event as any">
|
||||||
|
<!-- 原始视频 -->
|
||||||
|
<a-tab-pane key="video" title="原始视频">
|
||||||
|
<video v-if="previewUrl" :src="previewUrl" controls
|
||||||
|
style="width: 100%; max-height: 60vh; border-radius: 4px"></video>
|
||||||
|
</a-tab-pane>
|
||||||
|
|
||||||
|
<!-- 处理结果 -->
|
||||||
|
<a-tab-pane key="result" title="处理结果">
|
||||||
|
<a-spin :loading="loadingResult">
|
||||||
|
<a-space direction="vertical" size="medium" style="width: 100%">
|
||||||
|
<!-- 图片 -->
|
||||||
|
<img v-if="resultImgUrl" :src="resultImgUrl" style="max-width: 100%; border-radius: 4px" alt="last frame" />
|
||||||
|
|
||||||
|
<!-- JSON 预览 -->
|
||||||
|
<a-card title="results.json" size="small">
|
||||||
|
<pre>{{ JSON.stringify(resultJson, null, 2) }}</pre>
|
||||||
|
</a-card>
|
||||||
|
</a-space>
|
||||||
|
</a-spin>
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</a-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed } from 'vue'
|
import { ref, reactive, onMounted, watch } from 'vue'
|
||||||
import { Message } from '@arco-design/web-vue'
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import type { TableColumnData } from '@arco-design/web-vue'
|
||||||
|
import { IconUpload } from '@arco-design/web-vue/es/icon'
|
||||||
import {
|
import {
|
||||||
IconUpload,
|
getProjectList,
|
||||||
IconPlayCircle,
|
getTurbineList
|
||||||
IconDownload,
|
} from '@/apis/industrial-image'
|
||||||
IconVideoCamera,
|
import {
|
||||||
IconCheckCircle,
|
getVideoPage,
|
||||||
IconClockCircle,
|
uploadBatchVideo,
|
||||||
IconPlayArrowFill
|
uploadSingleVideo,
|
||||||
} from '@arco-design/web-vue/es/icon'
|
deleteVideo,
|
||||||
|
downloadVideo
|
||||||
|
} from '@/apis/video-monitor'
|
||||||
|
|
||||||
const showUploadModal = ref(false)
|
/* ---------------- 下拉 & 表单 ---------------- */
|
||||||
const videoModalVisible = ref(false)
|
const projectOptions = ref<{ label: string; value: string }[]>([])
|
||||||
const selectedVideo = ref<any>(null)
|
const turbineOptions = ref<{ label: string; value: string }[]>([]) // 筛选用
|
||||||
|
const turbineOptionsUpload = ref<{ label: string; value: string }[]>([]) // 上传弹窗用
|
||||||
|
const typeOptions = [
|
||||||
|
{ label: '净空', value: 'clearance' },
|
||||||
|
{ label: '形变', value: 'deformation' }
|
||||||
|
]
|
||||||
|
|
||||||
const filterForm = reactive({
|
const filterForm = reactive({
|
||||||
projectId: '',
|
projectId: '',
|
||||||
unitNumber: '',
|
turbineId: ''
|
||||||
status: ''
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const uploadForm = reactive({
|
const uploadForm = reactive({
|
||||||
projectId: '',
|
projectId: '',
|
||||||
unitNumber: '',
|
turbineId: '',
|
||||||
collector: '',
|
type: '',
|
||||||
windSpeed: null,
|
fileList: [] as any[]
|
||||||
rpm: null,
|
|
||||||
time: '',
|
|
||||||
fileList: []
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 示例数据结构
|
const uploadMode = ref<'single' | 'batch'>('single')
|
||||||
const projects = ref([
|
|
||||||
{
|
|
||||||
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'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
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 filteredProjects = computed(() => {
|
/* ---------------- 列表 ---------------- */
|
||||||
// 按筛选条件过滤
|
const columns: TableColumnData[] = [
|
||||||
return projects.value
|
{ title: '文件名', dataIndex: 'videoName', ellipsis: true, width: 220 },
|
||||||
.filter(p => !filterForm.projectId || p.id === filterForm.projectId)
|
// { title: '项目', dataIndex: 'projectName' },
|
||||||
.map(project => ({
|
// { title: '机组', dataIndex: 'turbineName' },
|
||||||
...project,
|
{ title: '类型', slotName: 'type' },
|
||||||
units: project.units
|
{ title: '上传时间', dataIndex: 'uploadTime' },
|
||||||
.filter(u => !filterForm.unitNumber || u.number === filterForm.unitNumber)
|
{ title: '状态', slotName: 'status' },
|
||||||
.map(unit => ({
|
{ title: '操作', slotName: 'action', width: 120, fixed: 'right' }
|
||||||
...unit,
|
]
|
||||||
videos: unit.videos.filter(v => !filterForm.status || v.status === filterForm.status)
|
|
||||||
}))
|
const tableData = ref<any[]>([])
|
||||||
.filter(u => u.videos.length > 0)
|
const loading = ref(false)
|
||||||
}))
|
const pagination = reactive({ current: 1, pageSize: 20, total: 0 })
|
||||||
.filter(p => p.units.length > 0)
|
|
||||||
|
/* ---------------- 控制弹窗 ---------------- */
|
||||||
|
const showUploadModal = ref(false)
|
||||||
|
const uploading = ref(false)
|
||||||
|
|
||||||
|
/* ---------------- 初始化 ---------------- */
|
||||||
|
onMounted(async () => {
|
||||||
|
const { data } = await getProjectList({ page: 1, pageSize: 1000 })
|
||||||
|
projectOptions.value = data.map((p: any) => ({ label: p.projectName, value: p.projectId }))
|
||||||
|
handleQuery()
|
||||||
})
|
})
|
||||||
|
const activePreviewTab = ref<'video' | 'result'>('video') // 当前标签页
|
||||||
|
const resultImgUrl = ref('')
|
||||||
|
const resultJson = ref<Record<string, any>>({})
|
||||||
|
const loadingResult = ref(false)
|
||||||
|
|
||||||
function handleFilterChange() {
|
async function loadResultFiles(row: any) {
|
||||||
// 这里可以加API请求
|
if (!row.preTreatment) return
|
||||||
|
loadingResult.value = true
|
||||||
|
try {
|
||||||
|
const base = import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, '')
|
||||||
|
// 图片
|
||||||
|
resultImgUrl.value = `${base}${row.preImagePath}/last_frame.jpg`
|
||||||
|
|
||||||
|
// JSON
|
||||||
|
const jsonUrl = `${base}${row.preImagePath}/results.json`
|
||||||
|
const res = await fetch(jsonUrl)
|
||||||
|
resultJson.value = await res.json()
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
resultJson.value = {}
|
||||||
|
} finally {
|
||||||
|
loadingResult.value = false
|
||||||
|
}
|
||||||
|
console.log('result', resultImgUrl.value)
|
||||||
}
|
}
|
||||||
|
/* 项目 -> 机组(筛选) */
|
||||||
|
watch(
|
||||||
|
() => filterForm.projectId,
|
||||||
|
async (val) => {
|
||||||
|
filterForm.turbineId = ''
|
||||||
|
turbineOptions.value = []
|
||||||
|
if (!val) return
|
||||||
|
const { data } = await getTurbineList({ projectId: val })
|
||||||
|
turbineOptions.value = data.map((t: any) => ({ label: t.turbineName, value: t.turbineId }))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const previewVisible = ref(false)
|
||||||
|
const previewUrl = ref('')
|
||||||
|
|
||||||
function handlePlayVideo(video: any) {
|
function handlePreview(row: any) {
|
||||||
selectedVideo.value = video
|
const base = import.meta.env.VITE_API_BASE_URL.replace(/\/+$/, '')
|
||||||
videoModalVisible.value = true
|
previewUrl.value = new URL(row.videoPath.replace(/^\/+/, ''), base).href
|
||||||
}
|
previewVisible.value = true
|
||||||
|
activePreviewTab.value = 'video' // 默认先显示视频
|
||||||
function handleViewUnitVideos(unit: any) {
|
if (row.preTreatment) {
|
||||||
// 可扩展为弹窗或跳转页面,展示该机组所有视频
|
loadResultFiles(row) // 预加载结果
|
||||||
Message.info(`查看机组 ${unit.number} 的所有视频`)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleAnalyzeUnit(unit: any) {
|
|
||||||
// 触发分析流程
|
|
||||||
Message.success(`已提交机组 ${unit.number} 的分析任务`)
|
|
||||||
// 实际应调用API并刷新状态
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
async function onProjectChangeUpload(projectId: string) {
|
||||||
case 'completed': return '已完成'
|
uploadForm.turbineId = ''
|
||||||
case 'pending': return '待分析'
|
turbineOptionsUpload.value = []
|
||||||
case 'analyzing': return '分析中'
|
if (!projectId) return
|
||||||
case 'failed': return '失败'
|
const { data } = await getTurbineList({ projectId })
|
||||||
default: return '未知'
|
turbineOptionsUpload.value = data.map((t: any) => ({ label: t.turbineName, value: t.turbineId }))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function getAnalysisButtonText(status: string) {
|
|
||||||
switch (status) {
|
/* ---------------- 查询 ---------------- */
|
||||||
case 'completed': return '重新分析'
|
function handleQuery() {
|
||||||
case 'pending': return '分析'
|
pagination.current = 1
|
||||||
case 'analyzing': return '分析中...'
|
loadTable()
|
||||||
case 'failed': return '重新分析'
|
}
|
||||||
default: return '分析'
|
async function loadTable() {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
pageNo: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
projectId: filterForm.projectId,
|
||||||
|
turbineId: filterForm.turbineId || undefined
|
||||||
|
}
|
||||||
|
const { data } = await getVideoPage(params)
|
||||||
|
console.log(data)
|
||||||
|
tableData.value = data
|
||||||
|
pagination.total = data.length
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBatchAnalysis() {
|
/* ---------------- 上传 ---------------- */
|
||||||
Message.success('批量分析任务已提交')
|
function openUploadModal() {
|
||||||
|
uploadForm.projectId = ''
|
||||||
|
uploadForm.turbineId = ''
|
||||||
|
uploadForm.type = ''
|
||||||
|
uploadForm.fileList = []
|
||||||
|
showUploadModal.value = true
|
||||||
}
|
}
|
||||||
function handleExportData() {
|
async function handleUpload() {
|
||||||
Message.success('数据导出成功')
|
if (!uploadForm.projectId || !uploadForm.type || !uploadForm.fileList.length) {
|
||||||
|
Message.warning('请完整填写')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uploading.value = true
|
||||||
|
try {
|
||||||
|
const files = uploadForm.fileList.map((f: any) => f.file)
|
||||||
|
if (uploadMode.value === 'single') {
|
||||||
|
await uploadSingleVideo(
|
||||||
|
uploadForm.projectId,
|
||||||
|
uploadForm.turbineId || '',
|
||||||
|
uploadForm.type,
|
||||||
|
files[0]
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
await uploadBatchVideo(
|
||||||
|
uploadForm.projectId,
|
||||||
|
uploadForm.turbineId || '',
|
||||||
|
uploadForm.type,
|
||||||
|
files
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Message.success('上传成功')
|
||||||
|
showUploadModal.value = false
|
||||||
|
loadTable()
|
||||||
|
} finally {
|
||||||
|
uploading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function handleUpload() {
|
|
||||||
Message.success('上传成功')
|
/* ---------------- 下载 / 删除 ---------------- */
|
||||||
showUploadModal.value = false
|
async function handleDownload(row: any) {
|
||||||
|
const url = await downloadVideo(row.videoId)
|
||||||
|
window.open(url, '_blank')
|
||||||
|
}
|
||||||
|
async function handleDelete(row: any) {
|
||||||
|
await deleteVideo(row.videoId)
|
||||||
|
Message.success('删除成功')
|
||||||
|
loadTable()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.raw-data-container {
|
.raw-data-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
Loading…
Reference in New Issue