목록관리 템플릿 추가 모달 정리
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
<script setup>
|
||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
featuredTemplates: { type: Array, required: true },
|
||||
availableTemplatesForFeatured: { type: Array, required: true },
|
||||
@@ -9,6 +11,44 @@ const props = defineProps({
|
||||
removeFeaturedTemplate: { type: Function, required: true },
|
||||
addFeaturedTemplate: { type: Function, required: true },
|
||||
})
|
||||
|
||||
const featuredTemplateSearchQuery = ref('')
|
||||
const featuredTemplatePickerOpen = ref(false)
|
||||
|
||||
const filteredAvailableTemplatesForFeatured = computed(() => {
|
||||
const query = featuredTemplateSearchQuery.value.trim().toLowerCase()
|
||||
if (!query) return props.availableTemplatesForFeatured
|
||||
return props.availableTemplatesForFeatured.filter((template) => {
|
||||
const haystack = `${template.name || ''} ${template.slug || ''} ${template.id || ''}`.toLowerCase()
|
||||
return haystack.includes(query)
|
||||
})
|
||||
})
|
||||
|
||||
function openFeaturedTemplatePicker() {
|
||||
featuredTemplateSearchQuery.value = ''
|
||||
featuredTemplatePickerOpen.value = true
|
||||
}
|
||||
|
||||
function closeFeaturedTemplatePicker() {
|
||||
featuredTemplatePickerOpen.value = false
|
||||
}
|
||||
|
||||
function chooseFeaturedTemplate(templateId) {
|
||||
props.addFeaturedTemplate(templateId)
|
||||
closeFeaturedTemplatePicker()
|
||||
}
|
||||
|
||||
function setFeaturedTransitionListRef(el) {
|
||||
props.featuredListRef(el?.$el || el)
|
||||
}
|
||||
|
||||
watch(featuredTemplatePickerOpen, (isOpen) => {
|
||||
document.body.classList.toggle('modal-scroll-lock', isOpen)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.body.classList.remove('modal-scroll-lock')
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -18,14 +58,23 @@ const props = defineProps({
|
||||
<div class="panel__title">홈 화면 상단 고정 순서</div>
|
||||
<div class="hint hint--tight">여기에 넣은 템플릿은 지정한 순서대로 먼저 노출되고, 나머지 템플릿은 최근 생성순으로 뒤에 이어집니다. 최대 50개까지 설정할 수 있어요.</div>
|
||||
</div>
|
||||
<button class="btn btn--primary" @click="props.saveFeaturedOrder">순서 저장</button>
|
||||
<div class="featuredOrderPanel__headActions">
|
||||
<button
|
||||
class="btn btn--ghost"
|
||||
:disabled="props.featuredTemplateIds.length >= 50 || !props.availableTemplatesForFeatured.length"
|
||||
@click="openFeaturedTemplatePicker"
|
||||
>
|
||||
템플릿 추가
|
||||
</button>
|
||||
<button class="btn btn--primary" @click="props.saveFeaturedOrder">순서 저장</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featuredOrderPanel">
|
||||
<div class="featuredOrderPanel__list">
|
||||
<div class="section__title">상단 고정 목록</div>
|
||||
<div v-if="!props.featuredTemplates.length" class="hint">아직 상단 고정 템플릿이 없어요.</div>
|
||||
<div v-else :ref="props.featuredListRef" class="featuredList">
|
||||
<TransitionGroup v-else :ref="setFeaturedTransitionListRef" tag="div" name="featured-list" class="featuredList">
|
||||
<article v-for="(template, index) in props.featuredTemplates" :key="template.id" class="featuredCard" :data-featured-id="template.id">
|
||||
<div class="featuredCard__meta">
|
||||
<span class="featuredCard__rank">{{ index + 1 }}</span>
|
||||
@@ -41,22 +90,35 @@ const props = defineProps({
|
||||
<button class="btn btn--danger btn--small" @click="props.removeFeaturedTemplate(template.id)">제외</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="featuredOrderPanel__picker">
|
||||
<div class="section__title">템플릿 추가</div>
|
||||
<div class="featuredPickerList">
|
||||
<div v-if="featuredTemplatePickerOpen" class="modalOverlay" @click.self="closeFeaturedTemplatePicker">
|
||||
<div class="modalCard" role="dialog" aria-modal="true">
|
||||
<div class="modalCard__titleRow">
|
||||
<div>
|
||||
<div class="modalCard__title">상단 고정 템플릿 추가</div>
|
||||
<div class="modalCard__desc">템플릿 이름, slug, ID로 검색한 뒤 고정 목록에 추가할 항목을 선택하세요.</div>
|
||||
</div>
|
||||
<button class="btn btn--ghost btn--small" @click="closeFeaturedTemplatePicker">닫기</button>
|
||||
</div>
|
||||
<div class="modalCard__form modalCard__form--search">
|
||||
<input v-model="featuredTemplateSearchQuery" class="input" placeholder="템플릿 이름 또는 slug 검색" autofocus />
|
||||
<span class="hint hint--tight">{{ filteredAvailableTemplatesForFeatured.length }}개 결과</span>
|
||||
</div>
|
||||
<div class="templatePickerModalList">
|
||||
<button
|
||||
v-for="template in props.availableTemplatesForFeatured"
|
||||
v-for="template in filteredAvailableTemplatesForFeatured"
|
||||
:key="template.id"
|
||||
class="featuredPickerItem"
|
||||
:disabled="props.featuredTemplateIds.length >= 50"
|
||||
@click="props.addFeaturedTemplate(template.id)"
|
||||
class="adminTemplatePicker__item"
|
||||
type="button"
|
||||
@click="chooseFeaturedTemplate(template.id)"
|
||||
>
|
||||
<span>{{ template.name }}</span>
|
||||
<span class="featuredPickerItem__id">{{ template.slug || template.id }}</span>
|
||||
<span class="adminTemplatePicker__name">{{ template.name }}</span>
|
||||
<span class="adminTemplatePicker__meta">{{ template.slug || template.id }}</span>
|
||||
</button>
|
||||
<div v-if="!filteredAvailableTemplatesForFeatured.length" class="hint hint--tight">추가할 수 있는 템플릿이 없어요.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,7 +64,6 @@ export function useAdminFeaturedTemplates({
|
||||
const [moved] = nextIds.splice(currentIndex, 1)
|
||||
nextIds.splice(nextIndex, 0, moved)
|
||||
featuredTemplateIds.value = nextIds
|
||||
syncFeaturedSortable()
|
||||
}
|
||||
|
||||
async function saveFeaturedOrder() {
|
||||
|
||||
@@ -3470,12 +3470,22 @@ function openUserProfile(user) {
|
||||
color: var(--theme-text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
body.modal-scroll-lock {
|
||||
overflow: hidden;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
.adminUiScope .featuredOrderPanel {
|
||||
margin-top: 14px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(260px, 0.95fr);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
.adminUiScope .featuredOrderPanel__headActions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.adminUiScope .featuredOrderPanel__list,
|
||||
.adminUiScope .featuredOrderPanel__picker {
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
@@ -3485,6 +3495,7 @@ function openUserProfile(user) {
|
||||
}
|
||||
.adminUiScope .featuredList,
|
||||
.adminUiScope .featuredPickerList {
|
||||
position: relative;
|
||||
margin-top: 10px;
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
@@ -3501,6 +3512,20 @@ function openUserProfile(user) {
|
||||
border: 1px solid var(--theme-border);
|
||||
background: var(--theme-pill-bg);
|
||||
}
|
||||
.adminUiScope .featured-list-move,
|
||||
.adminUiScope .featured-list-enter-active,
|
||||
.adminUiScope .featured-list-leave-active {
|
||||
transition: transform 180ms ease, opacity 180ms ease;
|
||||
}
|
||||
.adminUiScope .featured-list-enter-from,
|
||||
.adminUiScope .featured-list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
.adminUiScope .featured-list-leave-active {
|
||||
position: absolute;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
.adminUiScope .featuredCard__meta {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
|
||||
Reference in New Issue
Block a user