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>