fix(review-meeting): 修复导出下载与命名及按钮顺序
parent
8e37690ef6
commit
c413c5cc56
|
|
@ -87,6 +87,17 @@ export interface ReviewMeetingAgendaAttachmentRespVO {
|
|||
size: number
|
||||
}
|
||||
|
||||
export interface ReviewMeetingMaterialExportTaskRespVO {
|
||||
taskId: number
|
||||
reviewMeetingId: number
|
||||
status: number
|
||||
statusName: string
|
||||
totalProjectCount: number
|
||||
processedProjectCount: number
|
||||
errorMessage?: string
|
||||
downloadReady: boolean
|
||||
}
|
||||
|
||||
export interface ReviewMeetingAgendaGenerateReqVO {
|
||||
name: string
|
||||
organizationUnit: string
|
||||
|
|
@ -219,6 +230,27 @@ export const uploadMinutesAttachment = (
|
|||
export const getImportTemplate = () =>
|
||||
request.download({ url: '/project/review-meeting/get-import-template' })
|
||||
|
||||
/** 创建会议资料导出任务 */
|
||||
export const createReviewMeetingMaterialExportTask = (reviewMeetingId: number) =>
|
||||
request.post<ReviewMeetingMaterialExportTaskRespVO>({
|
||||
url: '/project/review-meeting/material-export-task/create',
|
||||
params: { reviewMeetingId }
|
||||
})
|
||||
|
||||
/** 查询会议资料导出任务 */
|
||||
export const getReviewMeetingMaterialExportTask = (taskId: number) =>
|
||||
request.get<ReviewMeetingMaterialExportTaskRespVO>({
|
||||
url: '/project/review-meeting/material-export-task/get',
|
||||
params: { taskId }
|
||||
})
|
||||
|
||||
/** 下载会议资料导出结果 */
|
||||
export const downloadReviewMeetingMaterialExportTask = (taskId: number) =>
|
||||
request.download<Blob>({
|
||||
url: '/project/review-meeting/material-export-task/download',
|
||||
params: { taskId }
|
||||
})
|
||||
|
||||
const uploadMeetingAttachmentByPresignedUrl = async (
|
||||
file: File
|
||||
): Promise<ReviewMeetingAgendaAttachmentRespVO> => {
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@
|
|||
<span :class="`status-text status-${row.status}`">{{ STATUS_LABEL[row.status] }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="360" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="430" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<template v-if="row.status === 0">
|
||||
<div class="op-group">
|
||||
|
|
@ -75,6 +75,14 @@
|
|||
<a v-if="row.mailSent" v-hasPermi="['review:meeting:send-mail']" class="op-link" @click="openMailStatus(row)">邮件状态</a>
|
||||
<a v-else v-hasPermi="['review:meeting:send-mail']" class="op-link" @click="handleSendMail(row)">发送议程</a>
|
||||
<a v-hasPermi="['review:meeting:update']" class="op-link" @click="triggerUploadMinutes(row)">上传纪要</a>
|
||||
<a
|
||||
v-hasPermi="['review:meeting:export']"
|
||||
class="op-link"
|
||||
:class="{ disabled: isExporting(row.id) }"
|
||||
@click="handleExportMaterials(row)"
|
||||
>
|
||||
{{ getExportText(row.id) }}
|
||||
</a>
|
||||
<a v-hasPermi="['review:meeting:finish']" class="op-link" @click="handleFinish(row)">结束</a>
|
||||
<a v-hasPermi="['review:meeting:cancel']" class="op-link op-danger" @click="handleCancel(row)">取消</a>
|
||||
</div>
|
||||
|
|
@ -109,7 +117,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import {
|
||||
|
|
@ -119,12 +127,17 @@ import {
|
|||
sendSmsInvitation,
|
||||
sendMailInvitation,
|
||||
uploadMinutesAttachment,
|
||||
createReviewMeetingMaterialExportTask,
|
||||
getReviewMeetingMaterialExportTask,
|
||||
downloadReviewMeetingMaterialExportTask,
|
||||
type ReviewMeetingRespVO,
|
||||
type ReviewMeetingPageReqVO
|
||||
type ReviewMeetingPageReqVO,
|
||||
type ReviewMeetingMaterialExportTaskRespVO
|
||||
} from '@/api/review/meeting'
|
||||
import SmsStatusDialog from './SmsStatusDialog.vue'
|
||||
import MailStatusDialog from './MailStatusDialog.vue'
|
||||
import { formatDate } from '@/utils/formatTime'
|
||||
import download from '@/utils/download'
|
||||
|
||||
defineOptions({ name: 'ReviewMeeting' })
|
||||
|
||||
|
|
@ -153,6 +166,8 @@ const smsStatusRef = ref()
|
|||
const mailStatusRef = ref()
|
||||
const minutesUploadInputRef = ref<HTMLInputElement>()
|
||||
const pendingMinutesMeeting = ref<ReviewMeetingRespVO>()
|
||||
const exportTaskMap = reactive<Record<number, ReviewMeetingMaterialExportTaskRespVO | undefined>>({})
|
||||
const exportPollingMap = new Map<number, ReturnType<typeof setTimeout>>()
|
||||
|
||||
const getList = async () => {
|
||||
loading.value = true
|
||||
|
|
@ -264,7 +279,111 @@ const openMailStatus = (row: ReviewMeetingRespVO) => {
|
|||
mailStatusRef.value?.open(row.id, row.name)
|
||||
}
|
||||
|
||||
const isExporting = (meetingId: number) => {
|
||||
const task = exportTaskMap[meetingId]
|
||||
return task?.status === 0 || task?.status === 1
|
||||
}
|
||||
|
||||
const getExportText = (meetingId: number) => {
|
||||
const task = exportTaskMap[meetingId]
|
||||
if (!task) return '导出资料'
|
||||
if (task.status === 0 || task.status === 1) {
|
||||
const total = task.totalProjectCount ?? 0
|
||||
const processed = task.processedProjectCount ?? 0
|
||||
return total > 0 ? `打包中 ${processed}/${total}` : '打包中'
|
||||
}
|
||||
return '导出资料'
|
||||
}
|
||||
|
||||
const clearExportPolling = (meetingId?: number) => {
|
||||
if (meetingId !== undefined) {
|
||||
const timer = exportPollingMap.get(meetingId)
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
exportPollingMap.delete(meetingId)
|
||||
}
|
||||
return
|
||||
}
|
||||
exportPollingMap.forEach((timer) => clearTimeout(timer))
|
||||
exportPollingMap.clear()
|
||||
}
|
||||
|
||||
const sanitizeFileName = (name: string) => name.replace(/[\\/:*?"<>|]/g, '_').trim()
|
||||
|
||||
const resolveMeetingDate = (value?: string) => {
|
||||
if (!value) return undefined
|
||||
const normalized = value.includes(' ') ? value.replace(' ', 'T') : value
|
||||
const date = new Date(normalized)
|
||||
return Number.isNaN(date.getTime()) ? undefined : date
|
||||
}
|
||||
|
||||
const formatYYYYMMDD = (date: Date) => {
|
||||
const year = String(date.getFullYear())
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}${month}${day}`
|
||||
}
|
||||
|
||||
const buildExportZipFileName = (meetingId: number) => {
|
||||
const meeting = list.value.find((item) => item.id === meetingId)
|
||||
const date = resolveMeetingDate(meeting?.startTime) || new Date()
|
||||
const meetingName = sanitizeFileName(meeting?.name || `会议${meetingId}`)
|
||||
return `${formatYYYYMMDD(date)}+${meetingName}+评审资料.zip`
|
||||
}
|
||||
|
||||
const triggerExportDownload = async (meetingId: number, taskId: number) => {
|
||||
const blob = await downloadReviewMeetingMaterialExportTask(taskId)
|
||||
download.zip(blob as Blob, buildExportZipFileName(meetingId))
|
||||
}
|
||||
|
||||
const pollExportTask = async (meetingId: number, taskId: number) => {
|
||||
try {
|
||||
const task = await getReviewMeetingMaterialExportTask(taskId)
|
||||
exportTaskMap[meetingId] = task
|
||||
if (task.status === 2 && task.downloadReady) {
|
||||
clearExportPolling(meetingId)
|
||||
try {
|
||||
await triggerExportDownload(meetingId, taskId)
|
||||
ElMessage.success('资料压缩包已生成并开始下载')
|
||||
} catch {
|
||||
ElMessage.error('资料压缩包下载失败')
|
||||
} finally {
|
||||
exportTaskMap[meetingId] = undefined
|
||||
}
|
||||
return
|
||||
}
|
||||
if (task.status === 3) {
|
||||
clearExportPolling(meetingId)
|
||||
ElMessage.error(task.errorMessage || '资料打包失败')
|
||||
exportTaskMap[meetingId] = undefined
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
clearExportPolling(meetingId)
|
||||
exportTaskMap[meetingId] = undefined
|
||||
ElMessage.error('查询导出进度失败')
|
||||
return
|
||||
}
|
||||
clearExportPolling(meetingId)
|
||||
exportPollingMap.set(
|
||||
meetingId,
|
||||
setTimeout(() => {
|
||||
pollExportTask(meetingId, taskId)
|
||||
}, 2000)
|
||||
)
|
||||
}
|
||||
|
||||
const handleExportMaterials = async (row: ReviewMeetingRespVO) => {
|
||||
if (isExporting(row.id)) {
|
||||
return
|
||||
}
|
||||
const task = await createReviewMeetingMaterialExportTask(row.id)
|
||||
exportTaskMap[row.id] = task
|
||||
await pollExportTask(row.id, task.taskId)
|
||||
}
|
||||
|
||||
onMounted(() => getList())
|
||||
onBeforeUnmount(() => clearExportPolling())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
Loading…
Reference in New Issue