230 lines
5.1 KiB
Vue
230 lines
5.1 KiB
Vue
<script setup>
|
|
import { onMounted, ref } from 'vue'
|
|
import { NModal, NUpload, useMessage } from 'naive-ui'
|
|
import BoxIcon from '@/components/icons/BoxIcon.vue'
|
|
import { useMerchImagesApi } from '@/api/merchImages.js'
|
|
|
|
const { uploadImage, getImageUrl, deleteImage } = useMerchImagesApi()
|
|
const message = useMessage()
|
|
|
|
const props = defineProps({
|
|
merchUuid: { type: String, required: true },
|
|
})
|
|
|
|
const showModal = ref(false)
|
|
const previewImageUrl = ref('')
|
|
const fileList = ref([])
|
|
|
|
function handlePreview(file) {
|
|
if (file.file) {
|
|
previewImageUrl.value = URL.createObjectURL(file.file)
|
|
} else if (file.url) {
|
|
previewImageUrl.value = file.url
|
|
}
|
|
showModal.value = true
|
|
}
|
|
|
|
function onModalClose() {
|
|
if (previewImageUrl.value && previewImageUrl.value.startsWith('blob:')) {
|
|
URL.revokeObjectURL(previewImageUrl.value)
|
|
}
|
|
previewImageUrl.value = ''
|
|
}
|
|
|
|
const fileInput = ref(null)
|
|
|
|
function triggerFileInput() {
|
|
fileInput.value?.click()
|
|
}
|
|
|
|
function onFileInputChange(event) {
|
|
const files = event.target.files
|
|
if (!files || files.length === 0) return
|
|
|
|
const file = files[0]
|
|
handleUpload({
|
|
fileList: [
|
|
{
|
|
file,
|
|
name: file.name,
|
|
status: 'pending',
|
|
},
|
|
],
|
|
})
|
|
event.target.value = ''
|
|
}
|
|
|
|
async function handleUpload({ fileList: newFileList }) {
|
|
const file = newFileList[newFileList.length - 1]
|
|
try {
|
|
await uploadImage(props.merchUuid, file.file)
|
|
|
|
const { imgUrl } = await getImageUrl(props.merchUuid, 'full')
|
|
|
|
message.success('Image uploaded successfully.')
|
|
|
|
fileList.value = [
|
|
{
|
|
name: file.name,
|
|
url: imgUrl,
|
|
status: 'finished',
|
|
},
|
|
]
|
|
} catch (error) {
|
|
message.error('Upload error: ' + (error.message || 'Unknown error.'))
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const { imgUrl } = await getImageUrl(props.merchUuid, 'full')
|
|
fileList.value = [
|
|
{
|
|
name: 'full.jpg',
|
|
url: imgUrl,
|
|
status: 'finished',
|
|
},
|
|
]
|
|
} catch (error) {
|
|
fileList.value = []
|
|
if (!error.message?.includes('404')) {
|
|
console.error('Error getting image: ', error)
|
|
}
|
|
}
|
|
})
|
|
|
|
const showConfirmDelete = ref(false)
|
|
|
|
const deleteImageHandler = async () => {
|
|
showConfirmDelete.value = true
|
|
return false //prevents instant "delete image" in n-upload before confirm
|
|
}
|
|
|
|
const confirmDeleteImage = async () => {
|
|
try {
|
|
await deleteImage(props.merchUuid)
|
|
fileList.value = []
|
|
message.success('Image deleted successfully.')
|
|
} catch (error) {
|
|
message.error('Image delete error: ' + (error.message || 'Unknown error.'))
|
|
} finally {
|
|
showConfirmDelete.value = false
|
|
}
|
|
}
|
|
|
|
const cancelDelete = () => {
|
|
showConfirmDelete.value = false
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div v-if="fileList.length === 0" class="upload-wrapper" @click="triggerFileInput">
|
|
<BoxIcon class="upload-icon" />
|
|
<span class="upload-text">Click to Upload</span>
|
|
</div>
|
|
|
|
<div v-else class="preview-container clickable-image">
|
|
<div>
|
|
<img
|
|
:src="fileList[0].url"
|
|
alt="Preview"
|
|
class="preview-image"
|
|
@click="triggerFileInput"
|
|
/>
|
|
</div>
|
|
<div class="button-container-evenly button-gap">
|
|
<n-button type="primary" @click="handlePreview(fileList[0])">Preview</n-button>
|
|
<n-button type="error" @click="deleteImageHandler">Delete</n-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<n-modal v-model:show="showModal" preset="card" title="Preview" @after-leave="onModalClose" class="image-preview-modal">
|
|
<img :src="previewImageUrl" alt="Preview" class="preview-modal-image"/>
|
|
</n-modal>
|
|
|
|
<n-modal v-model:show="showConfirmDelete" preset="dialog" title="Confirmation">
|
|
<template #default>
|
|
<p>Confirm delete image</p>
|
|
</template>
|
|
<template #action>
|
|
<n-button @click="confirmDeleteImage" type="error">Delete</n-button>
|
|
<n-button @click="cancelDelete">Cancel</n-button>
|
|
</template>
|
|
</n-modal>
|
|
|
|
<input
|
|
ref="fileInput"
|
|
type="file"
|
|
accept="image/*"
|
|
style="display: none"
|
|
@change="onFileInputChange"
|
|
/>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.upload-wrapper {
|
|
width: 300px;
|
|
max-width: 300px;
|
|
height: 300px;
|
|
max-height: 300px;
|
|
border: 1px dashed #ccc;
|
|
border-radius: 8px;
|
|
background-color: #fafafa;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition:
|
|
background-color 0.2s ease,
|
|
border-color 0.2s ease;
|
|
}
|
|
|
|
.upload-wrapper:hover {
|
|
border-color: #18a058;
|
|
background-color: #f0fdf4;
|
|
}
|
|
|
|
.upload-icon {
|
|
width: 150px;
|
|
max-width: 150px;
|
|
height: 150px;
|
|
max-height: 150px;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.upload-text {
|
|
margin-top: 8px;
|
|
font-size: 14px;
|
|
color: #555;
|
|
background-color: rgba(24, 160, 88, 0.4);
|
|
padding: 6px 12px;
|
|
border-radius: 6px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.preview-container {
|
|
display: block;
|
|
width: 100%;
|
|
max-width: 400px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.preview-image {
|
|
width: 100%;
|
|
height: auto;
|
|
display: block;
|
|
max-width: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.clickable-image {
|
|
cursor: pointer;
|
|
transition: opacity 0.2s;
|
|
}
|
|
.clickable-image:hover {
|
|
opacity: 0.9;
|
|
}
|
|
</style>
|