图片上传支持粘贴剪贴板与拖拽上传

This commit is contained in:
yml2213
2026-08-01 18:40:51 +08:00
parent de33bfa74c
commit 66193c5d59
3 changed files with 81 additions and 21 deletions
@@ -1,6 +1,7 @@
import { PlusOutlined } from '@ant-design/icons' import { PlusOutlined } from '@ant-design/icons'
import { App, Upload } from 'antd' import { App, Typography, Upload } from 'antd'
import type { UploadFile, UploadProps } from 'antd' import type { UploadFile, UploadProps } from 'antd'
import type { ClipboardEvent, DragEvent } from 'react'
import { uploadFile } from '@/services/files' import { uploadFile } from '@/services/files'
import type { UploadedFile } from '@/types/worker-platform' import type { UploadedFile } from '@/types/worker-platform'
@@ -11,6 +12,7 @@ type ImageUploadProps = {
scene: string scene: string
scope: 'admin' | 'worker' | 'collect' scope: 'admin' | 'worker' | 'collect'
maxCount?: number maxCount?: number
showPasteHint?: boolean
} }
export default function ImageUpload({ export default function ImageUpload({
@@ -19,6 +21,7 @@ export default function ImageUpload({
scene, scene,
scope, scope,
maxCount = 6, maxCount = 6,
showPasteHint = false,
}: ImageUploadProps) { }: ImageUploadProps) {
const { message } = App.useApp() const { message } = App.useApp()
const fileList: UploadFile[] = value.map((file) => ({ const fileList: UploadFile[] = value.map((file) => ({
@@ -29,6 +32,39 @@ export default function ImageUpload({
thumbUrl: file.thumbnailUrl || file.url, thumbUrl: file.thumbnailUrl || file.url,
})) }))
async function uploadFiles(files: File[]) {
const images = files.filter((file) => file.type.startsWith('image/'))
if (images.length === 0) return
const remaining = maxCount - value.length
if (remaining <= 0) {
message.warning(`最多上传 ${maxCount} 张图片`)
return
}
const next = [...value]
for (const file of images.slice(0, remaining)) {
try {
const uploaded = await uploadFile(file, scene, scope)
next.push(uploaded)
} catch (error) {
message.error(error instanceof Error ? error.message : '上传失败')
}
}
if (next.length > value.length) onChange?.(next)
}
function handlePaste(event: ClipboardEvent<HTMLDivElement>) {
const files = Array.from(event.clipboardData?.files || [])
if (files.length === 0) return
event.preventDefault()
void uploadFiles(files)
}
function handleDrop(event: DragEvent<HTMLDivElement>) {
event.preventDefault()
const files = Array.from(event.dataTransfer?.files || [])
if (files.length > 0) void uploadFiles(files)
}
const customRequest: UploadProps['customRequest'] = async (options) => { const customRequest: UploadProps['customRequest'] = async (options) => {
try { try {
const uploaded = await uploadFile(options.file as File, scene, scope) const uploaded = await uploadFile(options.file as File, scene, scope)
@@ -41,6 +77,12 @@ export default function ImageUpload({
} }
return ( return (
<div
className="image-upload-droppable"
onPaste={handlePaste}
onDrop={handleDrop}
onDragOver={(event) => event.preventDefault()}
>
<Upload <Upload
accept="image/jpeg,image/png,image/webp" accept="image/jpeg,image/png,image/webp"
listType="picture-card" listType="picture-card"
@@ -62,5 +104,11 @@ export default function ImageUpload({
</button> </button>
)} )}
</Upload> </Upload>
{showPasteHint ? (
<Typography.Text type="secondary" className="image-upload-hint">
</Typography.Text>
) : null}
</div>
) )
} }
@@ -571,6 +571,7 @@ export default function WorkerOrdersPage() {
value={acceptanceFiles} value={acceptanceFiles}
onChange={setAcceptanceFiles} onChange={setAcceptanceFiles}
maxCount={6} maxCount={6}
showPasteHint
/> />
</Form.Item> </Form.Item>
</Form> </Form>
+11
View File
@@ -1888,6 +1888,17 @@ select {
font-size: 12px; font-size: 12px;
} }
.image-upload-droppable {
display: grid;
gap: 8px;
justify-items: start;
}
.image-upload-hint {
font-size: 12px;
line-height: 1.5;
}
.image-preview-list { .image-preview-list {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;