Merge pull request 'test' (#102) from test into main

Reviewed-on: #102
This commit was merged in pull request #102.
This commit is contained in:
2026-08-20 10:21:29 +00:00
2 changed files with 193 additions and 9 deletions
+167 -6
View File
@@ -290,6 +290,47 @@
</v-card>
</v-dialog>
<!-- 이미지 크롭 다이얼로그 (CropperJS) -->
<v-dialog
v-model="show_crop_dialog"
max-width="900px"
persistent
>
<v-card class="cropper-card">
<v-card-title>
이미지 크롭 (1:1)
</v-card-title>
<v-card-text class="cropper-card-text">
<div class="cropper-canvas-wrap">
<img
v-if="crop_src"
ref="cropperImage"
:src="crop_src"
alt="crop"
class="cropper-dialog-img"
>
</div>
</v-card-text>
<v-card-actions v-show="!is_loading">
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="handleCropCancel"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="handleCropConfirm"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_delete_confirm_dialog"
max-width="400px"
@@ -325,6 +366,7 @@
<script>
import Draggable from "vuedraggable";
import debounce from "lodash/debounce";
import Cropper from 'cropperjs'
import * as seriesApi from "@/api/audio_content_series"
import * as memberApi from "@/api/member";
@@ -343,6 +385,11 @@ export default {
is_modify: false,
show_write_dialog: false,
show_delete_confirm_dialog: false,
// 이미지 크롭용 상태
show_crop_dialog: false,
cropper: null,
crop_src: null,
selected_file_for_crop: null,
selected_banner: {},
banner: {type: 'CREATOR', tab_id: 1, lang: 'ko'},
banners: [],
@@ -386,14 +433,101 @@ export default {
},
methods: {
imageAdd(payload) {
async imageAdd(payload) {
const file = payload;
if (file) {
this.banner.thumbnail_image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
} else {
this.banner.thumbnail_image_url = null
// 파일 해제 시 초기화
if (!file) {
if (this.banner.thumbnail_image_url) {
URL.revokeObjectURL(this.banner.thumbnail_image_url)
}
this.banner.thumbnail_image = null
this.banner.thumbnail_image_url = null
return
}
// CropperJS 다이얼로그 오픈 흐름
if (this.crop_src) {
URL.revokeObjectURL(this.crop_src)
}
this.selected_file_for_crop = file
this.crop_src = URL.createObjectURL(file)
this.show_crop_dialog = true
this.$nextTick(() => {
this.initCropper()
})
},
initCropper() {
const imageEl = this.$refs.cropperImage
if (!imageEl) return
// 기존 인스턴스 제거
if (this.cropper) {
this.cropper.destroy()
this.cropper = null
}
this.cropper = new Cropper(imageEl, {
aspectRatio: 1,
viewMode: 1,
autoCropArea: 1,
movable: true,
zoomable: true,
scalable: false,
rotatable: false,
responsive: true,
background: false,
})
},
destroyCropper() {
if (this.cropper) {
this.cropper.destroy()
this.cropper = null
}
},
async handleCropConfirm() {
if (!this.cropper || !this.selected_file_for_crop) return
try {
this.is_loading = true
const mime = this.selected_file_for_crop.type && /^image\//.test(this.selected_file_for_crop.type)
? this.selected_file_for_crop.type
: 'image/png'
const canvas = this.cropper.getCroppedCanvas()
await new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (!blob) return reject(new Error('크롭된 이미지를 생성할 수 없습니다.'))
const croppedFile = new File([blob], this.selected_file_for_crop.name || 'image.png', { type: mime })
// 기존 미리보기 URL 해제
if (this.banner.thumbnail_image_url) {
URL.revokeObjectURL(this.banner.thumbnail_image_url)
}
this.banner.thumbnail_image = croppedFile
this.banner.thumbnail_image_url = URL.createObjectURL(croppedFile)
resolve()
}, mime)
})
this.show_crop_dialog = false
} catch (e) {
this.notifyError('이미지 크롭 중 오류가 발생했습니다. 다시 시도해 주세요.')
} finally {
this.is_loading = false
this.cleanupCropDialog()
}
},
handleCropCancel() {
this.show_crop_dialog = false
this.cleanupCropDialog()
},
cleanupCropDialog() {
this.destroyCropper()
if (this.crop_src) {
URL.revokeObjectURL(this.crop_src)
this.crop_src = null
}
this.selected_file_for_crop = null
},
cancel() {
@@ -818,3 +952,30 @@ export default {
margin-top: 10px;
}
</style>
<style>
/* CropperJS 기본 스타일 */
@import '~cropperjs/dist/cropper.css';
.cropper-card {
max-height: 80vh; /* 뷰포트 대비 제한하여 하단 버튼이 항상 보이도록 */
display: flex;
flex-direction: column;
}
.cropper-card-text {
flex: 1;
overflow: auto; /* 내부 스크롤로 이미지만 스크롤되게 함 */
}
.cropper-canvas-wrap {
width: 100%;
height: 60vh; /* 이미지 표시 영역 고정 */
}
.cropper-dialog-img {
max-width: 100%;
max-height: 100%;
display: block;
}
</style>
+27 -4
View File
@@ -56,6 +56,22 @@
<v-card-title v-show="is_modify">
이벤트 수정
</v-card-title>
<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="event.lang"
:items="langItems"
item-text="text"
item-value="value"
label="언어 선택"
/>
</v-col>
</v-row>
</v-card-text>
<v-card-text>
<v-text-field
v-model="event.title"
@@ -288,10 +304,15 @@ export default {
is_loading: false,
is_modify: false,
events: [],
event: {isAdult: ''},
event: {isAdult: '', lang: 'ko'},
show_write_dialog: false,
show_delete_confirm_dialog: false,
selected_event: {},
langItems: [
{ text: '한국어', value: 'ko' },
{ text: '일본어', value: 'ja' },
{ text: '영어', value: 'en' }
]
}
},
@@ -357,6 +378,7 @@ export default {
showWriteDialog() {
this.event.isAdult = ''
this.event.lang = this.event.lang || 'ko'
this.show_write_dialog = true
},
@@ -378,7 +400,7 @@ export default {
cancel() {
this.is_modify = false
this.event = {isAdult: ''}
this.event = {isAdult: '', lang: 'ko'}
this.selected_event = {}
this.show_write_dialog = false
},
@@ -420,6 +442,7 @@ export default {
formData.append("isPopup", this.event.isPopup ? this.event.isPopup : false)
formData.append("startDate", this.event.startDate)
formData.append("endDate", this.event.endDate)
formData.append("lang", this.event.lang || 'ko')
if (this.event.detailImage != null) {
formData.append("detail", this.event.detailImage)
@@ -443,7 +466,7 @@ export default {
this.notifySuccess('등록되었습니다.')
this.page = 1
await this.getEvents()
this.event = {isAdult: ''}
this.event = {isAdult: '', lang: 'ko'}
}
} catch (e) {
this.notifyError('알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')
@@ -506,7 +529,7 @@ export default {
this.notifySuccess('수정되었습니다.')
this.page = 1
await this.getEvents()
this.event = {isAdult: ''}
this.event = {isAdult: '', lang: 'ko'}
this.is_modify = false
} else {
this.notifyError(res.data.message || '알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.')