fix: 관리자 이미지 대체 흐름 보정

This commit is contained in:
2026-04-06 10:50:18 +09:00
parent 7164d32ae8
commit a716ee0062
5 changed files with 64 additions and 44 deletions

View File

@@ -89,13 +89,12 @@ export function useAdminCustomItems({
modalTargetCustomItem.value = item || null
customItemModalDraftLabel.value = item?.label || ''
customItemModalTargetTemplateId.value = ''
customItemReplacementQuery.value = item?.label || ''
customItemReplacementQuery.value = ''
customItemReplacementItems.value = []
customItemReplacementTargetId.value = ''
customItemReplacementBusy.value = false
customItemModalOpen.value = true
pushCustomItemModalHistoryState()
void refreshReplacementCandidates()
}
function closeCustomItemModal({ fromPopState = false } = {}) {

View File

@@ -655,6 +655,7 @@ const visibleLinkedTemplates = computed(() =>
(modalTargetCustomItem.value?.linkedTemplates || []).filter((template) => template?.id && template.id !== 'freeform')
)
const linkedCustomItemTemplateIds = computed(() => new Set(visibleLinkedTemplates.value.map((template) => template.id).filter(Boolean)))
const canReplaceModalTarget = computed(() => modalTargetCustomItem.value?.sourceType === 'user')
const replacementCandidateCount = computed(() => customItemReplacementItems.value.length)
const imageStatsPeriodLabel = computed(() => (imageStatsMonth.value ? `${imageStatsMonth.value} 기준` : '전체 기간'))
@@ -2095,48 +2096,53 @@ function openUserProfile(user) {
<button class="btn btn--ghost" type="button" @click="openTemplatePickerModal('custom-item-target')">템플릿 선택</button>
<button class="btn btn--ghost btn--small customItemModal__createTemplateButton" type="button" @click="openTemplateCreateModal"> 템플릿 만들기</button>
</div>
<div class="customItemModal__pickerHead">
<div class="customItemModal__pickerEyebrow">IMAGE REPLACEMENT</div>
<div class="customItemModal__pickerTitle">대체할 이미지 선택</div>
</div>
<div class="adminSelectionCard">
<div class="adminSelectionCard__label">선택한 대체 이미지</div>
<div class="adminSelectionCard__title">{{ customItemReplacementTarget?.label || '아직 선택하지 않음' }}</div>
<div class="adminSelectionCard__meta">{{ customItemReplacementTarget?.sourceLabel || '검색 후 대체할 이미지를 골라 주세요.' }}</div>
</div>
<div class="customItemModal__pickerActions">
<input
v-model="customItemReplacementQuery"
class="input"
type="text"
maxlength="120"
placeholder="대체할 이미지 이름 또는 파일명 검색"
@keydown.enter.prevent="refreshReplacementCandidates"
/>
<button class="btn btn--ghost btn--small" type="button" @click="refreshReplacementCandidates">
{{ customItemReplacementLoading ? '검색중...' : '대체 이미지 검색' }}
</button>
</div>
<div class="customItemModal__replacementList">
<button
v-for="item in customItemReplacementItems"
:key="item.id"
class="adminTemplatePicker__item"
:class="{ 'adminTemplatePicker__item--active': customItemReplacementTargetId === item.id }"
type="button"
@click="customItemReplacementTargetId = item.id"
>
<span class="customItemModal__replacementRow">
<img class="customItemModal__replacementThumb" :src="toApiUrl(item.src)" :alt="item.label" />
<span class="customItemModal__replacementCopy">
<span class="adminTemplatePicker__name">{{ item.label }}</span>
<span class="adminTemplatePicker__meta">{{ item.sourceLabel }} · {{ item.ownerName || '알 수 없음' }}</span>
</span>
</span>
</button>
<div v-if="!customItemReplacementLoading && !replacementCandidateCount" class="hint hint--tight">
대체 후보가 없어요. 검색어를 바꾸거나 먼저 관리자 이미지를 등록해주세요.
<template v-if="canReplaceModalTarget">
<div class="customItemModal__pickerHead">
<div class="customItemModal__pickerEyebrow">IMAGE REPLACEMENT</div>
<div class="customItemModal__pickerTitle">대체할 이미지 선택</div>
</div>
<div class="adminSelectionCard">
<div class="adminSelectionCard__label">선택한 대체 이미지</div>
<div class="adminSelectionCard__title">{{ customItemReplacementTarget?.label || '아직 선택하지 않음' }}</div>
<div class="adminSelectionCard__meta">{{ customItemReplacementTarget?.sourceLabel || '검색 후 대체할 이미지를 골라 주세요.' }}</div>
</div>
<div class="customItemModal__pickerActions">
<input
v-model="customItemReplacementQuery"
class="input"
type="text"
maxlength="120"
placeholder="대체할 이미지 이름 또는 파일명 검색"
@keydown.enter.prevent="refreshReplacementCandidates"
/>
<button class="btn btn--ghost btn--small" type="button" @click="refreshReplacementCandidates">
{{ customItemReplacementLoading ? '검색중...' : '대체 이미지 검색' }}
</button>
</div>
<div class="customItemModal__replacementList">
<button
v-for="item in customItemReplacementItems"
:key="item.id"
class="adminTemplatePicker__item"
:class="{ 'adminTemplatePicker__item--active': customItemReplacementTargetId === item.id }"
type="button"
@click="customItemReplacementTargetId = item.id"
>
<span class="customItemModal__replacementRow">
<img class="customItemModal__replacementThumb" :src="toApiUrl(item.src)" :alt="item.label" />
<span class="customItemModal__replacementCopy">
<span class="adminTemplatePicker__name">{{ item.label }}</span>
<span class="adminTemplatePicker__meta">{{ item.sourceLabel }} · {{ item.ownerName || '알 수 없음' }}</span>
</span>
</span>
</button>
<div v-if="!customItemReplacementLoading && !replacementCandidateCount" class="hint hint--tight">
검색 전에는 대체 후보를 보여주지 않아요. 검색어를 입력한 직접 찾아 선택해주세요.
</div>
</div>
</template>
<div v-else class="hint hint--tight">
이미지 대체는 현재 사용자 업로드 아이템에서만 지원합니다.
</div>
</aside>
<div class="customItemModal__body">
@@ -2178,7 +2184,7 @@ function openUserProfile(user) {
<button class="btn btn--ghost customItemModal__action" :disabled="!customItemModalTargetTemplateId || modalTargetCustomItem.isPromoting" @click="promoteCustomItem(modalTargetCustomItem)">
{{ modalTargetCustomItem.isPromoting ? '추가중...' : '기본 템플릿에 추가' }}
</button>
<button class="btn btn--primary customItemModal__action" :disabled="!customItemReplacementTargetId || customItemReplacementBusy" @click="replaceCustomItem(modalTargetCustomItem)">
<button v-if="canReplaceModalTarget" class="btn btn--primary customItemModal__action" :disabled="!customItemReplacementTargetId || customItemReplacementBusy" @click="replaceCustomItem(modalTargetCustomItem)">
{{ customItemReplacementBusy ? '대체중...' : '선택한 이미지로 대체' }}
</button>
<button v-if="modalTargetCustomItem.canDelete" class="btn btn--danger customItemModal__action" :disabled="modalTargetCustomItem.sourceType === 'user' && (modalTargetCustomItem.usageCount > 0 || visibleLinkedTemplates.length > 0)" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>