导出下载

This commit is contained in:
cwchen 2025-12-26 11:15:04 +08:00
parent 1a82946c5a
commit 2d5a6e8254
4 changed files with 463 additions and 36 deletions

View File

@ -9,10 +9,28 @@ export function imageRecognitionListAPI(params) {
})
}
// 设备管理->图像识别->导出图像识别数据
// 设备管理->图像识别->导出图像识别数据(启动导出任务)
export function exportImageRecognitionAPI(params) {
return request({
url: '/smartCar/device/imageRecognition/exportImageRecognition',
url: '/smartCar/device/imageRecognition/export',
method: 'GET',
params
})
}
// 设备管理->图像识别->查询导出进度
export function getExportProgressAPI(params) {
return request({
url: '/smartCar/device/imageRecognition/progress',
method: 'GET',
params
})
}
// 设备管理->图像识别->下载导出文件
export function downloadExportFileAPI(params) {
return request({
url: '/smartCar/device/imageRecognition/download',
method: 'GET',
params,
responseType: 'blob'

View File

@ -87,6 +87,11 @@ export const constantRoutes = [
meta: { title: '个人中心', icon: 'user' }
}
]
},
{
path: '/device/image-recognition/export-progress',
component: () => import('@/views/device/image-recognition/ExportProgress'),
hidden: true
}
]

View File

@ -0,0 +1,414 @@
<template>
<div class="export-progress-container">
<div class="progress-card">
<div class="card-header">
<h3 class="title">导出进度</h3>
</div>
<div class="card-body">
<!-- 进度显示 -->
<div v-if="!isCompleted && !isError" class="progress-section">
<div class="progress-info">
<i class="el-icon-loading loading-icon"></i>
<span class="progress-text">{{ progressText }}</span>
</div>
<el-progress
:percentage="progress"
:status="progressStatus"
:stroke-width="8"
class="progress-bar">
</el-progress>
<div class="progress-detail">
<span>正在生成压缩包请稍候...</span>
</div>
</div>
<!-- 完成状态 -->
<div v-if="isCompleted" class="completed-section">
<div class="success-icon">
<i class="el-icon-success"></i>
</div>
<div class="success-text">压缩包生成完成</div>
<el-button
type="primary"
class="download-btn"
@click="handleDownload">
<i class="el-icon-download"></i>
下载文件
</el-button>
</div>
<!-- 错误状态 -->
<div v-if="isError" class="error-section">
<div class="error-icon">
<i class="el-icon-error"></i>
</div>
<div class="error-text">{{ errorMessage || '导出失败,请稍后重试' }}</div>
<el-button
type="default"
class="close-btn"
@click="handleClose">
关闭窗口
</el-button>
</div>
</div>
</div>
</div>
</template>
<script>
import { exportImageRecognitionAPI, getExportProgressAPI, downloadExportFileAPI } from '@/api/device/image-recognition'
export default {
name: 'ExportProgress',
data() {
return {
progress: 0,
progressText: '0%',
progressStatus: '',
isCompleted: false,
isError: false,
errorMessage: '',
taskId: null,
downloadUrl: null,
fileName: null,
pollTimer: null,
params: {}
}
},
created() {
// URL
const urlParams = new URLSearchParams(window.location.search)
const paramsStr = urlParams.get('params')
if (paramsStr) {
try {
this.params = JSON.parse(decodeURIComponent(paramsStr))
} catch (e) {
console.error('解析参数失败:', e)
this.isError = true
this.errorMessage = '参数解析失败'
return
}
}
//
this.startExport()
},
beforeDestroy() {
//
if (this.pollTimer) {
clearInterval(this.pollTimer)
}
},
methods: {
/** 开始导出任务 */
async startExport() {
try {
//
const res = await exportImageRecognitionAPI(this.params)
if (res.code === 200) {
// ID
if (res.data && res.data.taskId) {
this.taskId = res.data.taskId
//
this.startPolling()
} else if (res.data && res.data.downloadUrl) {
//
this.downloadUrl = res.data.downloadUrl
this.fileName = res.data.fileName || `图像识别数据_${new Date().getTime()}.zip`
this.isCompleted = true
this.progress = 100
} else if (res.data && typeof res.data === 'string') {
// ID
this.taskId = res.data
this.startPolling()
} else if (res.taskId) {
// ID
this.taskId = res.taskId
this.startPolling()
} else {
// ID
this.taskId = res.data || res.taskId
if (this.taskId) {
this.startPolling()
} else {
this.isError = true
this.errorMessage = '未获取到任务ID'
}
}
} else {
this.isError = true
this.errorMessage = res.msg || '启动导出任务失败'
}
} catch (error) {
console.error('启动导出任务失败:', error)
this.isError = true
this.errorMessage = error.message || '启动导出任务失败,请稍后重试'
}
},
/** 开始轮询进度 */
startPolling() {
//
this.checkProgress()
// 2
this.pollTimer = setInterval(() => {
this.checkProgress()
}, 2000)
},
/** 查询导出进度 */
async checkProgress() {
if (!this.taskId) {
this.isError = true
this.errorMessage = '任务ID不存在'
return
}
try {
const res = await getExportProgressAPI({ taskId: this.taskId })
if (res.code === 200) {
const data = res.data
//
this.progress = data.progress || 0
this.progressText = `${this.progress}%`
// 100%
if (this.progress >= 100 || data.status === 'completed' || data.status === 'success') {
this.isCompleted = true
this.downloadUrl = data.downloadUrl
this.fileName = data.fileName || `图像识别数据_${new Date().getTime()}.zip`
//
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
} else if (data.status === 'failed' || data.status === 'error') {
//
this.isError = true
this.errorMessage = data.message || '导出任务失败'
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
}
} else {
this.isError = true
this.errorMessage = res.msg || '查询进度失败'
if (this.pollTimer) {
clearInterval(this.pollTimer)
this.pollTimer = null
}
}
} catch (error) {
console.error('查询进度失败:', error)
//
}
},
/** 下载文件 */
async handleDownload() {
try {
let blob
let fileName = this.fileName || `图像识别数据_${new Date().getTime()}.zip`
if (this.downloadUrl && this.downloadUrl.startsWith('http')) {
// URL
window.open(this.downloadUrl, '_blank')
this.$message.success('开始下载')
return
} else if (this.taskId) {
// 使ID
const res = await downloadExportFileAPI({
taskId: this.taskId
})
// blob
if (res instanceof Blob) {
blob = res
} else if (res.code === 200 && res.data) {
blob = res.data instanceof Blob ? res.data : new Blob([res.data], { type: 'application/zip' })
if (res.data.fileName) {
fileName = res.data.fileName
}
} else {
this.$message.error(res.msg || '下载失败')
return
}
} else if (this.downloadUrl) {
// 使URL
const res = await downloadExportFileAPI({
downloadUrl: this.downloadUrl
})
if (res instanceof Blob) {
blob = res
} else if (res.code === 200 && res.data) {
blob = res.data instanceof Blob ? res.data : new Blob([res.data], { type: 'application/zip' })
} else {
this.$message.error(res.msg || '下载失败')
return
}
} else {
this.$message.error('下载地址不存在')
return
}
//
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = fileName
link.click()
window.URL.revokeObjectURL(url)
this.$message.success('下载成功')
} catch (error) {
console.error('下载失败:', error)
// 使downloadUrl
if (this.downloadUrl && this.downloadUrl.startsWith('http')) {
window.open(this.downloadUrl, '_blank')
} else {
this.$message.error('下载失败,请稍后重试')
}
}
},
/** 关闭窗口 */
handleClose() {
window.close()
}
}
}
</script>
<style scoped lang="scss">
.export-progress-container {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(180deg, #f1f6ff 20%, #e5efff 100%);
padding: 20px;
.progress-card {
width: 500px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
overflow: hidden;
.card-header {
padding: 20px 24px;
border-bottom: 1px solid #ebeef5;
background: #fff;
.title {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
}
}
.card-body {
padding: 40px 24px;
.progress-section {
text-align: center;
.progress-info {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20px;
.loading-icon {
font-size: 24px;
color: #1f72ea;
margin-right: 12px;
animation: rotating 2s linear infinite;
}
.progress-text {
font-size: 16px;
font-weight: 600;
color: #333;
}
}
.progress-bar {
margin-bottom: 16px;
}
.progress-detail {
color: #606266;
font-size: 14px;
}
}
.completed-section {
text-align: center;
.success-icon {
margin-bottom: 20px;
i {
font-size: 64px;
color: #67c23a;
}
}
.success-text {
font-size: 16px;
color: #333;
margin-bottom: 24px;
font-weight: 500;
}
.download-btn {
width: 160px;
height: 40px;
background: #1f72ea;
box-shadow: 0px 4px 8px 0px rgba(51, 135, 255, 0.5);
border-radius: 4px;
border: none;
font-size: 14px;
&:hover {
background: #4a8bff;
box-shadow: 0px 6px 12px 0px rgba(51, 135, 255, 0.6);
}
}
}
.error-section {
text-align: center;
.error-icon {
margin-bottom: 20px;
i {
font-size: 64px;
color: #f56c6c;
}
}
.error-text {
font-size: 16px;
color: #333;
margin-bottom: 24px;
}
.close-btn {
width: 160px;
height: 40px;
border-radius: 4px;
font-size: 14px;
}
}
}
}
}
@keyframes rotating {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>

View File

@ -184,13 +184,13 @@ export default {
const list = (res.rows || []).map(item => {
// 使 test_image
let imageUrl = this.test_image
if (item.filePath) {
/* if (item.filePath) {
if (isExternal(item.filePath)) {
imageUrl = item.filePath
} else {
imageUrl = process.env.VUE_APP_BASE_API + item.filePath
}
}
} */
return {
id: item.identificationDataId,
@ -236,39 +236,29 @@ export default {
},
/** 导出操作 */
async handleExport() {
try {
this.loading = true
const params = { ...this.queryParams }
// :00 :59
if (params.startTime && params.startTime.length === 16) {
params.startTime = params.startTime + ':00'
}
if (params.endTime && params.endTime.length === 16) {
params.endTime = params.endTime + ':59'
}
const res = await exportImageRecognitionAPI(params)
if (res.code === 200) {
//
const blob = new Blob([res], { type: 'application/vnd.ms-excel' })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = `图像识别数据_${new Date().getTime()}.xlsx`
link.click()
window.URL.revokeObjectURL(url)
this.$message.success('导出成功')
} else {
this.$message.error(res.msg || '导出失败')
}
} catch (error) {
console.error('导出失败:', error)
this.$message.error('导出失败')
} finally {
this.loading = false
handleExport() {
const params = { ...this.queryParams }
// :00 :59
if (params.startTime && params.startTime.length === 16) {
params.startTime = params.startTime + ':00'
}
if (params.endTime && params.endTime.length === 16) {
params.endTime = params.endTime + ':59'
}
// URL
const paramsStr = encodeURIComponent(JSON.stringify(params))
const url = `${window.location.origin}${process.env.VUE_APP_ENV === 'production' ? '/smart-car' : ''}/device/image-recognition/export-progress?params=${paramsStr}`
//
const width = 750
const height = 500
const left = (window.screen.width - width) / 2
const top = (window.screen.height - height) / 2
//
window.open(url, '_blank', `width=${width},height=${height},left=${left},top=${top},scrollbars=yes,resizable=yes`)
},
/** 时间范围变化 */