Compare commits

..

6 Commits

Author SHA1 Message Date
Yu Sung
71f71e9d77 fix(character-banner): 배너 등록 다이얼로그에서 언어 기본값을 한국어(ko)로 설정 2026-04-02 16:48:39 +09:00
Yu Sung
ffd6e41767 feat(live-recommend): 추천 크리에이터 등록 시 언어 선택(ko/ja/en) 추가 및 등록 요청에 lang 포함
- 등록 폼에 언어 v-select 추가(v-if="!is_modify")
- 데이터 모델에 banner.lang 기본값 'ko'와 langItems(ko/ja/en) 추가
- submit 요청에 ISO 639 코드(lang) 포함
- 취소 시 초기화에 언어 기본값 유지
2026-04-02 16:46:33 +09:00
Yu Sung
fbc10e83da feat(content-banner): 배너 등록 시 언어 선택(ko/ja/en) 추가 및 등록 요청에 lang 포함
- 등록 폼에 언어 v-select 추가(v-if="!is_modify")
- 데이터 모델에 banner.lang 기본값 'ko'와 langItems(ko/ja/en) 추가
- submit 요청에 ISO 639 코드(lang) 포함
- 취소 시 초기화에 언어 기본값 유지
2026-04-02 16:41:17 +09:00
Yu Sung
499d4e4432 fix(chat): 캐릭터 배너 수정 시 언어 선택 UI 숨김
- 수정 모드에서 언어 변경이 불가능하여 UI 비표시 처리\n- 불필요한 :disabled 속성 제거로 코드 명확화

Co-authored-by: Junie <junie@jetbrains.com>
2026-04-02 16:25:52 +09:00
Yu Sung
ad5c27abc3 feat(series-banner): 배너 등록 언어 선택 추가
- 배너 등록/수정 다이얼로그에 언어 선택(ko/ja/en) UI를 추가
2026-04-02 15:56:37 +09:00
Yu Sung
1fc619dfd0 feat(character-banner): 배너 등록 언어 선택 추가
- 배너 등록/수정 다이얼로그에 언어 선택(ko/ja/en) UI를 추가
2026-04-02 15:47:14 +09:00
6 changed files with 128 additions and 20 deletions

View File

@@ -43,7 +43,7 @@ async function getSeriesBannerList(page = 1, size = 20) {
async function createSeriesBanner(bannerData) { async function createSeriesBanner(bannerData) {
const formData = new FormData(); const formData = new FormData();
if (bannerData.image) formData.append("image", bannerData.image); if (bannerData.image) formData.append("image", bannerData.image);
const requestData = { seriesId: bannerData.seriesId }; const requestData = { seriesId: bannerData.seriesId, lang: bannerData.lang };
formData.append("request", JSON.stringify(requestData)); formData.append("request", JSON.stringify(requestData));
return Vue.axios.post("/admin/audio-content/series/banner/register", formData, { return Vue.axios.post("/admin/audio-content/series/banner/register", formData, {
headers: { "Content-Type": "multipart/form-data" } headers: { "Content-Type": "multipart/form-data" }

View File

@@ -115,9 +115,10 @@ async function createCharacterBanner(bannerData) {
// 이미지 FormData에 추가 // 이미지 FormData에 추가
if (bannerData.image) formData.append('image', bannerData.image) if (bannerData.image) formData.append('image', bannerData.image)
// 캐릭터 ID를 JSON 문자열로 변환하여 request 필드에 추가 // 캐릭터 ID와 언어 코드를 JSON 문자열로 변환하여 request 필드에 추가
const requestData = { const requestData = {
characterId: bannerData.characterId characterId: bannerData.characterId,
lang: bannerData.lang
} }
formData.append('request', JSON.stringify(requestData)) formData.append('request', JSON.stringify(requestData))

View File

@@ -184,6 +184,18 @@
</v-alert> </v-alert>
</v-col> </v-col>
</v-row> </v-row>
<v-row v-if="!isEdit">
<v-col cols="12">
<v-select
v-model="bannerForm.lang"
:items="languageOptions"
label="언어 선택"
item-text="text"
item-value="value"
outlined
/>
</v-col>
</v-row>
<v-row v-if="selectedCharacter"> <v-row v-if="selectedCharacter">
<v-col cols="12"> <v-col cols="12">
<v-alert <v-alert
@@ -302,8 +314,14 @@ export default {
image: null, image: null,
imageUrl: '', imageUrl: '',
characterId: null, characterId: null,
bannerId: null bannerId: null,
lang: 'ko'
}, },
languageOptions: [
{ text: '한국어', value: 'ko' },
{ text: '일본어', value: 'ja' },
{ text: '영어', value: 'en' }
],
imageRules: [ imageRules: [
v => !!v || this.isEdit || '이미지를 선택하세요' v => !!v || this.isEdit || '이미지를 선택하세요'
] ]
@@ -312,7 +330,7 @@ export default {
computed: { computed: {
isFormValid() { isFormValid() {
return (this.bannerForm.image || (this.isEdit && this.bannerForm.imageUrl)) && this.selectedCharacter; return (this.bannerForm.image || (this.isEdit && this.bannerForm.imageUrl)) && this.selectedCharacter && this.bannerForm.lang;
} }
}, },
@@ -393,7 +411,8 @@ export default {
image: null, image: null,
imageUrl: '', imageUrl: '',
characterId: null, characterId: null,
bannerId: null bannerId: null,
lang: 'ko'
}; };
this.previewImage = null; this.previewImage = null;
this.searchKeyword = ''; this.searchKeyword = '';
@@ -414,7 +433,8 @@ export default {
image: null, image: null,
imageUrl: banner.imageUrl, imageUrl: banner.imageUrl,
characterId: banner.characterId, characterId: banner.characterId,
bannerId: banner.id bannerId: banner.id,
lang: banner.lang || banner.language || null
}; };
this.previewImage = null; this.previewImage = null;
this.searchKeyword = ''; this.searchKeyword = '';
@@ -430,7 +450,8 @@ export default {
image: null, image: null,
imageUrl: '', imageUrl: '',
characterId: null, characterId: null,
bannerId: null bannerId: null,
lang: 'ko'
}; };
this.previewImage = null; this.previewImage = null;
this.searchKeyword = ''; this.searchKeyword = '';
@@ -501,7 +522,8 @@ export default {
// 배너 추가 // 배너 추가
const response = await createCharacterBanner({ const response = await createCharacterBanner({
image: this.bannerForm.image, image: this.bannerForm.image,
characterId: this.selectedCharacter.id characterId: this.selectedCharacter.id,
lang: this.bannerForm.lang
}); });
if (response && response.status === 200 && response.data && response.data.success === true) { if (response && response.status === 200 && response.data && response.data.success === true) {
this.notifySuccess('배너가 추가되었습니다.'); this.notifySuccess('배너가 추가되었습니다.');

View File

@@ -109,6 +109,23 @@
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<!-- 언어 선택: 등록 시에만 노출 (수정 비노출) -->
<v-card-text v-if="!is_modify">
<v-row align="center">
<v-col cols="4">
언어
</v-col>
<v-col cols="8">
<v-select
v-model="banner.lang"
:items="langItems"
item-text="text"
item-value="value"
label="언어 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
@@ -327,7 +344,7 @@ export default {
show_write_dialog: false, show_write_dialog: false,
show_delete_confirm_dialog: false, show_delete_confirm_dialog: false,
selected_banner: {}, selected_banner: {},
banner: {type: 'CREATOR', tab_id: 1}, banner: {type: 'CREATOR', tab_id: 1, lang: 'ko'},
banners: [], banners: [],
events: [], events: [],
creators: [], creators: [],
@@ -335,7 +352,12 @@ export default {
search_query_creator: '', search_query_creator: '',
search_query_series: '', search_query_series: '',
tabs: [], tabs: [],
selected_tab_id: 1 selected_tab_id: 1,
langItems: [
{ text: '한국어', value: 'ko' },
{ text: '일본어', value: 'ja' },
{ text: '영어', value: 'en' }
]
} }
}, },
@@ -379,7 +401,7 @@ export default {
this.is_selecting = false this.is_selecting = false
this.show_write_dialog = false this.show_write_dialog = false
this.show_delete_confirm_dialog = false this.show_delete_confirm_dialog = false
this.banner = {type: 'CREATOR', tab_id: 1} this.banner = {type: 'CREATOR', tab_id: 1, lang: 'ko'}
this.selected_banner = {} this.selected_banner = {}
this.search_query_creator = '' this.search_query_creator = ''
this.search_query_series = '' this.search_query_series = ''
@@ -432,6 +454,10 @@ export default {
this.banner.link = banner.link this.banner.link = banner.link
this.banner.is_adult = banner.isAdult this.banner.is_adult = banner.isAdult
this.banner.tab_id = banner.tabId this.banner.tab_id = banner.tabId
// 수정 시 언어는 변경 불가하므로 UI를 표시하지 않음. 필요 시 내부 유지만 함 (기본값 또는 서버 값 사용)
if (banner.lang) {
this.banner.lang = banner.lang
}
setTimeout(() => { setTimeout(() => {
this.is_selecting = false; // 선택 상태 해제 this.is_selecting = false; // 선택 상태 해제
@@ -497,7 +523,8 @@ export default {
let request = { let request = {
type: this.banner.type, type: this.banner.type,
isAdult: this.banner.is_adult isAdult: this.banner.is_adult,
lang: this.banner.lang || 'ko'
} }
if (this.banner.type === 'CREATOR') { if (this.banner.type === 'CREATOR') {

View File

@@ -153,6 +153,22 @@
</v-col> </v-col>
</v-row> </v-row>
</v-card-text> </v-card-text>
<v-card-text v-if="selected_recommend_live === null">
<v-row align="center">
<v-col cols="4">
언어
</v-col>
<v-col cols="8">
<v-select
v-model="lang"
:items="languageOptions"
item-text="label"
item-value="value"
label="언어 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text> <v-card-text>
<v-row align="center"> <v-row align="center">
<v-col cols="4"> <v-col cols="4">
@@ -234,6 +250,12 @@ export default {
start_date: null, start_date: null,
end_date: null, end_date: null,
is_adult: false, is_adult: false,
lang: 'ko',
languageOptions: [
{ label: '한국어', value: 'ko' },
{ label: '일본어', value: 'ja' },
{ label: '영어', value: 'en' },
],
headers: [ headers: [
{ {
text: '이미지', text: '이미지',
@@ -358,6 +380,7 @@ export default {
formData.append("start_date", this.start_date) formData.append("start_date", this.start_date)
formData.append("end_date", this.end_date) formData.append("end_date", this.end_date)
formData.append("is_adult", this.is_adult) formData.append("is_adult", this.is_adult)
formData.append("lang", this.lang)
const res = await api.createRecommendCreatorBanner(formData); const res = await api.createRecommendCreatorBanner(formData);
if (res.status === 200 && res.data.success === true) { if (res.status === 200 && res.data.success === true) {
@@ -383,7 +406,8 @@ export default {
this.image === null || this.image === null ||
this.creator_id === null || this.creator_id === null ||
this.start_date === null || this.start_date === null ||
this.end_date === null this.end_date === null ||
this.lang === null
) { ) {
this.notifyError('내용을 입력하세요') this.notifyError('내용을 입력하세요')
} else { } else {
@@ -398,6 +422,9 @@ export default {
this.creator_id = null this.creator_id = null
this.start_date = null this.start_date = null
this.end_date = null this.end_date = null
this.is_adult = false
this.lang = 'ko'
this.selected_recommend_live = null
}, },
notifyError(message) { notifyError(message) {
@@ -471,6 +498,8 @@ export default {
} }
}, },
showWriteDialog() { showWriteDialog() {
this.selected_recommend_live = null
this.lang = 'ko'
this.show_write_dialog = true this.show_write_dialog = true
}, },
} }

View File

@@ -66,6 +66,14 @@
/> />
<v-card-text class="text-center"> <v-card-text class="text-center">
<div>{{ resolveSeriesTitle(banner) }}</div> <div>{{ resolveSeriesTitle(banner) }}</div>
<v-chip
v-if="banner.lang"
x-small
label
class="mt-1"
>
{{ banner.lang === 'ko' ? '한국어' : banner.lang === 'ja' ? '일본어' : '영어' }}
</v-chip>
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer />
@@ -146,6 +154,17 @@
/> />
</v-col> </v-col>
</v-row> </v-row>
<v-row v-if="!isEdit">
<v-col cols="12">
<v-select
v-model="bannerForm.lang"
:items="languages"
label="언어 선택"
outlined
:rules="[v => !!v || '언어를 선택하세요']"
/>
</v-col>
</v-row>
<v-row> <v-row>
<v-col cols="12"> <v-col cols="12">
<v-text-field <v-text-field
@@ -293,11 +312,17 @@ export default {
searchResults: [], searchResults: [],
searchPerformed: false, searchPerformed: false,
previewImage: null, previewImage: null,
languages: [
{ text: '한국어', value: 'ko' },
{ text: '일본어', value: 'ja' },
{ text: '영어', value: 'en' }
],
bannerForm: { bannerForm: {
image: null, image: null,
imageUrl: '', imageUrl: '',
seriesId: null, seriesId: null,
bannerId: null bannerId: null,
lang: 'ko'
}, },
imageRules: [ imageRules: [
v => !!v || this.isEdit || '이미지를 선택하세요' v => !!v || this.isEdit || '이미지를 선택하세요'
@@ -306,7 +331,7 @@ export default {
}, },
computed: { computed: {
isFormValid() { isFormValid() {
return (this.bannerForm.image || (this.isEdit && this.bannerForm.imageUrl)) && this.selectedSeries return (this.bannerForm.image || (this.isEdit && this.bannerForm.imageUrl)) && this.selectedSeries && (this.isEdit || this.bannerForm.lang)
} }
}, },
watch: { watch: {
@@ -368,7 +393,7 @@ export default {
showAddDialog() { showAddDialog() {
this.isEdit = false this.isEdit = false
this.selectedSeries = null this.selectedSeries = null
this.bannerForm = { image: null, imageUrl: '', seriesId: null, bannerId: null } this.bannerForm = { image: null, imageUrl: '', seriesId: null, bannerId: null, lang: 'ko' }
this.previewImage = null this.previewImage = null
this.searchKeyword = '' this.searchKeyword = ''
this.searchResults = [] this.searchResults = []
@@ -387,7 +412,8 @@ export default {
image: null, image: null,
imageUrl: banner.imageUrl || banner.imagePath, imageUrl: banner.imageUrl || banner.imagePath,
seriesId: banner.seriesId, seriesId: banner.seriesId,
bannerId: banner.id bannerId: banner.id,
lang: banner.lang || 'ko'
} }
this.previewImage = null this.previewImage = null
this.searchKeyword = '' this.searchKeyword = ''
@@ -398,7 +424,7 @@ export default {
closeDialog() { closeDialog() {
this.showDialog = false this.showDialog = false
this.selectedSeries = null this.selectedSeries = null
this.bannerForm = { image: null, imageUrl: '', seriesId: null, bannerId: null } this.bannerForm = { image: null, imageUrl: '', seriesId: null, bannerId: null, lang: 'ko' }
this.previewImage = null this.previewImage = null
this.searchKeyword = '' this.searchKeyword = ''
this.searchResults = [] this.searchResults = []
@@ -450,13 +476,16 @@ export default {
}) })
if (response && response.status === 200 && response.data && response.data.success === true) { if (response && response.status === 200 && response.data && response.data.success === true) {
this.notifySuccess('배너가 수정되었습니다.') this.notifySuccess('배너가 수정되었습니다.')
this.closeDialog()
this.refreshBanners()
} else { } else {
this.notifyError('배너 수정을 실패했습니다.') this.notifyError('배너 수정을 실패했습니다.')
} }
} else { } else {
const response = await createSeriesBanner({ const response = await createSeriesBanner({
image: this.bannerForm.image, image: this.bannerForm.image,
seriesId: this.selectedSeries.id seriesId: this.selectedSeries.id,
lang: this.bannerForm.lang
}) })
if (response && response.status === 200 && response.data && response.data.success === true) { if (response && response.status === 200 && response.data && response.data.success === true) {
this.notifySuccess('배너가 추가되었습니다.') this.notifySuccess('배너가 추가되었습니다.')