Merge pull request 'fix(admin-series): 시리즈 수정 기능 추가' (#83) from test into main

Reviewed-on: #83
This commit is contained in:
2025-11-13 19:51:42 +00:00
2 changed files with 318 additions and 4 deletions

View File

@@ -24,11 +24,17 @@ async function searchSeriesList(searchWord) {
return Vue.axios.get("/admin/audio-content/series/search?search_word=" + searchWord) return Vue.axios.get("/admin/audio-content/series/search?search_word=" + searchWord)
} }
// 시리즈 수정
async function updateAudioContentSeries(request) {
return Vue.axios.put('/admin/audio-content/series', request);
}
export { export {
getAudioContentSeriesList, getAudioContentSeriesList,
getAudioContentSeriesGenreList, getAudioContentSeriesGenreList,
createAudioContentSeriesGenre, createAudioContentSeriesGenre,
updateAudioContentSeriesGenre, updateAudioContentSeriesGenre,
updateAudioContentSeriesGenreOrders, updateAudioContentSeriesGenreOrders,
searchSeriesList searchSeriesList,
updateAudioContentSeries
} }

View File

@@ -47,6 +47,9 @@
<th class="text-center"> <th class="text-center">
19 19
</th> </th>
<th class="text-center">
수정
</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -73,13 +76,13 @@
<td> <td>
<vue-show-more-text <vue-show-more-text
:text="item.title" :text="item.title"
:lines="3" :lines="2"
/> />
</td> </td>
<td style="max-width: 200px !important; word-break:break-all; height: auto;"> <td style="max-width: 200px !important; word-break:break-all; height: auto;">
<vue-show-more-text <vue-show-more-text
:text="item.introduction" :text="item.introduction"
:lines="3" :lines="2"
/> />
</td> </td>
<td>{{ item.creatorNickname }}</td> <td>{{ item.creatorNickname }}</td>
@@ -94,6 +97,17 @@
X X
</div> </div>
</td> </td>
<td class="text-center">
<v-btn
small
color="#3bb9f1"
dark
depressed
@click="openEditDialog(item)"
>
수정
</v-btn>
</td>
</tr> </tr>
</tbody> </tbody>
</template> </template>
@@ -111,6 +125,165 @@
</v-col> </v-col>
</v-row> </v-row>
</v-container> </v-container>
<v-dialog
v-model="show_edit_dialog"
max-width="700px"
persistent
>
<v-card>
<v-card-title>
시리즈 수정
</v-card-title>
<v-card-text>
<v-row>
<v-col
cols="3"
class="text-center"
>
<v-img
:src="edit_target.coverImageUrl"
max-width="120"
max-height="120"
class="rounded-circle"
/>
</v-col>
<v-col cols="9">
<div style="font-weight:600;">
{{ edit_target.title }}
</div>
<div
v-if="edit_target.introduction"
style="max-height:80px; overflow:auto; word-break:break-all;"
>
{{ edit_target.introduction }}
</div>
</v-col>
</v-row>
<v-divider class="my-4" />
<v-row align="center">
<v-col
cols="4"
class="d-flex align-center"
>
장르
</v-col>
<v-col
cols="8"
class="d-flex align-center"
>
<v-select
v-model="edit_form.genreId"
:items="genre_list"
item-text="genre"
item-value="id"
:loading="is_loading_genres"
:disabled="is_saving || is_loading_genres"
label="장르를 선택하세요"
dense
hide-details
/>
</v-col>
</v-row>
<v-row align="center">
<v-col
cols="4"
class="d-flex align-center"
>
연재 요일
</v-col>
<v-col
cols="8"
class="d-flex align-center"
>
<v-row
dense
class="flex-grow-1"
>
<v-col
v-for="opt in daysOfWeekOptions"
:key="opt.value"
cols="6"
sm="4"
md="3"
class="py-0 my-0"
>
<v-checkbox
v-model="edit_form.publishedDaysOfWeek"
:label="opt.text"
:value="opt.value"
:disabled="is_saving"
dense
hide-details
class="ma-0 pa-0"
/>
</v-col>
</v-row>
</v-col>
</v-row>
<v-row align="center">
<v-col
cols="4"
class="d-flex align-center"
>
오리지널
</v-col>
<v-col
cols="8"
class="d-flex align-center"
>
<v-checkbox
v-model="edit_form.isOriginal"
:disabled="is_saving"
dense
hide-details
class="ma-0 pa-0"
/>
</v-col>
</v-row>
<v-row align="center">
<v-col
cols="4"
class="d-flex align-center"
>
19
</v-col>
<v-col
cols="8"
class="d-flex align-center"
>
<v-checkbox
v-model="edit_form.isAdult"
:disabled="is_saving"
dense
hide-details
class="ma-0 pa-0"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
:disabled="is_saving"
@click="cancelEdit"
>
취소
</v-btn>
<v-btn
color="#3bb9f1"
dark
depressed
:loading="is_saving"
:disabled="is_saving"
@click="saveEdit"
>
저장
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div> </div>
</template> </template>
@@ -130,7 +303,52 @@ export default {
page: 1, page: 1,
total_page: 0, total_page: 0,
total_count: 0, total_count: 0,
series_list: [] series_list: [],
// 수정 다이얼로그 상태/데이터
show_edit_dialog: false,
is_saving: false,
is_loading_genres: false,
genre_list: [],
edit_target: {},
edit_form: {
genreId: null,
isOriginal: false,
isAdult: false,
publishedDaysOfWeek: []
},
daysOfWeekOptions: [
{ value: 'RANDOM', text: '랜덤' },
{ value: 'SUN', text: '일' },
{ value: 'MON', text: '월' },
{ value: 'TUE', text: '화' },
{ value: 'WED', text: '수' },
{ value: 'THU', text: '목' },
{ value: 'FRI', text: '금' },
{ value: 'SAT', text: '토' }
]
}
},
watch: {
'edit_form.publishedDaysOfWeek': {
handler(newVal, oldVal) {
if (!Array.isArray(newVal)) return;
const hasRandom = newVal.includes('RANDOM');
const hadRandom = Array.isArray(oldVal) && oldVal.includes('RANDOM');
const others = newVal.filter(v => v !== 'RANDOM');
// RANDOM과 특정 요일은 함께 설정될 수 없음
if (hasRandom && others.length > 0) {
if (hadRandom) {
// RANDOM 상태에서 다른 요일을 선택한 경우 → RANDOM 제거, 나머지만 유지
this.edit_form.publishedDaysOfWeek = others;
} else {
// 다른 요일이 선택된 상태에서 RANDOM을 선택한 경우 → RANDOM만 유지
this.edit_form.publishedDaysOfWeek = ['RANDOM'];
}
}
},
deep: true
} }
}, },
@@ -176,6 +394,96 @@ export default {
async next() { async next() {
await this.getAudioContentSeries() await this.getAudioContentSeries()
}, },
openEditDialog(item) {
this.edit_target = item
this.show_edit_dialog = true
this.is_saving = false
this.loadGenresThenInit()
},
async loadGenresThenInit() {
try {
this.is_loading_genres = true
if (!this.genre_list || this.genre_list.length === 0) {
const res = await api.getAudioContentSeriesGenreList()
if (res.status === 200 && res.data.success === true) {
this.genre_list = res.data.data || []
} else {
this.notifyError(res.data.message || '장르 목록을 불러오지 못했습니다.')
}
}
} catch (e) {
this.notifyError('장르 목록을 불러오지 못했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading_genres = false
this.initEditForm()
}
},
initEditForm() {
const item = this.edit_target || {}
let genreId = item.genreId || null
if (!genreId && item.genre && this.genre_list && this.genre_list.length > 0) {
const found = this.genre_list.find(g => g.genre === item.genre)
if (found) genreId = found.id
}
// 초기 publishedDaysOfWeek 정규화 (RANDOM과 특정 요일 혼재 금지)
let published = Array.isArray(item.publishedDaysOfWeek) ? [...item.publishedDaysOfWeek] : []
if (published.includes('RANDOM')) {
const others = published.filter(v => v !== 'RANDOM')
published = others.length > 0 ? ['RANDOM'] : ['RANDOM']
}
this.edit_form = {
genreId: genreId,
isOriginal: typeof item.isOriginal === 'boolean' ? item.isOriginal : false,
isAdult: typeof item.isAdult === 'boolean' ? item.isAdult : false,
publishedDaysOfWeek: published
}
},
cancelEdit() {
this.show_edit_dialog = false
this.edit_target = {}
this.edit_form = {
genreId: null,
isOriginal: false,
isAdult: false,
publishedDaysOfWeek: []
}
},
async saveEdit() {
if (this.is_saving) return
if (!this.edit_form.genreId) {
this.notifyError('장르를 선택해 주세요.')
return
}
this.is_saving = true
try {
const days = Array.isArray(this.edit_form.publishedDaysOfWeek) ? this.edit_form.publishedDaysOfWeek : []
const payloadDays = days.includes('RANDOM') ? ['RANDOM'] : days
const request = {
seriesId: this.edit_target.id,
genreId: this.edit_form.genreId,
isOriginal: this.edit_form.isOriginal,
isAdult: this.edit_form.isAdult,
publishedDaysOfWeek: payloadDays
}
const res = await api.updateAudioContentSeries(request)
if (res.status === 200 && res.data.success === true) {
this.notifySuccess('수정되었습니다.')
this.show_edit_dialog = false
await this.getAudioContentSeries()
} else {
this.notifyError(res.data.message || '수정에 실패했습니다. 다시 시도해 주세요.')
}
} catch (e) {
this.notifyError('수정에 실패했습니다. 다시 시도해 주세요.')
} finally {
this.is_saving = false
}
},
} }
} }
</script> </script>