feat: 新增 PC 端粘贴图片上传功能(hover 区域检测 + 网页图片链接自动填入)
This commit is contained in:
81
inventory-web/src/hooks/usePasteUpload.ts
Normal file
81
inventory-web/src/hooks/usePasteUpload.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
|
export function usePasteUpload(
|
||||||
|
customUpload: Function,
|
||||||
|
targetField: string,
|
||||||
|
containerSelector: string,
|
||||||
|
onLinkFound?: (link: string, field: string) => void
|
||||||
|
) {
|
||||||
|
const isHovering = ref(false)
|
||||||
|
|
||||||
|
const handleMouseMove = (event: MouseEvent) => {
|
||||||
|
const target = event.target as Element
|
||||||
|
if (!target) return
|
||||||
|
const isInsideTarget = !!target.closest(containerSelector)
|
||||||
|
if (isInsideTarget !== isHovering.value) {
|
||||||
|
isHovering.value = isInsideTarget
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleGlobalPaste = (event: ClipboardEvent) => {
|
||||||
|
if (!isHovering.value) return
|
||||||
|
|
||||||
|
const activeElement = document.activeElement
|
||||||
|
const activeTag = activeElement?.tagName.toLowerCase()
|
||||||
|
if (activeTag === 'input' || activeTag === 'textarea') return
|
||||||
|
|
||||||
|
const clipboardData = event.clipboardData
|
||||||
|
if (!clipboardData) return
|
||||||
|
|
||||||
|
// 1. 优先获取真实文件(本地截图/复制文件)
|
||||||
|
let imageFile: File | null = null
|
||||||
|
for (let i = 0; i < clipboardData.items.length; i++) {
|
||||||
|
if (clipboardData.items[i].type.indexOf('image') !== -1) {
|
||||||
|
const rawFile = clipboardData.items[i].getAsFile()
|
||||||
|
if (rawFile) {
|
||||||
|
const extension = rawFile.type.split('/')[1] || 'png'
|
||||||
|
const fileName = `paste_${targetField}_${new Date().getTime()}.${extension}`
|
||||||
|
imageFile = new File([rawFile], fileName, { type: rawFile.type })
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imageFile) {
|
||||||
|
event.preventDefault()
|
||||||
|
customUpload({ file: imageFile, onSuccess: () => {}, onError: () => {} }, targetField)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 没有真实文件时,解析 HTML 或文本里的图片 URL(网页复制场景)
|
||||||
|
const htmlData = clipboardData.getData('text/html')
|
||||||
|
const textData = clipboardData.getData('text/plain')
|
||||||
|
|
||||||
|
let imgUrl = ''
|
||||||
|
if (htmlData) {
|
||||||
|
const match = htmlData.match(/<img[^>]+src="([^">]+)"/)
|
||||||
|
if (match && match[1]) imgUrl = match[1]
|
||||||
|
}
|
||||||
|
if (!imgUrl && textData && textData.startsWith('http')) {
|
||||||
|
imgUrl = textData
|
||||||
|
}
|
||||||
|
|
||||||
|
if (imgUrl && onLinkFound) {
|
||||||
|
event.preventDefault()
|
||||||
|
ElMessage.success('检测到网页图片,已自动填入外部链接')
|
||||||
|
onLinkFound(imgUrl, targetField)
|
||||||
|
} else if (!imgUrl) {
|
||||||
|
ElMessage.warning('剪贴板中未检测到有效图片或链接')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.addEventListener('mousemove', handleMouseMove)
|
||||||
|
document.addEventListener('paste', handleGlobalPaste)
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove)
|
||||||
|
document.removeEventListener('paste', handleGlobalPaste)
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -457,7 +457,7 @@
|
|||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
<el-form-item label="产品图" prop="generalImage" v-if="hasFieldPermission('files')">
|
<el-form-item label="产品图" prop="generalImage" v-if="hasFieldPermission('files')">
|
||||||
<div class="upload-container">
|
<div class="upload-container" id="upload-generalImage">
|
||||||
<el-upload
|
<el-upload
|
||||||
v-model:file-list="fileListImage"
|
v-model:file-list="fileListImage"
|
||||||
action="#"
|
action="#"
|
||||||
@ -485,7 +485,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="说明书" prop="generalManual" v-if="hasFieldPermission('files')">
|
<el-form-item label="说明书" prop="generalManual" v-if="hasFieldPermission('files')">
|
||||||
<div class="upload-container">
|
<div class="upload-container" id="upload-generalManual">
|
||||||
<el-upload
|
<el-upload
|
||||||
v-model:file-list="fileListManual"
|
v-model:file-list="fileListManual"
|
||||||
action="#"
|
action="#"
|
||||||
@ -653,6 +653,7 @@ import {
|
|||||||
markWarningOrdered
|
markWarningOrdered
|
||||||
} from '@/api/material_base';
|
} from '@/api/material_base';
|
||||||
import { uploadFile, deleteFile } from '@/api/common/upload';
|
import { uploadFile, deleteFile } from '@/api/common/upload';
|
||||||
|
import { usePasteUpload } from '@/hooks/usePasteUpload';
|
||||||
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue';
|
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue';
|
||||||
|
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@ -1584,6 +1585,13 @@ const customUpload = async (options: any, targetField: 'generalImage' | 'general
|
|||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
const newUrl = res.data.url
|
const newUrl = res.data.url
|
||||||
form.value[targetField].push(newUrl)
|
form.value[targetField].push(newUrl)
|
||||||
|
// 同步更新 fileList,触发 el-upload UI 刷新
|
||||||
|
const fileObj = { name: newUrl.split('/').pop(), url: getImageUrl(newUrl) }
|
||||||
|
if (targetField === 'generalImage') {
|
||||||
|
fileListImage.value.push(fileObj)
|
||||||
|
} else {
|
||||||
|
fileListManual.value.push(fileObj)
|
||||||
|
}
|
||||||
ElMessage.success('上传成功')
|
ElMessage.success('上传成功')
|
||||||
onSuccess(res)
|
onSuccess(res)
|
||||||
} else {
|
} else {
|
||||||
@ -1597,6 +1605,13 @@ const customUpload = async (options: any, targetField: 'generalImage' | 'general
|
|||||||
finally { isUploading.value = false }
|
finally { isUploading.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 粘贴上传处理器(PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
|
||||||
|
const handlePasteLink = (link: string, field: string) => {
|
||||||
|
imageExternalUrl.value = link
|
||||||
|
}
|
||||||
|
usePasteUpload(customUpload, 'generalImage', '#upload-generalImage', handlePasteLink)
|
||||||
|
usePasteUpload(customUpload, 'generalManual', '#upload-generalManual', handlePasteLink)
|
||||||
|
|
||||||
const handleRemoveImage = async (uploadFile: any, targetField: 'generalImage' | 'generalManual') => {
|
const handleRemoveImage = async (uploadFile: any, targetField: 'generalImage' | 'generalManual') => {
|
||||||
const fileName = uploadFile.name || uploadFile.url?.split('/').pop() || '此文件'
|
const fileName = uploadFile.name || uploadFile.url?.split('/').pop() || '此文件'
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user