阶段性提交

This commit is contained in:
2025-09-21 14:43:59 +08:00
parent af2058f752
commit 690a530031
20 changed files with 1577 additions and 781 deletions

View File

@@ -2,25 +2,19 @@ import React, { useRef, useEffect, useState, useCallback } from 'react';
import { Stage, Layer, Image as KonvaImage, Line } from 'react-konva';
import { useAppStore } from '../store/useAppStore';
import { Button } from './ui/Button';
import { ZoomIn, ZoomOut, RotateCcw, Download, Eye, EyeOff, Eraser } from 'lucide-react';
import { cn } from '../utils/cn';
import { ZoomIn, ZoomOut, RotateCcw, Download } from 'lucide-react';
export const ImageCanvas: React.FC = () => {
const {
canvasImage,
canvasZoom,
setCanvasZoom,
canvasPan,
setCanvasPan,
brushStrokes,
addBrushStroke,
clearBrushStrokes,
showMasks,
setShowMasks,
selectedTool,
isGenerating,
brushSize,
setBrushSize,
showHistory,
showPromptPanel
} = useAppStore();
@@ -50,7 +44,7 @@ export const ImageCanvas: React.FC = () => {
}
}, 0);
}
}, []);
}, [setCanvasZoom]);
// 加载图像
useEffect(() => {
@@ -60,7 +54,7 @@ export const ImageCanvas: React.FC = () => {
console.log('开始加载图像URL:', canvasImage);
img = new window.Image();
let isCancelled = false;
const isCancelled = false;
img.onload = () => {
// 检查是否已取消
@@ -195,7 +189,7 @@ export const ImageCanvas: React.FC = () => {
image.src = '';
}
};
}, [canvasImage]); // 只依赖canvasImage避免其他依赖引起循环
}, [canvasImage, image, setCanvasZoom, stageSize.height, stageSize.width]); // 添加所有依赖项
// 处理舞台大小调整
useEffect(() => {
@@ -212,7 +206,7 @@ export const ImageCanvas: React.FC = () => {
updateSize();
window.addEventListener('resize', updateSize);
return () => window.removeEventListener('resize', updateSize);
}, []);
}, [showPromptPanel, showHistory]);
// 监听面板状态变化以调整画布大小
useEffect(() => {
@@ -243,14 +237,13 @@ export const ImageCanvas: React.FC = () => {
container.addEventListener('wheel', handleWheel, { passive: false });
return () => container.removeEventListener('wheel', handleWheel);
}, [canvasZoom]);
}, [canvasZoom, handleZoom]);
const handleMouseDown = (e: any) => {
const handleMouseDown = (e: Konva.KonvaEventObject<MouseEvent>) => {
if (selectedTool !== 'mask' || !image) return;
setIsDrawing(true);
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
// 使用 Konva 的 getRelativePointerPosition 获取准确坐标
const relativePos = stage.getRelativePointerPosition();
@@ -269,11 +262,10 @@ export const ImageCanvas: React.FC = () => {
}
};
const handleMouseMove = (e: any) => {
const handleMouseMove = (e: Konva.KonvaEventObject<MouseEvent>) => {
if (!isDrawing || selectedTool !== 'mask' || !image) return;
const stage = e.target.getStage();
const pos = stage.getPointerPosition();
// 使用 Konva 的 getRelativePointerPosition 获取准确坐标
const relativePos = stage.getRelativePointerPosition();
@@ -351,17 +343,17 @@ export const ImageCanvas: React.FC = () => {
// 下载第一个上传结果(通常是生成的图像)
const uploadResult = selectedRecord.uploadResults[0];
if (uploadResult.success && uploadResult.url) {
// 使用async IIFE处理异步操作
(async () => {
try {
// 首先尝试使用fetch获取图像数据
const response = await fetch(uploadResult.url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
// 创建下载链接
// 使用fetch获取图像数据并创建Blob URL以确保正确下载
// 添加更多缓存控制头以绕过CDN缓存
fetch(uploadResult.url, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
})
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
@@ -369,64 +361,21 @@ export const ImageCanvas: React.FC = () => {
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
console.log('上传后的图像下载成功:', uploadResult.url);
} catch (error) {
console.error('使用fetch下载上传后的图像时出错:', error);
// 如果fetch失败可能是跨域问题使用Canvas方案
try {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 设置跨域属性
img.onload = () => {
try {
// 创建canvas并绘制图像
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 将canvas转换为blob并下载
canvas.toBlob((blob) => {
if (blob) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `nano-banana-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
console.log('使用Canvas方案下载成功');
} else {
console.error('Canvas转换为blob失败');
}
}, 'image/png');
} catch (canvasError) {
console.error('Canvas处理失败:', canvasError);
}
};
img.onerror = (imgError) => {
console.error('图像加载失败:', imgError);
console.log('下载失败,未执行回退方案');
};
img.src = uploadResult.url;
} catch (canvasError) {
console.error('Canvas方案也失败了:', canvasError);
console.log('下载失败,未执行回退方案');
}
}
})();
URL.revokeObjectURL(url);
})
.catch(error => {
console.error('下载图像失败:', error);
// 如果fetch失败回退到直接使用a标签
const link = document.createElement('a');
link.href = uploadResult.url;
link.download = `nano-banana-${Date.now()}.png`;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
// 立即返回,让异步操作在后台进行
// 立即返回
return;
}
}
@@ -463,14 +412,17 @@ export const ImageCanvas: React.FC = () => {
document.body.removeChild(link);
} else if (canvasImage.startsWith('blob:')) {
// Blob URL格式
// 使用async IIFE处理异步操作
(async () => {
try {
const response = await fetch(canvasImage);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
// 使用fetch获取图像数据并创建Blob URL以确保正确下载
// 添加更多缓存控制头以绕过CDN缓存
fetch(canvasImage, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
})
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
@@ -478,68 +430,32 @@ export const ImageCanvas: React.FC = () => {
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
} catch (error) {
console.error('下载Blob图像时出错:', error);
// 如果fetch失败可能是跨域问题使用Canvas方案
try {
const img = new Image();
img.onload = () => {
try {
// 创建canvas并绘制图像
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 将canvas转换为blob并下载
canvas.toBlob((blob) => {
if (blob) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `nano-banana-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
console.log('使用Canvas方案下载成功');
} else {
console.error('Canvas转换为blob失败');
}
}, 'image/png');
} catch (canvasError) {
console.error('Canvas处理失败:', canvasError);
}
};
img.onerror = (imgError) => {
console.error('图像加载失败:', imgError);
console.log('下载失败,未执行回退方案');
};
img.src = canvasImage;
} catch (canvasError) {
console.error('Canvas方案也失败了:', canvasError);
console.log('下载失败,未执行回退方案');
}
}
})();
URL.revokeObjectURL(url);
})
.catch(error => {
console.error('下载图像失败:', error);
// 如果fetch失败回退到直接使用a标签
const link = document.createElement('a');
link.href = canvasImage;
link.download = `nano-banana-${Date.now()}.png`;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
} else {
// 普通URL格式
// 使用async IIFE处理异步操作
(async () => {
try {
const response = await fetch(canvasImage);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
// 使用fetch获取图像数据并创建Blob URL以确保正确下载
// 添加更多缓存控制头以绕过CDN缓存
fetch(canvasImage, {
headers: {
'Cache-Control': 'no-cache, no-store, must-revalidate',
'Pragma': 'no-cache',
'Expires': '0'
}
})
.then(response => response.blob())
.then(blob => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
@@ -547,59 +463,19 @@ export const ImageCanvas: React.FC = () => {
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
} catch (error) {
console.error('下载图像时出错:', error);
// 如果fetch失败可能是跨域问题使用Canvas方案
try {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 设置跨域属性
img.onload = () => {
try {
// 创建canvas并绘制图像
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
// 将canvas转换为blob并下载
canvas.toBlob((blob) => {
if (blob) {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `nano-banana-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
// 清理创建的URL
setTimeout(() => URL.revokeObjectURL(url), 100);
console.log('使用Canvas方案下载成功');
} else {
console.error('Canvas转换为blob失败');
}
}, 'image/png');
} catch (canvasError) {
console.error('Canvas处理失败:', canvasError);
}
};
img.onerror = (imgError) => {
console.error('图像加载失败:', imgError);
console.log('下载失败,未执行回退方案');
};
img.src = canvasImage;
} catch (canvasError) {
console.error('Canvas方案也失败了:', canvasError);
console.log('下载失败,未执行回退方案');
}
}
})();
URL.revokeObjectURL(url);
})
.catch(error => {
console.error('下载图像失败:', error);
// 如果fetch失败回退到直接使用a标签
const link = document.createElement('a');
link.href = canvasImage;
link.download = `nano-banana-${Date.now()}.png`;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
}
}
@@ -647,7 +523,7 @@ export const ImageCanvas: React.FC = () => {
width={stageSize.width}
height={stageSize.height}
draggable={selectedTool !== 'mask'}
onDragEnd={(e) => {
onDragEnd={() => {
// 通过stageRef直接获取和设置位置
const stage = stageRef.current;
if (stage) {