update: fix-02
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Descriptions,
|
||||
Progress,
|
||||
Alert,
|
||||
Empty,
|
||||
} from 'antd';
|
||||
import {
|
||||
CloudUploadOutlined,
|
||||
@@ -34,12 +35,39 @@ import type { EvidenceImage } from '../../types';
|
||||
|
||||
const { Dragger } = Upload;
|
||||
|
||||
type UploadBatchItem = {
|
||||
localId: string;
|
||||
fileName: string;
|
||||
fileSize: number;
|
||||
status: 'uploading' | 'success' | 'error';
|
||||
uploadedImageId?: string;
|
||||
previewUrl?: string;
|
||||
};
|
||||
|
||||
const formatFileSize = (size: number): string => {
|
||||
if (!Number.isFinite(size) || size <= 0) return '-';
|
||||
if (size < 1024) return `${size} B`;
|
||||
if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
|
||||
return `${(size / (1024 * 1024)).toFixed(1)} MB`;
|
||||
};
|
||||
|
||||
const Workspace: React.FC = () => {
|
||||
const { id = '1' } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const queryClient = useQueryClient();
|
||||
const { message } = App.useApp();
|
||||
const [uploadingCount, setUploadingCount] = useState(0);
|
||||
const [uploadBatchItems, setUploadBatchItems] = useState<UploadBatchItem[]>([]);
|
||||
const batchCounterRef = useRef<{ success: number; failed: number }>({ success: 0, failed: 0 });
|
||||
const batchActiveRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
uploadBatchItems.forEach((item) => {
|
||||
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
||||
});
|
||||
};
|
||||
}, [uploadBatchItems]);
|
||||
|
||||
const { data: currentCase } = useQuery({ queryKey: ['case', id], queryFn: () => fetchCase(id) });
|
||||
const { data: imagesData } = useQuery({ queryKey: ['images', id], queryFn: () => fetchImages(id) });
|
||||
@@ -206,29 +234,80 @@ const Workspace: React.FC = () => {
|
||||
accept="image/*"
|
||||
showUploadList={false}
|
||||
beforeUpload={(file) => {
|
||||
setUploadingCount((c) => c + 1);
|
||||
message.open({
|
||||
key: 'img-upload',
|
||||
type: 'loading',
|
||||
content: `正在上传截图(队列中 ${uploadingCount + 1} 张)...`,
|
||||
duration: 0,
|
||||
if (!batchActiveRef.current) {
|
||||
batchActiveRef.current = true;
|
||||
batchCounterRef.current = { success: 0, failed: 0 };
|
||||
setUploadBatchItems((prev) => {
|
||||
prev.forEach((item) => {
|
||||
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
||||
});
|
||||
return [];
|
||||
});
|
||||
}
|
||||
|
||||
const localId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
||||
const previewUrl = URL.createObjectURL(file as File);
|
||||
setUploadingCount((c) => {
|
||||
const next = c + 1;
|
||||
message.open({
|
||||
key: 'img-upload',
|
||||
type: 'loading',
|
||||
content: `正在上传截图(队列中 ${next} 张):${file.name}`,
|
||||
duration: 0,
|
||||
});
|
||||
return next;
|
||||
});
|
||||
setUploadBatchItems((prev) => [
|
||||
...prev,
|
||||
{
|
||||
localId,
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
status: 'uploading',
|
||||
previewUrl,
|
||||
},
|
||||
]);
|
||||
uploadImages(id, [file as File])
|
||||
.then(() => {
|
||||
message.success('截图上传成功');
|
||||
.then((uploaded) => {
|
||||
const uploadedImage = uploaded?.[0];
|
||||
setUploadBatchItems((prev) =>
|
||||
prev.map((item) =>
|
||||
item.localId === localId
|
||||
? {
|
||||
...item,
|
||||
status: 'success',
|
||||
uploadedImageId: uploadedImage?.id,
|
||||
}
|
||||
: item,
|
||||
),
|
||||
);
|
||||
batchCounterRef.current.success += 1;
|
||||
})
|
||||
.then(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['images', id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['case', id] });
|
||||
})
|
||||
.catch(() => {
|
||||
message.error('上传失败');
|
||||
setUploadBatchItems((prev) =>
|
||||
prev.map((item) =>
|
||||
item.localId === localId
|
||||
? { ...item, status: 'error' }
|
||||
: item,
|
||||
),
|
||||
);
|
||||
batchCounterRef.current.failed += 1;
|
||||
})
|
||||
.finally(() => {
|
||||
setUploadingCount((c) => {
|
||||
const next = Math.max(0, c - 1);
|
||||
if (next === 0) {
|
||||
batchActiveRef.current = false;
|
||||
message.destroy('img-upload');
|
||||
const summary = {
|
||||
success: batchCounterRef.current.success,
|
||||
failed: batchCounterRef.current.failed,
|
||||
};
|
||||
message.success(`本次上传完成:成功 ${summary.success} 张,失败 ${summary.failed} 张`);
|
||||
} else {
|
||||
message.open({
|
||||
key: 'img-upload',
|
||||
@@ -259,6 +338,75 @@ const Workspace: React.FC = () => {
|
||||
当前有 {uploadingCount} 张截图正在上传,请稍候...
|
||||
</Typography.Text>
|
||||
)}
|
||||
<Card
|
||||
size="small"
|
||||
title="本次上传清单"
|
||||
style={{ marginTop: 12 }}
|
||||
extra={
|
||||
uploadBatchItems.length > 0 ? (
|
||||
<Button
|
||||
size="small"
|
||||
type="link"
|
||||
onClick={() => navigate(`/cases/${id}/screenshots`)}
|
||||
>
|
||||
查看本次上传截图
|
||||
</Button>
|
||||
) : null
|
||||
}
|
||||
>
|
||||
{uploadBatchItems.length === 0 ? (
|
||||
<Empty description="暂无上传记录,请先拖拽截图上传" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
) : (
|
||||
<>
|
||||
<Typography.Text type="secondary">
|
||||
本次上传:共 {uploadBatchItems.length} 张,
|
||||
成功 {uploadBatchItems.filter((x) => x.status === 'success').length} 张,
|
||||
失败 {uploadBatchItems.filter((x) => x.status === 'error').length} 张,
|
||||
上传中 {uploadBatchItems.filter((x) => x.status === 'uploading').length} 张
|
||||
</Typography.Text>
|
||||
<Space direction="vertical" style={{ width: '100%', marginTop: 10 }} size={8}>
|
||||
{uploadBatchItems.map((item, index) => (
|
||||
<Row key={item.localId} justify="space-between" align="middle">
|
||||
<Col span={18}>
|
||||
<Space size={8}>
|
||||
<Typography.Text type="secondary">#{index + 1}</Typography.Text>
|
||||
<div
|
||||
style={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 4,
|
||||
overflow: 'hidden',
|
||||
border: '1px solid #f0f0f0',
|
||||
background: '#fafafa',
|
||||
}}
|
||||
>
|
||||
{item.previewUrl ? (
|
||||
<img
|
||||
src={item.previewUrl}
|
||||
alt={item.fileName}
|
||||
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<Typography.Text ellipsis style={{ maxWidth: 260 }}>
|
||||
{item.fileName}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
{formatFileSize(item.fileSize)}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col>
|
||||
{item.status === 'uploading' && <Tag color="processing">上传中</Tag>}
|
||||
{item.status === 'success' && <Tag color="success">上传成功</Tag>}
|
||||
{item.status === 'error' && <Tag color="error">上传失败</Tag>}
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
</Space>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
</Card>
|
||||
|
||||
<Card title="处理进度">
|
||||
|
||||
Reference in New Issue
Block a user