릴리스: 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>