diff --git a/docs/history.md b/docs/history.md index fd3887c..4b8794a 100644 --- a/docs/history.md +++ b/docs/history.md @@ -40,3 +40,8 @@ ## 2026-03-19 v0.1.9 - 로컬과 운영 환경을 완전히 같은 DB 계층으로 맞추기 위해 lowdb fallback을 제거하고 MariaDB만 지원하는 코드베이스로 정리했다. - 마이그레이션 종료 이후에는 레거시 JSON 저장소와 예외 실행 스크립트를 남겨두는 비용이 더 크다고 판단해 삭제하기로 결정했다. + +## 2026-03-19 v0.1.10 +- 관리자 업로드 작업은 "파일 선택 후 적용"이 더 정확하므로, 썸네일 버튼 문구와 활성화 조건을 그 흐름에 맞추기로 결정했다. +- 작은 화면에서 미리보기가 실제 작업 영역을 압박하지 않도록, 아이템 미리보기는 정사각형을 유지하되 최대 크기를 제한하는 방향을 채택했다. +- 파일 입력은 업로드 성공 후와 게임 전환 시 초기화해 같은 파일 재선택이 막히지 않도록 정리했다. diff --git a/docs/map.md b/docs/map.md index 507efc5..0949dcc 100644 --- a/docs/map.md +++ b/docs/map.md @@ -27,7 +27,7 @@ ## `/admin` - 화면 파일: `frontend/src/views/AdminView.vue` -- 역할: 작업 모드 선택, 기존 게임 선택 또는 새 게임 생성, 선택된 게임의 썸네일/아이템 관리, 파일 선택 즉시 미리보기, 아이템 삭제, 게임 삭제 +- 역할: 작업 모드 선택, 기존 게임 선택 또는 새 게임 생성, 선택된 게임의 썸네일/아이템 관리, 파일 선택 즉시 미리보기, 파일 입력 초기화, 아이템 삭제, 게임 삭제 - 연동 API: `POST /api/admin/games`, `POST /api/admin/games/:gameId/thumbnail`, `POST /api/admin/games/:gameId/images`, `DELETE /api/admin/games/:gameId/items/:itemId`, `DELETE /api/admin/games/:gameId` ## `/profile` diff --git a/docs/spec.md b/docs/spec.md index 109dc58..a105048 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -83,6 +83,12 @@ - `DELETE /api/admin/games/:gameId/items/:itemId` - `DELETE /api/admin/games/:gameId` +## 관리자 화면 메모 +- 썸네일은 16:9 비율 미리보기 후 `썸네일 적용` 버튼으로 실제 반영한다. +- 아이템 추가는 이름 입력, 파일 선택, 1:1 미리보기 확인 뒤 저장하는 흐름이다. +- 아이템 미리보기는 반응형 환경에서도 최대 `192px` 크기 안에서 표시한다. +- 게임 전환 또는 업로드 성공 뒤에는 파일 입력값을 초기화해 같은 파일도 다시 선택할 수 있다. + ## 운영 환경 변수 - 프런트엔드 - `VITE_API_ORIGIN`: API 및 업로드 파일 절대 기준 주소 diff --git a/docs/update.md b/docs/update.md index dce54d9..f7c4fad 100644 --- a/docs/update.md +++ b/docs/update.md @@ -67,3 +67,9 @@ - **MariaDB 전용 전환 완료**: `backend/src/db.js`에서 lowdb 분기와 `DB_CLIENT` 기반 fallback을 제거하고 MariaDB 전용 저장 계층으로 정리 - **레거시 파일 제거**: `backend/data/db.json`, `backend/scripts/migrate-lowdb-to-mariadb.js`, `dev:lowdb/start:lowdb/migrate:lowdb` 스크립트 및 `lowdb` 의존성 제거 - **실행 문서 정리**: `README.md`, `docs/local-mariadb.md`, `docs/spec.md`, `docs/todo.md`, `docs/history.md`를 현재 MariaDB 전용 개발/배포 흐름 기준으로 갱신 + +## 2026-03-19 v0.1.10 +- **관리자 썸네일 액션 정리**: 썸네일 버튼 문구를 `썸네일 적용`으로 바꾸고, 파일 선택 전에는 비활성화되도록 조정 +- **아이템 추가 폼 정리**: 아이템 이름 입력 너비를 줄이고, 과한 미리보기 안내 문구를 제거해 작업 집중도를 높임 +- **반응형 미리보기 보정**: 태블릿 이하 화면에서도 아이템 1:1 미리보기가 최대 `192px` 범위 안에서 보이도록 조정 +- **파일 재선택 버그 수정**: 아이템 추가나 게임 전환 뒤 파일 입력 값을 초기화해 같은 이미지를 다시 선택해도 정상 인식되도록 수정 diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue index 013e9f8..edeb5e6 100644 --- a/frontend/src/views/AdminView.vue +++ b/frontend/src/views/AdminView.vue @@ -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)