미디어 카테고리 관리 추가

This commit is contained in:
2026-05-02 17:56:00 +09:00
parent 04b8a7006a
commit dd0a643d73
11 changed files with 242 additions and 19 deletions

View File

@@ -4,8 +4,10 @@ definePageMeta({
})
const searchText = ref('')
const categoryFilter = ref('')
const editingUrl = ref('')
const editingName = ref('')
const editingCategory = ref('')
const deletingUrl = ref('')
const errorMessage = ref('')
const selectedMediaUrl = ref('')
@@ -16,19 +18,21 @@ const { data: mediaItems, refresh } = await useFetch('/admin/api/media', {
const selectedMedia = computed(() => mediaItems.value.find((item) => item.url === selectedMediaUrl.value) || null)
const mediaCategories = computed(() => [...new Set(mediaItems.value
.map((item) => item.category)
.filter(Boolean))]
.sort((left, right) => left.localeCompare(right)))
const filteredMediaItems = computed(() => {
const query = searchText.value.trim().toLowerCase()
const category = categoryFilter.value
if (!query) {
return mediaItems.value
}
return mediaItems.value.filter((item) => [
return mediaItems.value.filter((item) => (!category || item.category === category) && (!query || [
item.name,
item.url,
item.category,
...item.usage.map((usage) => usage.title)
].some((value) => value.toLowerCase().includes(query)))
].some((value) => value.toLowerCase().includes(query))))
})
/**
@@ -57,6 +61,7 @@ const openMediaDetail = (item) => {
selectedMediaUrl.value = item.url
editingUrl.value = item.url
editingName.value = item.title
editingCategory.value = item.category
errorMessage.value = ''
}
@@ -78,6 +83,27 @@ const cancelRename = () => {
editingName.value = ''
}
/**
* 미디어 카테고리 저장
* @returns {Promise<void>} 저장 결과
*/
const saveMediaCategory = async () => {
errorMessage.value = ''
try {
await $fetch('/admin/api/media', {
method: 'PUT',
body: {
url: selectedMedia.value.url,
category: editingCategory.value
}
})
await refresh()
} catch (error) {
errorMessage.value = error?.data?.message || '카테고리를 저장하지 못했습니다.'
}
}
/**
* 미디어 파일명 변경
* @returns {Promise<void>}
@@ -154,12 +180,20 @@ const deleteMedia = async (item) => {
미디어
</h1>
</div>
<input
v-model="searchText"
class="admin-media__search w-full rounded border border-line bg-white px-3 py-2 text-sm md:w-72"
type="search"
placeholder="파일명, 경로, 사용처 검색"
>
<div class="admin-media__filters flex w-full flex-wrap gap-2 md:w-auto">
<select v-model="categoryFilter" class="admin-media__category-filter w-full rounded border border-line bg-white px-3 py-2 text-sm md:w-44">
<option value="">전체 카테고리</option>
<option v-for="category in mediaCategories" :key="category" :value="category">
{{ category }}
</option>
</select>
<input
v-model="searchText"
class="admin-media__search w-full rounded border border-line bg-white px-3 py-2 text-sm md:w-72"
type="search"
placeholder="파일명, 경로, 카테고리, 사용처 검색"
>
</div>
</div>
<p v-if="errorMessage" class="admin-media__error mt-6 rounded border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
@@ -233,6 +267,29 @@ const deleteMedia = async (item) => {
</div>
</dl>
<div class="admin-media__category grid gap-2">
<label class="admin-media__category-label text-xs font-semibold text-muted" for="media-category">
카테고리
</label>
<div class="admin-media__category-row flex gap-2">
<input
id="media-category"
v-model="editingCategory"
class="admin-media__category-input min-w-0 flex-1 rounded border border-line px-3 py-2 text-sm"
type="text"
list="media-category-options"
placeholder="미분류"
@keydown.enter.prevent="saveMediaCategory"
>
<button class="admin-media__category-save rounded border border-line px-3 py-2 text-xs font-semibold" type="button" @click="saveMediaCategory">
저장
</button>
</div>
<datalist id="media-category-options">
<option v-for="category in mediaCategories" :key="category" :value="category" />
</datalist>
</div>
<div class="admin-media__usage rounded bg-surface p-3 text-xs">
<strong class="admin-media__usage-title text-ink">
사용 현황 {{ selectedMedia.usage.length }}