You've already forked Nano-Banana-AI-Image-Editor
修复 文件无法上传的问题;
修复 生成结果无法预览的问题;
This commit is contained in:
@@ -32,51 +32,124 @@ export const ImageCanvas: React.FC = () => {
|
||||
const [currentStroke, setCurrentStroke] = useState<number[]>([]);
|
||||
|
||||
const handleZoom = useCallback((delta: number) => {
|
||||
const newZoom = Math.max(0.1, Math.min(3, canvasZoom + delta));
|
||||
setCanvasZoom(newZoom);
|
||||
}, [canvasZoom, setCanvasZoom]);
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
const currentZoom = stage.scaleX();
|
||||
const newZoom = Math.max(0.1, Math.min(3, currentZoom + delta));
|
||||
|
||||
// 先更新React状态以确保Konva Image组件使用正确的缩放值
|
||||
setCanvasZoom(newZoom);
|
||||
|
||||
// 使用setTimeout确保DOM已更新后再设置Stage
|
||||
setTimeout(() => {
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
// 直接通过stageRef控制Stage
|
||||
stage.scale({ x: newZoom, y: newZoom });
|
||||
stage.batchDraw();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 加载图像并在 canvasImage 变化时自动适应
|
||||
// 加载图像
|
||||
useEffect(() => {
|
||||
let img: HTMLImageElement | null = null;
|
||||
|
||||
if (canvasImage) {
|
||||
console.log('开始加载图像,URL:', canvasImage);
|
||||
|
||||
img = new window.Image();
|
||||
let isCancelled = false;
|
||||
|
||||
img.onload = () => {
|
||||
// 检查是否已取消
|
||||
if (isCancelled) {
|
||||
console.log('图像加载被取消');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('图像加载成功,尺寸:', img.width, 'x', img.height);
|
||||
setImage(img);
|
||||
|
||||
// 每次有新图像时都自动适应画布,而不仅仅是在初始状态下
|
||||
// 通过比较图像数据来判断是否是新图像
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const padding = isMobile ? 0.9 : 0.8; // 在移动设备上使用更多屏幕空间
|
||||
|
||||
const scaleX = (stageSize.width * padding) / img.width;
|
||||
const scaleY = (stageSize.height * padding) / img.height;
|
||||
|
||||
const maxZoom = isMobile ? 0.3 : 0.8;
|
||||
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
|
||||
|
||||
setCanvasZoom(optimalZoom);
|
||||
|
||||
// 居中图像
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
// 只在图像首次加载时自动适应画布
|
||||
if (!isCancelled && img) {
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const padding = isMobile ? 0.9 : 0.8;
|
||||
|
||||
const scaleX = (stageSize.width * padding) / img.width;
|
||||
const scaleY = (stageSize.height * padding) / img.height;
|
||||
|
||||
const maxZoom = isMobile ? 0.3 : 0.8;
|
||||
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
|
||||
|
||||
// 立即更新React状态以确保Konva Image组件使用正确的缩放值
|
||||
setCanvasZoom(optimalZoom);
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
|
||||
// 使用setTimeout确保DOM已更新后再设置Stage
|
||||
setTimeout(() => {
|
||||
if (!isCancelled && img) {
|
||||
// 直接设置缩放,但保持Stage居中
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
stage.scale({ x: optimalZoom, y: optimalZoom });
|
||||
// 重置Stage位置以确保居中
|
||||
stage.position({ x: 0, y: 0 });
|
||||
stage.batchDraw();
|
||||
}
|
||||
|
||||
console.log('图像自动适应画布完成,缩放:', optimalZoom);
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
img.onerror = (error) => {
|
||||
if (!isCancelled) {
|
||||
console.error('图像加载失败');
|
||||
console.error('图像加载失败:', error);
|
||||
console.error('图像URL:', canvasImage);
|
||||
|
||||
// 检查是否是Blob URL
|
||||
if (canvasImage.startsWith('blob:')) {
|
||||
console.log('正在检查Blob URL是否有效...');
|
||||
|
||||
// 检查Blob URL是否仍然有效
|
||||
fetch(canvasImage)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
console.error('Blob URL无法访问:', response.status, response.statusText);
|
||||
} else {
|
||||
console.log('Blob URL可以访问');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('检查Blob URL时出错:', err);
|
||||
// 尝试从AppStore重新获取Blob
|
||||
import('../store/useAppStore').then((module) => {
|
||||
const useAppStore = module.useAppStore;
|
||||
const blob = useAppStore.getState().getBlob(canvasImage);
|
||||
if (blob) {
|
||||
console.log('从AppStore找到Blob,尝试重新创建URL...');
|
||||
// 重新创建Blob URL并重试加载
|
||||
const newUrl = URL.createObjectURL(blob);
|
||||
console.log('创建新的Blob URL:', newUrl);
|
||||
// 更新canvasImage为新的URL
|
||||
useAppStore.getState().setCanvasImage(newUrl);
|
||||
} else {
|
||||
console.error('AppStore中未找到Blob');
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('导入AppStore时出错:', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
img.src = canvasImage;
|
||||
} else {
|
||||
console.log('没有图像需要加载');
|
||||
// 当没有图像时,清理之前的图像对象
|
||||
if (image) {
|
||||
// 清理图像对象以释放内存
|
||||
@@ -89,6 +162,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
|
||||
// 清理函数
|
||||
return () => {
|
||||
console.log('清理图像加载资源');
|
||||
// 取消图像加载
|
||||
if (img) {
|
||||
img.onload = null;
|
||||
@@ -104,7 +178,7 @@ export const ImageCanvas: React.FC = () => {
|
||||
image.src = '';
|
||||
}
|
||||
};
|
||||
}, [canvasImage, stageSize, setCanvasZoom, setCanvasPan, image]);
|
||||
}, [canvasImage]); // 只依赖canvasImage,避免其他依赖引起循环
|
||||
|
||||
// 处理舞台大小调整
|
||||
useEffect(() => {
|
||||
@@ -226,8 +300,20 @@ export const ImageCanvas: React.FC = () => {
|
||||
const maxZoom = isMobile ? 0.3 : 0.8;
|
||||
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
|
||||
|
||||
// 同时更新React状态以确保Konva Image组件使用正确的缩放值
|
||||
setCanvasZoom(optimalZoom);
|
||||
setCanvasPan({ x: 0, y: 0 });
|
||||
|
||||
// 使用setTimeout确保DOM已更新后再设置Stage
|
||||
setTimeout(() => {
|
||||
// 直接通过stageRef控制Stage
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
stage.scale({ x: optimalZoom, y: optimalZoom });
|
||||
stage.position({ x: 0, y: 0 });
|
||||
stage.batchDraw();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -284,16 +370,17 @@ export const ImageCanvas: React.FC = () => {
|
||||
ref={stageRef}
|
||||
width={stageSize.width}
|
||||
height={stageSize.height}
|
||||
scaleX={canvasZoom}
|
||||
scaleY={canvasZoom}
|
||||
x={canvasPan.x * canvasZoom}
|
||||
y={canvasPan.y * canvasZoom}
|
||||
draggable={selectedTool !== 'mask'}
|
||||
onDragEnd={(e) => {
|
||||
setCanvasPan({
|
||||
x: e.target.x() / canvasZoom,
|
||||
y: e.target.y() / canvasZoom
|
||||
});
|
||||
// 通过stageRef直接获取和设置位置
|
||||
const stage = stageRef.current;
|
||||
if (stage) {
|
||||
const scale = stage.scaleX();
|
||||
setCanvasPan({
|
||||
x: stage.x() / scale,
|
||||
y: stage.y() / scale
|
||||
});
|
||||
}
|
||||
}}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMousemove={handleMouseMove}
|
||||
@@ -309,6 +396,9 @@ export const ImageCanvas: React.FC = () => {
|
||||
image={image}
|
||||
x={(stageSize.width / canvasZoom - image.width) / 2}
|
||||
y={(stageSize.height / canvasZoom - image.height) / 2}
|
||||
onRender={() => {
|
||||
console.log('KonvaImage组件渲染完成');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -79,15 +79,41 @@ function getImageHash(imageData: string): string {
|
||||
* @returns Blob对象
|
||||
*/
|
||||
async function getBlobFromUrl(blobUrl: string): Promise<Blob> {
|
||||
// 从AppStore获取Blob
|
||||
const { getBlob } = await import('../store/useAppStore');
|
||||
const blob = getBlob().getBlob(blobUrl);
|
||||
|
||||
if (!blob) {
|
||||
throw new Error('无法从Blob URL获取图像数据');
|
||||
try {
|
||||
// 从AppStore获取Blob
|
||||
const { useAppStore } = await import('../store/useAppStore');
|
||||
const blob = useAppStore.getState().getBlob(blobUrl);
|
||||
|
||||
if (!blob) {
|
||||
// 如果AppStore中没有找到Blob,尝试从URL获取
|
||||
console.warn('无法从AppStore获取Blob,尝试从URL获取:', blobUrl);
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取Blob失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return await response.blob();
|
||||
} catch (error) {
|
||||
console.error('从URL获取Blob失败:', error);
|
||||
throw new Error('无法从Blob URL获取图像数据');
|
||||
}
|
||||
}
|
||||
|
||||
return blob;
|
||||
} catch (error) {
|
||||
console.error('从AppStore获取Blob时出错:', error);
|
||||
// 如果导入AppStore失败,直接尝试从URL获取
|
||||
try {
|
||||
const response = await fetch(blobUrl);
|
||||
if (!response.ok) {
|
||||
throw new Error(`获取Blob失败: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
return await response.blob();
|
||||
} catch (fetchError) {
|
||||
console.error('从URL获取Blob失败:', fetchError);
|
||||
throw new Error('无法从Blob URL获取图像数据');
|
||||
}
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -148,16 +174,25 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string,
|
||||
// 发送POST请求
|
||||
const response = await fetch(UPLOAD_URL, {
|
||||
method: 'POST',
|
||||
headers: { accessToken },
|
||||
headers: {
|
||||
'accessToken': accessToken,
|
||||
// 添加其他可能需要的头部
|
||||
},
|
||||
body: formData,
|
||||
});
|
||||
|
||||
// 记录响应状态以帮助调试
|
||||
console.log('上传响应状态:', response.status, response.statusText);
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('上传失败响应内容:', errorText);
|
||||
throw new Error(`上传失败: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
console.log('上传响应结果:', result);
|
||||
|
||||
// 根据返回格式处理结果: {"code": 200,"msg": "上传成功","data": "9ecbaa0a0.jpg"}
|
||||
if (result.code === 200) {
|
||||
// 使用环境变量中的VITE_UPLOAD_ASSET_URL作为前缀
|
||||
@@ -185,7 +220,7 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传图像时出错:', error);
|
||||
const errorResult = { success: false, url: undefined, error: error instanceof Error ? error.message : String(error) };
|
||||
const errorResult = { success: false, error: error instanceof Error ? error.message : String(error) };
|
||||
|
||||
// 清理过期缓存
|
||||
cleanupExpiredCache();
|
||||
|
||||
@@ -215,7 +215,8 @@ export const useAppStore = create<AppState>()(
|
||||
|
||||
// 从存储中获取Blob
|
||||
getBlob: (url: string) => {
|
||||
return get().blobStore.get(url);
|
||||
const state = get();
|
||||
return state.blobStore.get(url);
|
||||
},
|
||||
|
||||
addGeneration: (generation) => {
|
||||
@@ -617,8 +618,6 @@ export const useAppStore = create<AppState>()(
|
||||
const state = get();
|
||||
const now = Date.now();
|
||||
|
||||
// 这里我们简单地清理所有Blob,因为在实际应用中很难跟踪哪些Blob正在使用
|
||||
// 在生产环境中,您可能需要更复杂的跟踪机制
|
||||
state.blobStore.forEach((blob, url) => {
|
||||
// 检查URL是否仍在使用中
|
||||
const isUsedInProject = state.currentProject && (
|
||||
|
||||
Reference in New Issue
Block a user