From 565205be92189cdb35fd0bf93c53f796a2b714db Mon Sep 17 00:00:00 2001 From: Yu Sung <hwchon1234@gmail.com> Date: Tue, 6 Feb 2024 21:50:19 +0900 Subject: [PATCH] =?UTF-8?q?=EC=BD=98=ED=85=90=EC=B8=A0=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EA=B4=80=EB=A6=AC=20UI=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/category.js | 28 ++ src/router/index.js | 5 + src/views/Content/ContentCategoryList.vue | 390 ++++++++++++++++++++++ 3 files changed, 423 insertions(+) create mode 100644 src/api/category.js create mode 100644 src/views/Content/ContentCategoryList.vue diff --git a/src/api/category.js b/src/api/category.js new file mode 100644 index 0000000..ea46bc9 --- /dev/null +++ b/src/api/category.js @@ -0,0 +1,28 @@ +import Vue from 'vue'; + +async function saveCategory(title) { + return Vue.axios.post('/category', {title: title, contentIdList: []}); +} + +async function modifyCategory(title, categoryId) { + return Vue.axios.put('/category', { + categoryId: categoryId, + title: title, + addContentIdList: [], + removeContentIdList: [] + }); +} + +async function deleteCategory(categoryId) { + return Vue.axios.delete('/category/' + categoryId) +} + +async function getCategoryList(creatorId) { + return Vue.axios.get('/category?creatorId=' + creatorId) +} + +async function updateCategoryOrders(ids) { + return Vue.axios.put('/category/orders', {ids: ids}) +} + +export {saveCategory, modifyCategory, deleteCategory, getCategoryList, updateCategoryOrders} diff --git a/src/router/index.js b/src/router/index.js index 139af9c..5ef7e7e 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -20,6 +20,11 @@ const routes = [ name: 'ContentList', component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentList.vue') }, + { + path: '/content/category/list', + name: 'ContentCategoryList', + component: () => import(/* webpackChunkName: "content" */ '../views/Content/ContentCategoryList.vue') + }, { path: '/calculate/live', name: 'CalculateLive', diff --git a/src/views/Content/ContentCategoryList.vue b/src/views/Content/ContentCategoryList.vue new file mode 100644 index 0000000..828cc86 --- /dev/null +++ b/src/views/Content/ContentCategoryList.vue @@ -0,0 +1,390 @@ +<template> + <div> + <v-container> + <v-row> + <v-col cols="3"> + <v-btn + block + color="#3bb9f1" + dark + depressed + @click="showCreateCategoryDialog" + > + 카테고리 추가 + </v-btn> + <br><br> + <draggable + :list="category_list" + class="list-group" + @end="onDropCategoryCallback()" + > + <v-list + v-for="category in category_list" + :key="category.category" + class="py-0" + rounded + > + <v-list-item :class="{ 'category-selected-item': category === selected_category }"> + <v-list-item-title @click="clickCategory(category)"> + {{ category.category }} + </v-list-item-title> + <v-list-item-icon @click="clickModifyCategory(category)"> + <v-icon>mdi-pencil</v-icon> + </v-list-item-icon> + </v-list-item> + </v-list> + </draggable> + </v-col> + <v-col cols="1" /> + <v-col cols="8"> + <v-row> + <v-col cols="9" /> + <v-col cols="3"> + <v-btn + block + color="#3bb9f1" + dark + depressed + @click="showAddContent" + > + 콘텐츠 추가 + </v-btn> + </v-col> + </v-row> + <br><br> + <p v-if="selected_category !== null && selected_category !== undefined"> + 선택된 카테고리 : {{ selected_category.category }} + </p> + <p v-else> + 카테고리를 선택해 주세요 + </p> + <v-data-table + :headers="headers" + :items="content_list" + :loading="is_loading" + :items-per-page="-1" + item-key="id" + class="elevation-1" + hide-default-footer + > + <template v-slot:body="props"> + <draggable + v-model="props.items" + tag="tbody" + > + <tr + v-for="(item, index) in props.items" + :key="index" + > + <td> + <img + :src="item.image" + alt="" + height="100" + width="100" + > + </td> + <td><h3>{{ item.title }}</h3></td> + <td> + <h3 v-if="item.isAdult"> + O + </h3> + <h3 v-else> + X + </h3> + </td> + <td> + <v-btn + :disabled="is_loading" + > + 삭제 + </v-btn> + </td> + </tr> + </draggable> + </template> + </v-data-table> + </v-col> + </v-row> + </v-container> + + <v-container> + <v-row> + <v-dialog + v-model="show_create_category_dialog" + max-width="1000px" + persistent + > + <v-card> + <v-card-title v-if="modify_category"> + 카테고리 수정 + </v-card-title> + <v-card-title v-else> + 카테고리 추가 + </v-card-title> + <v-card-text> + <v-text-field + v-model="title" + label="카테고리 제목" + required + /> + </v-card-text> + <v-card-actions v-show="!is_loading"> + <v-spacer /> + <v-btn + color="blue darken-1" + text + @click="cancelCreateCategory" + > + 취소 + </v-btn> + <v-btn + v-if="modify_category" + color="blue darken-1" + text + @click="deleteCategory" + > + 삭제 + </v-btn> + <v-btn + v-if="modify_category" + color="blue darken-1" + text + @click="modifyCategory" + > + 수정 + </v-btn> + <v-btn + v-else + color="blue darken-1" + text + @click="saveCategory" + > + 등록 + </v-btn> + </v-card-actions> + </v-card> + </v-dialog> + </v-row> + </v-container> + + <v-container> + <v-row /> + </v-container> + </div> +</template> + +<script> +import Draggable from 'vuedraggable'; + +import * as api from "@/api/category"; + +export default { + name: "ContentCategoryList", + components: {Draggable}, + data() { + return { + is_loading: false, + show_add_content_dialog: false, + show_create_category_dialog: false, + modify_category: false, + + title: '', + selected_category: null, + modify_selected_category: null, + + category_list: [], + content_list: [], + + headers: [ + { + text: '커버이미지', + align: 'center', + sortable: false, + value: 'image', + }, + { + text: '제목', + align: 'center', + sortable: false, + value: 'title', + }, + { + text: '19금', + align: 'center', + sortable: false, + value: 'isAdult', + }, + { + text: '관리', + align: 'center', + sortable: false, + value: 'management' + }, + ] + } + }, + + async created() { + await this.getCategoryList() + }, + + methods: { + notifyError(message) { + this.$dialog.notify.error(message) + }, + + notifySuccess(message) { + this.$dialog.notify.success(message) + }, + + showCreateCategoryDialog() { + if (this.category_list.length >= 10) { + this.notifyError('카테고리는 최대 10개 까지 등록 가능합니다.') + return + } + + this.show_create_category_dialog = true + }, + + showAddContent() { + if (this.selected_category === null || this.selected_category === undefined) { + this.notifyError('카테고리를 선택하세요') + return + } + + this.show_add_content_dialog = true + }, + + cancelCreateCategory() { + this.title = '' + this.modify_category = false + this.modify_selected_category = null + this.show_create_category_dialog = false + }, + + clickCategory(category) { + this.selected_category = category + }, + + clickModifyCategory(category) { + this.modify_category = true + this.modify_selected_category = category + + this.title = category.category + this.show_create_category_dialog = true + }, + + async getCategoryList() { + this.is_loading = true + + try { + let res = await api.getCategoryList(this.$store.state.accountStore.userId) + if (res.data.success === true) { + this.category_list = res.data.data + } else { + this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + } catch (e) { + this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } finally { + this.is_loading = false + } + }, + + async saveCategory() { + if (this.title.trim().length <= 0) { + this.notifyError("제목을 입력하세요") + return + } + + if (this.is_loading) return; + + try { + this.is_loading = true + let res = await api.saveCategory(this.title) + if (res.data.success === true) { + this.title = '' + this.category_list = [] + this.selected_category = null + this.cancelCreateCategory() + await this.getCategoryList() + } else { + this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + } catch (e) { + this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } finally { + this.is_loading = false + } + }, + + async deleteCategory() { + try { + this.is_loading = true + let res = await api.deleteCategory(this.modify_selected_category.categoryId) + if (res.data.success === true) { + this.title = '' + this.category_list = [] + this.selected_category = null + this.cancelCreateCategory() + await this.getCategoryList() + } else { + this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + } catch (e) { + this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } finally { + this.is_loading = false + } + }, + + async modifyCategory() { + if (this.title.trim().length <= 0) { + this.notifyError("제목을 입력하세요") + return + } + + if (this.is_loading) return; + + try { + this.is_loading = true + let res = await api.modifyCategory(this.title, this.modify_selected_category.categoryId) + if (res.data.success === true) { + this.title = '' + this.category_list = [] + this.selected_category = null + this.cancelCreateCategory() + await this.getCategoryList() + } else { + this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + } catch (e) { + this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } finally { + this.is_loading = false + } + }, + + async onDropCategoryCallback() { + const ids = this.category_list.map((category) => { + return category.categoryId + }) + + const res = await api.updateCategoryOrders(ids) + if (res.status === 200 && res.data.success === true) { + this.notifySuccess('수정되었습니다.') + } else { + this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.') + } + }, + } +} +</script> + +<style scoped> +.category-selected-item { + background-color: #3bb9f1; + color: white !important; +} +</style>