roim-picx/src/views/UploadImages.vue

219 lines
6.3 KiB
Vue

<template>
<div class="mx-auto max-w-6xl my-4 px-4">
<div class="text-gray-800 text-lg">上传图片</div>
<div class="mb-4 text-sm text-gray-500">
每张图片大小不超过 {{ formatBytes(imageSizeLimit) }}
</div>
<div class="border-2 border-dashed border-slate-400 rounded-md relative">
<loading-overlay :loading="loading" />
<div class="grid p-4 gap-4 grid-cols-4 min-h-[240px]" @drop.prevent="onFileDrop" @dragover.prevent>
<div v-if="convertedImages.length === 0"
class="absolute -z-10 left-0 top-0 w-full h-full flex items-center justify-center">
<div class="text-gray-500">
<font-awesome-icon :icon="faCopy" />
粘贴或拖动图片至此处
</div>
</div>
<transition-group name="el-fade-in-linear">
<div class="col-span-3 md:col-span-1" v-for="item in convertedImages" :key="item.tmpSrc">
<image-box :src="item.tmpSrc" :size="item.file.size" :name="item.file.name"
@delete="removeImage(item.tmpSrc)" mode="converted" />
</div>
</transition-group>
</div>
</div>
<div class="w-full rounded-md shadow-sm overflow-hidden mt-4 grid grid-cols-8">
<div class="md:col-span-1 col-span-8">
<div class="w-full h-10 bg-blue-500 cursor-pointer flex items-center justify-center text-white" :class="{
'area-disabled': loading
}" @click="input?.click()">
<font-awesome-icon :icon="faImages" class="mr-2" />
选择图片
</div>
</div>
<div class="md:col-span-4 col-span-8">
<div class="w-full h-10 bg-slate-200 leading-10 px-4 text-center md:text-left">
已选择 {{ convertedImages.length }} 张,共 {{ formatBytes(imagesTotalSize) }}
</div>
</div>
<div class="md:col-span-1 col-span-3">
<div class="w-full bg-red-500 cursor-pointer h-10 flex items-center justify-center text-white" :class="{
'area-disabled': loading
}" @click="clipboardUpload">
<font-awesome-icon :icon="faTrashAlt" class="mr-2" />
剪切板上传
</div>
</div>
<div class="md:col-span-1 col-span-3">
<div class="w-full bg-red-500 cursor-pointer h-10 flex items-center justify-center text-white" :class="{
'area-disabled': loading
}" @click="clearInput">
<font-awesome-icon :icon="faTrashAlt" class="mr-2" />
清除
</div>
</div>
<div class="md:col-span-1 col-span-5">
<div class="w-full h-10 flex items-center justify-center text-white bg-green-500 cursor-pointer" :class="{
'area-disabled': convertedImages.length === 0 || loading
}" @click="uploadImages">
<font-awesome-icon :icon="faUpload" class="mr-2" />
上传
</div>
</div>
</div>
<result-list v-show="imgResultList && imgResultList.length" :image-list="imgResultList" ref="resultList"
class="mt-4" />
</div>
<input ref="input" type="file" accept="image/*" class="hidden" multiple @change="onInputChange" />
</template>
<script setup lang="ts">
import { faImages, faTrashAlt, faCopy } from '@fortawesome/free-regular-svg-icons'
import { faUpload } from '@fortawesome/free-solid-svg-icons'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import LoadingOverlay from '../components/LoadingOverlay.vue'
import formatBytes from '../utils/format-bytes'
import { ElNotification as elNotify } from 'element-plus'
import { requestUploadImages } from '../utils/request'
import { useRouter } from 'vue-router'
import ImageBox from '../components/ImageBox.vue'
import ResultList from '../components/ResultList.vue'
import type { ConvertedImage, ImgItem } from '../utils/types'
const convertedImages = ref<ConvertedImage[]>([])
const imgResultList = ref<ImgItem[]>([])
const imagesTotalSize = computed(() =>
convertedImages.value.reduce((total, item) => total + item.file.size, 0)
)
const imageSizeLimit = 20 * 1024 * 1024
const input = ref<HTMLInputElement>()
const loading = ref(false)
const router = useRouter()
const onInputChange = () => {
appendConvertedImages(input.value?.files)
}
const onFileDrop = (e: DragEvent) => {
appendConvertedImages(e.dataTransfer?.files)
}
const onPaste = (e: ClipboardEvent) => {
appendConvertedImages(e.clipboardData?.files)
}
onMounted(() => {
document.onpaste = onPaste
})
onUnmounted(() => {
document.onpaste = null
convertedImages.value.forEach((item) => URL.revokeObjectURL(item.tmpSrc))
})
const clearInput = () => {
convertedImages.value = []
imgResultList.value = []
}
const clipboardUpload = () => {
navigator.clipboard.read().then(clipboardContents => {
for (const item of clipboardContents) {
if (!item.types.includes("image/png")) {
throw new Error("剪切板中不是图片!");
}
item.getType("image/png").then(blob => {
const file = new File([blob], "clipboard.png", {
type: "image/png",
});
convertedImages.value = [
...convertedImages.value,
{
file,
tmpSrc: URL.createObjectURL(file)
}
]
});
}
});
}
const appendConvertedImages = async (files: FileList | null | undefined) => {
if (!files) return
loading.value = true
for (let i = 0; i < files.length; i++) {
const file = files.item(i)
if (!file) return
if (file.size > imageSizeLimit) {
elNotify({
message: `${file.name} 文件过大`,
type: 'error'
})
continue
}
if (!file.type.startsWith('image/')) {
elNotify({
message: `${file.name} 不是图片文件`,
type: 'error'
})
continue
}
convertedImages.value = [
...convertedImages.value,
{
file,
tmpSrc: URL.createObjectURL(file)
}
]
}
loading.value = false
}
const removeImage = (tmpSrc: string) => {
convertedImages.value = convertedImages.value.filter((item) => item.tmpSrc !== tmpSrc)
URL.revokeObjectURL(tmpSrc)
}
const uploadImages = () => {
loading.value = true
const formData = new FormData()
for (let item of convertedImages.value) {
formData.append('files', item.file)
}
requestUploadImages(formData)
.then((res) => {
elNotify({
title: '上传完成',
message: `${convertedImages.value.length} 张图片,${formatBytes(
imagesTotalSize.value
)}`,
type: 'success'
})
convertedImages.value = []
imgResultList.value = res
// console.log(res)
// router.push('/')
})
.catch(() => { })
.finally(() => {
loading.value = false
})
}
</script>
<style>
.remove-now-btn .el-picker-panel__footer button:first-child {
display: none;
}
</style>