This commit is contained in:
SeoJHeasdw 2025-06-13 11:19:32 +09:00
commit 336f7dd8eb

View File

@ -1,100 +1,48 @@
//* src/views/ContentManagementView.vue //* src/views/ContentManagementView.vue
<template> <template>
<v-container fluid class="pa-4"> <v-container fluid class="pa-4">
<!-- 콘텐츠 타입 필터 - 형태로 변경 --> <!-- 상단 헤더 - 제목과 생성 버튼만 -->
<div class="mb-6"> <div class="d-flex justify-space-between align-center mb-6">
<div class="d-flex align-center flex-wrap ga-2"> <!-- 콘텐츠 생성 버튼 -->
<span class="text-subtitle-2 font-weight-medium mr-3">콘텐츠 타입:</span> <v-btn
color="primary"
<!-- 전체 필터 --> size="large"
<v-chip prepend-icon="mdi-plus"
:color="selectedContentType === 'all' ? 'primary' : 'default'" @click="$router.push('/content/create')"
:variant="selectedContentType === 'all' ? 'flat' : 'outlined'" >
class="cursor-pointer" 콘텐츠 생성
@click="selectContentType('all')" </v-btn>
>
<v-icon start size="16">mdi-view-grid</v-icon>
전체 ({{ getTotalCount() }})
</v-chip>
<!-- Instagram 필터 -->
<v-chip
:color="selectedContentType === 'instagram' ? 'pink' : 'default'"
:variant="selectedContentType === 'instagram' ? 'flat' : 'outlined'"
class="cursor-pointer"
@click="selectContentType('instagram')"
>
<v-icon start size="16" color="pink">mdi-instagram</v-icon>
Instagram ({{ getTypeCount('instagram') }})
</v-chip>
<!-- 네이버 블로그 필터 -->
<v-chip
:color="selectedContentType === 'blog' ? 'green' : 'default'"
:variant="selectedContentType === 'blog' ? 'flat' : 'outlined'"
class="cursor-pointer"
@click="selectContentType('blog')"
>
<v-icon start size="16" color="green">mdi-blogger</v-icon>
네이버 블로그 ({{ getTypeCount('blog') }})
</v-chip>
<!-- 포스터 필터 -->
<v-chip
:color="selectedContentType === 'poster' ? 'orange' : 'default'"
:variant="selectedContentType === 'poster' ? 'flat' : 'outlined'"
class="cursor-pointer"
@click="selectContentType('poster')"
>
<v-icon start size="16" color="orange">mdi-file-image</v-icon>
포스터 ({{ getTypeCount('poster') }})
</v-chip>
</div>
</div> </div>
<!-- 추가 필터 정렬 --> <!-- 필터 영역 - 통합된 필터 -->
<v-card class="mb-6"> <v-card class="mb-6">
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<!-- 추가 필터 --> <!-- 콘텐츠 타입 필터 -->
<v-col cols="12" md="6"> <v-col cols="12" md="3">
<!-- 검색 --> <v-select
<v-spacer /> v-model="selectedContentType"
:items="contentTypeOptions"
label="콘텐츠 타입"
variant="outlined"
density="compact"
prepend-inner-icon="mdi-filter-variant"
@update:model-value="applyFilters"
/>
</v-col>
<!-- 제목 검색 -->
<v-col cols="12" md="4">
<v-text-field <v-text-field
v-model="searchQuery" v-model="searchQuery"
label="제목, 해시태그로 검색" label="제목, 해시태그로 검색"
prepend-inner-icon="mdi-magnify" prepend-inner-icon="mdi-magnify"
variant="outlined" variant="outlined"
density="compact" density="compact"
style="min-width: 300px;"
clearable clearable
@update:model-value="applyFilters" @update:model-value="applyFilters"
/> />
</v-col> </v-col>
<!-- 정렬 기간 필터 -->
<v-col cols="12" md="3">
<v-select
v-model="filters.period"
:items="periodOptions"
label="생성 기간"
variant="outlined"
density="compact"
@update:model-value="applyFilters"
/>
</v-col>
<v-col cols="12" md="3">
<!-- 콘텐츠 생성 버튼 -->
<v-btn
color="primary"
size="large"
prepend-icon="mdi-plus"
@click="$router.push('/content/create')"
>
콘텐츠 생성
</v-btn>
</v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
</v-card> </v-card>
@ -135,7 +83,7 @@
</v-btn> </v-btn>
</div> </div>
<!-- 리스트 - 테이블 형태 --> <!-- 리스트 - 정렬 가능한 테이블 -->
<div v-else> <div v-else>
<v-table> <v-table>
<thead> <thead>
@ -149,7 +97,27 @@
</th> </th>
<th width="450">제목</th> <th width="450">제목</th>
<th width="150">플랫폼</th> <th width="150">플랫폼</th>
<th width="200">프로모션 기간</th> <!-- 프로모션 기간 - 정렬 가능 -->
<th
width="200"
class="sortable-header cursor-pointer"
@click="sortByPromotionDate"
>
<div class="d-flex align-center">
<span>프로모션 기간</span>
<v-icon
:color="promotionSortOrder === 'none' ? 'grey-lighten-1' : 'primary'"
size="16"
class="ml-1"
>
{{
promotionSortOrder === 'asc' ? 'mdi-arrow-up' :
promotionSortOrder === 'desc' ? 'mdi-arrow-down' :
'mdi-unfold-more-horizontal'
}}
</v-icon>
</div>
</th>
<th width="120">액션</th> <th width="120">액션</th>
</tr> </tr>
</thead> </thead>
@ -429,6 +397,7 @@ import { useContentStore } from '@/store/content'
* - 생성된 콘텐츠 목록 조회 * - 생성된 콘텐츠 목록 조회
* - 필터링 검색 * - 필터링 검색
* - 콘텐츠 상세 보기, 수정, 삭제 * - 콘텐츠 상세 보기, 수정, 삭제
* - 프로모션 기간 정렬 기능
*/ */
// //
@ -446,16 +415,18 @@ const itemsPerPage = ref(20)
// //
const selectedContentType = ref('all') const selectedContentType = ref('all')
// // ( )
const filters = ref({ const filters = ref({
published: false, published: false,
draft: false, draft: false
period: '전체'
}) })
// //
const sortBy = ref('latest') const sortBy = ref('latest')
//
const promotionSortOrder = ref('none') // 'none', 'asc', 'desc'
// //
const showDetailDialog = ref(false) const showDetailDialog = ref(false)
const selectedContent = ref(null) const selectedContent = ref(null)
@ -472,6 +443,13 @@ const successMessage = ref('')
const errorMessage = ref('') const errorMessage = ref('')
// //
const contentTypeOptions = [
{ title: '전체', value: 'all' },
{ title: 'Instagram', value: 'instagram' },
{ title: '네이버 블로그', value: 'blog' },
{ title: '포스터', value: 'poster' }
]
const statusOptions = [ const statusOptions = [
{ title: '발행됨', value: 'published' }, { title: '발행됨', value: 'published' },
{ title: '임시저장', value: 'draft' }, { title: '임시저장', value: 'draft' },
@ -485,14 +463,6 @@ const sortOptions = [
{ title: '조회수순', value: 'views' } { title: '조회수순', value: 'views' }
] ]
const periodOptions = [
{ title: '전체', value: '전체' },
{ title: '최근 1주일', value: '1주일' },
{ title: '최근 1개월', value: '1개월' },
{ title: '최근 3개월', value: '3개월' },
{ title: '최근 6개월', value: '6개월' }
]
const titleRules = [ const titleRules = [
v => !!v || '제목을 입력해주세요', v => !!v || '제목을 입력해주세요',
v => v.length <= 100 || '제목은 100자 이내로 입력해주세요' v => v.length <= 100 || '제목은 100자 이내로 입력해주세요'
@ -525,46 +495,35 @@ const filteredContents = computed(() => {
return false return false
}) })
} }
// // ( )
if (filters.value.period !== '전체') { if (promotionSortOrder.value === 'none') {
const now = new Date() switch (sortBy.value) {
const startDate = new Date() case 'latest':
contents.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
switch (filters.value.period) {
case '1주일':
startDate.setDate(now.getDate() - 7)
break break
case '1개월': case 'oldest':
startDate.setMonth(now.getMonth() - 1) contents.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
break break
case '3개월': case 'title':
startDate.setMonth(now.getMonth() - 3) contents.sort((a, b) => a.title.localeCompare(b.title))
break break
case '6개월': case 'views':
startDate.setMonth(now.getMonth() - 6) contents.sort((a, b) => (b.views || 0) - (a.views || 0))
break break
} }
} else {
contents = contents.filter(content => //
new Date(content.createdAt) >= startDate contents.sort((a, b) => {
) const dateA = new Date(a.startDate || 0)
} const dateB = new Date(b.startDate || 0)
// if (promotionSortOrder.value === 'asc') {
switch (sortBy.value) { return dateA - dateB
case 'latest': } else {
contents.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) return dateB - dateA
break }
case 'oldest': })
contents.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))
break
case 'title':
contents.sort((a, b) => a.title.localeCompare(b.title))
break
case 'views':
contents.sort((a, b) => (b.views || 0) - (a.views || 0))
break
} }
return contents return contents
@ -675,17 +634,35 @@ const formatDateRange = (startDate, endDate) => {
return `${start} ~ ${end}` return `${start} ~ ${end}`
} }
// //
const selectContentType = (type) => { const sortByPromotionDate = () => {
selectedContentType.value = type //
if (promotionSortOrder.value === 'none') {
promotionSortOrder.value = 'asc'
} else if (promotionSortOrder.value === 'asc') {
promotionSortOrder.value = 'desc'
} else {
promotionSortOrder.value = 'none'
}
//
if (promotionSortOrder.value !== 'none') {
sortBy.value = 'latest' //
}
currentPage.value = 1 currentPage.value = 1
} }
//
const applyFilters = () => { const applyFilters = () => {
//
promotionSortOrder.value = 'none'
currentPage.value = 1 currentPage.value = 1
} }
const applySorting = () => { const applySorting = () => {
//
promotionSortOrder.value = 'none'
currentPage.value = 1 currentPage.value = 1
} }
@ -807,6 +784,14 @@ onMounted(async () => {
watch(selectedItems, (newVal) => { watch(selectedItems, (newVal) => {
selectAll.value = newVal.length === paginatedContents.value.length && newVal.length > 0 selectAll.value = newVal.length === paginatedContents.value.length && newVal.length > 0
}) })
//
watch(promotionSortOrder, (newVal) => {
if (newVal !== 'none') {
//
console.log(`프로모션 기간 정렬: ${newVal === 'asc' ? '오름차순' : '내림차순'}`)
}
})
</script> </script>
<style scoped> <style scoped>
@ -820,6 +805,19 @@ watch(selectedItems, (newVal) => {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* 정렬 가능한 헤더 스타일 */
.sortable-header {
transition: background-color 0.2s ease-in-out;
}
.sortable-header:hover {
background-color: rgba(0, 0, 0, 0.04);
}
.sortable-header .v-icon {
transition: color 0.2s ease-in-out;
}
/* 버튼 hover 효과 */ /* 버튼 hover 효과 */
.v-btn { .v-btn {
transition: all 0.2s ease-in-out; transition: all 0.2s ease-in-out;
@ -855,18 +853,6 @@ watch(selectedItems, (newVal) => {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
} }
/* 칩 필터 스타일링 */
.v-chip.cursor-pointer:hover {
transform: scale(1.05);
transition: transform 0.2s ease-in-out;
}
/* 플랫폼 칩 특별 스타일링 */
.v-chip.v-chip--variant-flat {
font-weight: 600;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 비활성화된 입력 필드 스타일 */ /* 비활성화된 입력 필드 스타일 */
.bg-grey-lighten-4 { .bg-grey-lighten-4 {
background-color: #f5f5f5; background-color: #f5f5f5;
@ -883,25 +869,52 @@ watch(selectedItems, (newVal) => {
background-color: #f8f9fa; background-color: #f8f9fa;
font-weight: 600; font-weight: 600;
color: #495057; color: #495057;
position: relative;
} }
.v-table tbody td { .v-table tbody td {
vertical-align: middle; vertical-align: middle;
} }
/* 필터 카드 스타일 */
.v-card {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 반응형 디자인 */ /* 반응형 디자인 */
@media (max-width: 768px) { @media (max-width: 768px) {
.d-flex.align-center.flex-wrap.ga-2 { .d-flex.justify-space-between.align-center {
flex-direction: column; flex-direction: column;
align-items: stretch; align-items: stretch;
gap: 16px;
} }
.d-flex.align-center.flex-wrap.ga-2 .v-chip { .v-row .v-col {
margin-bottom: 8px; margin-bottom: 8px;
} }
.v-text-field { .v-table {
min-width: 100% !important; font-size: 0.875rem;
}
.v-table th,
.v-table td {
padding: 8px 4px;
}
.sortable-header {
font-size: 0.8rem;
} }
} }
/* 정렬 아이콘 애니메이션 */
@keyframes sortActive {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.sortable-header .v-icon[style*="color: rgb(25, 118, 210)"] {
animation: sortActive 0.3s ease-in-out;
}
</style> </style>