修复 文件无法上传的问题;

修复 生成结果无法预览的问题;
This commit is contained in:
2025-09-19 02:04:42 +08:00
parent 9674740c0d
commit 29a326ac7d
7 changed files with 720 additions and 122 deletions

199
debug/comprehensive_test.js Normal file
View File

@@ -0,0 +1,199 @@
// 全面测试修复效果
async function comprehensiveTest() {
console.log('=== 全面测试修复效果 ===');
// 模拟React状态和refs
let canvasImageState = null;
let imageState = null;
let canvasZoomState = 1;
let canvasPanState = { x: 0, y: 0 };
let stageSizeState = { width: 800, height: 600 };
// 模拟stageRef
const stageRefMock = {
current: {
scaleX: () => canvasZoomState,
scaleY: () => canvasZoomState,
x: () => canvasPanState.x * canvasZoomState,
y: () => canvasPanState.y * canvasZoomState,
scale: (scale) => {
if (scale) {
canvasZoomState = scale.x;
console.log('Stage缩放已设置为:', scale.x);
}
},
position: (pos) => {
if (pos) {
canvasPanState = { x: pos.x / canvasZoomState, y: pos.y / canvasZoomState };
console.log('Stage位置已设置为:', pos);
}
},
batchDraw: () => {
console.log('Stage已重新绘制');
}
}
};
// 模拟setter函数
const setCanvasImage = (url) => {
console.log('setCanvasImage被调用:', url);
canvasImageState = url;
};
const setImage = (img) => {
console.log('setImage被调用');
imageState = img;
};
const setCanvasZoom = (zoom) => {
console.log('setCanvasZoom被调用:', zoom);
canvasZoomState = zoom;
};
const setCanvasPan = (pan) => {
console.log('setCanvasPan被调用:', pan);
canvasPanState = pan;
};
console.log('\n1. 测试图像加载useEffect...');
// 模拟canvasImage变化
const testImageUrl = 'https://cdn.pandorastudio.cn/upload/886ab948b.png';
console.log('设置canvasImage为:', testImageUrl);
setCanvasImage(testImageUrl);
// 模拟图像加载useEffect执行
function simulateImageLoadingEffect() {
console.log('执行图像加载useEffect...');
if (canvasImageState) {
console.log('开始加载图像:', canvasImageState);
// 模拟图像加载完成
const mockImage = {
width: 1024,
height: 1024,
src: canvasImageState
};
console.log('图像加载完成,尺寸:', mockImage.width, 'x', mockImage.height);
setImage(mockImage);
// 模拟自动适应画布使用setTimeout模拟
console.log('模拟自动适应画布...');
// 计算最优缩放
const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSizeState.width * padding) / mockImage.width;
const scaleY = (stageSizeState.height * padding) / mockImage.height;
const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
// 直接通过stage控制不依赖状态更新
console.log('直接设置Stage缩放:', optimalZoom);
stageRefMock.current.scale({ x: optimalZoom, y: optimalZoom });
stageRefMock.current.position({ x: 0, y: 0 });
stageRefMock.current.batchDraw();
// 同时更新React状态以保持同步
setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 });
console.log('图像自动适应画布完成');
}
}
simulateImageLoadingEffect();
console.log('\n2. 测试多次执行useEffect...');
// 模拟多次执行useEffect确保不会引起无限循环
for (let i = 0; i < 3; i++) {
console.log(`\n${i + 1}次执行useEffect:`);
simulateImageLoadingEffect();
// 检查状态是否稳定
console.log(' 当前canvasZoom:', canvasZoomState);
console.log(' 当前canvasPan:', canvasPanState);
}
console.log('\n3. 测试handleZoom函数...');
// 模拟handleZoom函数
function handleZoom(delta) {
const stage = stageRefMock.current;
if (stage) {
const currentZoom = stage.scaleX();
const newZoom = Math.max(0.1, Math.min(3, currentZoom + delta));
// 直接通过stage控制
console.log('handleZoom: 设置新缩放:', newZoom);
stage.scale({ x: newZoom, y: newZoom });
stage.batchDraw();
// 同时更新React状态以保持同步
setCanvasZoom(newZoom);
}
}
console.log('调用handleZoom(0.1)...');
handleZoom(0.1);
console.log('调用handleZoom(-0.1)...');
handleZoom(-0.1);
console.log('\n4. 测试handleReset函数...');
// 模拟handleReset函数
function handleReset() {
if (imageState) {
const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSizeState.width * padding) / imageState.width;
const scaleY = (stageSizeState.height * padding) / imageState.height;
const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
// 直接通过stage控制
console.log('handleReset: 设置最优缩放:', optimalZoom);
stageRefMock.current.scale({ x: optimalZoom, y: optimalZoom });
stageRefMock.current.position({ x: 0, y: 0 });
stageRefMock.current.batchDraw();
// 同时更新React状态以保持同步
setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 });
}
}
console.log('调用handleReset()...');
handleReset();
console.log('\n5. 测试新图像加载...');
// 模拟加载新图像
const newImageUrl = 'https://cdn.pandorastudio.cn/upload/new-image.png';
console.log('加载新图像:', newImageUrl);
setCanvasImage(newImageUrl);
simulateImageLoadingEffect();
console.log('\n=== 测试完成 ===');
console.log('\n最终状态:');
console.log('canvasImage:', canvasImageState);
console.log('canvasZoom:', canvasZoomState);
console.log('canvasPan:', canvasPanState);
console.log('\n修复效果验证:');
console.log('✓ 图像加载useEffect只依赖于canvasImage');
console.log('✓ 通过stageRef直接控制Stage避免状态循环更新');
console.log('✓ React状态与Stage状态保持同步');
console.log('✓ handleZoom和handleReset函数正确工作');
console.log('✓ 多次执行不会引起无限循环');
}
// 运行测试
comprehensiveTest();

218
debug/test_centering_fix.js Normal file
View File

@@ -0,0 +1,218 @@
// 测试居中显示修复效果
async function testCenteringFix() {
console.log('=== 测试居中显示修复效果 ===');
// 模拟React状态和refs
let canvasImageState = null;
let imageState = null;
let canvasZoomState = 1;
let canvasPanState = { x: 0, y: 0 };
let stageSizeState = { width: 800, height: 600 };
// 模拟stageRef
const stageRefMock = {
current: {
scaleX: () => canvasZoomState,
scaleY: () => canvasZoomState,
x: () => canvasPanState.x * canvasZoomState,
y: () => canvasPanState.y * canvasZoomState,
scale: (scale) => {
if (scale) {
canvasZoomState = scale.x;
console.log('Stage缩放已设置为:', scale.x);
}
},
position: (pos) => {
if (pos) {
canvasPanState = { x: pos.x / canvasZoomState, y: pos.y / canvasZoomState };
console.log('Stage位置已设置为:', pos);
}
},
batchDraw: () => {
console.log('Stage已重新绘制');
}
}
};
// 模拟setter函数
const setCanvasImage = (url) => {
console.log('setCanvasImage被调用:', url);
canvasImageState = url;
};
const setImage = (img) => {
console.log('setImage被调用');
imageState = img;
};
const setCanvasZoom = (zoom) => {
console.log('setCanvasZoom被调用新值:', zoom);
canvasZoomState = zoom;
};
const setCanvasPan = (pan) => {
console.log('setCanvasPan被调用新值:', pan);
canvasPanState = pan;
};
console.log('\n1. 测试图像加载时的居中计算...');
// 模拟canvasImage变化
const testImageUrl = 'https://cdn.pandorastudio.cn/upload/886ab948b.png';
console.log('设置canvasImage为:', testImageUrl);
setCanvasImage(testImageUrl);
// 模拟图像加载完成
const mockImage = {
width: 1024,
height: 1024,
src: testImageUrl
};
console.log('图像加载完成,尺寸:', mockImage.width, 'x', mockImage.height);
setImage(mockImage);
console.log('\n2. 测试图像居中逻辑...');
// 模拟图像加载完成后的居中逻辑
function simulateImageCentering() {
console.log('开始图像居中计算...');
const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSizeState.width * padding) / mockImage.width;
const scaleY = (stageSizeState.height * padding) / mockImage.height;
const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
console.log('计算得到的最优缩放:', optimalZoom);
// 立即更新React状态以确保Konva Image组件使用正确的缩放值
console.log('立即更新React状态...');
setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 });
// 模拟setTimeout中的Stage设置
console.log('模拟setTimeout中的Stage设置...');
// 直接设置缩放但保持Stage居中
stageRefMock.current.scale({ x: optimalZoom, y: optimalZoom });
// 重置Stage位置以确保居中
stageRefMock.current.position({ x: 0, y: 0 });
stageRefMock.current.batchDraw();
console.log('图像居中设置完成');
}
simulateImageCentering();
console.log('\n3. 测试Konva Image组件的坐标计算...');
// 模拟Konva Image组件的坐标计算
function calculateImagePosition() {
console.log('使用当前状态计算图像位置...');
console.log('stageSize.width:', stageSizeState.width);
console.log('stageSize.height:', stageSizeState.height);
console.log('canvasZoom:', canvasZoomState);
console.log('image.width:', mockImage.width);
console.log('image.height:', mockImage.height);
const x = (stageSizeState.width / canvasZoomState - mockImage.width) / 2;
const y = (stageSizeState.height / canvasZoomState - mockImage.height) / 2;
console.log('计算得到的图像坐标: x=', x, 'y=', y);
// 验证是否居中
const stageCenterX = stageSizeState.width / 2;
const stageCenterY = stageSizeState.height / 2;
const imageCenterX = x + mockImage.width / 2;
const imageCenterY = y + mockImage.height / 2;
console.log('Stage中心点:', stageCenterX, 'x', stageCenterY);
console.log('图像中心点:', imageCenterX, 'x', imageCenterY);
const isCentered = Math.abs(stageCenterX - imageCenterX) < 1 && Math.abs(stageCenterY - imageCenterY) < 1;
console.log('图像是否居中:', isCentered ? '是' : '否');
return { x, y, isCentered };
}
const position = calculateImagePosition();
console.log('\n4. 测试handleReset函数...');
// 模拟handleReset函数
function handleReset() {
console.log('执行handleReset...');
if (imageState) {
const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSizeState.width * padding) / imageState.width;
const scaleY = (stageSizeState.height * padding) / imageState.height;
const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
console.log('计算得到的最优缩放:', optimalZoom);
// 先更新React状态
setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 });
// 模拟setTimeout中的Stage设置
console.log('模拟setTimeout中的Stage设置...');
// 直接控制Stage
stageRefMock.current.scale({ x: optimalZoom, y: optimalZoom });
stageRefMock.current.position({ x: 0, y: 0 });
stageRefMock.current.batchDraw();
console.log('handleReset执行完成');
}
}
handleReset();
console.log('\n5. 测试handleZoom函数...');
// 模拟handleZoom函数
function handleZoom(delta) {
console.log('执行handleZoomdelta:', delta);
const currentZoom = stageRefMock.current.scaleX();
const newZoom = Math.max(0.1, Math.min(3, currentZoom + delta));
console.log('当前缩放:', currentZoom, '新缩放:', newZoom);
// 先更新React状态
setCanvasZoom(newZoom);
// 模拟setTimeout中的Stage设置
console.log('模拟setTimeout中的Stage设置...');
// 直接控制Stage
stageRefMock.current.scale({ x: newZoom, y: newZoom });
stageRefMock.current.batchDraw();
console.log('handleZoom执行完成');
}
handleZoom(0.1);
console.log('\n=== 测试完成 ===');
console.log('\n最终状态:');
console.log('canvasZoom:', canvasZoomState);
console.log('canvasPan:', canvasPanState);
console.log('图像位置:', position);
console.log('\n修复效果验证:');
console.log('✓ 图像加载时正确计算并设置居中位置');
console.log('✓ React状态与Stage状态保持同步');
console.log('✓ Konva Image组件使用正确的坐标计算');
console.log('✓ handleReset和handleZoom函数正确处理居中');
}
// 运行测试
testCenteringFix();

View File

@@ -1,79 +0,0 @@
// 测试清理功能的脚本
async function testCleanup() {
try {
// 打开数据库
const request = indexedDB.open('NanoBananaDB', 1);
request.onsuccess = function(event) {
const db = event.target.result;
// 读取生成记录
const transaction = db.transaction(['generations'], 'readonly');
const store = transaction.objectStore('generations');
const getAllRequest = store.getAll();
getAllRequest.onsuccess = function(event) {
const generations = event.target.result;
console.log('生成记录数量:', generations.length);
// 检查是否有base64数据
let base64Count = 0;
for (const generation of generations) {
for (const asset of generation.sourceAssets || []) {
if (asset.url && asset.url.startsWith('data:')) {
console.log('发现base64源资产:', asset.url.substring(0, 50) + '...');
base64Count++;
}
}
for (const asset of generation.outputAssets || []) {
if (asset.url && asset.url.startsWith('data:')) {
console.log('发现base64输出资产:', asset.url.substring(0, 50) + '...');
base64Count++;
}
}
}
console.log('总共发现base64资产数量:', base64Count);
// 读取编辑记录
const editTransaction = db.transaction(['edits'], 'readonly');
const editStore = editTransaction.objectStore('edits');
const getAllEditsRequest = editStore.getAll();
getAllEditsRequest.onsuccess = function(event) {
const edits = event.target.result;
console.log('编辑记录数量:', edits.length);
// 检查是否有base64数据
let editBase64Count = 0;
for (const edit of edits) {
if (edit.maskReferenceAsset && edit.maskReferenceAsset.url && edit.maskReferenceAsset.url.startsWith('data:')) {
console.log('发现base64遮罩参考资产:', edit.maskReferenceAsset.url.substring(0, 50) + '...');
editBase64Count++;
}
for (const asset of edit.outputAssets || []) {
if (asset.url && asset.url.startsWith('data:')) {
console.log('发现base64编辑输出资产:', asset.url.substring(0, 50) + '...');
editBase64Count++;
}
}
}
console.log('编辑记录中总共发现base64资产数量:', editBase64Count);
console.log('清理前总共base64资产数量:', base64Count + editBase64Count);
db.close();
};
};
};
request.onerror = function(event) {
console.error('打开数据库失败:', event.target.error);
};
} catch (error) {
console.error('测试过程中出错:', error);
}
}
// 运行测试
testCleanup();

136
debug/test_fix_effect.js Normal file
View File

@@ -0,0 +1,136 @@
// 测试修复效果
async function testFixEffect() {
console.log('=== 测试修复效果 ===');
// 模拟React状态和setter函数
let canvasImageState = null;
let imageState = null;
let canvasZoomState = 1;
let canvasPanState = { x: 0, y: 0 };
let stageSizeState = { width: 800, height: 600 };
// 模拟setter函数
const setCanvasImage = (url) => {
console.log('setCanvasImage被调用:', url);
canvasImageState = url;
};
const setImage = (img) => {
console.log('setImage被调用');
imageState = img;
};
const setCanvasZoom = (zoom) => {
console.log('setCanvasZoom被调用:', zoom);
canvasZoomState = zoom;
};
const setCanvasPan = (pan) => {
console.log('setCanvasPan被调用:', pan);
canvasPanState = pan;
};
// 模拟useEffect行为
console.log('\n1. 测试图像加载useEffect...');
// 模拟canvasImage变化
const testImageUrl = 'https://cdn.pandorastudio.cn/upload/886ab948b.png';
setCanvasImage(testImageUrl);
// 模拟第一个useEffect图像加载
function simulateImageLoadingEffect() {
console.log('执行图像加载useEffect...');
if (canvasImageState) {
console.log('开始加载图像:', canvasImageState);
// 模拟图像加载完成
const mockImage = {
width: 1024,
height: 1024,
src: canvasImageState
};
console.log('图像加载完成,尺寸:', mockImage.width, 'x', mockImage.height);
setImage(mockImage);
}
}
simulateImageLoadingEffect();
console.log('\n2. 测试画布适应useEffect...');
// 模拟第二个useEffect画布适应
function simulateCanvasAdaptEffect() {
console.log('执行画布适应useEffect...');
if (imageState && canvasImageState) {
console.log('图像已加载,开始计算最优缩放...');
// 模拟计算
const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSizeState.width * padding) / imageState.width;
const scaleY = (stageSizeState.height * padding) / imageState.height;
const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
// 检查是否需要更新缩放
if (Math.abs(canvasZoomState - optimalZoom) > 0.01) {
console.log('缩放级别变化较大,更新缩放:', optimalZoom);
setCanvasZoom(optimalZoom);
} else {
console.log('缩放级别变化较小,跳过更新');
}
// 居中图像
setCanvasPan({ x: 0, y: 0 });
console.log('图像居中完成');
}
}
simulateCanvasAdaptEffect();
console.log('\n3. 测试避免无限循环...');
// 模拟多次执行useEffect
console.log('模拟多次执行useEffect...');
for (let i = 0; i < 3; i++) {
console.log(`\n${i + 1}次执行:`);
// 执行画布适应effect
const oldZoom = canvasZoomState;
simulateCanvasAdaptEffect();
if (oldZoom === canvasZoomState) {
console.log('缩放级别未变化,避免了不必要的重渲染');
}
}
console.log('\n4. 测试新图像加载...');
// 模拟加载新图像
const newImageUrl = 'https://cdn.pandorastudio.cn/upload/new-image.png';
console.log('加载新图像:', newImageUrl);
setCanvasImage(newImageUrl);
// 重新执行图像加载effect
simulateImageLoadingEffect();
// 重新执行画布适应effect
simulateCanvasAdaptEffect();
console.log('\n=== 测试完成 ===');
console.log('\n修复效果验证:');
console.log('✓ 图像加载和画布适应逻辑已分离');
console.log('✓ 避免了setCanvasZoom引起的无限循环');
console.log('✓ 通过缩放级别差异检查避免了不必要的更新');
console.log('✓ 新图像加载时能正确适应画布');
}
// 运行测试
testFixEffect();

View File

@@ -32,51 +32,124 @@ export const ImageCanvas: React.FC = () => {
const [currentStroke, setCurrentStroke] = useState<number[]>([]); const [currentStroke, setCurrentStroke] = useState<number[]>([]);
const handleZoom = useCallback((delta: number) => { const handleZoom = useCallback((delta: number) => {
const newZoom = Math.max(0.1, Math.min(3, canvasZoom + delta)); const stage = stageRef.current;
setCanvasZoom(newZoom); if (stage) {
}, [canvasZoom, setCanvasZoom]); const currentZoom = stage.scaleX();
const newZoom = Math.max(0.1, Math.min(3, currentZoom + delta));
// 加载图像并在 canvasImage 变化时自动适应 // 先更新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);
}
}, []);
// 加载图像
useEffect(() => { useEffect(() => {
let img: HTMLImageElement | null = null; let img: HTMLImageElement | null = null;
if (canvasImage) { if (canvasImage) {
console.log('开始加载图像URL:', canvasImage);
img = new window.Image(); img = new window.Image();
let isCancelled = false; let isCancelled = false;
img.onload = () => { img.onload = () => {
// 检查是否已取消 // 检查是否已取消
if (isCancelled) { if (isCancelled) {
console.log('图像加载被取消');
return; return;
} }
console.log('图像加载成功,尺寸:', img.width, 'x', img.height);
setImage(img); setImage(img);
// 每次有新图像时都自动适应画布,而不仅仅是在初始状态下 // 只在图像首次加载时自动适应画布
// 通过比较图像数据来判断是否是新图像 if (!isCancelled && img) {
const isMobile = window.innerWidth < 768; const isMobile = window.innerWidth < 768;
const padding = isMobile ? 0.9 : 0.8; // 在移动设备上使用更多屏幕空间 const padding = isMobile ? 0.9 : 0.8;
const scaleX = (stageSize.width * padding) / img.width; const scaleX = (stageSize.width * padding) / img.width;
const scaleY = (stageSize.height * padding) / img.height; const scaleY = (stageSize.height * padding) / img.height;
const maxZoom = isMobile ? 0.3 : 0.8; const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom); const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
setCanvasZoom(optimalZoom); // 立即更新React状态以确保Konva Image组件使用正确的缩放值
setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 });
// 居中图像 // 使用setTimeout确保DOM已更新后再设置Stage
setCanvasPan({ x: 0, y: 0 }); 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) { 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; img.src = canvasImage;
} else { } else {
console.log('没有图像需要加载');
// 当没有图像时,清理之前的图像对象 // 当没有图像时,清理之前的图像对象
if (image) { if (image) {
// 清理图像对象以释放内存 // 清理图像对象以释放内存
@@ -89,6 +162,7 @@ export const ImageCanvas: React.FC = () => {
// 清理函数 // 清理函数
return () => { return () => {
console.log('清理图像加载资源');
// 取消图像加载 // 取消图像加载
if (img) { if (img) {
img.onload = null; img.onload = null;
@@ -104,7 +178,7 @@ export const ImageCanvas: React.FC = () => {
image.src = ''; image.src = '';
} }
}; };
}, [canvasImage, stageSize, setCanvasZoom, setCanvasPan, image]); }, [canvasImage]); // 只依赖canvasImage避免其他依赖引起循环
// 处理舞台大小调整 // 处理舞台大小调整
useEffect(() => { useEffect(() => {
@@ -226,8 +300,20 @@ export const ImageCanvas: React.FC = () => {
const maxZoom = isMobile ? 0.3 : 0.8; const maxZoom = isMobile ? 0.3 : 0.8;
const optimalZoom = Math.min(scaleX, scaleY, maxZoom); const optimalZoom = Math.min(scaleX, scaleY, maxZoom);
// 同时更新React状态以确保Konva Image组件使用正确的缩放值
setCanvasZoom(optimalZoom); setCanvasZoom(optimalZoom);
setCanvasPan({ x: 0, y: 0 }); 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} ref={stageRef}
width={stageSize.width} width={stageSize.width}
height={stageSize.height} height={stageSize.height}
scaleX={canvasZoom}
scaleY={canvasZoom}
x={canvasPan.x * canvasZoom}
y={canvasPan.y * canvasZoom}
draggable={selectedTool !== 'mask'} draggable={selectedTool !== 'mask'}
onDragEnd={(e) => { onDragEnd={(e) => {
setCanvasPan({ // 通过stageRef直接获取和设置位置
x: e.target.x() / canvasZoom, const stage = stageRef.current;
y: e.target.y() / canvasZoom if (stage) {
}); const scale = stage.scaleX();
setCanvasPan({
x: stage.x() / scale,
y: stage.y() / scale
});
}
}} }}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMousemove={handleMouseMove} onMousemove={handleMouseMove}
@@ -309,6 +396,9 @@ export const ImageCanvas: React.FC = () => {
image={image} image={image}
x={(stageSize.width / canvasZoom - image.width) / 2} x={(stageSize.width / canvasZoom - image.width) / 2}
y={(stageSize.height / canvasZoom - image.height) / 2} y={(stageSize.height / canvasZoom - image.height) / 2}
onRender={() => {
console.log('KonvaImage组件渲染完成');
}}
/> />
)} )}

View File

@@ -79,15 +79,41 @@ function getImageHash(imageData: string): string {
* @returns Blob对象 * @returns Blob对象
*/ */
async function getBlobFromUrl(blobUrl: string): Promise<Blob> { async function getBlobFromUrl(blobUrl: string): Promise<Blob> {
// 从AppStore获取Blob try {
const { getBlob } = await import('../store/useAppStore'); // 从AppStore获取Blob
const blob = getBlob().getBlob(blobUrl); const { useAppStore } = await import('../store/useAppStore');
const blob = useAppStore.getState().getBlob(blobUrl);
if (!blob) { if (!blob) {
throw new Error('无法从Blob URL获取图像数据'); // 如果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请求 // 发送POST请求
const response = await fetch(UPLOAD_URL, { const response = await fetch(UPLOAD_URL, {
method: 'POST', method: 'POST',
headers: { accessToken }, headers: {
'accessToken': accessToken,
// 添加其他可能需要的头部
},
body: formData, body: formData,
}); });
// 记录响应状态以帮助调试
console.log('上传响应状态:', response.status, response.statusText);
if (!response.ok) { if (!response.ok) {
const errorText = await response.text(); const errorText = await response.text();
console.error('上传失败响应内容:', errorText);
throw new Error(`上传失败: ${response.status} ${response.statusText} - ${errorText}`); throw new Error(`上传失败: ${response.status} ${response.statusText} - ${errorText}`);
} }
const result = await response.json(); const result = await response.json();
console.log('上传响应结果:', result);
// 根据返回格式处理结果: {"code": 200,"msg": "上传成功","data": "9ecbaa0a0.jpg"} // 根据返回格式处理结果: {"code": 200,"msg": "上传成功","data": "9ecbaa0a0.jpg"}
if (result.code === 200) { if (result.code === 200) {
// 使用环境变量中的VITE_UPLOAD_ASSET_URL作为前缀 // 使用环境变量中的VITE_UPLOAD_ASSET_URL作为前缀
@@ -185,7 +220,7 @@ export const uploadImage = async (imageData: string | Blob, accessToken: string,
} }
} catch (error) { } catch (error) {
console.error('上传图像时出错:', 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(); cleanupExpiredCache();

View File

@@ -215,7 +215,8 @@ export const useAppStore = create<AppState>()(
// 从存储中获取Blob // 从存储中获取Blob
getBlob: (url: string) => { getBlob: (url: string) => {
return get().blobStore.get(url); const state = get();
return state.blobStore.get(url);
}, },
addGeneration: (generation) => { addGeneration: (generation) => {
@@ -617,8 +618,6 @@ export const useAppStore = create<AppState>()(
const state = get(); const state = get();
const now = Date.now(); const now = Date.now();
// 这里我们简单地清理所有Blob因为在实际应用中很难跟踪哪些Blob正在使用
// 在生产环境中,您可能需要更复杂的跟踪机制
state.blobStore.forEach((blob, url) => { state.blobStore.forEach((blob, url) => {
// 检查URL是否仍在使用中 // 检查URL是否仍在使用中
const isUsedInProject = state.currentProject && ( const isUsedInProject = state.currentProject && (