feat(review-meeting): 右下角展示导出打包进度与文案
parent
0b140a145e
commit
06cd96067d
|
|
@ -217,6 +217,22 @@
|
|||
:file-name="minutesPreviewFileName"
|
||||
/>
|
||||
|
||||
<transition name="export-progress-fade">
|
||||
<div v-if="exportProgressWidgetVisible" class="export-progress-widget">
|
||||
<el-progress
|
||||
:percentage="exportProgressPercentage"
|
||||
:indeterminate="exportProgressIndeterminate"
|
||||
:status="exportProgressStatusType"
|
||||
:stroke-width="8"
|
||||
:show-text="!exportProgressIndeterminate"
|
||||
:striped="exportProgressStatus === 0 || exportProgressStatus === 1"
|
||||
:striped-flow="exportProgressStatus === 0 || exportProgressStatus === 1"
|
||||
duration="12"
|
||||
/>
|
||||
<div class="export-progress-label">{{ exportProgressText }}</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<input
|
||||
ref="minutesUploadInputRef"
|
||||
type="file"
|
||||
|
|
@ -228,7 +244,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import {
|
||||
|
|
@ -295,6 +311,42 @@ const exportTaskMap = reactive<Record<number, ReviewMeetingMaterialExportTaskRes
|
|||
{}
|
||||
)
|
||||
const exportPollingMap = new Map<number, ReturnType<typeof setTimeout>>()
|
||||
const exportProgressMeetingId = ref<number>()
|
||||
const exportProgressStatus = ref<number>()
|
||||
const exportProgressTotal = ref(0)
|
||||
const exportProgressProcessed = ref(0)
|
||||
const exportProgressWidgetVisible = ref(false)
|
||||
|
||||
const exportProgressIndeterminate = computed(
|
||||
() =>
|
||||
(exportProgressStatus.value === 0 || exportProgressStatus.value === 1) &&
|
||||
exportProgressTotal.value <= 0
|
||||
)
|
||||
const exportProgressPercentage = computed(() => {
|
||||
if (exportProgressStatus.value === 2) {
|
||||
return 100
|
||||
}
|
||||
if (exportProgressTotal.value <= 0) {
|
||||
return 0
|
||||
}
|
||||
const percentage = Math.round((exportProgressProcessed.value / exportProgressTotal.value) * 100)
|
||||
return Math.min(100, Math.max(0, percentage))
|
||||
})
|
||||
const exportProgressStatusType = computed<'success' | ''>(() => {
|
||||
if (exportProgressStatus.value === 2) {
|
||||
return 'success'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
const exportProgressText = computed(() => {
|
||||
if (exportProgressStatus.value === 0 || exportProgressStatus.value === 1) {
|
||||
if (exportProgressTotal.value > 0) {
|
||||
return `打包中 ${exportProgressProcessed.value}/${exportProgressTotal.value}`
|
||||
}
|
||||
return '打包中'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const syncActionColumnMode = () => {
|
||||
useFixedActionColumn.value = window.innerWidth >= 1680
|
||||
|
|
@ -574,6 +626,14 @@ const buildExportZipFileName = (meetingId: number) => {
|
|||
return `${formatYYYYMMDD(date)}+${meetingName}+评审资料.zip`
|
||||
}
|
||||
|
||||
const syncExportProgress = (meetingId: number, task: ReviewMeetingMaterialExportTaskRespVO) => {
|
||||
exportProgressMeetingId.value = meetingId
|
||||
exportProgressStatus.value = task.status
|
||||
exportProgressTotal.value = task.totalProjectCount ?? 0
|
||||
exportProgressProcessed.value = task.processedProjectCount ?? 0
|
||||
exportProgressWidgetVisible.value = true
|
||||
}
|
||||
|
||||
const triggerExportDownload = async (meetingId: number, taskId: number) => {
|
||||
const blob = await downloadReviewMeetingMaterialExportTask(taskId)
|
||||
download.zip(blob as Blob, buildExportZipFileName(meetingId))
|
||||
|
|
@ -583,11 +643,17 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
|||
try {
|
||||
const task = await getReviewMeetingMaterialExportTask(taskId)
|
||||
exportTaskMap[meetingId] = task
|
||||
syncExportProgress(meetingId, task)
|
||||
if (task.status === 2 && task.downloadReady) {
|
||||
clearExportPolling(meetingId)
|
||||
try {
|
||||
await triggerExportDownload(meetingId, taskId)
|
||||
ElMessage.success('资料压缩包已生成并开始下载')
|
||||
setTimeout(() => {
|
||||
if (exportProgressMeetingId.value === meetingId && exportProgressStatus.value === 2) {
|
||||
exportProgressWidgetVisible.value = false
|
||||
}
|
||||
}, 1200)
|
||||
} catch {
|
||||
ElMessage.error('资料压缩包下载失败')
|
||||
} finally {
|
||||
|
|
@ -597,6 +663,9 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
|||
}
|
||||
if (task.status === 3) {
|
||||
clearExportPolling(meetingId)
|
||||
if (exportProgressMeetingId.value === meetingId) {
|
||||
exportProgressWidgetVisible.value = false
|
||||
}
|
||||
ElMessage.error(task.errorMessage || '资料打包失败')
|
||||
exportTaskMap[meetingId] = undefined
|
||||
return
|
||||
|
|
@ -604,6 +673,9 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
|||
} catch (error) {
|
||||
clearExportPolling(meetingId)
|
||||
exportTaskMap[meetingId] = undefined
|
||||
if (exportProgressMeetingId.value === meetingId) {
|
||||
exportProgressWidgetVisible.value = false
|
||||
}
|
||||
ElMessage.error('查询导出进度失败')
|
||||
return
|
||||
}
|
||||
|
|
@ -618,10 +690,20 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
|||
|
||||
const handleExportMaterials = async (row: ReviewMeetingRespVO) => {
|
||||
if (isExporting(row.id)) {
|
||||
const runningTask = exportTaskMap[row.id]
|
||||
if (runningTask) {
|
||||
syncExportProgress(row.id, runningTask)
|
||||
}
|
||||
return
|
||||
}
|
||||
exportProgressMeetingId.value = row.id
|
||||
exportProgressStatus.value = 0
|
||||
exportProgressTotal.value = 0
|
||||
exportProgressProcessed.value = 0
|
||||
exportProgressWidgetVisible.value = true
|
||||
const task = await createReviewMeetingMaterialExportTask(row.id)
|
||||
exportTaskMap[row.id] = task
|
||||
syncExportProgress(row.id, task)
|
||||
await pollExportTask(row.id, task.taskId)
|
||||
}
|
||||
|
||||
|
|
@ -869,6 +951,32 @@ onBeforeUnmount(() => {
|
|||
color: #c22e35;
|
||||
background-color: rgba(252, 79, 84, 0.12);
|
||||
}
|
||||
.export-progress-widget {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
z-index: 3000;
|
||||
width: 280px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 10px;
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
}
|
||||
.export-progress-label {
|
||||
margin-top: 6px;
|
||||
color: #475569;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
.export-progress-fade-enter-active,
|
||||
.export-progress-fade-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
.export-progress-fade-enter-from,
|
||||
.export-progress-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* ── 表格样式 ── */
|
||||
:deep(.review-table .el-table__header-wrapper th) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue