게시물 가져오기 중복 복원 방지

This commit is contained in:
2026-07-23 18:17:26 +09:00
parent f510a88e56
commit 85b6fe7033
10 changed files with 396 additions and 72 deletions

View File

@@ -121,6 +121,29 @@ const postExportDateRangeModeOptions = [
{ value: 'month', label: '특정월' },
{ value: 'custom', label: '직접 지정' }
]
const postImportStatusSummary = computed(() => {
if (!postImportResult.value) {
return ''
}
const result = postImportResult.value
const segments = [
`게시물 ${Number(result.importedCount || 0)}개 가져옴`
]
if (Number(result.skippedCount || 0) > 0) {
segments.push(`${Number(result.skippedCount || 0)}개 건너뜀`)
}
segments.push(`신규 자산 ${Number(result.assetCount || 0)}개`)
if (Number(result.reusedAssetCount || 0) > 0) {
segments.push(`재사용 자산 ${Number(result.reusedAssetCount || 0)}개`)
}
return segments.join(', ')
})
/** 블로그 제목·설명 카드 편집 모드 여부 */
const editTitleDesc = ref(false)
/** 사이트 정보 카드 편집 모드 여부 */
@@ -975,6 +998,10 @@ const requestPostExport = async () => {
* @returns {void}
*/
const openPostImportFilePicker = () => {
if (importingPosts.value) {
return
}
postImportInputRef.value?.click()
}
@@ -984,6 +1011,10 @@ const openPostImportFilePicker = () => {
* @returns {boolean} 선택 성공 여부
*/
const selectPostImportFile = (file) => {
if (importingPosts.value) {
return false
}
if (!file) {
return false
}
@@ -1028,6 +1059,10 @@ const dropPostImportFile = (event) => {
* @returns {Promise<void>}
*/
const importSelectedPostFile = async () => {
if (importingPosts.value) {
return
}
if (!postImportFile.value) {
showToast('error', '가져올 ZIP 파일을 먼저 선택해 주세요.')
return
@@ -1049,7 +1084,7 @@ const importSelectedPostFile = async () => {
postImportResult.value = result
postImportFile.value = null
postImportFileName.value = ''
showToast('success', `게시물 ${result.importedCount}개를 가져왔습니다.`)
showToast('success', postImportStatusSummary.value || '게시물 가져오기가 완료되었습니다.')
} catch (error) {
const message = error?.data?.message || '게시물 가져오기를 완료하지 못했습니다.'
showToast('error', message)
@@ -3914,7 +3949,7 @@ onBeforeUnmount(() => {
게시물 가져오기
</h2>
<p class="mt-1 text-sm leading-relaxed text-[#657080]">
내보내기로 만든 ZIP 백업을 게시물과 미디어 파일로 복원합니다.
내보내기로 만든 ZIP 백업을 게시물과 미디어 파일로 복원합니다. 이미 같은 slug가 있는 게시물은 건너뜁니다.
</p>
</div>
<button
@@ -3922,6 +3957,7 @@ onBeforeUnmount(() => {
type="button"
:aria-expanded="postImportExportPanel === 'import'"
aria-controls="admin-settings-import-panel"
:disabled="importingPosts"
@click="togglePostImportExportPanel('import')"
>
{{ postImportExportPanel === 'import' ? '접기' : '가져오기' }}
@@ -3938,6 +3974,7 @@ onBeforeUnmount(() => {
class="sr-only"
type="file"
accept=".zip,application/zip"
:disabled="importingPosts"
@change="handlePostImportFileChange"
>
<button
@@ -3956,10 +3993,18 @@ onBeforeUnmount(() => {
</svg>
<span>{{ postImportFileName || 'ZIP 파일을 드롭하거나 선택하세요.' }}</span>
<span class="text-xs font-medium text-[#8a94a3]">
같은 슬러그가 있으면 덮어쓰지 않고 새 슬러그로 가져옵니다.
같은 slug의 게시물은 덮어쓰지 않고 건너뛰며, 건너뛴 게시물의 자산도 다시 저장하지 않습니다.
</span>
</span>
</button>
<div
v-if="importingPosts"
class="admin-settings-screen__import-progress rounded-md bg-[#eef5ff] px-3 py-2 text-sm text-[#1f5f99] ring-1 ring-inset ring-[#bdd8f3]"
role="status"
aria-live="polite"
>
게시물을 가져오는 중입니다. 대량 ZIP은 시간이 걸릴 수 있으니 이 화면을 닫지 마세요.
</div>
<div class="flex items-center justify-end">
<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]"
@@ -3974,7 +4019,15 @@ onBeforeUnmount(() => {
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 }}개를 가져왔습니다.
{{ postImportStatusSummary }}
<ul v-if="postImportResult.skippedPosts?.length" class="mt-2 grid gap-1 text-xs leading-relaxed text-[#24633f]">
<li
v-for="post in postImportResult.skippedPosts"
:key="`${post.slug}-${post.reason}`"
>
건너뜀: {{ post.title }} ({{ post.slug }})
</li>
</ul>
</div>
<div
v-if="postImportResult?.warningCount"