릴리스: v0.1.40 관리자 아이템 관리 UX 보강
This commit is contained in:
@@ -22,6 +22,7 @@ const customItemPage = ref(1)
|
||||
const customItemLimit = ref(50)
|
||||
const customItemTotal = ref(0)
|
||||
const customItemOrphanOnly = ref(false)
|
||||
const customItemTargetGameId = ref('')
|
||||
|
||||
const users = ref([])
|
||||
|
||||
@@ -72,12 +73,18 @@ function resetMessages() {
|
||||
function setTab(tab) {
|
||||
resetMessages()
|
||||
activeTab.value = tab
|
||||
if (tab === 'items' && !customItemTargetGameId.value && games.value.length) {
|
||||
customItemTargetGameId.value = games.value[0].id
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshGames() {
|
||||
try {
|
||||
const data = await api.listGames()
|
||||
games.value = data.games || []
|
||||
if (!customItemTargetGameId.value && games.value.length) {
|
||||
customItemTargetGameId.value = games.value[0].id
|
||||
}
|
||||
featuredGameIds.value = games.value
|
||||
.filter((game) => game.displayRank != null)
|
||||
.sort((a, b) => a.displayRank - b.displayRank)
|
||||
@@ -360,13 +367,18 @@ async function saveGameItemLabel(item) {
|
||||
error.value = '아이템 이름을 입력해주세요.'
|
||||
return
|
||||
}
|
||||
if (nextLabel === item.label) return
|
||||
|
||||
try {
|
||||
await api.updateAdminGameItem(selectedGameId.value, item.id, { label: nextLabel })
|
||||
await loadGame()
|
||||
item.isSavingLabel = true
|
||||
const data = await api.updateAdminGameItem(selectedGameId.value, item.id, { label: nextLabel })
|
||||
item.label = data.item.label
|
||||
item.draftLabel = data.item.label
|
||||
success.value = '기본 아이템 이름을 수정했어요.'
|
||||
} catch (e) {
|
||||
error.value = '기본 아이템 이름 수정에 실패했어요.'
|
||||
} finally {
|
||||
item.isSavingLabel = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,6 +518,26 @@ async function removeUnusedCustomItems() {
|
||||
}
|
||||
}
|
||||
|
||||
async function promoteCustomItem(item) {
|
||||
resetMessages()
|
||||
if (!customItemTargetGameId.value) {
|
||||
error.value = '가져올 게임을 먼저 선택해주세요.'
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
item.isPromoting = true
|
||||
await api.promoteAdminCustomItem(item.id, { gameId: customItemTargetGameId.value })
|
||||
const targetGameName = games.value.find((game) => game.id === customItemTargetGameId.value)?.name || customItemTargetGameId.value
|
||||
if (selectedGameId.value === customItemTargetGameId.value) await loadGame()
|
||||
success.value = `"${item.label}" 아이템을 ${targetGameName} 기본 템플릿으로 추가했어요.`
|
||||
} catch (e) {
|
||||
error.value = '커스텀 아이템을 기본 템플릿으로 가져오지 못했어요.'
|
||||
} finally {
|
||||
item.isPromoting = false
|
||||
}
|
||||
}
|
||||
|
||||
const displayThumbnailUrl = computed(() => {
|
||||
if (thumbPreviewUrl.value) return thumbPreviewUrl.value
|
||||
if (selectedGame.value?.game?.thumbnailSrc) return toApiUrl(selectedGame.value.game.thumbnailSrc)
|
||||
@@ -734,7 +766,13 @@ async function saveFeaturedOrder() {
|
||||
<img class="thumb thumb--game" :src="toApiUrl(item.src)" :alt="item.label" />
|
||||
<input v-model="item.draftLabel" class="input input--labelEdit" placeholder="아이템 이름" />
|
||||
<div class="thumbCard__actions">
|
||||
<button class="btn btn--ghost btn--small" @click="saveGameItemLabel(item)">이름 저장</button>
|
||||
<button
|
||||
class="btn btn--ghost btn--small"
|
||||
:disabled="item.isSavingLabel || !item.draftLabel?.trim() || item.draftLabel.trim() === item.label"
|
||||
@click="saveGameItemLabel(item)"
|
||||
>
|
||||
{{ item.isSavingLabel ? '저장중...' : '이름 저장' }}
|
||||
</button>
|
||||
<button class="btn btn--danger btn--small" @click="removeGameItem(item.id)">아이템 삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -763,6 +801,10 @@ async function saveFeaturedOrder() {
|
||||
</div>
|
||||
|
||||
<div class="toolbar toolbar--secondary">
|
||||
<select v-model="customItemTargetGameId" class="select toolbar__select">
|
||||
<option value="">가져올 게임 선택</option>
|
||||
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }}</option>
|
||||
</select>
|
||||
<label class="checkRow checkRow--toolbar">
|
||||
<input v-model="customItemOrphanOnly" type="checkbox" @change="toggleCustomItemOrphanOnly" />
|
||||
<span>미사용 커스텀 이미지만 보기</span>
|
||||
@@ -784,6 +826,9 @@ async function saveFeaturedOrder() {
|
||||
<div class="customItemCard__meta">{{ fmt(item.createdAt) }}</div>
|
||||
<div class="customItemCard__actions">
|
||||
<a class="btn btn--small btn--ghost" :href="toApiUrl(item.src)" :download="item.label">이미지 다운로드</a>
|
||||
<button class="btn btn--small btn--ghost" :disabled="!customItemTargetGameId || item.isPromoting" @click="promoteCustomItem(item)">
|
||||
{{ item.isPromoting ? '추가중...' : '기본 템플릿에 추가' }}
|
||||
</button>
|
||||
<button class="btn btn--small btn--danger" :disabled="item.usageCount > 0" @click="removeCustomItem(item)">개별 삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1038,7 +1083,7 @@ async function saveFeaturedOrder() {
|
||||
align-items: end;
|
||||
}
|
||||
.toolbar--secondary {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
}
|
||||
.toolbar__search,
|
||||
@@ -1324,7 +1369,7 @@ async function saveFeaturedOrder() {
|
||||
}
|
||||
.customItemCard__actions {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
@@ -1417,6 +1462,9 @@ async function saveFeaturedOrder() {
|
||||
.itemComposer {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.toolbar--secondary {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.itemPreviewCard {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user