Compress site images before upload
This commit is contained in:
@@ -22,6 +22,62 @@ const SERVICE_TYPE_LABEL: Record<AftersalesServiceType, string> = {
|
||||
maintenance: '售后维保',
|
||||
};
|
||||
|
||||
const SITE_IMAGE_MAX_EDGE = 1600;
|
||||
const SITE_IMAGE_QUALITY = 0.78;
|
||||
|
||||
async function compressSiteImage(file: File): Promise<File> {
|
||||
if (!file.type.startsWith('image/') || file.type === 'image/gif') {
|
||||
return file;
|
||||
}
|
||||
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
try {
|
||||
const img = await loadImage(imageUrl);
|
||||
const scale = Math.min(1, SITE_IMAGE_MAX_EDGE / Math.max(img.width, img.height));
|
||||
const width = Math.max(1, Math.round(img.width * scale));
|
||||
const height = Math.max(1, Math.round(img.height * scale));
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return file;
|
||||
|
||||
ctx.drawImage(img, 0, 0, width, height);
|
||||
const blob = await canvasToBlob(canvas, 'image/jpeg', SITE_IMAGE_QUALITY);
|
||||
if (!blob || blob.size >= file.size) {
|
||||
return file;
|
||||
}
|
||||
|
||||
const name = file.name.replace(/\.[^.]+$/, '') || 'site-image';
|
||||
return new File([blob], `${name}.jpg`, {
|
||||
type: 'image/jpeg',
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
} catch {
|
||||
return file;
|
||||
} finally {
|
||||
URL.revokeObjectURL(imageUrl);
|
||||
}
|
||||
}
|
||||
|
||||
function loadImage(src: string): Promise<HTMLImageElement> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(img);
|
||||
img.onerror = reject;
|
||||
img.src = src;
|
||||
});
|
||||
}
|
||||
|
||||
function canvasToBlob(
|
||||
canvas: HTMLCanvasElement,
|
||||
type: string,
|
||||
quality: number,
|
||||
): Promise<Blob | null> {
|
||||
return new Promise((resolve) => canvas.toBlob(resolve, type, quality));
|
||||
}
|
||||
|
||||
function AftersalesConfirmPage() {
|
||||
const { serialNumber = '' } = useParams<{ serialNumber: string }>();
|
||||
const [order, setOrder] = useState<AftersalesPublicView | null>(null);
|
||||
@@ -125,7 +181,8 @@ function AftersalesConfirmPage() {
|
||||
if (!files || files.length === 0) return;
|
||||
setUploadingImages(true);
|
||||
try {
|
||||
const images = await aftersalesApi.uploadSiteImages(serialNumber, Array.from(files));
|
||||
const compressedFiles = await Promise.all(Array.from(files).map(compressSiteImage));
|
||||
const images = await aftersalesApi.uploadSiteImages(serialNumber, compressedFiles);
|
||||
setOrder((prev) => (prev ? { ...prev, siteImages: images } : prev));
|
||||
message.success('现场图片上传成功');
|
||||
} catch (err: any) {
|
||||
@@ -298,7 +355,7 @@ function AftersalesConfirmPage() {
|
||||
<label className="aftersales-upload-trigger">
|
||||
<UploadOutlined />
|
||||
<span>{uploadingImages ? '上传中...' : '上传现场图片'}</span>
|
||||
<small>最多 6 张,单张不超过 5MB</small>
|
||||
<small>最多 6 张,上传前自动压缩</small>
|
||||
<input
|
||||
type="file"
|
||||
accept="image/jpeg,image/png,image/webp,image/heic,image/heif"
|
||||
|
||||
Reference in New Issue
Block a user