250 lines
5.1 KiB
Vue
250 lines
5.1 KiB
Vue
<template>
|
|
<div class="image-uploader">
|
|
<div class="image-list">
|
|
<div
|
|
v-for="(item, index) in localPreviewList"
|
|
:key="item.uid"
|
|
class="image-item"
|
|
@click.stop
|
|
>
|
|
<img :src="item.url" class="uploaded-image" alt="上传的图片" />
|
|
<div class="image-index">{{ index + 1 }}</div>
|
|
<div class="delete-icon" @click="handleDelete(index)">
|
|
<i-ep-close />
|
|
</div>
|
|
</div>
|
|
<div v-if="localPreviewList.length < limit" class="upload-trigger" @click="triggerUpload">
|
|
<i-ep-plus />
|
|
<span>参考内容</span>
|
|
</div>
|
|
</div>
|
|
<el-upload
|
|
ref="uploadRef"
|
|
v-show="false"
|
|
:action="uploadurl"
|
|
multiple
|
|
:limit="limit"
|
|
:before-upload="beforeUpload"
|
|
:on-success="handleSuccess"
|
|
:on-error="handleError"
|
|
:on-exceed="handleExceed"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { genFileId } from 'element-plus'
|
|
|
|
const props = defineProps({
|
|
limit: {
|
|
type: Number,
|
|
default: 1
|
|
},
|
|
modelValue: {
|
|
type: Array,
|
|
default: () => []
|
|
}
|
|
})
|
|
|
|
const emit = defineEmits(['update:modelValue'])
|
|
|
|
const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
|
|
const uploadRef = ref(null)
|
|
const imageList = ref([...props.modelValue])
|
|
const localPreviewList = ref([...props.modelValue])
|
|
|
|
watch(() => props.modelValue, (newVal) => {
|
|
imageList.value = [...newVal]
|
|
if (newVal.length === 0) {
|
|
localPreviewList.value = []
|
|
}
|
|
}, { deep: true })
|
|
|
|
const triggerUpload = () => {
|
|
uploadRef.value.$el.querySelector('input').click()
|
|
}
|
|
|
|
const beforeUpload = (rawFile) => {
|
|
const allowedTypes = ['image/jpeg', 'image/png']
|
|
|
|
if (!allowedTypes.includes(rawFile.type)) {
|
|
ElMessage.error('不支持的文件类型,请上传 JPG、PNG 格式的图片')
|
|
return false
|
|
}
|
|
|
|
if (rawFile.size / 1024 / 1024 > 10) {
|
|
ElMessage.error('图片大小不能超过 10MB')
|
|
return false
|
|
}
|
|
}
|
|
|
|
const handleSuccess = (response, uploadFile) => {
|
|
ElMessage.success('上传成功')
|
|
|
|
const localUrl = URL.createObjectURL(uploadFile.raw)
|
|
|
|
const newImage = {
|
|
uid: uploadFile.uid,
|
|
url: response.url
|
|
}
|
|
imageList.value.push(newImage)
|
|
emit('update:modelValue', [...imageList.value])
|
|
|
|
const newPreview = {
|
|
uid: uploadFile.uid,
|
|
url: localUrl
|
|
}
|
|
localPreviewList.value.push(newPreview)
|
|
}
|
|
|
|
const handleError = () => {
|
|
ElMessage.error('上传失败,请重新选择图片')
|
|
}
|
|
|
|
const handleExceed = () => {
|
|
ElMessage.warning(`最多只能上传 ${props.limit} 张图片`)
|
|
}
|
|
|
|
const handleDelete = (index) => {
|
|
const previewItem = localPreviewList.value[index]
|
|
if (previewItem && previewItem.url.startsWith('blob:')) {
|
|
URL.revokeObjectURL(previewItem.url)
|
|
}
|
|
|
|
localPreviewList.value.splice(index, 1)
|
|
imageList.value.splice(index, 1)
|
|
emit('update:modelValue', [...imageList.value])
|
|
}
|
|
|
|
const handleSelect = async (url) => {
|
|
const initialFile = await fetch(url)
|
|
const blob = await initialFile.blob()
|
|
const file = new File([blob], `selected_image_${Date.now()}.jpg`, { type: blob.type })
|
|
file.uid = genFileId()
|
|
|
|
if (props.limit === 1 && imageList.value.length === 1) {
|
|
imageList.value = []
|
|
}
|
|
|
|
uploadRef.value.handleStart(file)
|
|
uploadRef.value.submit()
|
|
}
|
|
|
|
defineExpose({
|
|
handleSelect,
|
|
triggerUpload
|
|
})
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.image-uploader {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.image-list {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
}
|
|
|
|
.image-item {
|
|
position: relative;
|
|
width: 56px;
|
|
height: 56px;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover {
|
|
transform: scale(1.1);
|
|
z-index: 10;
|
|
|
|
.delete-icon {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.uploaded-image {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
transition: all 0.3s ease;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.image-index {
|
|
position: absolute;
|
|
bottom: 4px;
|
|
right: 4px;
|
|
width: 20px;
|
|
height: 20px;
|
|
background: rgba(0, 15, 51, 0.8);
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
font-weight: bold;
|
|
z-index: 5;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.delete-icon {
|
|
position: absolute;
|
|
top: -6px;
|
|
right: -6px;
|
|
width: 20px;
|
|
height: 20px;
|
|
background: #ef4444;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-size: 12px;
|
|
cursor: pointer;
|
|
opacity: 0;
|
|
transition: all 0.3s ease;
|
|
z-index: 20;
|
|
|
|
&:hover {
|
|
background: #dc2626;
|
|
transform: scale(1.1);
|
|
}
|
|
}
|
|
}
|
|
|
|
.upload-trigger {
|
|
width: 56px;
|
|
height: 56px;
|
|
// border: 2px dashed #d1d5db;
|
|
background-color: #F8F9FA;
|
|
border-radius: 8px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
color: #9ca3af;
|
|
font-size: 15px;
|
|
transition: all 0.3s ease;
|
|
|
|
span {
|
|
color: #999;
|
|
font-family: "Microsoft YaHei";
|
|
font-size: 12px;
|
|
font-style: normal;
|
|
font-weight: 400;
|
|
line-height: normal;
|
|
}
|
|
|
|
&:hover {
|
|
background-color: #F0F1F2;
|
|
border-color: #626aef;
|
|
// color: #626aef;
|
|
transform: scale(1.05);
|
|
}
|
|
}
|
|
</style>
|