릴리스: v0.1.10 관리자 업로드 UX 정리
This commit is contained in:
@@ -23,6 +23,8 @@ const uploadFile = ref(null)
|
||||
const thumbFile = ref(null)
|
||||
const itemPreviewUrl = ref('')
|
||||
const thumbPreviewUrl = ref('')
|
||||
const itemFileInput = ref(null)
|
||||
const thumbFileInput = ref(null)
|
||||
|
||||
onMounted(async () => {
|
||||
await auth.refresh()
|
||||
@@ -60,6 +62,8 @@ function setMode(mode) {
|
||||
uploadLabel.value = ''
|
||||
uploadFile.value = null
|
||||
thumbFile.value = null
|
||||
resetFileInput('item')
|
||||
resetFileInput('thumb')
|
||||
clearPreviewUrl('item')
|
||||
clearPreviewUrl('thumb')
|
||||
}
|
||||
@@ -75,8 +79,25 @@ function clearPreviewUrl(type) {
|
||||
}
|
||||
}
|
||||
|
||||
function resetFileInput(type) {
|
||||
if (type === 'item' && itemFileInput.value) {
|
||||
itemFileInput.value.value = ''
|
||||
}
|
||||
if (type === 'thumb' && thumbFileInput.value) {
|
||||
thumbFileInput.value.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGame() {
|
||||
resetMessages()
|
||||
uploadLabel.value = ''
|
||||
uploadFile.value = null
|
||||
thumbFile.value = null
|
||||
resetFileInput('item')
|
||||
resetFileInput('thumb')
|
||||
clearPreviewUrl('item')
|
||||
clearPreviewUrl('thumb')
|
||||
|
||||
if (!selectedGameId.value) {
|
||||
selectedGame.value = null
|
||||
return
|
||||
@@ -146,6 +167,7 @@ async function uploadThumbnail() {
|
||||
if (!res.ok) throw new Error('failed')
|
||||
|
||||
thumbFile.value = null
|
||||
resetFileInput('thumb')
|
||||
clearPreviewUrl('thumb')
|
||||
await refreshGames()
|
||||
await loadGame()
|
||||
@@ -175,6 +197,7 @@ async function uploadItem() {
|
||||
|
||||
uploadLabel.value = ''
|
||||
uploadFile.value = null
|
||||
resetFileInput('item')
|
||||
clearPreviewUrl('item')
|
||||
await loadGame()
|
||||
success.value = '아이템이 추가됐어요.'
|
||||
@@ -219,6 +242,11 @@ async function removeGame() {
|
||||
const deletedName = selectedGame.value.game.name
|
||||
selectedGameId.value = ''
|
||||
selectedGame.value = null
|
||||
uploadLabel.value = ''
|
||||
uploadFile.value = null
|
||||
thumbFile.value = null
|
||||
resetFileInput('item')
|
||||
resetFileInput('thumb')
|
||||
clearPreviewUrl('item')
|
||||
clearPreviewUrl('thumb')
|
||||
await refreshGames()
|
||||
@@ -233,6 +261,9 @@ const displayThumbnailUrl = computed(() => {
|
||||
if (selectedGame.value?.game?.thumbnailSrc) return toApiUrl(selectedGame.value.game.thumbnailSrc)
|
||||
return ''
|
||||
})
|
||||
|
||||
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedGameId.value)
|
||||
const canAddItem = computed(() => !!uploadFile.value && !!selectedGameId.value)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -295,30 +326,30 @@ const displayThumbnailUrl = computed(() => {
|
||||
:src="displayThumbnailUrl"
|
||||
:alt="selectedGame.game.name"
|
||||
/>
|
||||
<div v-else class="selectedThumb selectedThumb--empty">16:9 미리보기</div>
|
||||
<div v-else class="selectedThumb selectedThumb--empty">썸네일 미리보기</div>
|
||||
<div class="uploadPreviewMeta">
|
||||
<div class="uploadPreviewTitle">대표 썸네일</div>
|
||||
<div class="uploadPreviewDesc">
|
||||
파일을 선택하면 먼저 미리보기로 확인되고, 업로드 버튼을 눌렀을 때 실제 저장됩니다.
|
||||
파일을 선택하면 먼저 미리보기로 확인되고, 적용 버튼을 눌렀을 때 실제 저장됩니다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" accept="image/*" class="inputFile" @change="onThumb" />
|
||||
<button class="btn" @click="uploadThumbnail">썸네일 업로드</button>
|
||||
<input ref="thumbFileInput" type="file" accept="image/*" class="inputFile" @change="onThumb" />
|
||||
<button class="btn" :disabled="!canApplyThumbnail" @click="uploadThumbnail">썸네일 적용</button>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section__title">아이템 추가</div>
|
||||
<div class="itemComposer">
|
||||
<div class="itemComposer__form">
|
||||
<input v-model="uploadLabel" class="input" placeholder="아이템 이름" />
|
||||
<input type="file" accept="image/*" class="inputFile" @change="onFile" />
|
||||
<button class="btn" @click="uploadItem">아이템 추가</button>
|
||||
<input v-model="uploadLabel" class="input input--compact" placeholder="아이템 이름" />
|
||||
<input ref="itemFileInput" type="file" accept="image/*" class="inputFile" @change="onFile" />
|
||||
<button class="btn" :disabled="!canAddItem" @click="uploadItem">아이템 추가</button>
|
||||
</div>
|
||||
<div class="itemPreviewCard">
|
||||
<div class="itemPreviewFrame">
|
||||
<img v-if="itemPreviewUrl" class="itemPreviewImage" :src="itemPreviewUrl" alt="item preview" />
|
||||
<div v-else class="itemPreviewEmpty">1:1 미리보기</div>
|
||||
<div v-else class="itemPreviewEmpty">이미지를 선택해주세요</div>
|
||||
</div>
|
||||
<div class="thumbLabel thumbLabel--preview">{{ uploadLabel || '아이템 이름 미리보기' }}</div>
|
||||
</div>
|
||||
@@ -449,6 +480,9 @@ const displayThumbnailUrl = computed(() => {
|
||||
outline: none;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.input--compact {
|
||||
max-width: 360px;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 10px;
|
||||
opacity: 0.78;
|
||||
@@ -468,6 +502,10 @@ const displayThumbnailUrl = computed(() => {
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
}
|
||||
.btn:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.45;
|
||||
}
|
||||
.btn--primary {
|
||||
background: rgba(96, 165, 250, 0.2);
|
||||
}
|
||||
@@ -503,7 +541,7 @@ const displayThumbnailUrl = computed(() => {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.selectedThumb {
|
||||
width: 256px;
|
||||
width: min(100%, 256px);
|
||||
aspect-ratio: 16 / 9;
|
||||
object-fit: cover;
|
||||
border-radius: 16px;
|
||||
@@ -518,7 +556,7 @@ const displayThumbnailUrl = computed(() => {
|
||||
.itemComposer {
|
||||
margin-top: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 180px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(160px, 192px);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -532,12 +570,13 @@ const displayThumbnailUrl = computed(() => {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
.itemPreviewFrame {
|
||||
width: 100%;
|
||||
width: min(100%, 192px);
|
||||
aspect-ratio: 1 / 1;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
margin: 0 auto;
|
||||
}
|
||||
.itemPreviewImage {
|
||||
width: 100%;
|
||||
@@ -589,10 +628,16 @@ const displayThumbnailUrl = computed(() => {
|
||||
.itemComposer {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.itemPreviewCard {
|
||||
max-width: 192px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.selectedThumb {
|
||||
width: 100%;
|
||||
width: min(100%, 256px);
|
||||
}
|
||||
.itemPreviewCard {
|
||||
max-width: 192px;
|
||||
}
|
||||
.thumbGrid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
Reference in New Issue
Block a user