feat(review-meeting): 右下角展示导出打包进度与文案
parent
0b140a145e
commit
06cd96067d
|
|
@ -217,6 +217,22 @@
|
||||||
:file-name="minutesPreviewFileName"
|
: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
|
<input
|
||||||
ref="minutesUploadInputRef"
|
ref="minutesUploadInputRef"
|
||||||
type="file"
|
type="file"
|
||||||
|
|
@ -228,7 +244,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { useRouter } from 'vue-router'
|
||||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||||
import {
|
import {
|
||||||
|
|
@ -295,6 +311,42 @@ const exportTaskMap = reactive<Record<number, ReviewMeetingMaterialExportTaskRes
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
const exportPollingMap = new Map<number, ReturnType<typeof setTimeout>>()
|
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 = () => {
|
const syncActionColumnMode = () => {
|
||||||
useFixedActionColumn.value = window.innerWidth >= 1680
|
useFixedActionColumn.value = window.innerWidth >= 1680
|
||||||
|
|
@ -574,6 +626,14 @@ const buildExportZipFileName = (meetingId: number) => {
|
||||||
return `${formatYYYYMMDD(date)}+${meetingName}+评审资料.zip`
|
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 triggerExportDownload = async (meetingId: number, taskId: number) => {
|
||||||
const blob = await downloadReviewMeetingMaterialExportTask(taskId)
|
const blob = await downloadReviewMeetingMaterialExportTask(taskId)
|
||||||
download.zip(blob as Blob, buildExportZipFileName(meetingId))
|
download.zip(blob as Blob, buildExportZipFileName(meetingId))
|
||||||
|
|
@ -583,11 +643,17 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
||||||
try {
|
try {
|
||||||
const task = await getReviewMeetingMaterialExportTask(taskId)
|
const task = await getReviewMeetingMaterialExportTask(taskId)
|
||||||
exportTaskMap[meetingId] = task
|
exportTaskMap[meetingId] = task
|
||||||
|
syncExportProgress(meetingId, task)
|
||||||
if (task.status === 2 && task.downloadReady) {
|
if (task.status === 2 && task.downloadReady) {
|
||||||
clearExportPolling(meetingId)
|
clearExportPolling(meetingId)
|
||||||
try {
|
try {
|
||||||
await triggerExportDownload(meetingId, taskId)
|
await triggerExportDownload(meetingId, taskId)
|
||||||
ElMessage.success('资料压缩包已生成并开始下载')
|
ElMessage.success('资料压缩包已生成并开始下载')
|
||||||
|
setTimeout(() => {
|
||||||
|
if (exportProgressMeetingId.value === meetingId && exportProgressStatus.value === 2) {
|
||||||
|
exportProgressWidgetVisible.value = false
|
||||||
|
}
|
||||||
|
}, 1200)
|
||||||
} catch {
|
} catch {
|
||||||
ElMessage.error('资料压缩包下载失败')
|
ElMessage.error('资料压缩包下载失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|
@ -597,6 +663,9 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
||||||
}
|
}
|
||||||
if (task.status === 3) {
|
if (task.status === 3) {
|
||||||
clearExportPolling(meetingId)
|
clearExportPolling(meetingId)
|
||||||
|
if (exportProgressMeetingId.value === meetingId) {
|
||||||
|
exportProgressWidgetVisible.value = false
|
||||||
|
}
|
||||||
ElMessage.error(task.errorMessage || '资料打包失败')
|
ElMessage.error(task.errorMessage || '资料打包失败')
|
||||||
exportTaskMap[meetingId] = undefined
|
exportTaskMap[meetingId] = undefined
|
||||||
return
|
return
|
||||||
|
|
@ -604,6 +673,9 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clearExportPolling(meetingId)
|
clearExportPolling(meetingId)
|
||||||
exportTaskMap[meetingId] = undefined
|
exportTaskMap[meetingId] = undefined
|
||||||
|
if (exportProgressMeetingId.value === meetingId) {
|
||||||
|
exportProgressWidgetVisible.value = false
|
||||||
|
}
|
||||||
ElMessage.error('查询导出进度失败')
|
ElMessage.error('查询导出进度失败')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -618,10 +690,20 @@ const pollExportTask = async (meetingId: number, taskId: number) => {
|
||||||
|
|
||||||
const handleExportMaterials = async (row: ReviewMeetingRespVO) => {
|
const handleExportMaterials = async (row: ReviewMeetingRespVO) => {
|
||||||
if (isExporting(row.id)) {
|
if (isExporting(row.id)) {
|
||||||
|
const runningTask = exportTaskMap[row.id]
|
||||||
|
if (runningTask) {
|
||||||
|
syncExportProgress(row.id, runningTask)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
exportProgressMeetingId.value = row.id
|
||||||
|
exportProgressStatus.value = 0
|
||||||
|
exportProgressTotal.value = 0
|
||||||
|
exportProgressProcessed.value = 0
|
||||||
|
exportProgressWidgetVisible.value = true
|
||||||
const task = await createReviewMeetingMaterialExportTask(row.id)
|
const task = await createReviewMeetingMaterialExportTask(row.id)
|
||||||
exportTaskMap[row.id] = task
|
exportTaskMap[row.id] = task
|
||||||
|
syncExportProgress(row.id, task)
|
||||||
await pollExportTask(row.id, task.taskId)
|
await pollExportTask(row.id, task.taskId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -869,6 +951,32 @@ onBeforeUnmount(() => {
|
||||||
color: #c22e35;
|
color: #c22e35;
|
||||||
background-color: rgba(252, 79, 84, 0.12);
|
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) {
|
:deep(.review-table .el-table__header-wrapper th) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue