This commit is contained in:
2026-07-02 19:42:47 +08:00
parent 82db946631
commit 545c4ffc55
5 changed files with 104 additions and 1030 deletions
+101 -25
View File
@@ -992,6 +992,58 @@ const AIChatPage: React.FC = () => {
});
};
const getImageDimensions = (file: File): Promise<{ width: number; height: number }> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
window.URL.revokeObjectURL(img.src);
resolve({ width: img.naturalWidth, height: img.naturalHeight });
};
img.onerror = () => {
window.URL.revokeObjectURL(img.src);
reject(new Error('无法读取图片尺寸'));
};
img.src = URL.createObjectURL(file);
});
};
const getVideoDimensions = (file: File): Promise<{ width: number; height: number; duration: number }> => {
return new Promise((resolve, reject) => {
const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = () => {
window.URL.revokeObjectURL(video.src);
resolve({ width: video.videoWidth, height: video.videoHeight, duration: video.duration });
};
video.onerror = () => {
window.URL.revokeObjectURL(video.src);
reject(new Error('无法读取视频尺寸'));
};
video.src = URL.createObjectURL(file);
});
};
// 图片尺寸校验:宽高比 (0.4, 2.5),宽高长度 (300, 6000)
const validateImageDimensions = (width: number, height: number): string | null => {
if (width < 300 || width > 6000) return `图片宽度需在 300~6000px 之间,当前为 ${width}px`;
if (height < 300 || height > 6000) return `图片高度需在 300~6000px 之间,当前为 ${height}px`;
const ratio = width / height;
if (ratio < 0.4 || ratio > 2.5) return `图片宽高比需在 0.4~2.5 之间,当前为 ${ratio.toFixed(2)}`;
return null;
};
// 视频尺寸校验:宽高比 [0.4, 2.5],宽高 [300, 6000],总像素 [409600, 8295044]
const validateVideoDimensions = (width: number, height: number): string | null => {
if (width < 300 || width > 6000) return `视频宽度需在 300~6000px 之间,当前为 ${width}px`;
if (height < 300 || height > 6000) return `视频高度需在 300~6000px 之间,当前为 ${height}px`;
const ratio = width / height;
if (ratio < 0.4 || ratio > 2.5) return `视频宽高比需在 0.4~2.5 之间,当前为 ${ratio.toFixed(2)}`;
const totalPixels = width * height;
if (totalPixels < 409600) return `视频总像素数过小(${width}×${height}=${totalPixels}),需 ≥ 640×640=409600`;
if (totalPixels > 8295044) return `视频总像素数过大(${width}×${height}=${totalPixels}),需 ≤ 3326×2494=8295044`;
return null;
};
const handleUpload = async (file: File, frameTarget?: 'first' | 'last' | File[]) => {
const isImage = file.type.startsWith('image/');
const isVideo = file.type.startsWith('video/');
@@ -1059,6 +1111,21 @@ const AIChatPage: React.FC = () => {
return false;
}
// 图片尺寸校验
if (isImage && mediaType !== 'video') {
try {
const { width, height } = await getImageDimensions(file);
const error = validateImageDimensions(width, height);
if (error) {
message.error(error);
return false;
}
} catch {
message.error('无法读取图片尺寸,请检查文件是否损坏');
return false;
}
}
const imageCount = currentMedia.filter((m) => m.type === 'image').length;
if (isImage && imageCount >= maxImage) {
message.error(`该引擎最多上传${maxImage}张图片`);
@@ -1086,8 +1153,15 @@ const AIChatPage: React.FC = () => {
message.error(`所有视频素材总时长不能超过 15 秒,当前 ${(existingVideoDuration + videoDuration).toFixed(1)}`);
return false;
}
// 视频尺寸校验
const { width, height } = await getVideoDimensions(file);
const error = validateVideoDimensions(width, height);
if (error) {
message.error(error);
return false;
}
} catch {
message.error('无法获取视频时长');
message.error('无法获取视频信息,请检查文件是否损坏');
return false;
}
}
@@ -2016,7 +2090,7 @@ const AIChatPage: React.FC = () => {
}}
>
{/* 上方输入布局 */}
<div style={{ display: 'flex', gap: isFirstLastFrameComposer ? 18 : 18, alignItems: 'flex-start', marginBottom: 14 }}>
<div style={{ display: 'flex', gap: isFirstLastFrameComposer ? 18 : 18, alignItems: 'flex-end', marginBottom: 14 }}>
{/* 左侧附件区域 */}
<div style={{ width: isFirstLastFrameComposer ? 220 : 128, minWidth: isFirstLastFrameComposer ? 220 : 128, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'flex-start', paddingTop: 2 }}>
{/* 首尾帧模式 */}
@@ -2173,29 +2247,31 @@ const AIChatPage: React.FC = () => {
style={{ width: 50, height: 54, objectFit: 'cover', borderRadius: 11, cursor: 'pointer', border: '2px solid rgba(255,255,255,0.98)', boxShadow: '0 8px 18px rgba(31, 41, 55, 0.14)' }}
/>
)}
<button
onClick={() => handleRemoveMedia(idx)}
style={{
position: 'absolute',
top: -5,
right: -5,
width: 18,
height: 18,
border: '1px solid rgba(255,255,255,0.9)',
background: '#A45B5B',
borderRadius: 50,
cursor: 'pointer',
color: '#fff',
fontSize: 9,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 10px rgba(164, 91, 91, 0.28)',
zIndex: 10,
}}
>
<DeleteOutlined style={{ fontSize: 9 }} />
</button>
{mediaStackHovered && (
<button
onClick={() => handleRemoveMedia(idx)}
style={{
position: 'absolute',
top: -5,
right: -5,
width: 18,
height: 18,
border: '1px solid rgba(255,255,255,0.9)',
background: '#A45B5B',
borderRadius: 50,
cursor: 'pointer',
color: '#fff',
fontSize: 9,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
boxShadow: '0 4px 10px rgba(164, 91, 91, 0.28)',
zIndex: 10,
}}
>
<DeleteOutlined style={{ fontSize: 9 }} />
</button>
)}
</div>
</div>
);