릴리스: v0.1.51 관리자 미리보기와 요청 조건 정리

This commit is contained in:
2026-03-27 11:59:17 +09:00
parent 9644eabf00
commit 7b4a80f47d
6 changed files with 63 additions and 23 deletions

View File

@@ -1,13 +1,11 @@
<script setup>
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRouter } from 'vue-router'
import Sortable from 'sortablejs'
import { api } from '../lib/api'
import { toApiUrl } from '../lib/runtime'
import { useAuthStore } from '../stores/auth'
import { useToast } from '../composables/useToast'
const router = useRouter()
const auth = useAuthStore()
const toast = useToast()
const isAdmin = computed(() => !!auth.user?.isAdmin)
@@ -42,6 +40,8 @@ const importModalItems = ref([])
const importModalTargetGameId = ref('')
const importModalNewGameId = ref('')
const importModalNewGameName = ref('')
const previewModalOpen = ref(false)
const previewTierList = ref(null)
const users = ref([])
@@ -651,7 +651,18 @@ function tierListVisibilityLabel(tierList) {
}
function openAdminTierList(tierList) {
router.push(`/editor/${tierList.gameId}/${tierList.id}`)
previewTierList.value = tierList
previewModalOpen.value = true
}
function closePreviewModal() {
previewModalOpen.value = false
previewTierList.value = null
}
function previewTierListUrl(tierList) {
if (!tierList?.gameId || !tierList?.id) return ''
return `/editor/${tierList.gameId}/${tierList.id}`
}
function openTierListImportModal(tierList, items) {
@@ -1259,6 +1270,24 @@ async function saveFeaturedOrder() {
</div>
</div>
</div>
<div v-if="previewModalOpen" class="modalOverlay" @click.self="closePreviewModal">
<div class="modalCard modalCard--preview" role="dialog" aria-modal="true">
<div class="modalCard__titleRow">
<div>
<div class="modalCard__title">{{ previewTierList?.title || '티어표 미리보기' }}</div>
<div class="modalCard__desc">관리 화면을 벗어나지 않고 완성본만 확인할 수 있어요.</div>
</div>
<button class="btn btn--ghost btn--small" @click="closePreviewModal">닫기</button>
</div>
<iframe
v-if="previewTierList"
class="previewFrame"
:src="previewTierListUrl(previewTierList)"
title="티어표 미리보기"
/>
</div>
</div>
</template>
<template v-else>
@@ -2128,6 +2157,16 @@ async function saveFeaturedOrder() {
border: 1px solid rgba(255, 255, 255, 0.12);
background: rgba(11, 18, 32, 0.96);
}
.modalCard--preview {
width: min(1200px, 100%);
}
.modalCard__titleRow {
display: flex;
gap: 12px;
align-items: flex-start;
justify-content: space-between;
flex-wrap: wrap;
}
.modalCard__title {
font-size: 18px;
font-weight: 900;
@@ -2146,6 +2185,13 @@ async function saveFeaturedOrder() {
justify-content: flex-end;
flex-wrap: wrap;
}
.previewFrame {
width: 100%;
min-height: min(80vh, 820px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
background: rgba(255, 255, 255, 0.02);
}
.importModeTabs {
display: flex;
gap: 10px;

View File

@@ -91,9 +91,8 @@ const customItems = computed(() =>
.filter((item) => item?.origin === 'custom')
.sort((a, b) => (a.label || '').localeCompare(b.label || '', 'ko'))
)
const hasPlacedItems = computed(() => groups.value.some((group) => (group.itemIds || []).length > 0))
const canRequestTemplateCreate = computed(
() => canEdit.value && !isNewTierList.value && gameId.value === 'freeform' && !hasPlacedItems.value && customItems.value.length > 0
() => canEdit.value && !isNewTierList.value && gameId.value === 'freeform' && customItems.value.length > 0
)
const canRequestTemplateUpdate = computed(
() => canEdit.value && !isNewTierList.value && gameId.value !== 'freeform' && customItems.value.length > 0
@@ -104,11 +103,6 @@ const templateRequestChecks = computed(() => [
label: '티어표 이름(게임 이름)을 직접 입력했는지',
passed: !!(title.value || '').trim() && (title.value || '').trim() !== (gameName.value || '').trim(),
},
{
id: 'empty-board',
label: '등록한 이미지를 티어에 배치하지 않은 원본 상태인지',
passed: !hasPlacedItems.value,
},
])
const canSubmitTemplateCreateRequest = computed(() => templateRequestChecks.value.every((item) => item.passed))
@@ -530,10 +524,6 @@ async function requestTemplate(type) {
toast.error('이미 처리 대기 중인 같은 요청이 있어요.')
return
}
if (e?.status === 400 && e?.data?.error === 'board_must_be_empty') {
toast.error('템플릿 등록 요청은 보드를 비운 상태에서만 보낼 수 있어요.')
return
}
if (e?.status === 400 && e?.data?.error === 'custom_items_required') {
toast.error('먼저 커스텀 아이템을 추가한 뒤 요청해주세요.')
return
@@ -712,7 +702,7 @@ onUnmounted(() => {
</div>
</div>
<div class="requestChecklist__hint">
제목이 명확하고, 보드는 비워둔 채 원본 아이템만 정리되어 있을수록 관리자가 새 게임 템플릿으로 빠르게 등록하기 쉬워져요.
제목만 명확하게 적어두면 관리자가 어떤 게임 템플릿 요청인지 빠르게 파악할 수 있어요. 여러 사용자가 비슷한 주제로 요청할 수 있으니 게임 이름을 구체적으로 적어주세요.
</div>
<div class="modalCard__actions">
<button class="btn btn--ghost" @click="closeTemplateRequestModal">취소</button>