게시물 Export ZIP Import 추가 v1.5.27

This commit is contained in:
2026-06-02 10:20:43 +09:00
parent 5732a27498
commit ef1a9d9032
13 changed files with 852 additions and 10 deletions

View File

@@ -24,6 +24,9 @@ const requestingPostExport = ref(false)
const deletingPostExportJobIds = ref([])
const downloadingPostExportJobIds = ref([])
const retryingPostExportJobIds = ref([])
const importingPosts = ref(false)
const postImportFileName = ref('')
const postImportResult = ref(null)
const postExportDateRangeMode = ref('all')
const postExportYear = ref(new Date().getFullYear())
const postExportMonth = ref(new Date().getMonth() + 1)
@@ -37,6 +40,7 @@ const toast = ref(null)
const logoInputRef = ref(null)
const homeCoverInputRef = ref(null)
const homeCoverDarkInputRef = ref(null)
const postImportInputRef = ref(null)
const mainScrollRef = ref(null)
const navSearchQuery = ref('')
const activeSectionId = ref('admin-settings-section-title')
@@ -606,6 +610,58 @@ const requestPostExport = async () => {
}
}
/**
* 게시물 Import 파일 선택창을 연다.
* @returns {void}
*/
const openPostImportFilePicker = () => {
postImportInputRef.value?.click()
}
/**
* 게시물 Import 파일을 업로드한다.
* @param {Event} event - 파일 선택 이벤트
* @returns {Promise<void>}
*/
const importPostsFromFile = async (event) => {
const target = event.target
const file = target instanceof HTMLInputElement ? target.files?.[0] : null
if (!file) {
return
}
if (!file.name.toLowerCase().endsWith('.zip')) {
showToast('error', 'ZIP 파일만 Import할 수 있습니다.')
target.value = ''
return
}
importingPosts.value = true
postImportFileName.value = file.name
postImportResult.value = null
showToast('info', '게시물 Import를 시작합니다.')
try {
const formData = new FormData()
formData.append('file', file)
const result = await $fetch('/admin/api/posts/import', {
method: 'POST',
body: formData
})
postImportResult.value = result
showToast('success', `게시물 ${result.importedCount}개를 Import했습니다.`)
} catch (error) {
const message = error?.data?.message || '게시물 Import를 완료하지 못했습니다.'
showToast('error', message)
} finally {
importingPosts.value = false
target.value = ''
}
}
/**
* Export 파일 다운로드 URL을 만든다.
* @param {Object} file - Export 파일
@@ -2235,11 +2291,41 @@ onBeforeUnmount(() => {
백업 ZIP 가져오기
</p>
<p class="mt-1 text-sm leading-relaxed text-[#657080]">
Export ZIP 구조를 다시 게시물 가져오는 기능은 단계에서 연결합니다.
Export 만든 Obsidian 호환 ZIP을 게시물 미디어 파일로 가져옵니다.
</p>
</div>
<div class="rounded-md border border-[#e6e8eb] bg-white px-4 py-5 text-sm text-[#657080]">
Import 구현 전까지는 영역만 접힌 상태로 보관합니다.
<div class="grid gap-3 rounded-md border border-[#e6e8eb] bg-white p-4">
<input
ref="postImportInputRef"
class="sr-only"
type="file"
accept=".zip,application/zip"
@change="importPostsFromFile"
>
<div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
<div class="min-w-0">
<p class="text-sm font-semibold text-[#15171a]">
{{ postImportFileName || 'ZIP 파일을 선택해 주세요.' }}
</p>
<p class="mt-1 text-xs leading-relaxed text-[#657080]">
같은 슬러그가 있으면 덮어쓰지 않고 슬러그로 Import합니다.
</p>
</div>
<button
class="inline-flex h-10 shrink-0 cursor-pointer items-center justify-center rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white transition hover:bg-black disabled:cursor-not-allowed disabled:bg-[#c7cdd4]"
type="button"
:disabled="importingPosts"
@click="openPostImportFilePicker"
>
{{ importingPosts ? 'Import 중...' : 'ZIP 선택' }}
</button>
</div>
<div
v-if="postImportResult"
class="rounded-md bg-[#f4fbf7] px-3 py-2 text-sm text-[#147a45] ring-1 ring-inset ring-[#b9e7cd]"
>
게시물 {{ postImportResult.importedCount }}, 자산 {{ postImportResult.assetCount }}개를 가져왔습니다.
</div>
</div>
</div>