릴리스: v1.3.9 관리자 최적화 패널 범위와 티어 행 삭제 UX 정리
This commit is contained in:
@@ -257,6 +257,9 @@ const imageDiagnosticsCards = computed(() => {
|
||||
{ label: '절감률', value: `${Math.round((stats.savingsRatio || 0) * 100)}%` },
|
||||
]
|
||||
})
|
||||
const visibleLinkedGames = computed(() =>
|
||||
(modalTargetCustomItem.value?.linkedGames || []).filter((game) => game?.id && game.id !== 'freeform')
|
||||
)
|
||||
|
||||
const imageStatsPeriodLabel = computed(() => (imageStatsMonth.value ? `${imageStatsMonth.value} 기준` : '전체 기간'))
|
||||
|
||||
@@ -1726,11 +1729,11 @@ async function saveFeaturedOrder() {
|
||||
</select>
|
||||
</div>
|
||||
<div class="customItemModal__linked">
|
||||
<span class="customItemModal__label">이미 사용 중인 게임</span>
|
||||
<div v-if="modalTargetCustomItem.linkedGames?.length" class="customItemModal__chips">
|
||||
<span v-for="game in modalTargetCustomItem.linkedGames" :key="game.id" class="pill">{{ game.name }}</span>
|
||||
<span class="customItemModal__label">템플릿에 사용 중인 게임</span>
|
||||
<div v-if="visibleLinkedGames.length" class="customItemModal__chips">
|
||||
<span v-for="game in visibleLinkedGames" :key="game.id" class="pill">{{ game.name }}</span>
|
||||
</div>
|
||||
<div v-else class="hint hint--tight">아직 연결된 게임이 없어요.</div>
|
||||
<div v-else class="hint hint--tight">아직 템플릿에 연결된 게임이 없어요.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="customItemModal__body">
|
||||
@@ -1925,7 +1928,7 @@ async function saveFeaturedOrder() {
|
||||
</section>
|
||||
|
||||
|
||||
<section class="adminSidebar__panel">
|
||||
<section v-if="activeTab === 'featured'" class="adminSidebar__panel">
|
||||
<div class="adminSidebar__label">Image Optimization</div>
|
||||
<div class="adminSidebar__group">
|
||||
<input v-model="imageStatsMonth" class="input" type="month" />
|
||||
|
||||
@@ -45,6 +45,8 @@ const isTemplateUpdateModalOpen = ref(false)
|
||||
const templateRequestDraftTitle = ref('')
|
||||
const templateRequestDraftDescription = ref('')
|
||||
const isDeleteModalOpen = ref(false)
|
||||
const isGroupDeleteModalOpen = ref(false)
|
||||
const pendingRemoveGroupId = ref('')
|
||||
const ownerId = ref('')
|
||||
const authorName = ref('')
|
||||
const authorAccountName = ref('')
|
||||
@@ -280,7 +282,7 @@ async function addGroup() {
|
||||
await syncSortables()
|
||||
}
|
||||
|
||||
async function removeGroup(groupId) {
|
||||
async function performRemoveGroup(groupId) {
|
||||
if (groups.value.length <= 1) return
|
||||
const target = groups.value.find((group) => group.id === groupId)
|
||||
if (!target) return
|
||||
@@ -290,6 +292,24 @@ async function removeGroup(groupId) {
|
||||
await syncSortables()
|
||||
}
|
||||
|
||||
function openGroupDeleteModal(groupId) {
|
||||
if (!canEdit.value || groups.value.length <= 1 || !groupId) return
|
||||
pendingRemoveGroupId.value = groupId
|
||||
isGroupDeleteModalOpen.value = true
|
||||
}
|
||||
|
||||
function closeGroupDeleteModal() {
|
||||
isGroupDeleteModalOpen.value = false
|
||||
pendingRemoveGroupId.value = ''
|
||||
}
|
||||
|
||||
async function confirmRemoveGroup() {
|
||||
const groupId = pendingRemoveGroupId.value
|
||||
closeGroupDeleteModal()
|
||||
if (!groupId) return
|
||||
await performRemoveGroup(groupId)
|
||||
}
|
||||
|
||||
function addCustomImage(file) {
|
||||
if (!file || !file.type.startsWith('image/')) return
|
||||
const url = URL.createObjectURL(file)
|
||||
@@ -833,6 +853,19 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isGroupDeleteModalOpen" class="modalOverlay" @click.self="closeGroupDeleteModal">
|
||||
<div class="modalCard" role="dialog" aria-modal="true" aria-labelledby="deleteGroupTitle">
|
||||
<div id="deleteGroupTitle" class="modalCard__title">티어 라인 삭제</div>
|
||||
<div class="modalCard__desc">
|
||||
이 라인을 삭제하면 현재 들어 있는 아이템은 모두 아래 아이템 영역으로 이동합니다. 삭제 후에도 아이템 자체는 유지돼요.
|
||||
</div>
|
||||
<div class="modalCard__actions">
|
||||
<button class="btn btn--ghost" @click="closeGroupDeleteModal">취소</button>
|
||||
<button class="btn btn--danger" @click="confirmRemoveGroup">라인 삭제</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="layout" :style="{ '--thumb-size': `${iconSize}px` }">
|
||||
<div class="editorMain">
|
||||
<section class="head">
|
||||
@@ -884,9 +917,18 @@ onUnmounted(() => {
|
||||
<div class="row__exportName">{{ g.name }}</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<button
|
||||
v-if="canEdit"
|
||||
class="rowRemoveIcon"
|
||||
type="button"
|
||||
title="티어 라인 삭제"
|
||||
:disabled="groups.length <= 1"
|
||||
@click="openGroupDeleteModal(g.id)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<span class="grab" title="드래그로 순서 변경" data-group-handle>↕</span>
|
||||
<input v-model="g.name" class="groupName" :readonly="!canEdit" />
|
||||
<button v-if="canEdit" class="rowRemoveBtn" :disabled="groups.length <= 1" @click="removeGroup(g.id)">삭제</button>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
@@ -1146,7 +1188,7 @@ onUnmounted(() => {
|
||||
.previewOnly__label {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 10px 8px;
|
||||
padding: 10px 12px;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
border-radius: 14px;
|
||||
@@ -1512,6 +1554,7 @@ onUnmounted(() => {
|
||||
align-items: stretch;
|
||||
}
|
||||
.row__label {
|
||||
position: relative;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
@@ -1519,7 +1562,7 @@ onUnmounted(() => {
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 8px;
|
||||
padding: 10px 12px;
|
||||
font-weight: 900;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1547,26 +1590,37 @@ onUnmounted(() => {
|
||||
outline: none;
|
||||
min-width: 0;
|
||||
}
|
||||
.rowRemoveIcon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
background: rgba(15, 15, 15, 0.7);
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
}
|
||||
.rowRemoveIcon:hover {
|
||||
background: rgba(70, 20, 20, 0.82);
|
||||
border-color: rgba(239, 68, 68, 0.34);
|
||||
}
|
||||
.rowRemoveIcon:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.row__exportName {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-weight: 900;
|
||||
word-break: break-word;
|
||||
}
|
||||
.rowRemoveBtn {
|
||||
padding: 6px 10px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(239, 68, 68, 0.28);
|
||||
background: rgba(239, 68, 68, 0.12);
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.rowRemoveBtn:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.row__drop {
|
||||
border-radius: 16px;
|
||||
background: rgba(0, 0, 0, 0.18);
|
||||
|
||||
Reference in New Issue
Block a user