게시물 Export 일괄 다운로드와 재시도 추가 v1.5.24

This commit is contained in:
2026-06-01 16:02:24 +09:00
parent a4c1b42369
commit 5735fd5046
12 changed files with 321 additions and 12 deletions

View File

@@ -22,6 +22,8 @@ const uploadingHomeCover = ref(false)
const uploadingHomeCoverDark = ref(false)
const requestingPostExport = ref(false)
const deletingPostExportJobIds = ref([])
const downloadingPostExportJobIds = ref([])
const retryingPostExportJobIds = ref([])
const postExportDateRangeMode = ref('all')
const postExportYear = ref(new Date().getFullYear())
const postExportMonth = ref(new Date().getMonth() + 1)
@@ -585,6 +587,88 @@ const requestPostExport = async () => {
*/
const getPostExportDownloadUrl = (file) => `/admin/api/posts/export-jobs/${file.id}/download`
/**
* Export 작업의 다운로드 가능한 파일만 추린다.
* @param {Object} job - Export 작업
* @returns {Array} 다운로드 가능한 파일 목록
*/
const getReadyPostExportFiles = (job) => Array.isArray(job?.files)
? job.files.filter((file) => file.status === 'ready' && file.filePath)
: []
/**
* Export 일괄 다운로드 중 여부
* @param {string} jobId - 작업 ID
* @returns {boolean} 다운로드 중 여부
*/
const isDownloadingPostExportJob = (jobId) => downloadingPostExportJobIds.value.includes(jobId)
/**
* Export 작업 재시도 중 여부
* @param {string} jobId - 작업 ID
* @returns {boolean} 재시도 중 여부
*/
const isRetryingPostExportJob = (jobId) => retryingPostExportJobIds.value.includes(jobId)
/**
* Export 분할 파일을 브라우저에서 순차 다운로드한다.
* @param {Object} job - Export 작업
* @returns {Promise<void>}
*/
const downloadPostExportJobFiles = async (job) => {
const readyFiles = getReadyPostExportFiles(job)
if (!job?.id || readyFiles.length === 0 || isDownloadingPostExportJob(job.id)) {
return
}
downloadingPostExportJobIds.value = [...downloadingPostExportJobIds.value, job.id]
showToast('info', `${readyFiles.length}개 파일을 순차 다운로드합니다.`)
try {
for (const file of readyFiles) {
const link = document.createElement('a')
link.href = getPostExportDownloadUrl(file)
link.download = file.fileName || ''
link.rel = 'noopener'
document.body.append(link)
link.click()
link.remove()
await new Promise((resolve) => window.setTimeout(resolve, 700))
}
showToast('success', '일괄 다운로드 요청을 보냈습니다.')
} finally {
downloadingPostExportJobIds.value = downloadingPostExportJobIds.value.filter((id) => id !== job.id)
}
}
/**
* 실패한 Export 작업을 재시도한다.
* @param {Object} job - Export 작업
* @returns {Promise<void>}
*/
const retryPostExportJob = async (job) => {
if (!job?.id || job.status !== 'failed' || isRetryingPostExportJob(job.id)) {
return
}
retryingPostExportJobIds.value = [...retryingPostExportJobIds.value, job.id]
showToast('info', '실패한 Export 작업을 다시 대기열에 등록하는 중입니다.')
try {
await $fetch(`/admin/api/posts/export-jobs/${job.id}/retry`, {
method: 'POST'
})
await refreshPostExportJobs()
showToast('success', 'Export 작업을 다시 시작했습니다.')
} catch (error) {
errorMessage.value = error?.data?.message || 'Export 작업을 다시 시작하지 못했습니다.'
showToast('error', errorMessage.value)
} finally {
retryingPostExportJobIds.value = retryingPostExportJobIds.value.filter((id) => id !== job.id)
}
}
/**
* Export 작업 삭제 중 여부
* @param {string} jobId - 작업 ID
@@ -2079,11 +2163,21 @@ onBeforeUnmount(() => {
</div>
<div class="flex shrink-0 items-center gap-2">
<button
class="inline-flex h-9 shrink-0 cursor-not-allowed items-center justify-center rounded-md border border-[#dce0e5] px-3 text-xs font-semibold text-[#9aa3ad]"
class="inline-flex h-9 shrink-0 cursor-pointer items-center justify-center rounded-md border border-[#15171a] bg-[#15171a] px-3 text-xs font-semibold text-white transition hover:bg-black disabled:cursor-not-allowed disabled:border-[#dce0e5] disabled:bg-white disabled:text-[#9aa3ad]"
type="button"
disabled
:disabled="getReadyPostExportFiles(job).length === 0 || isDownloadingPostExportJob(job.id)"
@click="downloadPostExportJobFiles(job)"
>
일괄 다운로드 준비
{{ isDownloadingPostExportJob(job.id) ? '다운로드 중' : `일괄 다운로드 ${getReadyPostExportFiles(job).length || ''}` }}
</button>
<button
v-if="job.status === 'failed'"
class="inline-flex h-9 shrink-0 cursor-pointer items-center justify-center rounded-md border border-[#dce0e5] px-3 text-xs font-semibold text-[#15171a] transition hover:bg-[#f4f6f8] disabled:cursor-not-allowed disabled:text-[#a6b0bb]"
type="button"
:disabled="isRetryingPostExportJob(job.id)"
@click="retryPostExportJob(job)"
>
{{ isRetryingPostExportJob(job.id) ? '재시도 중' : '재시도' }}
</button>
<button
class="inline-flex h-9 shrink-0 cursor-pointer items-center justify-center rounded-md border border-[#ffd5d5] px-3 text-xs font-semibold text-[#d64545] transition hover:bg-[#fff3f3] disabled:cursor-not-allowed disabled:border-[#e1e5ea] disabled:text-[#a6b0bb]"