2 Commits

6 changed files with 145 additions and 10 deletions

View 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)
})
}

View File

@ -457,7 +457,7 @@
</el-row>
<el-form-item label="产品图" prop="generalImage" v-if="hasFieldPermission('files')">
<div class="upload-container">
<div class="upload-container" id="upload-generalImage">
<el-upload
v-model:file-list="fileListImage"
action="#"
@ -485,7 +485,7 @@
</el-form-item>
<el-form-item label="说明书" prop="generalManual" v-if="hasFieldPermission('files')">
<div class="upload-container">
<div class="upload-container" id="upload-generalManual">
<el-upload
v-model:file-list="fileListManual"
action="#"
@ -653,6 +653,7 @@ import {
markWarningOrdered
} from '@/api/material_base';
import { uploadFile, deleteFile } from '@/api/common/upload';
import { usePasteUpload } from '@/hooks/usePasteUpload';
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue';
const userStore = useUserStore();
@ -1584,6 +1585,13 @@ const customUpload = async (options: any, targetField: 'generalImage' | 'general
if (res.code === 200) {
const newUrl = res.data.url
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('上传成功')
onSuccess(res)
} else {
@ -1597,6 +1605,13 @@ const customUpload = async (options: any, targetField: 'generalImage' | 'general
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 fileName = uploadFile.name || uploadFile.url?.split('/').pop() || '此文件'
try {

View File

@ -147,7 +147,7 @@
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="图片上传" required>
<el-form-item label="图片上传" required id="upload-purchase-images">
<el-upload
v-model:file-list="fileList"
:http-request="customUpload"
@ -244,6 +244,7 @@ import {
approvePurchase, getPurchaseApprovers, autoFillPurchase
} from '@/api/purchase'
import { uploadFile, deleteFile } from '@/api/common/upload'
import { usePasteUpload } from '@/hooks/usePasteUpload'
import type { FormInstance } from 'element-plus'
const userStore = useUserStore()
@ -490,6 +491,8 @@ const customUpload = async (options: any) => {
if (res.code === 200) {
const newUrl = res.data.url
form.value.images!.push(newUrl)
// 同步更新 fileList触发 el-upload UI 刷新
fileList.value.push({ name: newUrl.split('/').pop(), url: getImageUrl(newUrl) })
onSuccess(res)
} else {
ElMessage.error(res.msg || '上传失败')
@ -512,6 +515,12 @@ const handleRemoveImage = async (uploadFile: any) => {
}
}
// 粘贴上传处理器PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
const handlePasteLink = (link: string, field: string) => {
// 采购单没有独立的外链输入框,暂不支持网页图片链接自动填入
}
usePasteUpload(customUpload, 'images', '#upload-purchase-images', handlePasteLink)
// --- 提交 ---
const submitForm = async () => {
if (!form.value.name.trim()) { ElMessage.warning('请选择或填写采购物品'); return }

View File

@ -447,7 +447,7 @@
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="到货图片" prop="arrival_photo">
<div class="upload-container">
<div class="upload-container" id="upload-arrival_photo">
<el-upload
v-model:file-list="arrivalFileList"
action="#"
@ -466,7 +466,7 @@
</el-col>
<el-col :span="24">
<el-form-item label="检测报告" prop="inspection_report">
<div class="upload-container">
<div class="upload-container" id="upload-inspection_report">
<el-upload
v-model:file-list="reportFileList"
action="#"
@ -698,6 +698,7 @@ import {
} from '@/api/inbound/buy'
import {getLabelPreview, executePrint} from '@/api/common/print'
import { getWarehouseTree } from '@/api/common/warehouse'
import { usePasteUpload } from '@/hooks/usePasteUpload'
import WebRtcCamera from '@/components/Camera/WebRtcCamera.vue'
import WarehouseSelector from '@/components/WarehouseSelector.vue'
import SmartScannerDialog from '@/components/SmartScannerDialog.vue'
@ -1532,6 +1533,16 @@ const handleRemoveImage = async (uploadFile: any, targetField: 'arrival_photo' |
ElMessage.success('已删除')
} catch (e) { console.error(e) }
}
// 粘贴上传处理器PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
const handlePasteLink = (link: string, field: string) => {
if (field === 'inspection_report') {
inspection_report_url.value = link
}
}
usePasteUpload(customUpload, 'arrival_photo', '#upload-arrival_photo', handlePasteLink)
usePasteUpload(customUpload, 'inspection_report', '#upload-inspection_report', handlePasteLink)
const handlePreviewPicture = (uploadFile: any) => { dialogImageUrl.value = uploadFile.url!; dialogVisibleImage.value = true }
const triggerCamera = (field: 'arrival_photo' | 'inspection_report') => {
currentCameraField.value = field;

View File

@ -388,7 +388,7 @@
</el-col>
<el-col :span="dialogStatus === 'update' ? 12 : 18">
<el-form-item label="成品实拍" prop="product_photo">
<div class="upload-container">
<div class="upload-container" id="upload-product_photo">
<el-upload v-model:file-list="productPhotoList" action="#" list-type="picture-card" multiple :http-request="(opts) => customUpload(opts, 'product_photo')" :on-preview="handlePreviewPicture" :on-remove="(file) => handleRemoveImage(file, 'product_photo')" :before-upload="beforeAvatarUpload">
<el-icon><Plus /></el-icon>
</el-upload>
@ -402,7 +402,7 @@
<el-row :gutter="24">
<el-col :span="12">
<el-form-item label="质量报告" prop="quality_report_link">
<div class="upload-container">
<div class="upload-container" id="upload-quality_report_link">
<el-upload v-model:file-list="qualityFileList" action="#" list-type="picture-card" multiple :http-request="(opts) => customUpload(opts, 'quality_report_link')" :on-preview="handlePreviewPicture" :on-remove="(file) => handleRemoveImage(file, 'quality_report_link')" :before-upload="beforeAvatarUpload">
<el-icon><Plus /></el-icon>
</el-upload>
@ -415,7 +415,7 @@
<el-col :span="12">
<el-form-item label="检测报告" prop="inspection_report_link">
<div class="upload-container">
<div class="upload-container" id="upload-inspection_report_link">
<el-upload v-model:file-list="inspectionFileList" action="#" list-type="picture-card" multiple :http-request="(opts) => customUpload(opts, 'inspection_report_link')" :on-preview="handlePreviewPicture" :on-remove="(file) => handleRemoveImage(file, 'inspection_report_link')" :before-upload="beforeAvatarUpload">
<el-icon><Plus /></el-icon>
</el-upload>
@ -592,6 +592,7 @@ import WarehouseSelector from '@/components/WarehouseSelector.vue'
import SmartScannerDialog from '@/components/SmartScannerDialog.vue'
import { getLabelPreview, executePrint } from '@/api/common/print'
import { getWarehouseTree } from '@/api/common/warehouse'
import { usePasteUpload } from '@/hooks/usePasteUpload'
import { useUserStore } from '@/stores/user'
// ------------------------------------
@ -1235,6 +1236,15 @@ const handleRemoveImage = async (uploadFile: any, targetField: 'product_photo' |
}
const handlePreviewPicture = (uploadFile: any) => { dialogImageUrl.value = uploadFile.url!; dialogVisibleImage.value = true }
// 粘贴上传处理器PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
const handlePasteLink = (link: string, field: string) => {
if (field === 'quality_report_link') quality_url.value = link
else if (field === 'inspection_report_link') inspection_url.value = link
}
usePasteUpload(customUpload, 'product_photo', '#upload-product_photo', handlePasteLink)
usePasteUpload(customUpload, 'quality_report_link', '#upload-quality_report_link', handlePasteLink)
usePasteUpload(customUpload, 'inspection_report_link', '#upload-inspection_report_link', handlePasteLink)
const triggerCamera = (field: any) => {
currentCameraField.value = field;
cameraDialogVisible.value = true;

View File

@ -467,7 +467,7 @@
<el-row :gutter="24">
<el-col :span="24">
<el-form-item label="到货图片" prop="arrival_photo">
<div class="upload-container">
<div class="upload-container" id="upload-arrival_photo">
<el-upload v-model:file-list="arrivalFileList" action="#" list-type="picture-card" multiple :http-request="(opts) => customUpload(opts, 'arrival_photo')" :on-preview="handlePreviewPicture" :on-remove="(file) => handleRemoveImage(file, 'arrival_photo')" :before-upload="beforeAvatarUpload">
<el-icon><Plus /></el-icon>
</el-upload>
@ -479,7 +479,7 @@
<el-col :span="24">
<el-form-item label="质量报告" prop="quality_report_link">
<div class="upload-container">
<div class="upload-container" id="upload-quality_report_link">
<el-upload v-model:file-list="reportFileList" action="#" list-type="picture-card" multiple :http-request="(opts) => customUpload(opts, 'quality_report_link')" :on-preview="handlePreviewPicture" :on-remove="(file) => handleRemoveImage(file, 'quality_report_link')" :before-upload="beforeAvatarUpload">
<el-icon><Plus /></el-icon>
</el-upload>
@ -647,6 +647,7 @@ import WarehouseSelector from '@/components/WarehouseSelector.vue'
import SmartScannerDialog from '@/components/SmartScannerDialog.vue'
import {getLabelPreview, executePrint} from '@/api/common/print'
import { getWarehouseTree } from '@/api/common/warehouse'
import { usePasteUpload } from '@/hooks/usePasteUpload'
import { useUserStore } from '@/stores/user'
// ------------------------------------
@ -1324,6 +1325,14 @@ const handleRemoveImage = async (uploadFile: any, targetField: 'arrival_photo' |
} catch (e) { console.error(e) }
}
const handlePreviewPicture = (uploadFile: any) => { dialogImageUrl.value = uploadFile.url!; dialogVisibleImage.value = true }
// 粘贴上传处理器PC 端:鼠标悬停 + Ctrl+V 直接粘贴图片)
const handlePasteLink = (link: string, field: string) => {
if (field === 'quality_report_link') quality_report_url.value = link
}
usePasteUpload(customUpload, 'arrival_photo', '#upload-arrival_photo', handlePasteLink)
usePasteUpload(customUpload, 'quality_report_link', '#upload-quality_report_link', handlePasteLink)
const triggerCamera = (field: 'arrival_photo' | 'quality_report_link') => {
currentCameraField.value = field;
cameraDialogVisible.value = true;