导出下载
This commit is contained in:
parent
1a82946c5a
commit
2d5a6e8254
|
|
@ -9,10 +9,28 @@ export function imageRecognitionListAPI(params) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设备管理->图像识别->导出图像识别数据
|
// 设备管理->图像识别->导出图像识别数据(启动导出任务)
|
||||||
export function exportImageRecognitionAPI(params) {
|
export function exportImageRecognitionAPI(params) {
|
||||||
return request({
|
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',
|
method: 'GET',
|
||||||
params,
|
params,
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,11 @@ export const constantRoutes = [
|
||||||
meta: { title: '个人中心', icon: 'user' }
|
meta: { title: '个人中心', icon: 'user' }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/device/image-recognition/export-progress',
|
||||||
|
component: () => import('@/views/device/image-recognition/ExportProgress'),
|
||||||
|
hidden: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
||||||
|
|
@ -184,13 +184,13 @@ export default {
|
||||||
const list = (res.rows || []).map(item => {
|
const list = (res.rows || []).map(item => {
|
||||||
// 处理图片路径,默认使用 test_image
|
// 处理图片路径,默认使用 test_image
|
||||||
let imageUrl = this.test_image
|
let imageUrl = this.test_image
|
||||||
if (item.filePath) {
|
/* if (item.filePath) {
|
||||||
if (isExternal(item.filePath)) {
|
if (isExternal(item.filePath)) {
|
||||||
imageUrl = item.filePath
|
imageUrl = item.filePath
|
||||||
} else {
|
} else {
|
||||||
imageUrl = process.env.VUE_APP_BASE_API + item.filePath
|
imageUrl = process.env.VUE_APP_BASE_API + item.filePath
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.identificationDataId,
|
id: item.identificationDataId,
|
||||||
|
|
@ -236,39 +236,29 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 导出操作 */
|
/** 导出操作 */
|
||||||
async handleExport() {
|
handleExport() {
|
||||||
try {
|
const params = { ...this.queryParams }
|
||||||
this.loading = true
|
|
||||||
const params = { ...this.queryParams }
|
// 导出时给开始时间添加 :00 秒,结束时间添加 :59 秒
|
||||||
|
if (params.startTime && params.startTime.length === 16) {
|
||||||
// 导出时给开始时间添加 :00 秒,结束时间添加 :59 秒
|
params.startTime = params.startTime + ':00'
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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`)
|
||||||
},
|
},
|
||||||
|
|
||||||
/** 时间范围变化 */
|
/** 时间范围变化 */
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue