first commit

This commit is contained in:
Yu Sung
2023-08-04 23:02:15 +09:00
commit c60930a566
83 changed files with 38615 additions and 0 deletions

View File

@@ -0,0 +1,460 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>콘텐츠 큐레이션</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-container>
<v-row>
<v-col cols="10" />
<v-col>
<v-btn
block
color="#9970ff"
dark
depressed
@click="showWriteDialog"
>
큐레이션 등록
</v-btn>
</v-col>
</v-row>
<v-row>
<v-col>
<v-data-table
:headers="headers"
:items="curations"
:loading="is_loading"
item-key="id"
class="elevation-1"
hide-default-footer
disable-pagination
>
<template v-slot:body="props">
<draggable
v-model="props.items"
tag="tbody"
@end="onDropCallback(props.items)"
>
<tr
v-for="(item, index) in props.items"
:key="index"
>
<td>
{{ item.title }}
</td>
<td>
{{ item.description }}
</td>
<td>
<h3 v-if="item.isAdult">
O
</h3>
<h3 v-else>
X
</h3>
</td>
<td>
<v-row>
<v-col />
<v-col>
<v-btn
:disabled="is_loading"
@click="showModifyDialog(item)"
>
수정
</v-btn>
</v-col>
<v-col>
<v-btn
:disabled="is_loading"
@click="deleteConfirm(item)"
>
삭제
</v-btn>
</v-col>
<v-col />
</v-row>
</td>
</tr>
</draggable>
</template>
</v-data-table>
</v-col>
</v-row>
</v-container>
<v-row>
<v-dialog
v-model="show_write_dialog"
max-width="1000px"
persistent
>
<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-text-field
v-model="curation.title"
label="제목"
required
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row align="center">
<v-col cols="4">
설명
</v-col>
<v-col cols="8">
<v-text-field
v-model="curation.description"
label="설명"
required
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
19
</v-col>
<v-col cols="8">
<input
v-model="curation.is_adult"
type="checkbox"
>
</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
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-row>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
persistent
>
<v-card>
<v-card-text />
<v-card-text>
"{{ selected_curation.title }}" 삭제하시겠습니까?
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="deleteCancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="deleteCuration"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import Draggable from 'vuedraggable';
import * as api from "@/api/audio_content"
export default {
name: "AudioContentCuration",
components: {Draggable},
data() {
return {
is_loading: false,
is_modify: false,
show_delete_confirm_dialog: false,
show_write_dialog: false,
selected_curation: {},
curation: {is_adult: false},
curations: [],
headers: [
{
text: '제목',
align: 'center',
sortable: false,
value: 'title',
},
{
text: '설명',
align: 'center',
sortable: false,
value: 'description',
},
{
text: '19금',
align: 'center',
sortable: false,
value: 'isAdult',
},
{
text: '관리',
align: 'center',
sortable: false,
value: 'management'
},
],
}
},
async created() {
await this.getCurations()
},
methods: {
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
showWriteDialog() {
this.show_write_dialog = true
},
showModifyDialog(item) {
this.is_modify = true
this.selected_curation = item
this.curation.id = item.id
this.curation.title = item.title
this.curation.description = item.description
this.curation.is_adult = item.isAdult
this.show_write_dialog = true
},
cancel() {
this.curation = {is_adult: false}
this.selected_curation = {}
this.is_modify = false
this.show_write_dialog = false
},
validate() {
if (
this.curation.title === null ||
this.curation.title === undefined ||
this.curation.title.trim().length <= 0
) {
this.notifyError("제목을 입력하세요")
return false
}
if (
this.curation.description === null ||
this.curation.description === undefined ||
this.curation.description.trim().length <= 0
) {
this.notifyError("설명을 입력하세요")
return false
}
return true
},
deleteConfirm(curation) {
this.selected_curation = curation
this.show_delete_confirm_dialog = true
},
deleteCancel() {
this.selected_curation = {}
this.show_delete_confirm_dialog = false
},
async submit() {
if (!this.validate()) return;
if (this.is_loading) return;
this.isLoading = true
try {
const request = {
title: this.curation.title,
description: this.curation.description,
isAdult: this.curation.is_adult
}
const res = await api.saveCuration(request)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('등록되었습니다.')
this.curations = []
await this.getCurations()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async modify() {
if (!this.validate()) return;
if (this.is_loading) return;
this.isLoading = true
try {
let request = {id: this.curation.id}
if (this.selected_curation.title !== this.curation.title && this.curation.title.trim().length > 0) {
request.title = this.curation.title
}
if (
this.selected_curation.description !== this.curation.description &&
this.curation.description.trim().length > 0
) {
request.description = this.curation.description
}
if (this.selected_curation.isAdult !== this.curation.is_adult) {
request.isAdult = this.curation.is_adult
}
const res = await api.modifyCuration(request)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('수정되었습니다.')
this.curations = []
await this.getCurations()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async deleteCuration() {
if (this.is_loading) return;
this.is_loading = true
try {
let request = {id: this.selected_curation.id, isActive: false}
const res = await api.modifyCuration(request)
if (res.status === 200 && res.data.success === true) {
this.show_delete_confirm_dialog = false
this.cancel()
this.notifySuccess('삭제되었습니다.')
this.curations = []
await this.getCurations()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async onDropCallback(items) {
this.curations = items
const ids = items.map((item) => {
return item.id
})
try {
this.is_loading = true
const res = await api.updateCurationOrders(ids)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message)
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
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
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
}
},
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,623 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>콘텐츠 리스트</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-container>
<v-row>
<v-col>
<v-text-field
v-model="utm_source"
label="예) youtube, google"
/>
</v-col>
<v-col>
<v-text-field
v-model="utm_medium"
label="예) email, cpc"
/>
</v-col>
<v-col>
<v-text-field
v-model="utm_campaign"
label="예) 화이트데이"
/>
</v-col>
<v-col cols="2" />
<v-col cols="4">
<v-text-field
v-model="search_word"
label="콘텐츠 제목 혹은 크리에이터 닉네임을 입력하세요"
@keyup.enter="search"
>
<v-btn
slot="append"
color="#9970ff"
dark
@click="search"
>
검색
</v-btn>
</v-text-field>
</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">
큐레이션
</th>
<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>
<th class="text-center">
듣기
</th>
<th class="text-center">
등록일
</th>
<th class="text-center">
관리
</th>
</tr>
</thead>
<tbody>
<tr
v-for="item in audio_contents"
:key="item.audioContentId"
>
<td>{{ item.audioContentId }}</td>
<td align="center">
<v-img
max-width="100"
max-height="100"
:src="item.coverImageUrl"
class="rounded-circle"
/>
</td>
<td>{{ item.title }}</td>
<td style="max-width: 350px !important; word-break:break-all; height: auto;">
{{ item.detail }}
</td>
<td>{{ item.curationTitle || '없음' }}</td>
<td>{{ item.creatorNickname }}</td>
<td>{{ item.theme }}</td>
<td style="max-width: 200px !important; word-break:break-all; height: auto;">
{{ item.tags }}
</td>
<td v-if="item.price > 0">
{{ item.price }} 코인
</td>
<td v-else>
무료
</td>
<td>
<div v-if="item.isAdult">
O
</div>
<div v-else>
X
</div>
</td>
<td>{{ item.remainingTime }}</td>
<td>
<vuetify-audio
:file="item.contentUrl"
:downloadable="true"
:auto-play="false"
/>
</td>
<td>{{ item.date }}</td>
<td>
<v-row>
<v-col>
<v-btn
:disabled="is_loading"
@click="showModifyDialog(item)"
>
수정
</v-btn>
</v-col>
<v-spacer />
<v-col>
<v-btn
:disabled="is_loading"
@click="deleteConfirm(item)"
>
삭제
</v-btn>
</v-col>
<v-spacer />
<v-col>
<v-btn
:disabled="is_loading"
@click="shareAudioContent(item)"
>
공유
</v-btn>
</v-col>
</v-row>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-col>
</v-row>
<v-row class="text-center">
<v-col>
<v-pagination
v-model="page"
:length="total_page"
circle
@input="next"
/>
</v-col>
</v-row>
</v-container>
<v-row>
<v-dialog
v-model="show_modify_dialog"
max-width="1000px"
persistent
>
<v-card>
<v-card-title>
콘텐츠 수정
</v-card-title>
<v-card-text>
<v-row align="center">
<v-col cols="4">
제목
</v-col>
<v-col cols="8">
<v-text-field
v-model="audio_content.title"
label="제목"
required
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row align="center">
<v-col cols="4">
내용
</v-col>
<v-col cols="8">
<v-text-field
v-model="audio_content.detail"
label="내용"
required
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
19
</v-col>
<v-col cols="8">
<input
v-model="audio_content.is_adult"
type="checkbox"
>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
댓글 가능
</v-col>
<v-col cols="8">
<input
v-model="audio_content.is_comment_available"
type="checkbox"
>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
기본 커버이미지로 변경
</v-col>
<v-col cols="8">
<input
v-model="audio_content.is_default_cover_image"
type="checkbox"
>
</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-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="modify"
>
수정
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
persistent
>
<v-card>
<v-card-text />
<v-card-text>
"{{ selected_audio_content.title }}" 삭제하시겠습니까?
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="deleteCancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="deleteAudioContent"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import * as api from '@/api/audio_content'
import * as dynamicLink from "@/api/firebase_dynamic_link";
import VuetifyAudio from 'vuetify-audio'
export default {
name: "AudioContentList",
components: {VuetifyAudio},
data() {
return {
is_loading: false,
show_modify_dialog: false,
show_delete_confirm_dialog: false,
page: 1,
total_page: 0,
search_word: '',
audio_content: {},
audio_contents: [],
curations: [],
selected_audio_content: {},
utm_source: '',
utm_medium: '',
utm_campaign: '',
}
},
async created() {
await this.getCurations()
await this.getAudioContent()
},
methods: {
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
deleteConfirm(item) {
this.selected_audio_content = item
this.show_delete_confirm_dialog = true
},
deleteCancel() {
this.selected_audio_content = {}
this.show_delete_confirm_dialog = false
},
showModifyDialog(item) {
this.selected_audio_content = item
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.is_adult = item.isAdult
this.audio_content.is_comment_available = item.isCommentAvailable
this.audio_content.is_default_cover_image = false
console.log(this.audio_content)
this.show_modify_dialog = true
},
cancel() {
this.selected_audio_content = {}
this.audio_content = {}
this.show_modify_dialog = false
this.show_delete_confirm_dialog = false
},
async modify() {
if (
this.audio_content.title === null ||
this.audio_content.title === undefined ||
this.audio_content.title.trim().length <= 0
) {
this.notifyError("제목을 입력하세요")
return
}
if (
this.audio_content.detail === null ||
this.audio_content.detail === undefined ||
this.audio_content.detail.trim().length <= 0
) {
this.notifyError("내용을 입력하세요")
return
}
if (this.is_loading) return;
this.isLoading = true
try {
const request = {
id: this.audio_content.id,
isDefaultCoverImage: this.audio_content.is_default_cover_image
}
if (
this.selected_audio_content.title !== this.audio_content.title &&
this.audio_content.title.trim().length > 0
) {
request.title = this.audio_content.title
}
if (
this.selected_audio_content.detail !== this.audio_content.detail &&
this.audio_content.detail.trim().length > 0
) {
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.isAdult !== this.audio_content.is_adult) {
request.isAdult = this.audio_content.is_adult
}
if (this.selected_audio_content.isCommentAvailable !== this.audio_content.is_comment_available) {
request.isCommentAvailable = this.audio_content.is_comment_available
}
console.log(request)
const res = await api.modifyAudioContent(request)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('수정되었습니다.')
this.audio_contents = []
await this.getAudioContent()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async deleteAudioContent() {
if (this.is_loading) return;
this.is_loading = true
try {
let request = {id: this.selected_audio_content.audioContentId, isActive: false}
const res = await api.modifyAudioContent(request)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('삭제되었습니다.')
this.audio_contents = []
await this.getAudioContent()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async next() {
if (this.search_word.length < 2) {
this.search_word = ''
await this.getAudioContent()
} else {
await this.searchAudioContent()
}
},
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 {
const res = await api.getAudioContentList(this.page)
if (res.status === 200 && res.data.success === true) {
const data = res.data.data
const total_page = Math.ceil(data.totalCount / 10)
this.audio_contents = data.items
if (total_page <= 0)
this.total_page = 1
else
this.total_page = total_page
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
this.is_loading = false
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
}
},
async search() {
this.page = 1
await this.searchAudioContent()
},
async searchAudioContent() {
if (this.search_word.length === 0) {
await this.getAudioContent()
} else if (this.search_word.length < 2) {
this.notifyError('검색어를 2글자 이상 입력하세요.')
} else {
this.is_loading = true
try {
const res = await api.searchAudioContent(this.search_word, this.page)
if (res.status === 200 && res.data.success === true) {
const data = res.data.data
const total_page = Math.ceil(data.totalCount / 10)
this.audio_contents = data.items
if (total_page <= 0)
this.total_page = 1
else
this.total_page = total_page
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
this.is_loading = false
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
}
}
},
async shareAudioContent(item) {
this.is_loading = true
try {
const linkData = await dynamicLink.shareAudioContent(item, this.utm_source, this.utm_medium, this.utm_campaign);
if (linkData.status === 200) {
await navigator.clipboard.writeText(linkData.data.shortLink)
this.notifySuccess("링크가 복사되었습니다.")
} else {
this.notifyError("링크를 생성하지 못했습니다.")
}
} finally {
this.is_loading = false
}
},
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,576 @@
<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="10" />
<v-col>
<v-btn
block
color="#9970ff"
dark
depressed
v-bind="attrs"
v-on="on"
>
배너 등록
</v-btn>
</v-col>
</v-row>
<draggable
v-model="banners"
class="row"
@end="onDropCallback(banners)"
>
<v-col
v-for="(item, i) in banners"
:key="i"
cols="3"
>
<v-card>
<v-card-title>
<v-spacer />
<v-img :src="item.thumbnailImageUrl" />
<v-spacer />
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="showModifyBannerDialog(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-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.type"
row
>
<v-radio
value="CREATOR"
label="크리에이터 채널"
/>
<v-radio
value="LINK"
label="외부링크"
/>
<v-radio
value="EVENT"
label="이벤트"
/>
</v-radio-group>
</v-col>
</v-row>
</v-card-text>
<v-card-text v-if="banner.type === 'CREATOR'">
<v-row align="center">
<v-col cols="4">
크리에이터
</v-col>
<v-col cols="8">
<v-select
v-model="banner.creator_id"
:items="creators"
item-text="name"
item-value="value"
label="크리에이터 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text v-else-if="banner.type === 'LINK'">
<v-row align="center">
<v-col cols="4">
링크
</v-col>
<v-col cols="8">
<v-text-field
v-model="banner.link"
label="링크"
required
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text v-else>
<v-row align="center">
<v-col cols="4">
이벤트
</v-col>
<v-col cols="8">
<v-select
v-model="banner.event_id"
:items="events"
item-text="title"
item-value="value"
label="이벤트 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-row>
<v-col cols="4">
19
</v-col>
<v-col cols="8">
<input
v-model="banner.is_adult"
type="checkbox"
>
</v-col>
</v-row>
</v-card-text>
<div class="image-select">
<label for="image">
배너 이미지 등록
</label>
<v-file-input
id="image"
v-model="banner.thumbnail_image"
@change="imageAdd"
/>
</div>
<img
v-if="banner.thumbnail_image_url"
:src="banner.thumbnail_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="deleteCancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="deleteBanner"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
import Draggable from "vuedraggable";
import * as accountApi from "@/api/member";
import * as eventApi from "@/api/event";
import * as api from "@/api/audio_content"
export default {
name: "AudioContentMainTopBanner",
components: {Draggable},
data() {
return {
is_loading: false,
is_modify: false,
show_write_dialog: false,
show_delete_confirm_dialog: false,
selected_banner: {},
banner: {type: 'CREATOR'},
banners: [],
events: [],
creators: [],
}
},
async created() {
await this.getCreatorList()
await this.getEvents()
await this.getBanners()
},
methods: {
imageAdd(payload) {
const file = payload;
if (file) {
this.banner.thumbnail_image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
} else {
this.banner.thumbnail_image_url = null
}
},
cancel() {
this.is_modify = false
this.show_write_dialog = false
this.banner = {type: 'CREATOR'}
this.selected_banner = {}
},
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
showModifyBannerDialog(banner) {
this.is_modify = true
this.selected_banner = banner
this.banner.id = banner.id
this.banner.type = banner.type
this.banner.thumbnail_image_url = banner.thumbnailImageUrl
this.banner.event_id = banner.eventId
this.banner.event_thumbnail_image = banner.eventThumbnailImage
this.banner.creator_id = banner.creatorId
this.banner.creator_nickname = banner.creatorNickname
this.banner.link = banner.link
this.banner.is_adult = banner.isAdult
this.show_write_dialog = true
},
validate() {
if (this.banner.type === 'EVENT' && (this.banner.event_id === null || this.banner.event_id === undefined)) {
this.notifyError("이벤트를 선택하세요")
return false;
}
if (
this.banner.type === 'CREATOR' &&
(this.banner.creator_id === null || this.banner.creator_id === undefined)) {
this.notifyError("크리에이터를 선택하세요")
return false;
}
if (
this.banner.type === 'LINK' &&
(this.banner.link === null || this.banner.link === undefined || this.banner.link.trim().length <= 0)
) {
this.notifyError("링크를 입력하세요")
return false;
}
if (this.banner.thumbnail_image == null) {
this.notifyError("썸네일 이미지를 등록하세요")
return false;
}
return true
},
deleteConfirm(banner) {
this.selected_banner = banner
this.show_delete_confirm_dialog = true
},
deleteCancel() {
this.show_delete_confirm_dialog = 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.banner.thumbnail_image)
let request = {
type: this.banner.type,
isAdult: this.banner.is_adult
}
if (this.banner.type === 'CREATOR') {
request.creatorId = this.banner.creator_id
} else if (this.banner.type === 'EVENT') {
request.eventId = this.banner.event_id
} else if (this.banner.type === 'LINK') {
request.link = this.banner.link
}
formData.append("request", JSON.stringify(request))
const res = await api.saveBanner(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('등록되었습니다.')
this.banners = []
await this.getBanners()
} 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.banner.id}
if (this.banner.thumbnail_image !== null) {
formData.append("image", this.banner.thumbnail_image)
}
if (
this.selected_banner.eventId !== this.banner.event_id &&
this.banner.event_id !== null &&
this.banner.event_id !== undefined
) {
request.type = this.banner.type
request.eventId = this.banner.event_id
}
if (
this.selected_banner.creator_id !== this.banner.creator_id &&
this.banner.creator_id !== null &&
this.banner.creator_id !== undefined
) {
request.type = this.banner.type
request.creatorId = this.banner.creator_id
}
if (
this.selected_banner.link !== this.banner.link &&
this.banner.link !== null &&
this.banner.link !== undefined
) {
request.type = this.banner.type
request.link = this.banner.link
}
if (this.selected_banner.isAdult !== this.banner.is_adult) {
request.isAdult = this.banner.is_adult
}
formData.append("request", JSON.stringify(request))
const res = await api.modifyBanner(formData)
if (res.status === 200 && res.data.success === true) {
this.cancel()
this.notifySuccess('수정되었습니다.')
this.banners = []
await this.getBanners()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
}
},
async deleteBanner() {
if (this.is_loading) return;
this.is_loading = true
try {
const formData = new FormData()
formData.append("request", JSON.stringify({id: this.selected_banner.id, isActive: false}))
const res = await api.modifyBanner(formData)
if (res.status === 200 && res.data.success === true) {
this.show_delete_confirm_dialog = false
this.cancel()
this.notifySuccess('삭제되었습니다.')
this.banners = []
await this.getBanners()
} 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.updateBannerOrders(ids)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message)
}
},
async getCreatorList() {
this.is_loading = true
try {
const res = await accountApi.getCounselorList()
if (res.status === 200 && res.data.success === true) {
this.creators = res.data.data.map((item) => {
return {name: item.nickname, value: item.id}
})
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
async getEvents() {
this.is_loading = true
try {
const res = await eventApi.getEvents(1)
if (res.status === 200 && res.data.success === true) {
this.events = res.data.data.eventList.map((item) => {
return {title: item.title, value: item.id}
})
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
this.is_loading = false
} finally {
this.is_loading = false
}
},
async getBanners() {
this.is_loading = true
try {
const res = await api.getBannerList()
if (res.status === 200 && res.data.success === true) {
this.banners = res.data.data
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} 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,268 @@
<template>
<div>
<v-toolbar dark>
<v-spacer />
<v-toolbar-title>콘텐츠 테마 관리</v-toolbar-title>
<v-spacer />
</v-toolbar>
<br>
<v-row>
<v-dialog
v-model="show_dialog"
max-width="400px"
persistent
>
<template v-slot:activator="{ on, attrs }">
<v-container>
<v-row>
<v-col cols="10" />
<v-col>
<v-btn
block
color="#9970ff"
dark
depressed
v-bind="attrs"
v-on="on"
>
테마 추가등록
</v-btn>
</v-col>
</v-row>
<draggable
v-model="themes"
class="row"
@end="onDropCallback(themes)"
>
<v-col
v-for="(theme, i) in themes"
:key="i"
cols="2"
>
<v-card>
<v-card-title>
<v-spacer />
<v-img
:src="theme.image"
class="rounded-circle"
/>
<v-spacer />
</v-card-title>
<v-card-title>
<v-spacer />
{{ theme.theme }}
<v-spacer />
</v-card-title>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="deleteTheme(theme)"
>
삭제
</v-btn>
<v-spacer />
</v-card-actions>
</v-card>
</v-col>
</draggable>
</v-container>
</template>
<v-card>
<v-card-title>테마 등록</v-card-title>
<v-card-text>
<v-text-field
v-model="theme_text"
label="테마"
required
/>
</v-card-text>
<div class="image-select">
<label for="image">
이미지 등록
</label>
<v-file-input
id="image"
v-model="image"
@change="imageAdd"
/>
</div>
<img
v-if="image_url"
:src="image_url"
alt=""
class="image-preview"
>
<v-card-actions v-show="!isLoading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="submit"
>
등록
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-row>
</div>
</template>
<script>
import * as api from '@/api/audio_content_theme'
import Draggable from "vuedraggable";
export default {
name: "AudioContentTheme",
components: {Draggable},
data() {
return {
isLoading: false,
themes: [],
show_dialog: false,
theme_text: null,
image: null,
image_url: null
}
},
async created() {
await this.getThemes()
},
methods: {
async onDropCallback(items) {
const ids = items.map((item) => {
return item.id
})
const res = await api.updateThemeOrders(ids)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message)
}
},
imageAdd(payload) {
const file = payload;
if (file) {
this.image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
} else {
this.image_url = null
}
},
notifyError(message) {
this.$dialog.notify.error(message)
},
notifySuccess(message) {
this.$dialog.notify.success(message)
},
cancel() {
this.show_dialog = false
this.image = null
this.image_url = null
this.theme = null
},
async getThemes() {
this.isLoading = true
try {
let res = await api.getThemes();
this.themes = res.data.data
} catch (e) {
this.notifyError("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
} finally {
this.isLoading = false
}
},
async deleteTheme(theme) {
this.isLoading = true
try {
let res = await api.deleteTheme(theme.id)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess(res.data.message)
await this.getThemes()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
} finally {
this.isLoading = false
}
},
async submit() {
this.isLoading = true
const formData = new FormData()
formData.append("image", this.image)
formData.append("request", JSON.stringify({theme: this.theme_text}))
const res = await api.enrollment(formData)
if (res.status === 200 && res.data.success === true) {
this.show_dialog = false
this.image = null
this.image_url = null
this.theme_text = null
this.themes = []
this.notifySuccess(res.data.message)
await this.getThemes()
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
}
this.isLoading = 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>