AI_Painting_V2.0/src/platforms/video/imageUploader.vue

331 lines
7.2 KiB
Vue

<template>
<div class="video-image-uploader">
<div class="image-list">
<div
v-for="(item, index) in localPreviewList"
:key="item.uid"
class="image-item"
>
<img :src="item.url" class="uploaded-image" :alt="getFrameLabel(index)" />
<div class="frame-label">{{ getFrameLabel(index) }}</div>
<div class="delete-icon" @click.stop="handleDelete(index)">
<i-ep-close />
</div>
</div>
<template v-for="i in maxImages" :key="i">
<div
v-if="localPreviewList.length < maxImages && !localPreviewList[i - 1]"
class="upload-trigger"
@click="triggerUpload(i - 1)"
>
<i-ep-plus color="#333333" />
<div class="upload-text">{{ getUploadText(i) }}</div>
</div>
</template>
</div>
<el-upload
v-for="i in maxImages"
v-show="false"
:key="i"
:ref="el => setUploadRef(el, i - 1)"
:action="uploadurl"
:limit="1"
:before-upload="beforeUpload"
:on-success="(response, uploadFile) => handleSuccess(response, uploadFile, i - 1)"
:on-error="handleError"
/>
</div>
</template>
<script setup>
import { genFileId } from 'element-plus'
const props = defineProps({
modelType: {
type: String,
default: 'text'
},
imagesCount: {
type: Number,
default: 1
},
modelValue: {
type: Array,
default: () => []
}
})
const emit = defineEmits(['update:modelValue'])
const uploadurl = import.meta.env.VITE_API_WORKFLOW_UPLOAD
const uploadRefs = ref([])
const imageList = ref([...props.modelValue])
const localPreviewList = ref([...props.modelValue])
const isUploading = ref(false)
const showEndFrame = computed(() => props.modelType === 'image' && props.imagesCount === 2)
const maxImages = computed(() => props.modelType === 'image' ? (showEndFrame.value ? 2 : 1) : 1)
const setUploadRef = (el, index) => {
if (el) {
uploadRefs.value[index] = el
}
}
const getFrameLabel = (index) => {
if (props.modelType === 'digitalHuman') {
return ''
}
return index === 0 ? '首帧' : '尾帧'
}
const getUploadText = (i) => {
if (props.modelType === 'digitalHuman') {
return '参考内容'
}
return i === 1 ? '首帧' : '尾帧'
}
watch(() => props.modelValue, async (newVal) => {
if (isUploading.value) {
return
}
imageList.value = [...newVal]
const newPreviewList = []
for (const img of newVal) {
let previewImg = { ...img }
if (img.url && !img.url.startsWith('blob:')) {
try {
const response = await fetch(img.url)
const blob = await response.blob()
previewImg = {
...img,
url: URL.createObjectURL(blob),
serverUrl: img.url
}
} catch (error) {
console.error('Failed to create blob URL:', error)
previewImg = {
...img,
serverUrl: img.url
}
}
}
newPreviewList.push(previewImg)
}
localPreviewList.value = newPreviewList
}, { deep: true, immediate: true })
const triggerUpload = (index) => {
if (uploadRefs.value[index]) {
uploadRefs.value[index].$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, index) => {
ElMessage.success('上传成功')
isUploading.value = true
const localUrl = URL.createObjectURL(uploadFile.raw)
const newImage = {
uid: uploadFile.uid,
url: response.url
}
if (imageList.value[index]) {
const previewItem = localPreviewList.value[index]
if (previewItem && previewItem.url && previewItem.url.startsWith('blob:')) {
URL.revokeObjectURL(previewItem.url)
}
imageList.value[index] = newImage
localPreviewList.value[index] = {
uid: uploadFile.uid,
url: localUrl,
serverUrl: response.url
}
} else {
imageList.value[index] = newImage
localPreviewList.value[index] = {
uid: uploadFile.uid,
url: localUrl,
serverUrl: response.url
}
}
emit('update:modelValue', [...imageList.value])
nextTick(() => {
isUploading.value = false
})
}
const handleError = () => {
ElMessage.error('上传失败,请重新选择图片')
}
const handleDelete = (index) => {
const previewItem = localPreviewList.value[index]
if (previewItem && previewItem.url && 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, index = 0) => {
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 (uploadRefs.value[index]) {
uploadRefs.value[index].handleStart(file)
uploadRefs.value[index].submit()
}
}
defineExpose({
handleSelect
})
</script>
<style lang="less" scoped>
.video-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;
}
.frame-label {
position: absolute;
bottom: 4px;
right: 4px;
width: 32px;
height: 20px;
background: rgba(0, 15, 51, 0.8);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 10px;
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;
background: rgba(0, 15, 51, 0.04);
backdrop-filter: blur(5px);
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: #333;
font-family: "Microsoft YaHei";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
&:hover {
background-color: #F0F1F2;
border-color: #626aef;
transform: scale(1.05);
}
}
.upload-text {
color: #666;
font-family: "Microsoft YaHei";
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: normal;
}
</style>