관리자 미디어 API 페이지 응답 적용
This commit is contained in:
@@ -80,10 +80,82 @@ const { openMenuId, closeMenu } = useAdminRowMenu()
|
||||
*/
|
||||
const getFolderMenuId = (folder) => `folder:${folder}`
|
||||
|
||||
const { data: mediaItems, refresh } = await useFetch('/admin/api/media', {
|
||||
default: () => []
|
||||
const mediaQuery = computed(() => ({
|
||||
paged: '1',
|
||||
tab: activeTab.value,
|
||||
page: mediaPage.value,
|
||||
pageSize: mediaPageSize.value,
|
||||
search: searchText.value.trim() || undefined,
|
||||
folder: activeFolder.value || undefined,
|
||||
kind: activeMediaKind.value,
|
||||
unused: showUnusedOnly.value ? '1' : undefined
|
||||
}))
|
||||
|
||||
const { data: mediaResponse, refresh } = await useFetch('/admin/api/media', {
|
||||
query: mediaQuery,
|
||||
default: () => ({
|
||||
items: [],
|
||||
pagination: {
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
total: 0,
|
||||
pageCount: 1
|
||||
},
|
||||
counts: {
|
||||
tabs: {
|
||||
library: 0,
|
||||
postThumbnails: 0,
|
||||
postBodyThumbnails: 0,
|
||||
thumbnails: 0
|
||||
},
|
||||
folders: {},
|
||||
kinds: {
|
||||
all: 0,
|
||||
image: 0,
|
||||
video: 0,
|
||||
audio: 0,
|
||||
file: 0
|
||||
},
|
||||
usage: {
|
||||
all: 0,
|
||||
unused: 0
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const mediaItems = computed(() => Array.isArray(mediaResponse.value)
|
||||
? mediaResponse.value
|
||||
: (mediaResponse.value?.items || []))
|
||||
|
||||
const mediaPagination = computed(() => Array.isArray(mediaResponse.value)
|
||||
? {
|
||||
page: 1,
|
||||
pageSize: mediaPageSize.value,
|
||||
total: mediaItems.value.length,
|
||||
pageCount: Math.max(1, Math.ceil(mediaItems.value.length / mediaPageSize.value))
|
||||
}
|
||||
: (mediaResponse.value?.pagination || {
|
||||
page: 1,
|
||||
pageSize: mediaPageSize.value,
|
||||
total: 0,
|
||||
pageCount: 1
|
||||
}))
|
||||
|
||||
const mediaCounts = computed(() => Array.isArray(mediaResponse.value)
|
||||
? {
|
||||
tabs: {},
|
||||
folders: {},
|
||||
kinds: {},
|
||||
usage: {}
|
||||
}
|
||||
: (mediaResponse.value?.counts || {
|
||||
tabs: {},
|
||||
folders: {},
|
||||
kinds: {},
|
||||
usage: {}
|
||||
}))
|
||||
|
||||
/**
|
||||
* 썸네일 디스크 경로 여부
|
||||
* @param {Object} item - 미디어 항목
|
||||
@@ -338,42 +410,18 @@ const canRegeneratePostBodyThumbnail = (item) => (
|
||||
*/
|
||||
const getMediaItemKind = (item) => item?.kind || 'image'
|
||||
|
||||
const libraryMediaItems = computed(() => (mediaItems.value || []).filter((item) => !isThumbnailDiskItem(item) && !isPostCardThumbnailDiskItem(item) && !isPageCoverThumbnailDiskItem(item) && !isPostBodyThumbnailDiskItem(item)))
|
||||
|
||||
const postThumbnailMediaItems = computed(() => (mediaItems.value || []).filter((item) => isPostCardThumbnailDiskItem(item) || isPageCoverThumbnailDiskItem(item)))
|
||||
|
||||
const postBodyThumbnailMediaItems = computed(() => (mediaItems.value || []).filter((item) => isPostBodyThumbnailDiskItem(item)))
|
||||
|
||||
const thumbnailMediaItems = computed(() => (mediaItems.value || []).filter((item) => isThumbnailDiskItem(item)))
|
||||
|
||||
const scopeItems = computed(() => {
|
||||
if (activeTab.value === 'thumbnails') {
|
||||
return thumbnailMediaItems.value
|
||||
}
|
||||
|
||||
if (activeTab.value === 'postThumbnails') {
|
||||
return postThumbnailMediaItems.value
|
||||
}
|
||||
|
||||
if (activeTab.value === 'postBodyThumbnails') {
|
||||
return postBodyThumbnailMediaItems.value
|
||||
}
|
||||
|
||||
return libraryMediaItems.value
|
||||
})
|
||||
|
||||
const systemManagedTab = computed(() => ['thumbnails', 'postThumbnails', 'postBodyThumbnails'].includes(activeTab.value))
|
||||
|
||||
const activeSystemMediaCount = computed(() => {
|
||||
if (activeTab.value === 'postThumbnails') {
|
||||
return postThumbnailMediaItems.value.length
|
||||
return mediaCounts.value.tabs?.postThumbnails || 0
|
||||
}
|
||||
|
||||
if (activeTab.value === 'postBodyThumbnails') {
|
||||
return postBodyThumbnailMediaItems.value.length
|
||||
return mediaCounts.value.tabs?.postBodyThumbnails || 0
|
||||
}
|
||||
|
||||
return thumbnailMediaItems.value.length
|
||||
return mediaCounts.value.tabs?.thumbnails || 0
|
||||
})
|
||||
|
||||
const activeSystemMediaTitle = computed(() => {
|
||||
@@ -401,21 +449,10 @@ const activeSystemMediaHint = computed(() => {
|
||||
})
|
||||
|
||||
const mediaKindFilterOptions = computed(() => {
|
||||
const baseItems = scopeItems.value.filter((item) => {
|
||||
const folder = activeFolder.value
|
||||
|
||||
return systemManagedTab.value
|
||||
? true
|
||||
: (!folder || item.category === folder || item.category?.startsWith(`${folder}/`))
|
||||
})
|
||||
const countByKind = baseItems.reduce((counts, item) => {
|
||||
const kind = getMediaItemKind(item)
|
||||
counts[kind] = (counts[kind] || 0) + 1
|
||||
return counts
|
||||
}, {})
|
||||
const countByKind = mediaCounts.value.kinds || {}
|
||||
|
||||
return [
|
||||
{ id: 'all', label: '전체', count: baseItems.length },
|
||||
{ id: 'all', label: '전체', count: countByKind.all || 0 },
|
||||
{ id: 'image', label: '이미지', count: countByKind.image || 0 },
|
||||
{ id: 'video', label: '영상', count: countByKind.video || 0 },
|
||||
{ id: 'audio', label: '음악', count: countByKind.audio || 0 },
|
||||
@@ -424,20 +461,11 @@ const mediaKindFilterOptions = computed(() => {
|
||||
})
|
||||
|
||||
const mediaUsageFilterOptions = computed(() => {
|
||||
const folder = activeFolder.value
|
||||
const mediaKind = activeMediaKind.value
|
||||
const baseItems = scopeItems.value.filter((item) => {
|
||||
const matchesFolder = systemManagedTab.value
|
||||
? true
|
||||
: (!folder || item.category === folder || item.category?.startsWith(`${folder}/`))
|
||||
const matchesKind = mediaKind === 'all' || getMediaItemKind(item) === mediaKind
|
||||
|
||||
return matchesFolder && matchesKind
|
||||
})
|
||||
const usageCounts = mediaCounts.value.usage || {}
|
||||
|
||||
return [
|
||||
{ id: 'all', label: '전체', count: baseItems.length, unusedOnly: false },
|
||||
{ id: 'unused', label: '미사용', count: baseItems.filter((item) => !isMediaItemLocked(item)).length, unusedOnly: true }
|
||||
{ id: 'all', label: '전체', count: usageCounts.all || 0, unusedOnly: false },
|
||||
{ id: 'unused', label: '미사용', count: usageCounts.unused || 0, unusedOnly: true }
|
||||
]
|
||||
})
|
||||
|
||||
@@ -542,6 +570,7 @@ const selectedMedia = computed(() => mediaItems.value.find((item) => item.url ==
|
||||
|
||||
const normalizedFolders = computed(() => {
|
||||
const folderSet = new Set(['미분류'])
|
||||
const folderCounts = mediaCounts.value.folders || {}
|
||||
|
||||
mediaFolders.value.forEach((folder) => {
|
||||
String(folder || '').split('/').filter(Boolean).reduce((parentPath, segment) => {
|
||||
@@ -551,8 +580,8 @@ const normalizedFolders = computed(() => {
|
||||
}, '')
|
||||
})
|
||||
|
||||
libraryMediaItems.value.forEach((item) => {
|
||||
String(item.category || '미분류').split('/').filter(Boolean).reduce((parentPath, segment) => {
|
||||
Object.keys(folderCounts).forEach((folder) => {
|
||||
String(folder || '미분류').split('/').filter(Boolean).reduce((parentPath, segment) => {
|
||||
const nextPath = parentPath ? `${parentPath}/${segment}` : segment
|
||||
folderSet.add(nextPath)
|
||||
return nextPath
|
||||
@@ -563,46 +592,27 @@ const normalizedFolders = computed(() => {
|
||||
})
|
||||
|
||||
const folderMediaCounts = computed(() => normalizedFolders.value.reduce((counts, folder) => {
|
||||
counts[folder] = libraryMediaItems.value.filter((item) => item.category === folder || item.category?.startsWith(`${folder}/`)).length
|
||||
counts[folder] = mediaCounts.value.folders?.[folder] || 0
|
||||
return counts
|
||||
}, {}))
|
||||
|
||||
const filteredMediaItems = computed(() => {
|
||||
const query = searchText.value.trim().toLowerCase()
|
||||
const folder = activeFolder.value
|
||||
const mediaKind = activeMediaKind.value
|
||||
const unusedOnly = showUnusedOnly.value
|
||||
const base = scopeItems.value
|
||||
|
||||
return base.filter((item) => {
|
||||
const matchesFolder = systemManagedTab.value
|
||||
? true
|
||||
: (!folder || item.category === folder || item.category?.startsWith(`${folder}/`))
|
||||
const usageTitles = item.usage?.map((usage) => usage.title) || []
|
||||
const matchesQuery = !query || [
|
||||
item.name,
|
||||
...usageTitles
|
||||
].some((value) => String(value || '').toLowerCase().includes(query))
|
||||
const matchesKind = mediaKind === 'all' || getMediaItemKind(item) === mediaKind
|
||||
const matchesUsage = !unusedOnly || !isMediaItemLocked(item)
|
||||
|
||||
return matchesFolder && matchesQuery && matchesKind && matchesUsage
|
||||
})
|
||||
})
|
||||
const filteredMediaItems = computed(() => mediaItems.value)
|
||||
|
||||
const filteredMediaUrls = computed(() => filteredMediaItems.value.map((item) => item.url))
|
||||
|
||||
const mediaPageCount = computed(() => Math.max(1, Math.ceil(filteredMediaItems.value.length / mediaPageSize.value)))
|
||||
const mediaPageCount = computed(() => Math.max(1, Number(mediaPagination.value.pageCount) || 1))
|
||||
|
||||
const mediaPageStartIndex = computed(() => filteredMediaItems.value.length
|
||||
? (mediaPage.value - 1) * mediaPageSize.value
|
||||
const mediaPageTotal = computed(() => Number(mediaPagination.value.total) || 0)
|
||||
|
||||
const mediaPageStartIndex = computed(() => mediaPageTotal.value
|
||||
? ((Number(mediaPagination.value.page) || mediaPage.value) - 1) * mediaPageSize.value
|
||||
: 0)
|
||||
|
||||
const mediaPageDisplayStart = computed(() => filteredMediaItems.value.length ? mediaPageStartIndex.value + 1 : 0)
|
||||
const mediaPageDisplayStart = computed(() => mediaPageTotal.value ? mediaPageStartIndex.value + 1 : 0)
|
||||
|
||||
const mediaPageEndIndex = computed(() => Math.min(mediaPageStartIndex.value + mediaPageSize.value, filteredMediaItems.value.length))
|
||||
const mediaPageEndIndex = computed(() => Math.min(mediaPageStartIndex.value + filteredMediaItems.value.length, mediaPageTotal.value))
|
||||
|
||||
const paginatedMediaItems = computed(() => filteredMediaItems.value.slice(mediaPageStartIndex.value, mediaPageEndIndex.value))
|
||||
const paginatedMediaItems = computed(() => filteredMediaItems.value)
|
||||
|
||||
const filteredSelectableMediaItems = computed(() => paginatedMediaItems.value.filter((item) => !isMediaItemLocked(item)))
|
||||
|
||||
@@ -660,7 +670,8 @@ const mediaPaginationItems = computed(() => {
|
||||
*/
|
||||
const setMediaPage = (page) => {
|
||||
mediaPage.value = Math.min(Math.max(Number(page) || 1, 1), mediaPageCount.value)
|
||||
lastSelectedIndex.value = -1
|
||||
clearMediaSelection()
|
||||
closeMediaDetail()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -671,7 +682,8 @@ const setMediaPage = (page) => {
|
||||
const setMediaPageSize = (event) => {
|
||||
mediaPageSize.value = Number(event.target.value) || 50
|
||||
mediaPage.value = 1
|
||||
lastSelectedIndex.value = -1
|
||||
clearMediaSelection()
|
||||
closeMediaDetail()
|
||||
}
|
||||
|
||||
const selectedDeletableMediaItems = computed(() => {
|
||||
@@ -1300,13 +1312,23 @@ watch(filteredMediaUrls, (urls) => {
|
||||
|
||||
watch([activeTab, activeFolder, activeMediaKind, showUnusedOnly, searchText], () => {
|
||||
mediaPage.value = 1
|
||||
lastSelectedIndex.value = -1
|
||||
clearMediaSelection()
|
||||
closeMediaDetail()
|
||||
})
|
||||
|
||||
watch(mediaPageCount, (pageCount) => {
|
||||
if (mediaPage.value > pageCount) {
|
||||
mediaPage.value = pageCount
|
||||
lastSelectedIndex.value = -1
|
||||
clearMediaSelection()
|
||||
closeMediaDetail()
|
||||
}
|
||||
})
|
||||
|
||||
watch(() => mediaPagination.value.page, (page) => {
|
||||
const normalizedPage = Number(page) || 1
|
||||
|
||||
if (normalizedPage !== mediaPage.value) {
|
||||
mediaPage.value = normalizedPage
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1432,7 +1454,7 @@ watch(mediaPageCount, (pageCount) => {
|
||||
@drop.prevent="dropMediaOnFolder('미분류')"
|
||||
>
|
||||
<span>전체 미디어</span>
|
||||
<span>{{ libraryMediaItems.length }}</span>
|
||||
<span>{{ mediaCounts.tabs?.library || 0 }}</span>
|
||||
</button>
|
||||
|
||||
<div class="admin-media__folder-list mt-3 grid gap-1">
|
||||
@@ -1511,7 +1533,7 @@ watch(mediaPageCount, (pageCount) => {
|
||||
{{ systemManagedTab ? activeSystemMediaTitle : (activeFolder || '전체 미디어') }}
|
||||
</h2>
|
||||
<p class="admin-media__folder-summary mt-1 text-xs text-muted">
|
||||
{{ filteredMediaItems.length }}개 중 {{ mediaPageDisplayStart }}-{{ mediaPageEndIndex }}개 표시
|
||||
{{ mediaPageTotal }}개 중 {{ mediaPageDisplayStart }}-{{ mediaPageEndIndex }}개 표시
|
||||
</p>
|
||||
</div>
|
||||
<div class="admin-media__selection-actions flex flex-wrap items-center justify-end gap-2">
|
||||
|
||||
Reference in New Issue
Block a user