mirror of
https://github.com/flipped-aurora/gin-vue-admin.git
synced 2026-10-19 16:15:42 +00:00
+图片直接粘贴上传(png、jpg)
This commit is contained in:
@@ -64,4 +64,20 @@ export const uploadFile = (data) => {
|
||||
method: "post",
|
||||
data,
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传文件(FormData):粘贴图片等场景使用,noSave=1 不保存进媒体库
|
||||
* @param data FormData
|
||||
* @returns {*}
|
||||
*/
|
||||
export const uploadAttachment = (data) => {
|
||||
return service({
|
||||
url: '/fileUploadAndDownload/upload?noSave=1',
|
||||
method: 'post',
|
||||
data,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -27,16 +27,23 @@
|
||||
:preview-src-list="srcList"
|
||||
fit="cover"
|
||||
/>
|
||||
<template v-else>
|
||||
<div
|
||||
v-else
|
||||
class="text-gray-600 group-hover:bg-gray-200 group-hover:opacity-60 w-full h-full flex justify-center items-center"
|
||||
@click="chooseItem"
|
||||
>
|
||||
<el-icon>
|
||||
<plus />
|
||||
</el-icon>
|
||||
上传
|
||||
</div>
|
||||
<div id="paste-zone" tabindex="0"
|
||||
class="absolute z-10 w-16 h-16 -mt-18 ml-22 text-xs flex items-center border border-dashed justify-center p-0.5 rounded border-gray-300 outline-none transition-colors focus:border-blue-400 focus:text-blue-400 text-gray-500"
|
||||
@click="(e) => e.currentTarget.focus()"
|
||||
@paste="onPaste"
|
||||
>
|
||||
先点击这里,然后粘贴(Ctrl / ⌘ + V)
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 删除按钮在外层容器中 -->
|
||||
<div
|
||||
@@ -66,7 +73,7 @@
|
||||
}
|
||||
})
|
||||
|
||||
const emits = defineEmits(['chooseItem', 'deleteItem'])
|
||||
const emits = defineEmits(['chooseItem', 'deleteItem', 'pasteFile'])
|
||||
|
||||
const chooseItem = () => {
|
||||
emits('chooseItem')
|
||||
@@ -83,4 +90,28 @@
|
||||
const srcList = computed(() => {
|
||||
return imgUrl.value ? [imgUrl.value] : []
|
||||
})
|
||||
|
||||
// 粘贴图片:必须在事件回调内同步提取文件,异步后 clipboardData 会失效
|
||||
const onPaste = (e) => {
|
||||
const clipboardData = e.clipboardData
|
||||
const files = []
|
||||
if (clipboardData?.items) {
|
||||
for (let i = 0; i < clipboardData.items.length; i++) {
|
||||
const item = clipboardData.items[i]
|
||||
if (item.kind === 'file' && item.type.startsWith('image/')) {
|
||||
const file = item.getAsFile()
|
||||
if (file) files.push(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
// items 为空时兜底(部分场景只有 files)
|
||||
if (!files.length && clipboardData?.files) {
|
||||
for (const file of clipboardData.files) {
|
||||
if (file.type.startsWith('image/')) files.push(file)
|
||||
}
|
||||
}
|
||||
if (!files.length) return // 非图片内容交给浏览器默认行为
|
||||
e.preventDefault()
|
||||
files.forEach((file) => emits('pasteFile', file))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div>
|
||||
<selectComponent :rounded="rounded" v-if="!props.multiple" :model="model" @chooseItem="openChooseImg" @deleteItem="openChooseImg" />
|
||||
<selectComponent :rounded="rounded" v-if="!props.multiple" :model="model" @chooseItem="openChooseImg" @deleteItem="openChooseImg" @pasteFile="onPasteFile" />
|
||||
<div v-else class="w-full gap-4 flex flex-wrap">
|
||||
<draggable
|
||||
v-model="model"
|
||||
@@ -24,7 +24,7 @@
|
||||
</template>
|
||||
</draggable>
|
||||
<selectComponent :rounded="rounded" v-if="currentImageCount < props.maxUpdateCount || props.maxUpdateCount === 0"
|
||||
@chooseItem="openChooseImg" @deleteItem="openChooseImg"
|
||||
@chooseItem="openChooseImg" @deleteItem="openChooseImg" @pasteFile="onPasteFile"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
<script setup>
|
||||
import { getUrl, isVideoExt } from '@/utils/image'
|
||||
import { computed, ref } from 'vue'
|
||||
import { getFileList, editFileName, deleteFile } from '@/api/fileUploadAndDownload'
|
||||
import {getFileList, editFileName, deleteFile, uploadAttachment} from '@/api/fileUploadAndDownload'
|
||||
import UploadImage from '@/components/upload/image.vue'
|
||||
import UploadCommon from '@/components/upload/common.vue'
|
||||
import WarningBar from '@/components/warningBar/warningBar.vue'
|
||||
@@ -485,6 +485,55 @@ const onDragEnd = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 粘贴上传:noSave 不保存进媒体库,成功后直接回填 model(单图覆盖 / 多图追加)
|
||||
const onPasteFile = async(file) => {
|
||||
if (props.fileType && props.fileType !== 'image') {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '当前类型不支持使用'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (props.multiple && props.maxUpdateCount > 0 && (model.value?.length || 0) >= props.maxUpdateCount) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: `最多只能选择 ${props.maxUpdateCount} 张图片`
|
||||
})
|
||||
return
|
||||
}
|
||||
// 截图类文件常无可用文件名,补一个(后端按文件名识别类型)
|
||||
const ext = (file.type.split('/')[1] || 'png').replace('jpeg', 'jpg')
|
||||
const fd = new FormData()
|
||||
fd.append('file', file, file.name || `paste-${Date.now()}.${ext}`)
|
||||
|
||||
const res = await uploadAttachment(fd)
|
||||
if (res.code !== 0) return // 失败提示由响应拦截器统一处理
|
||||
const url = res.data?.file.url
|
||||
if (!url) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: '上传失败'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (props.multiple) {
|
||||
// 并发粘贴多张时可能同时通过上限校验,回填前再检查一次
|
||||
if (props.maxUpdateCount > 0 && (model.value?.length || 0) >= props.maxUpdateCount) {
|
||||
ElMessage({
|
||||
type: 'error',
|
||||
message: `最多只能选择 ${props.maxUpdateCount} 张图片`
|
||||
})
|
||||
return
|
||||
}
|
||||
model.value = [...(model.value || []), url]
|
||||
} else {
|
||||
model.value = url
|
||||
}
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '粘贴上传成功'
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.selected {
|
||||
|
||||
Reference in New Issue
Block a user