feat(content): 콘텐츠 커버 이미지 크롭 기능 추가 및 이슈 수정

This commit is contained in:
Yu Sung
2026-02-27 19:09:50 +09:00
parent 9cba90fc93
commit ef7d5b71bb
5 changed files with 166 additions and 2 deletions

View File

@@ -711,6 +711,45 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="show_cropper_dialog"
max-width="800px"
persistent
>
<v-card>
<v-card-title>
이미지 크롭
</v-card-title>
<v-card-text>
<div class="cropper-wrapper">
<img
ref="cropper_image"
:src="cropper_image_url"
alt="Cropper Image"
style="max-width: 100%;"
>
</div>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
color="blue darken-1"
text
@click="cancelCropper"
>
취소
</v-btn>
<v-btn
color="blue darken-1"
text
@click="cropImage"
>
확인
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
@@ -720,10 +759,12 @@ import VuetifyAudio from 'vuetify-audio'
// Main JS (in UMD format)
import VueTimepicker from 'vue2-timepicker'
import DatePicker from 'vue2-datepicker';
import Cropper from 'cropperjs';
import 'vue2-datepicker/index.css';
// CSS
import 'vue2-timepicker/dist/VueTimepicker.css'
import 'cropperjs/dist/cropper.css';
export default {
name: "AudioContentList",
@@ -740,6 +781,9 @@ export default {
show_create_dialog: false,
show_modify_dialog: false,
show_delete_confirm_dialog: false,
show_cropper_dialog: false,
cropper_image_url: '',
cropper: null,
page: 1,
total_page: 0,
search_word: '',
@@ -834,13 +878,63 @@ export default {
imageAdd(payload) {
const file = payload;
if (file) {
this.audio_content.cover_image_url = URL.createObjectURL(file)
URL.revokeObjectURL(file)
// 이미 크롭 처리된 파일인 경우 다시 다이얼로그를 띄우지 않음
if (file._isCropped) return;
this.cropper_image_url = URL.createObjectURL(file)
this.show_cropper_dialog = true
this.$nextTick(() => {
if (this.cropper) {
this.cropper.destroy()
}
this.cropper = new Cropper(this.$refs.cropper_image, {
aspectRatio: 1,
viewMode: 1,
})
})
} else {
this.audio_content.cover_image_url = null
this.audio_content.cover_image = null
}
},
cancelCropper() {
this.show_cropper_dialog = false
if (this.cropper) {
this.cropper.destroy()
this.cropper = null
}
this.cropper_image_url = ''
this.audio_content.cover_image = null
},
cropImage() {
const canvas = this.cropper.getCroppedCanvas()
let finalCanvas = canvas
const MAX_SIZE = 800
if (canvas.width > MAX_SIZE || canvas.height > MAX_SIZE) {
const resizeCanvas = document.createElement('canvas')
resizeCanvas.width = MAX_SIZE
resizeCanvas.height = MAX_SIZE
const ctx = resizeCanvas.getContext('2d')
ctx.drawImage(canvas, 0, 0, MAX_SIZE, MAX_SIZE)
finalCanvas = resizeCanvas
}
finalCanvas.toBlob((blob) => {
const file = new File([blob], 'cover_image.png', { type: 'image/png' })
file._isCropped = true // 크롭된 파일임을 표시하여 재진입 방지
this.audio_content.cover_image = file
this.audio_content.cover_image_url = URL.createObjectURL(blob)
this.show_cropper_dialog = false
if (this.cropper) {
this.cropper.destroy()
this.cropper = null
}
}, 'image/png')
},
async getAudioContentThemeList() {
this.is_loading = true
try {
@@ -1235,4 +1329,9 @@ export default {
object-fit: cover;
margin-top: 10px;
}
.cropper-wrapper {
max-height: 500px;
overflow: hidden;
}
</style>