test #56

Merged
klaus merged 14 commits from test into main 2025-02-09 13:25:36 +00:00
11 changed files with 1860 additions and 68 deletions

View File

@ -19,8 +19,8 @@ async function modifyAudioContent(request) {
return Vue.axios.put("/admin/audio-content", request)
}
async function getBannerList() {
return Vue.axios.get("/admin/audio-content/banner")
async function getBannerList(tabId) {
return Vue.axios.get("/admin/audio-content/banner?tabId=" + tabId)
}
async function saveBanner(formData) {
@ -43,8 +43,8 @@ async function updateBannerOrders(ids) {
return Vue.axios.put('/admin/audio-content/banner/orders', {ids: ids})
}
async function getCurations() {
return Vue.axios.get("/admin/audio-content/curation")
async function getCurations(tabId) {
return Vue.axios.get("/admin/audio-content/curation?tabId=" + tabId)
}
async function saveCuration(request) {
@ -63,6 +63,36 @@ async function getAudioContentThemeList() {
return Vue.axios.get("/admin/audio-content/theme")
}
async function getAudioContentMainTabList() {
return Vue.axios.get("/admin/audio-content/main/tab")
}
async function getCurationItems(curationId) {
return Vue.axios.get("/admin/audio-content/curation/items?curationId=" + curationId)
}
async function searchContentItem(curationId, searchWord) {
return Vue.axios.get("/admin/audio-content/curation/search/content?curationId=" + curationId + "&searchWord=" + searchWord)
}
async function searchSeriesItem(curationId, searchWord) {
return Vue.axios.get("/admin/audio-content/curation/search/series?curationId=" + curationId + "&searchWord=" + searchWord)
}
async function addItemToCuration(curationId, itemIdList){
return Vue.axios.post(
"/admin/audio-content/curation/add/item",
{curationId: curationId, itemIdList: itemIdList}
)
}
async function removeItemInCuration(curationId, itemId){
return Vue.axios.put(
"/admin/audio-content/curation/remove/item",
{curationId: curationId, itemId: itemId}
)
}
export {
getAudioContentList,
searchAudioContent,
@ -75,5 +105,11 @@ export {
saveCuration,
modifyCuration,
updateCurationOrders,
getAudioContentThemeList
getAudioContentThemeList,
getAudioContentMainTabList,
getCurationItems,
searchSeriesItem,
searchContentItem,
addItemToCuration,
removeItemInCuration
}

View File

@ -0,0 +1,32 @@
import Vue from 'vue';
async function getRecommendSeriesList(isFree) {
return Vue.axios.get("/admin/audio-content/series/recommend?isFree=" + isFree);
}
async function saveRecommendSeries(formData) {
return Vue.axios.post('/admin/audio-content/series/recommend', formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
}
async function modifyRecommendSeries(formData) {
return Vue.axios.put('/admin/audio-content/series/recommend', formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
}
async function updateRecommendSeriesOrders(ids) {
return Vue.axios.put('/admin/audio-content/series/recommend/orders', {ids: ids})
}
export {
getRecommendSeriesList,
saveRecommendSeries,
modifyRecommendSeries,
updateRecommendSeriesOrders
}

View File

@ -80,15 +80,30 @@ const routes = [
name: 'ContentCuration',
component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentCuration.vue')
},
{
path: '/content/curation/detail',
name: 'ContentCurationDetail',
component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentCurationDetail.vue')
},
{
path: '/content/series/list',
name: 'ContentSeriesList',
component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentSeriesList.vue')
component: () => import(/* webpackChunkName: "series" */ '../views/Series/ContentSeriesList.vue')
},
{
path: '/content/series/genre',
name: 'ContentSeriesGenre',
component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentSeriesGenre.vue')
component: () => import(/* webpackChunkName: "series" */ '../views/Series/ContentSeriesGenre.vue')
},
{
path: '/content/series/new',
name: 'ContentSeriesNew',
component: () => import(/* webpackChunkName: "series" */ '../views/Series/ContentSeriesNew.vue')
},
{
path: '/content/series/recommend-free',
name: 'ContentSeriesRecommendFree',
component: () => import(/* webpackChunkName: "series" */ '../views/Series/ContentSeriesRecommendFree.vue')
},
{
path: '/promotion/event',

View File

@ -10,11 +10,25 @@
<v-container>
<v-row>
<v-col cols="10" />
<v-col cols="9">
<v-radio-group
v-model="selected_tab_id"
row
@change="getCurations"
>
<v-radio
v-for="tab in tabs"
:key="tab.tabId"
:label="tab.title"
:value="tab.tabId"
/>
</v-radio-group>
</v-col>
<v-spacer />
<v-col>
<v-btn
block
color="#9970ff"
color="#3bb9f1"
dark
depressed
@click="showWriteDialog"
@ -45,12 +59,24 @@
v-for="(item, index) in props.items"
:key="index"
>
<td>
<td
@click="handleItemClick(item)"
>
{{ item.title }}
</td>
<td>
<td
@click="handleItemClick(item)"
>
{{ item.description }}
</td>
<td>
<h3 v-if="item.isSeries">
O
</h3>
<h3 v-else>
X
</h3>
</td>
<td>
<h3 v-if="item.isAdult">
O
@ -103,6 +129,26 @@
<v-card-title v-else>
콘텐츠 큐레이션 등록
</v-card-title>
<v-card-text v-if="is_modify === false">
<v-row align="center">
<v-col cols="4">
메인
</v-col>
<v-col cols="8">
<v-radio-group
v-model="curation.tab_id"
row
>
<v-radio
v-for="tab in tabs"
:key="tab.tabId"
:label="tab.title"
:value="tab.tabId"
/>
</v-radio-group>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row align="center">
<v-col cols="4">
@ -131,6 +177,19 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text v-if="is_modify === false">
<v-row>
<v-col cols="4">
시리즈 큐레이션
</v-col>
<v-col cols="8">
<input
v-model="curation.is_series"
type="checkbox"
>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
@ -220,8 +279,10 @@ export default {
show_delete_confirm_dialog: false,
show_write_dialog: false,
selected_curation: {},
curation: {is_adult: false},
curation: {is_adult: false, is_series: false},
curations: [],
tabs: [],
selected_tab_id: 1,
headers: [
{
text: '제목',
@ -235,6 +296,12 @@ export default {
sortable: false,
value: 'description',
},
{
text: '시리즈 큐레이션',
align: 'center',
sortable: false,
value: 'isSeries',
},
{
text: '19금',
align: 'center',
@ -252,7 +319,7 @@ export default {
},
async created() {
await this.getCurations()
await this.getAudioContentMainTabList()
},
methods: {
@ -273,22 +340,48 @@ export default {
this.selected_curation = item
this.curation.id = item.id
this.curation.tab_id = item.tabId
this.curation.title = item.title
this.curation.description = item.description
this.curation.is_series = item.isSeries
this.curation.is_adult = item.isAdult
this.show_write_dialog = true
},
cancel() {
this.curation = {is_adult: false}
this.curation = {is_adult: false, is_series: false}
this.selected_curation = {}
this.is_modify = false
this.show_write_dialog = false
},
handleItemClick(item) {
this.$router.push(
{
name: 'ContentCurationDetail',
params: {
curation_id: item.id,
title: item.title,
description: item.description,
is_series: item.isSeries,
is_adult: item.isAdult
}
}
)
},
validate() {
if (
this.curation.tab_id === null ||
this.curation.tab_id === undefined ||
this.curation.tab_id <= 0
) {
this.notifyError("메인 탭을 선택하세요")
return false
}
if (
this.curation.title === null ||
this.curation.title === undefined ||
@ -320,6 +413,27 @@ export default {
this.show_delete_confirm_dialog = false
},
async getAudioContentMainTabList() {
this.is_loading = true
try {
const res = await api.getAudioContentMainTabList()
if (res.status === 200 && res.data.success === true) {
const data = res.data.data
this.tabs = data.filter(item => item.title !== '홈' && item.title !== '단편')
this.selected_tab_id = this.tabs[0].tabId
await this.getCurations()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async submit() {
if (!this.validate()) return;
if (this.is_loading) return;
@ -328,8 +442,10 @@ export default {
try {
const request = {
tabId: this.curation.tab_id,
title: this.curation.title,
description: this.curation.description,
isSeries: this.curation.is_series,
isAdult: this.curation.is_adult
}
@ -357,6 +473,10 @@ export default {
try {
let request = {id: this.curation.id}
if (this.selected_curation.tab_id !== this.curation.tab_id) {
request.tabId = this.curation.tab_id
}
if (this.selected_curation.title !== this.curation.title && this.curation.title.trim().length > 0) {
request.title = this.curation.title
}
@ -368,6 +488,10 @@ export default {
request.description = this.curation.description
}
if (this.selected_curation.isSeries !== this.curation.is_series) {
request.isSeries = this.curation.is_series
}
if (this.selected_curation.isAdult !== this.curation.is_adult) {
request.isAdult = this.curation.is_adult
}
@ -439,7 +563,7 @@ export default {
this.is_loading = true
try {
const res = await api.getCurations()
const res = await api.getCurations(this.selected_tab_id)
if (res.status === 200 && res.data.success === true) {
this.curations = res.data.data
} else {

View File

@ -0,0 +1,604 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>{{ curation_title }}</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-container>
<v-row>
<v-col
cols="4"
align="right"
>
19 :
</v-col>
<v-col
align="left"
>
<div v-if="is_adult">
O
</div>
<div v-else>
X
</div>
</v-col>
<v-spacer />
<v-col>
<v-btn
v-if="is_series"
block
color="#3bb9f1"
dark
depressed
@click="showAddSeries"
>
시리즈 등록
</v-btn>
<v-btn
v-else
block
color="#3bb9f1"
dark
depressed
@click="showAddContent"
>
콘텐츠 등록
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col
cols="4"
align="right"
>
내용 :
</v-col>
<v-col
cols="8"
align="left"
>
<vue-show-more-text
:style="{ padding: '0' }"
:text="curation_description"
:lines="2"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-simple-table class="elevation-10">
<template>
<thead>
<tr>
<th class="text-center">
썸네일
</th>
<th class="text-center">
제목
</th>
<th class="text-center">
내용
</th>
<th class="text-center">
크리에이터
</th>
<th class="text-center">
19
</th>
<th class="text-center">
관리
</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in items"
:key="item.id"
>
<td align="center">
<v-img
max-width="70"
max-height="70"
:src="item.coverImageUrl"
class="rounded-circle"
/>
</td>
<td>
<vue-show-more-text
:text="item.title"
:lines="3"
/>
</td>
<td style="max-width: 200px !important; word-break:break-all; height: auto;">
<vue-show-more-text
:text="item.detail"
:lines="3"
/>
</td>
<td>{{ item.creatorNickname }}</td>
<td>
<div v-if="item.isAdult">
O
</div>
<div v-else>
X
</div>
</td>
<td>
<v-btn
:disabled="is_loading"
@click="deleteConfirm(item)"
>
삭제
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
</v-container>
<v-dialog
v-model="show_add_content_dialog"
max-width="1000px"
persistent
>
<v-card>
<v-card-title>
콘텐츠 추가
</v-card-title>
<v-card-text>
<v-text-field
v-model="search_word"
label="콘텐츠 제목"
@keyup.enter="searchContentItem"
>
<v-btn
slot="append"
color="#3bb9f1"
dark
@click="searchContentItem"
>
검색
</v-btn>
</v-text-field>
</v-card-text>
<v-card-text v-if="search_item_list.length > 0 || add_item_list.length > 0">
<v-row>
<v-col>
검색결과
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-center">
제목
</th>
<th />
</tr>
</thead>
<tbody>
<tr
v-for="item in search_item_list"
:key="item.id"
>
<td>{{ item.title }}</td>
<td>
<v-btn
color="#3bb9f1"
@click="addItem(item)"
>
추가
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
<v-col v-if="add_item_list.length > 0">
추가할 콘텐츠
<v-simple-table>
<template>
<thead>
<tr>
<th class="text-center">
제목
</th>
<th />
</tr>
</thead>
<tbody>
<tr
v-for="item in add_item_list"
:key="item.id"
>
<td>{{ item.title }}</td>
<td>
<v-btn
color="#3bb9f1"
@click="removeItem(item)"
>
제거
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="addItemInCuration"
>
추가
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_add_series_dialog"
max-width="1000px"
persistent
>
<v-card>
<v-card-title>
시리즈 추가
</v-card-title>
<v-card-text>
<v-text-field
v-model="search_word"
label="시리즈 제목"
@keyup.enter="searchSeriesItem"
>
<v-btn
slot="append"
color="#3bb9f1"
dark
@click="searchSeriesItem"
>
검색
</v-btn>
</v-text-field>
</v-card-text>
<v-card-text v-if="search_item_list.length > 0 || add_item_list.length > 0">
<v-row>
<v-col>
검색결과
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-center">
제목
</th>
<th />
</tr>
</thead>
<tbody>
<tr
v-for="item in search_item_list"
:key="item.id"
>
<td>{{ item.title }}</td>
<td>
<v-btn
color="#3bb9f1"
@click="addItem(item)"
>
추가
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
<v-col v-if="add_item_list.length > 0">
추가할 시리즈
<v-simple-table>
<template>
<thead>
<tr>
<th class="text-center">
제목
</th>
<th />
</tr>
</thead>
<tbody>
<tr
v-for="item in add_item_list"
:key="item.id"
>
<td>{{ item.title }}</td>
<td>
<v-btn
color="#3bb9f1"
@click="removeItem(item)"
>
제거
</v-btn>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="addItemInCuration"
>
추가
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
persistent
>
<v-card>
<v-card-text />
<v-card-text v-if="selected_item !== null">
{{ selected_item.title }} 삭제하시겠습니까?
</v-card-text>
<v-card-text v-else>
삭제하시겠습니까?
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="removeItemInCuration"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import * as api from "@/api/audio_content"
import VueShowMoreText from 'vue-show-more-text'
export default {
name: 'ContentCurationDetail',
components: {VueShowMoreText},
data() {
return {
is_loading: false,
curation_id: 0,
curation_title: '',
curation_description: '',
is_series: false,
is_adult: false,
items: [],
show_add_series_dialog: false,
show_add_content_dialog: false,
show_delete_confirm_dialog: false,
search_word: '',
selected_item: null,
add_item_list: [],
search_item_list: [],
}
},
async created() {
this.curation_id = this.$route.params.curation_id
this.curation_title = this.$route.params.title
this.curation_description = this.$route.params.description
this.is_series = this.$route.params.is_series
this.is_adult = this.$route.params.is_adult
await this.getCurationItems()
},
methods: {
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
cancel() {
this.search_word = ''
this.add_item_list = []
this.search_item_list = []
this.selected_item = null
this.show_add_series_dialog = false
this.show_add_content_dialog = false
this.show_delete_confirm_dialog = false
},
deleteConfirm(item) {
this.selected_item = item
this.show_delete_confirm_dialog = true
},
showAddContent() {
this.show_add_content_dialog = true
},
showAddSeries() {
this.show_add_series_dialog = true
},
addItem(item) {
this.search_item_list = this.search_item_list.filter((t) => {
return t.id !== item.id
});
this.add_item_list.push(item)
},
removeItem(item) {
this.add_item_list = this.add_item_list.filter((t) => {
return t.id !== item.id
});
this.search_item_list.push(item)
},
async searchContentItem() {
if (this.search_word.length < 2) {
this.notifyError('검색어를 2글자 이상 입력하세요.')
return
}
this.is_loading = true
try {
const res = await api.searchContentItem(this.curation_id, this.search_word)
if (res.data.success === true) {
this.search_item_list = res.data.data
if (res.data.data.length <= 0) {
this.notifyError('검색결과가 없습니다.')
}
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async searchSeriesItem() {
if (this.search_word.length < 2) {
this.notifyError('검색어를 2글자 이상 입력하세요.')
return
}
this.is_loading = true
try {
const res = await api.searchSeriesItem(this.curation_id, this.search_word)
if (res.data.success === true) {
this.search_item_list = res.data.data
if (res.data.data.length <= 0) {
this.notifyError('검색결과가 없습니다.')
}
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async addItemInCuration() {
this.is_loading = true
const itemIdList = this.add_item_list.map((item) => {
return item.id
})
try {
const res = await api.addItemToCuration(this.curation_id, itemIdList)
if (res.status === 200 && res.data.success === true) {
this.cancel()
await this.getCurationItems()
} else {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async removeItemInCuration() {
this.is_loading = true
try {
const res = await api.removeItemInCuration(this.curation_id, this.selected_item.id)
if (res.status === 200 && res.data.success === true) {
this.cancel()
await this.getCurationItems()
} else {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async getCurationItems() {
this.is_loading = true
try {
const res = await api.getCurationItems(this.curation_id)
if (res.status === 200 && res.data.success === true) {
this.items = res.data.data
} else {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
},
}
</script>
<style scoped>
</style>

View File

@ -63,9 +63,6 @@
<th class="text-center">
내용
</th>
<th class="text-center">
큐레이션
</th>
<th class="text-center">
크리에이터
</th>
@ -134,7 +131,6 @@
:lines="3"
/>
</td>
<td>{{ item.curationTitle || '없음' }}</td>
<td>{{ item.creatorNickname }}</td>
<td>{{ item.theme }}</td>
<td style="max-width: 100px !important; word-break:break-all; height: auto;">
@ -307,22 +303,6 @@
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
큐레이션
</v-col>
<v-col cols="8">
<v-select
v-model="audio_content.curation_id"
:items="curations"
item-text="title"
item-value="value"
label="큐레이션 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
@ -415,7 +395,6 @@ export default {
search_word: '',
audio_content: {},
audio_contents: [],
curations: [],
themeList: [],
selected_audio_content: {},
utm_source: '',
@ -426,7 +405,6 @@ export default {
async created() {
await this.getAudioContentThemeList();
await this.getCurations()
await this.getAudioContent()
},
@ -455,7 +433,6 @@ export default {
this.audio_content.id = item.audioContentId
this.audio_content.title = item.title
this.audio_content.detail = item.detail
this.audio_content.curation_id = item.curationId
this.audio_content.theme_id = item.themeId
this.audio_content.is_adult = item.isAdult
this.audio_content.is_comment_available = item.isCommentAvailable
@ -513,10 +490,6 @@ export default {
request.detail = this.audio_content.detail
}
if (this.selected_audio_content.curationId !== this.audio_content.curation_id) {
request.curationId = this.audio_content.curation_id
}
if (this.selected_audio_content.themeId !== this.audio_content.theme_id) {
request.themeId = this.audio_content.theme_id
}
@ -598,26 +571,6 @@ export default {
}
},
async getCurations() {
this.is_loading = true
try {
const res = await api.getCurations()
if (res.status === 200 && res.data.success === true) {
this.curations = res.data.data.map((curation) => {
return {title: curation.title, value: curation.id}
})
this.curations.unshift({title: '없음', value: 0})
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async getAudioContent() {
this.is_loading = true
try {

View File

@ -16,11 +16,25 @@
<template v-slot:activator="{ on, attrs }">
<v-container>
<v-row>
<v-col cols="10" />
<v-col cols="9">
<v-radio-group
v-model="selected_tab_id"
row
@change="getBanners"
>
<v-radio
v-for="tab in tabs"
:key="tab.tabId"
:label="tab.title"
:value="tab.tabId"
/>
</v-radio-group>
</v-col>
<v-spacer />
<v-col>
<v-btn
block
color="#9970ff"
color="#3BB9F1"
dark
depressed
v-bind="attrs"
@ -75,6 +89,26 @@
<v-card-title v-else>
배너 등록
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col cols="4">
메인
</v-col>
<v-col cols="8">
<v-radio-group
v-model="banner.tab_id"
row
>
<v-radio
v-for="tab in tabs"
:key="tab.tabId"
:label="tab.title"
:value="tab.tabId"
/>
</v-radio-group>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row align="center">
<v-col cols="4">
@ -293,13 +327,15 @@ export default {
show_write_dialog: false,
show_delete_confirm_dialog: false,
selected_banner: {},
banner: {type: 'CREATOR'},
banner: {type: 'CREATOR', tab_id: 1},
banners: [],
events: [],
creators: [],
series: [],
search_query_creator: '',
search_query_series: '',
tabs: [],
selected_tab_id: 1
}
},
@ -319,7 +355,7 @@ export default {
async created() {
await this.getEvents()
await this.getBanners()
await this.getAudioContentMainTabList()
},
mounted() {
@ -343,7 +379,7 @@ export default {
this.is_selecting = false
this.show_write_dialog = false
this.show_delete_confirm_dialog = false
this.banner = {type: 'CREATOR'}
this.banner = {type: 'CREATOR', tab_id: 1}
this.selected_banner = {}
this.search_query_creator = ''
this.search_query_series = ''
@ -357,6 +393,27 @@ export default {
this.$dialog.notify.success(message)
},
async getAudioContentMainTabList() {
this.is_loading = true
try {
const res = await api.getAudioContentMainTabList()
if (res.status === 200 && res.data.success === true) {
const data = res.data.data
this.tabs = data
this.selected_tab_id = data[0].tabId
await this.getBanners()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
showModifyBannerDialog(banner) {
this.is_modify = true
this.selected_banner = banner
@ -374,6 +431,7 @@ export default {
this.banner.series_title = banner.seriesTitle
this.banner.link = banner.link
this.banner.is_adult = banner.isAdult
this.banner.tab_id = banner.tabId
setTimeout(() => {
this.is_selecting = false; //
@ -452,6 +510,10 @@ export default {
request.seriesId = this.banner.series_id
}
if (this.banner.tab_id !== 1) {
request.tabId = this.banner.tab_id
}
formData.append("request", JSON.stringify(request))
const res = await api.saveBanner(formData)
@ -523,6 +585,10 @@ export default {
request.isAdult = this.banner.is_adult
}
if (this.selected_banner.tabId !== this.banner.tab_id) {
request.tabId = this.banner.tab_id
}
formData.append("request", JSON.stringify(request))
const res = await api.modifyBanner(formData)
@ -680,7 +746,7 @@ export default {
async getBanners() {
this.is_loading = true
try {
const res = await api.getBannerList()
const res = await api.getBannerList(this.selected_tab_id)
if (res.status === 200 && res.data.success === true) {
this.banners = res.data.data
} else {

View File

@ -0,0 +1,481 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>새로운 시리즈</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-dialog
v-model="show_write_dialog"
max-width="1000px"
persistent
>
<template v-slot:activator="{ on, attrs }">
<v-container>
<v-row>
<v-col cols="9" />
<v-spacer />
<v-col>
<v-btn
block
color="#3BB9F1"
dark
depressed
v-bind="attrs"
v-on="on"
>
새로운 시리즈 등록
</v-btn>
</v-col>
</v-row>
<draggable
v-model="recommend_series_list"
class="row"
@end="onDropCallback(recommend_series_list)"
>
<v-col
v-for="(item, i) in recommend_series_list"
:key="i"
cols="3"
>
<v-card>
<v-card-title>
<v-spacer />
<v-img :src="item.imageUrl" />
<v-spacer />
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="showModifyRecommendSeriesDialog(item)"
>
수정
</v-btn>
<v-btn
text
@click="deleteConfirm(item)"
>
삭제
</v-btn>
<v-spacer />
</v-card-actions>
</v-card>
</v-col>
</draggable>
</v-container>
</template>
<v-card>
<v-card-title v-if="is_modify === true">
새로운 시리즈 수정
</v-card-title>
<v-card-title v-else>
새로운 시리즈 등록
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col cols="4">
시리즈
</v-col>
<v-col cols="8">
<v-combobox
v-model="recommend_series.series_title"
:items="series"
:loading="is_loading"
:search-input.sync="search_query_series"
label="시리즈를 검색하세요"
item-text="name"
item-value="value"
no-data-text="No results found"
hide-selected
clearable
@change="onSelectSeries"
@update:search-input="onSearchSeriesUpdate"
/>
</v-col>
</v-row>
</v-card-text>
<div class="image-select">
<label for="image">
새로운 시리즈 이미지 등록
</label>
<v-file-input
id="image"
v-model="recommend_series.image"
@change="imageAdd"
/>
</div>
<img
v-if="recommend_series.image_url"
:src="recommend_series.image_url"
alt=""
class="image-preview"
>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
v-if="is_modify === true"
color="blue darken-1"
text
@click="modify"
>
수정
</v-btn>
<v-btn
v-else
color="blue darken-1"
text
@click="submit"
>
등록
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
persistent
>
<v-card>
<v-card-text />
<v-card-text>
삭제하시겠습니까?
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="deleteRecommendSeries"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import Draggable from "vuedraggable";
import debounce from "lodash/debounce";
import * as api from "@/api/audio_content_series_recommend"
import * as seriesApi from "@/api/audio_content_series";
export default {
name: "ContentSeriesNew",
components: {Draggable},
data() {
return {
is_selecting: false,
is_loading: false,
is_modify: false,
show_write_dialog: false,
show_delete_confirm_dialog: false,
selected_recommend_series: {},
recommend_series: {},
recommend_series_list: [],
series: [],
search_query_series: '',
}
},
watch: {
search_query_series() {
if (!this.is_selecting) {
this.debouncedSearchSeries();
}
}
},
async created() {
await this.getRecommendSeriesList();
},
mounted() {
this.debouncedSearchSeries = debounce(this.searchSeries, 500);
},
methods: {
imageAdd(payload) {
const file = payload;
if (file) {
this.recommend_series.image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
} else {
this.recommend_series.image_url = null
}
},
cancel() {
this.is_modify = false
this.is_selecting = false
this.show_write_dialog = false
this.show_delete_confirm_dialog = false
this.recommend_series = {}
this.selected_recommend_series = {}
this.search_query_series = ''
this.series = []
},
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
showModifyRecommendSeriesDialog(recommendSeries) {
this.is_modify = true
this.selected_recommend_series = recommendSeries
this.is_selecting = true; //
this.recommend_series.id = recommendSeries.id
this.recommend_series.image_url = recommendSeries.imageUrl
this.recommend_series.series_id = recommendSeries.seriesId
this.recommend_series.series_title = recommendSeries.seriesTitle
setTimeout(() => {
this.is_selecting = false; //
}, 1000);
this.show_write_dialog = true
},
deleteConfirm(recommendSeries) {
this.selected_recommend_series = recommendSeries
this.show_delete_confirm_dialog = true
},
onSelectSeries(value) {
this.recommend_series.series_id = value.value
this.is_selecting = true; //
setTimeout(() => {
this.is_selecting = false; //
}, 0);
},
onSearchSeriesUpdate(value) {
if (!this.is_selecting) {
this.search_query_series = value
}
},
validate() {
if (this.recommend_series.series_id === null || this.recommend_series.series_id === undefined) {
this.notifyError("시리즈를 선택하세요")
return false;
}
return true;
},
async getRecommendSeriesList() {
this.is_loading = true
try {
const res = await api.getRecommendSeriesList(false)
if (res.status === 200 && res.data.success === true) {
this.recommend_series_list = res.data.data
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
async submit() {
if (!this.validate()) return;
if (this.is_loading === true) return;
this.is_loading = true
try {
const formData = new FormData()
formData.append("image", this.recommend_series.image)
let request = {
seriesId: this.recommend_series.series_id,
isFree: false
}
formData.append("request", JSON.stringify(request))
const res = await api.saveRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('등록되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message);
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async modify() {
if (this.is_loading) return;
this.is_loading = true
try {
const formData = new FormData()
let request = {id: this.recommend_series.id}
if (this.recommend_series.image !== null) {
formData.append("image", this.recommend_series.image)
}
if (
this.selected_recommend_series.series_id !== this.recommend_series.series_id &&
this.recommend_series.series_id !== null &&
this.recommend_series.series_id !== undefined
) {
request.seriesId = this.recommend_series.series_id
}
formData.append("request", JSON.stringify(request))
const res = await api.modifyRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('수정되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async deleteRecommendSeries() {
if (this.is_loading) return;
this.is_loading = true
try {
const formData = new FormData()
formData.append("request", JSON.stringify({id: this.selected_recommend_series.id, isActive: false}))
const res = await api.modifyRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.show_delete_confirm_dialog = false
this.cancel()
this.notifySuccess('삭제되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async onDropCallback(items) {
const ids = items.map((item) => {
return item.id
})
const res = await api.updateRecommendSeriesOrders(ids)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message || '수정되었습니다.')
}
},
async searchSeries() {
if (this.search_query_series === null || this.search_query_series.length < 2) {
this.series = [];
return;
}
this.is_loading = true;
try {
const res = await seriesApi.searchSeriesList(this.search_query_series);
if (res.status === 200 && res.data.success === true) {
this.series = res.data.data.map((item) => {
return {name: item.title, value: item.id}
})
} else {
this.series = []
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
}
}
</script>
<style scoped>
.image-select label {
display: inline-block;
padding: 10px 20px;
background-color: #232d4a;
color: #fff;
vertical-align: middle;
font-size: 15px;
cursor: pointer;
border-radius: 5px;
}
.v-file-input {
position: absolute;
width: 0;
height: 0;
padding: 0;
overflow: hidden;
border: 0;
}
.image-preview {
max-width: 100%;
width: 250px;
object-fit: cover;
margin-top: 10px;
}
</style>

View File

@ -0,0 +1,481 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>무료 추천 시리즈</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-dialog
v-model="show_write_dialog"
max-width="1000px"
persistent
>
<template v-slot:activator="{ on, attrs }">
<v-container>
<v-row>
<v-col cols="9" />
<v-spacer />
<v-col>
<v-btn
block
color="#3BB9F1"
dark
depressed
v-bind="attrs"
v-on="on"
>
무료 추천 시리즈 등록
</v-btn>
</v-col>
</v-row>
<draggable
v-model="recommend_series_list"
class="row"
@end="onDropCallback(recommend_series_list)"
>
<v-col
v-for="(item, i) in recommend_series_list"
:key="i"
cols="3"
>
<v-card>
<v-card-title>
<v-spacer />
<v-img :src="item.imageUrl" />
<v-spacer />
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="showModifyRecommendSeriesDialog(item)"
>
수정
</v-btn>
<v-btn
text
@click="deleteConfirm(item)"
>
삭제
</v-btn>
<v-spacer />
</v-card-actions>
</v-card>
</v-col>
</draggable>
</v-container>
</template>
<v-card>
<v-card-title v-if="is_modify === true">
추천 시리즈 수정
</v-card-title>
<v-card-title v-else>
추천 시리즈 등록
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col cols="4">
시리즈
</v-col>
<v-col cols="8">
<v-combobox
v-model="recommend_series.series_title"
:items="series"
:loading="is_loading"
:search-input.sync="search_query_series"
label="시리즈를 검색하세요"
item-text="name"
item-value="value"
no-data-text="No results found"
hide-selected
clearable
@change="onSelectSeries"
@update:search-input="onSearchSeriesUpdate"
/>
</v-col>
</v-row>
</v-card-text>
<div class="image-select">
<label for="image">
추천 시리즈 이미지 등록
</label>
<v-file-input
id="image"
v-model="recommend_series.image"
@change="imageAdd"
/>
</div>
<img
v-if="recommend_series.image_url"
:src="recommend_series.image_url"
alt=""
class="image-preview"
>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
v-if="is_modify === true"
color="blue darken-1"
text
@click="modify"
>
수정
</v-btn>
<v-btn
v-else
color="blue darken-1"
text
@click="submit"
>
등록
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
persistent
>
<v-card>
<v-card-text />
<v-card-text>
삭제하시겠습니까?
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="deleteRecommendSeries"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import Draggable from "vuedraggable";
import debounce from "lodash/debounce";
import * as api from "@/api/audio_content_series_recommend"
import * as seriesApi from "@/api/audio_content_series";
export default {
name: "ContentSeriesRecommendFree",
components: {Draggable},
data() {
return {
is_selecting: false,
is_loading: false,
is_modify: false,
show_write_dialog: false,
show_delete_confirm_dialog: false,
selected_recommend_series: {},
recommend_series: {},
recommend_series_list: [],
series: [],
search_query_series: '',
}
},
watch: {
search_query_series() {
if (!this.is_selecting) {
this.debouncedSearchSeries();
}
}
},
async created() {
await this.getRecommendSeriesList();
},
mounted() {
this.debouncedSearchSeries = debounce(this.searchSeries, 500);
},
methods: {
imageAdd(payload) {
const file = payload;
if (file) {
this.recommend_series.image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
} else {
this.recommend_series.image_url = null
}
},
cancel() {
this.is_modify = false
this.is_selecting = false
this.show_write_dialog = false
this.show_delete_confirm_dialog = false
this.recommend_series = {}
this.selected_recommend_series = {}
this.search_query_series = ''
this.series = []
},
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
showModifyRecommendSeriesDialog(recommendSeries) {
this.is_modify = true
this.selected_recommend_series = recommendSeries
this.is_selecting = true; //
this.recommend_series.id = recommendSeries.id
this.recommend_series.image_url = recommendSeries.imageUrl
this.recommend_series.series_id = recommendSeries.seriesId
this.recommend_series.series_title = recommendSeries.seriesTitle
setTimeout(() => {
this.is_selecting = false; //
}, 1000);
this.show_write_dialog = true
},
deleteConfirm(recommendSeries) {
this.selected_recommend_series = recommendSeries
this.show_delete_confirm_dialog = true
},
onSelectSeries(value) {
this.recommend_series.series_id = value.value
this.is_selecting = true; //
setTimeout(() => {
this.is_selecting = false; //
}, 0);
},
onSearchSeriesUpdate(value) {
if (!this.is_selecting) {
this.search_query_series = value
}
},
validate() {
if (this.recommend_series.series_id === null || this.recommend_series.series_id === undefined) {
this.notifyError("시리즈를 선택하세요")
return false;
}
return true;
},
async getRecommendSeriesList() {
this.is_loading = true
try {
const res = await api.getRecommendSeriesList(true)
if (res.status === 200 && res.data.success === true) {
this.recommend_series_list = res.data.data
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
async submit() {
if (!this.validate()) return;
if (this.is_loading === true) return;
this.is_loading = true
try {
const formData = new FormData()
formData.append("image", this.recommend_series.image)
let request = {
seriesId: this.recommend_series.series_id,
isFree: true
}
formData.append("request", JSON.stringify(request))
const res = await api.saveRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('등록되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message);
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async modify() {
if (this.is_loading) return;
this.is_loading = true
try {
const formData = new FormData()
let request = {id: this.recommend_series.id}
if (this.recommend_series.image !== null) {
formData.append("image", this.recommend_series.image)
}
if (
this.selected_recommend_series.series_id !== this.recommend_series.series_id &&
this.recommend_series.series_id !== null &&
this.recommend_series.series_id !== undefined
) {
request.seriesId = this.recommend_series.series_id
}
formData.append("request", JSON.stringify(request))
const res = await api.modifyRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('수정되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async deleteRecommendSeries() {
if (this.is_loading) return;
this.is_loading = true
try {
const formData = new FormData()
formData.append("request", JSON.stringify({id: this.selected_recommend_series.id, isActive: false}))
const res = await api.modifyRecommendSeries(formData)
if (res.status === 200 && res.data.success === true) {
this.show_delete_confirm_dialog = false
this.cancel()
this.notifySuccess('삭제되었습니다.')
this.recommend_series_list = []
await this.getRecommendSeriesList()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async onDropCallback(items) {
const ids = items.map((item) => {
return item.id
})
const res = await api.updateRecommendSeriesOrders(ids)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message || '수정되었습니다.')
}
},
async searchSeries() {
if (this.search_query_series === null || this.search_query_series.length < 2) {
this.series = [];
return;
}
this.is_loading = true;
try {
const res = await seriesApi.searchSeriesList(this.search_query_series);
if (res.status === 200 && res.data.success === true) {
this.series = res.data.data.map((item) => {
return {name: item.title, value: item.id}
})
} else {
this.series = []
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
}
}
</script>
<style scoped>
.image-select label {
display: inline-block;
padding: 10px 20px;
background-color: #232d4a;
color: #fff;
vertical-align: middle;
font-size: 15px;
cursor: pointer;
border-radius: 5px;
}
.v-file-input {
position: absolute;
width: 0;
height: 0;
padding: 0;
overflow: hidden;
border: 0;
}
.image-preview {
max-width: 100%;
width: 250px;
object-fit: cover;
margin-top: 10px;
}
</style>