릴리스: v1.3.59 관리자 템플릿 요청 중복 방지 및 신규 템플릿 연결 흐름 정리

This commit is contained in:
2026-04-02 12:46:59 +09:00
parent 4f300e7dbc
commit aa114a170e
11 changed files with 303 additions and 32 deletions

View File

@@ -110,6 +110,7 @@ const featuredListEl = ref(null)
const featuredSortable = ref(null)
const gameItemListEl = ref(null)
const gameItemSortable = ref(null)
let gameItemSortableSyncTimer = null
const savedGameItemOrderIds = ref([])
const userAvatarInputs = ref({})
const isGameLoading = ref(false)
@@ -124,14 +125,49 @@ function setItemFileInputRef(el) {
itemFileInput.value = el
}
function scheduleGameItemSortableSync() {
if (gameItemSortableSyncTimer) {
clearTimeout(gameItemSortableSyncTimer)
gameItemSortableSyncTimer = null
}
if (!gameItemListEl.value || !selectedGame.value?.items?.length) return
gameItemSortableSyncTimer = setTimeout(() => {
gameItemSortableSyncTimer = null
syncGameItemSortable()
}, 0)
}
function setGameItemListRef(el) {
gameItemListEl.value = el
if (!el) return
scheduleGameItemSortableSync()
}
function normalizeAdminSrc(src) {
if (typeof src !== 'string') return ''
const raw = src.trim()
if (!raw) return ''
if (raw.startsWith('/uploads/')) return raw
try {
const url = new URL(raw)
return url.pathname || raw
} catch (e) {
return raw
}
}
const hasSelectedGame = computed(() => !!selectedGame.value?.game?.id)
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedGameId.value)
const canAddItem = computed(() => uploadItemDrafts.value.length > 0 && uploadItemDrafts.value.every((item) => !!item.label.trim()) && !!selectedGameId.value)
const stagedRequestDraftCount = computed(() => uploadItemDrafts.value.filter((item) => item.kind === 'request').length)
const appliedRequestItemCount = computed(() => {
if (!activeTemplateRequest.value?.id || !selectedGame.value?.items?.length) return 0
const sourceRequest = templateRequests.value.find((request) => request.id === activeTemplateRequest.value.id)
if (!sourceRequest?.items?.length) return 0
const gameSrcs = new Set((selectedGame.value.items || []).map((item) => normalizeAdminSrc(item?.src)).filter(Boolean))
return sourceRequest.items.filter((item) => gameSrcs.has(normalizeAdminSrc(item?.src))).length
})
const hasGameItemOrderChanges = computed(() => {
const currentIds = (selectedGame.value?.items || []).map((item) => item.id)
return currentIds.join('|') !== savedGameItemOrderIds.value.join('|')
@@ -316,6 +352,10 @@ onUnmounted(() => {
if (typeof document !== 'undefined') document.body.style.overflow = previousBodyOverflow.value || ''
clearPreviewUrl('item')
clearPreviewUrl('thumb')
if (gameItemSortableSyncTimer) {
clearTimeout(gameItemSortableSyncTimer)
gameItemSortableSyncTimer = null
}
destroyFeaturedSortable()
destroyGameItemSortable()
})
@@ -438,6 +478,14 @@ watch(
}
)
watch(
() => [selectedGame.value?.game?.id || '', selectedGame.value?.items?.length || 0, !!gameItemListEl.value],
([gameId, itemCount, hasListEl]) => {
if (!gameId || !itemCount || !hasListEl) return
scheduleGameItemSortableSync()
}
)
watch(
() => isAnyModalOpen.value,
@@ -746,6 +794,7 @@ const {
const {
destroyGameItemSortable,
syncGameItemSortable,
mergeRequestItemsIntoDrafts,
removeUploadDraft,
loadGame,
@@ -1265,7 +1314,13 @@ function templateRequestTypeLabel(request) {
}
function templateRequestTargetLabel(request) {
return request.type === 'create' ? '새 게임 템플릿 생성' : request.targetGameName || request.targetGameId || request.sourceGameName
if (request.type === 'create') {
if (request.targetGameName || request.targetGameId) {
return `연결된 게임 · ${request.targetGameName || request.targetGameId}`
}
return '연결된 게임 없음'
}
return request.targetGameName || request.targetGameId || request.sourceGameName
}
const displayThumbnailUrl = computed(() => {
@@ -1334,6 +1389,7 @@ function userAvatarFallback(user) {
:active-template-request="activeTemplateRequest"
:template-request-source-url="templateRequestSourceUrl"
:staged-request-draft-count="stagedRequestDraftCount"
:applied-request-item-count="appliedRequestItemCount"
:open-game-create-modal="openGameCreateModal"
:is-game-loading="isGameLoading"
:has-selected-game="hasSelectedGame"
@@ -2521,6 +2577,10 @@ function userAvatarFallback(user) {
.adminUiScope .chosen {
outline: 2px solid rgba(96, 165, 250, 0.45);
}
.adminUiScope .thumbCard--dragging {
box-shadow: 0 18px 38px rgba(15, 23, 42, 0.34);
opacity: 0.96;
}
.adminUiScope .btn:disabled {
cursor: not-allowed;
opacity: 0.45;
@@ -2673,6 +2733,10 @@ function userAvatarFallback(user) {
border: 1px solid rgba(255, 255, 255, 0.12);
background: var(--theme-surface-soft);
}
.adminUiScope .itemPreviewCard__submit {
margin-top: 12px;
width: 100%;
}
.adminUiScope .itemPreviewGrid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -2744,6 +2808,8 @@ function userAvatarFallback(user) {
min-width: 0;
cursor: grab;
user-select: none;
-webkit-user-drag: none;
touch-action: none;
}
.adminUiScope .thumbCard:active {
cursor: grabbing;
@@ -3868,6 +3934,9 @@ function userAvatarFallback(user) {
.adminUiScope .itemPreviewCard {
max-width: none;
}
.adminUiScope .itemDraftList {
grid-template-columns: 1fr;
}
.adminUiScope .userCard__identity {
width: 100%;
}