From 6507b025de3601731bdc796be5f71d5f1b7e21a0 Mon Sep 17 00:00:00 2001 From: Yu Sung Date: Mon, 15 Sep 2025 04:27:22 +0900 Subject: [PATCH] =?UTF-8?q?feat(original):=20=EC=9B=90=EC=9E=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 등록, 수정, 삭제 - 캐릭터 연결, 해제 기능 추가 --- src/api/original.js | 80 ++++++++ src/components/SideMenu.vue | 5 + src/router/index.js | 21 +- src/views/Chat/OriginalDetail.vue | 321 ++++++++++++++++++++++++++++++ src/views/Chat/OriginalForm.vue | 305 ++++++++++++++++++++++++++++ src/views/Chat/OriginalList.vue | 205 +++++++++++++++++++ 6 files changed, 934 insertions(+), 3 deletions(-) create mode 100644 src/api/original.js create mode 100644 src/views/Chat/OriginalDetail.vue create mode 100644 src/views/Chat/OriginalForm.vue create mode 100644 src/views/Chat/OriginalList.vue diff --git a/src/api/original.js b/src/api/original.js new file mode 100644 index 0000000..32221c4 --- /dev/null +++ b/src/api/original.js @@ -0,0 +1,80 @@ +import Vue from 'vue'; + +// 공통: 빈 문자열 -> null +function toNullIfBlank(value) { + if (typeof value === 'string') { + return value.trim() === '' ? null : value; + } + return value === '' ? null : value; +} + +// 원작 리스트 +export async function getOriginalList(page = 1, size = 20) { + return Vue.axios.get('/admin/chat/original/list', { + params: { page: page - 1, size } + }) +} + +// 원작 등록 +export async function createOriginal(data) { + const formData = new FormData(); + if (data.image) formData.append('image', data.image); + const request = { + title: toNullIfBlank(data.title), + contentType: toNullIfBlank(data.contentType), + category: toNullIfBlank(data.category), + isAdult: !!data.isAdult, + description: toNullIfBlank(data.description), + originalLink: toNullIfBlank(data.originalLink) + }; + formData.append('request', JSON.stringify(request)); + return Vue.axios.post('/admin/chat/original/register', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +// 원작 수정 +export async function updateOriginal(data, image = null) { + const formData = new FormData(); + if (image) formData.append('image', image); + const processed = {}; + Object.keys(data).forEach(key => { + const value = data[key]; + if (typeof value === 'string' || value === '') { + processed[key] = toNullIfBlank(value) + } else { + processed[key] = value + } + }) + formData.append('request', JSON.stringify(processed)); + return Vue.axios.put('/admin/chat/original/update', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +// 원작 삭제 +export async function deleteOriginal(id) { + return Vue.axios.delete(`/admin/chat/original/${id}`) +} + +// 원작 상세 +export async function getOriginal(id) { + return Vue.axios.get(`/admin/chat/original/${id}`) +} + +// 원작 속 캐릭터 조회 +export async function getOriginalCharacters(id, page = 1, size = 20) { + return Vue.axios.get(`/admin/chat/original/${id}/characters`, { + params: { page: page - 1, size } + }) +} + +// 원작에 캐릭터 연결 +export async function assignCharactersToOriginal(id, characterIds = []) { + return Vue.axios.post(`/admin/chat/original/${id}/assign-characters`, { characterIds }) +} + +// 원작에서 캐릭터 연결 해제 +export async function unassignCharactersFromOriginal(id, characterIds = []) { + return Vue.axios.post(`/admin/chat/original/${id}/unassign-characters`, { characterIds }) +} diff --git a/src/components/SideMenu.vue b/src/components/SideMenu.vue index 6c66ce0..722e4e7 100644 --- a/src/components/SideMenu.vue +++ b/src/components/SideMenu.vue @@ -122,6 +122,11 @@ export default { route: '/character/calculate', items: null }, + { + title: '원작', + route: '/original-work', + items: null + }, ] }) } else { diff --git a/src/router/index.js b/src/router/index.js index acf9fd3..7a01988 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -291,9 +291,24 @@ const routes = [ component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCurationDetail.vue') }, { - path: '/character/calculate', - name: 'CharacterCalculate', - component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCalculateList.vue') + path: '/character/calculate', + name: 'CharacterCalculate', + component: () => import(/* webpackChunkName: "character" */ '../views/Chat/CharacterCalculateList.vue') + }, + { + path: '/original-work', + name: 'OriginalList', + component: () => import(/* webpackChunkName: "original" */ '../views/Chat/OriginalList.vue') + }, + { + path: '/original-work/form', + name: 'OriginalForm', + component: () => import(/* webpackChunkName: "original" */ '../views/Chat/OriginalForm.vue') + }, + { + path: '/original-work/detail', + name: 'OriginalDetail', + component: () => import(/* webpackChunkName: "original" */ '../views/Chat/OriginalDetail.vue') }, ] }, diff --git a/src/views/Chat/OriginalDetail.vue b/src/views/Chat/OriginalDetail.vue new file mode 100644 index 0000000..f2dc361 --- /dev/null +++ b/src/views/Chat/OriginalDetail.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/src/views/Chat/OriginalForm.vue b/src/views/Chat/OriginalForm.vue new file mode 100644 index 0000000..f4e40de --- /dev/null +++ b/src/views/Chat/OriginalForm.vue @@ -0,0 +1,305 @@ + + + + + diff --git a/src/views/Chat/OriginalList.vue b/src/views/Chat/OriginalList.vue new file mode 100644 index 0000000..56c2e3c --- /dev/null +++ b/src/views/Chat/OriginalList.vue @@ -0,0 +1,205 @@ + + + + +