Skip to content

Commit 2f43ee1

Browse files
Merge pull request #42 from wdyy20041223/main
fix:拼图编辑器的16:9裁剪,并适配自定义形状、保存配置、分享代码、预览效果功能,优化自定义形状时的预览效果功能
2 parents c17e746 + 72f1c57 commit 2f43ee1

File tree

12 files changed

+301
-57
lines changed

12 files changed

+301
-57
lines changed

src/components/editor/DifficultySettings.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface DifficultySettingsProps {
1414
onShapeChange: (shape: PieceShape) => void;
1515
onRecrop?: () => void; // 新增:重新剪裁回调
1616
hasUploadedImage?: boolean; // 新增:是否有已上传图片
17+
onCustomGridChange?: (rows: number, cols: number) => void; // 新增:自定义网格更新回调
1718
}
1819

1920
export const DifficultySettings: React.FC<DifficultySettingsProps> = ({
@@ -26,7 +27,8 @@ export const DifficultySettings: React.FC<DifficultySettingsProps> = ({
2627
onDifficultyChange,
2728
onShapeChange,
2829
onRecrop,
29-
hasUploadedImage
30+
hasUploadedImage,
31+
onCustomGridChange
3032
}) => {
3133
const [customRows, setCustomRows] = useState('3');
3234
const [customCols, setCustomCols] = useState('3');
@@ -146,7 +148,10 @@ export const DifficultySettings: React.FC<DifficultySettingsProps> = ({
146148
if (rows >= 2 && rows <= 10 && cols >= 2 && cols <= 10) {
147149
// 更新配置并关闭自定义设置面板
148150
setShowCustomInputs(false);
149-
// 这里可以添加其他配置更新逻辑
151+
// 调用父组件的回调函数更新临时状态
152+
if (onCustomGridChange) {
153+
onCustomGridChange(rows, cols);
154+
}
150155
console.log(`自定义网格已更新: ${rows}×${cols}`);
151156
} else {
152157
alert('请输入有效的行数和列数(2-10之间)');

src/components/editor/PreviewModal.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@
141141
left: 5%;
142142
}
143143

144+
/* 16:9宽屏比例的网格样式 */
145+
.grid-overlay-inner.widescreen {
146+
width: 90%;
147+
height: 50.625%; /* 16:9比例的高度计算:90% * 9/16 = 50.625% */
148+
top: 25%;
149+
left: 5%;
150+
}
151+
144152
.grid-line {
145153
position: absolute;
146154
background: #2563eb;

src/components/editor/PreviewModal.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ interface PreviewModalProps {
1010
showPuzzleGrid?: boolean;
1111
gridSize?: string;
1212
pieceShape?: string; // 新增:拼块形状
13+
aspectRatio?: string; // 新增:画幅比例
1314
}
1415

1516
export const PreviewModal: React.FC<PreviewModalProps> = ({
@@ -19,7 +20,8 @@ export const PreviewModal: React.FC<PreviewModalProps> = ({
1920
imageTitle,
2021
showPuzzleGrid = false,
2122
gridSize = '4x4',
22-
pieceShape = 'square' // 默认方形
23+
pieceShape = 'square', // 默认方形
24+
aspectRatio = '1:1' // 默认1:1比例
2325
}) => {
2426
if (!isOpen) return null;
2527

@@ -35,6 +37,10 @@ export const PreviewModal: React.FC<PreviewModalProps> = ({
3537
const [rows, cols] = gridSize.split('x').map(Number);
3638
const gridLines = [];
3739

40+
// 根据画幅比例调整网格显示
41+
const isWidescreen = aspectRatio === '16:9';
42+
const gridContainerClass = isWidescreen ? 'grid-overlay-inner widescreen' : 'grid-overlay-inner';
43+
3844
// 添加垂直线
3945
for (let i = 1; i < cols; i++) {
4046
gridLines.push(
@@ -103,7 +109,7 @@ export const PreviewModal: React.FC<PreviewModalProps> = ({
103109

104110
return (
105111
<div className="puzzle-grid-overlay">
106-
<div className="grid-overlay-inner">
112+
<div className={gridContainerClass}>
107113
{gridLines}
108114
</div>
109115
</div>

src/components/editor/PuzzleEditor.tsx

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
7777
const [importPreviewImage, setImportPreviewImage] = useState<string | null>(null);
7878
const [importPreviewShape, setImportPreviewShape] = useState<string>('');
7979
const [importPreviewGrid, setImportPreviewGrid] = useState<string>('');
80+
const [importPreviewAspectRatio, setImportPreviewAspectRatio] = useState<string>('');
8081

8182
// 监听分享代码输入,实时解析图片
8283
const handleImportCodeChange = (val: string) => {
@@ -106,10 +107,17 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
106107
} else {
107108
setImportPreviewGrid('');
108109
}
110+
// 裁剪比例
111+
if (data.aspectRatio) {
112+
setImportPreviewAspectRatio(data.aspectRatio);
113+
} else {
114+
setImportPreviewAspectRatio('');
115+
}
109116
} catch {
110117
setImportPreviewImage(null);
111118
setImportPreviewShape('');
112119
setImportPreviewGrid('');
120+
setImportPreviewAspectRatio('');
113121
}
114122
};
115123

@@ -245,15 +253,22 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
245253
}
246254
// 生成唯一id
247255
const id = Date.now().toString();
256+
// 确保保存完整的配置信息,包括裁剪比例
248257
const newPuzzle = {
249258
id,
250259
name: customPuzzleConfig.name,
251-
data: customPuzzleConfig,
260+
data: {
261+
...customPuzzleConfig,
262+
// 明确包含裁剪比例信息
263+
aspectRatio: customPuzzleConfig.aspectRatio || '1:1',
264+
// 包含裁剪后的图片数据
265+
croppedImageData: customPuzzleConfig.croppedImageData
266+
},
252267
date: new Date().toLocaleString(),
253268
};
254269
puzzles.push(newPuzzle);
255270
localStorage.setItem('savedPuzzles', JSON.stringify(puzzles));
256-
setShowSavedPage({ highlightId: id });
271+
setShowSavedPage({ highlightId: id });
257272
}, [customPuzzleConfig]);
258273

259274
// 分享弹窗相关状态
@@ -352,6 +367,7 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
352367
gridSize,
353368
pieceShape,
354369
name: customPuzzleConfig.name || '自定义拼图',
370+
aspectRatio: customPuzzleConfig.aspectRatio,
355371
});
356372
if (typeof onStartGame === 'function') {
357373
onStartGame(puzzleConfig);
@@ -475,11 +491,13 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
475491
{importPreviewImage && (
476492
<img src={importPreviewImage} alt="预览" style={{ maxWidth: 180, maxHeight: 120, borderRadius: 4, boxShadow: '0 1px 6px #0001', marginBottom: (importPreviewShape || importPreviewGrid) ? 6 : 0 }} />
477493
)}
478-
{(importPreviewShape || importPreviewGrid) && (
494+
{(importPreviewShape || importPreviewGrid || importPreviewAspectRatio) && (
479495
<div style={{ fontSize: 13, color: '#333', marginBottom: 2 }}>
480496
{importPreviewShape && <span>形状:{importPreviewShape}</span>}
481497
{importPreviewShape && importPreviewGrid && <span style={{ margin: '0 6px' }}>|</span>}
482498
{importPreviewGrid && <span>块数:{importPreviewGrid}</span>}
499+
{(importPreviewShape || importPreviewGrid) && importPreviewAspectRatio && <span style={{ margin: '0 6px' }}>|</span>}
500+
{importPreviewAspectRatio && <span>比例:{importPreviewAspectRatio}</span>}
483501
</div>
484502
)}
485503
</div>
@@ -515,8 +533,8 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
515533
<button onClick={() => { setImportDialogOpen(false); setImportError(''); setImportCode(''); setImportPreviewImage(null); }} style={{ padding: '6px 16px', fontSize: 14, cursor: 'pointer' }}>取消</button>
516534
</div>
517535
<div style={{ fontSize: 12, color: '#888', marginTop: 8 }}>
518-
分享代码可由好友生成,包含图片、形状、难度、块数等信息
519-
</div>
536+
分享代码可由好友生成,包含图片、形状、难度、块数、裁剪比例等信息
537+
</div>
520538
</div>
521539
</div>
522540
)}
@@ -585,7 +603,8 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
585603
selectedShape={tempPieceShape}
586604
onDifficultyChange={handleTempDifficultyChange}
587605
onShapeChange={handleTempPieceShapeChange}
588-
// 新增:自定义行列同步(已移除无用 prop)
606+
// 新增:自定义行列同步
607+
onCustomGridChange={handleTempCustomGrid}
589608
// 新增:重新剪裁功能
590609
onRecrop={() => setCurrentStep('crop')}
591610
hasUploadedImage={!!uploadedImage}
@@ -755,14 +774,25 @@ export const PuzzleEditor: React.FC<PuzzleEditorProps> = ({ onBackToMenu, onBack
755774
showPuzzleGrid={true}
756775
gridSize={
757776
currentStep === 'settings'
758-
? (tempDifficulty === 'easy' ? '3x3' : tempDifficulty === 'medium' ? '4x4' : tempDifficulty === 'hard' ? '5x5' : '6x6')
759-
: (customPuzzleConfig.difficulty === 'easy' ? '3x3' : customPuzzleConfig.difficulty === 'medium' ? '4x4' : customPuzzleConfig.difficulty === 'hard' ? '5x5' : '6x6')
777+
? (tempDifficulty === 'easy' ? '3x3'
778+
: tempDifficulty === 'medium' ? '4x4'
779+
: tempDifficulty === 'hard' ? '5x5'
780+
: tempDifficulty === 'expert' ? '6x6'
781+
: tempDifficulty === 'custom' ? `${tempCustomRows}x${tempCustomCols}`
782+
: '4x4')
783+
: (customPuzzleConfig.difficulty === 'easy' ? '3x3'
784+
: customPuzzleConfig.difficulty === 'medium' ? '4x4'
785+
: customPuzzleConfig.difficulty === 'hard' ? '5x5'
786+
: customPuzzleConfig.difficulty === 'expert' ? '6x6'
787+
: customPuzzleConfig.difficulty === 'custom' ? `${customPuzzleConfig.customRows || 3}x${customPuzzleConfig.customCols || 3}`
788+
: '4x4')
760789
}
761790
pieceShape={
762791
currentStep === 'settings'
763792
? tempPieceShape
764793
: customPuzzleConfig.pieceShape
765794
}
795+
aspectRatio={customPuzzleConfig.aspectRatio}
766796
/>
767797
{/* 分享代码弹窗 */}
768798
{shareDialogOpen && (

src/components/game/AnswerGrid.css

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
max-width: 100%;
1111
max-height: 100%;
1212
box-sizing: border-box;
13-
overflow: hidden;
13+
overflow: hidden; /* 默认隐藏溢出 */
1414
align-items: center; /* 新增:内容水平居中 */
1515
justify-content: center; /* 新增:内容垂直居中 */
1616
}
1717

18+
/* 为16:9答题卡添加额外的滚动容器包装 */
19+
.answer-grid-container[data-aspect-ratio="16:9"] {
20+
overflow: auto; /* 16:9答题卡允许滚动 */
21+
position: relative;
22+
}
23+
1824
.background-image {
1925
position: absolute;
2026
top: 20px;
@@ -65,6 +71,82 @@
6571
min-width: 0; /* 重要:允许内容收缩 */
6672
}
6773

74+
/* 16:9答题卡网格放大效果 */
75+
/* 为16:9答题卡添加专门的滚动容器 */
76+
.answer-grid-container[data-aspect-ratio="16:9"] {
77+
overflow: auto; /* 16:9答题卡允许滚动 */
78+
position: relative;
79+
display: flex;
80+
flex-direction: column;
81+
align-items: center;
82+
justify-content: flex-start; /* 从顶部开始对齐 */
83+
84+
/* 向下平移header的高度,避免被area-header挡住 */
85+
margin-top: 30px; /* 减少margin-top,避免顶部被截断 */
86+
margin-bottom: -30px; /* 相应减少抵消值 */
87+
88+
/* 确保底部内容不被网格信息区域挡住 */
89+
padding-bottom: 60px; /* 增加底部内边距 */
90+
padding-top: 10px; /* 添加顶部内边距确保内容可见 */
91+
}
92+
93+
/* 16:9答题卡网格放大效果 - 动态调整放大倍数 */
94+
.answer-grid-container[data-aspect-ratio="16:9"] .answer-grid {
95+
transform: scale(var(--grid-scale-factor, 1.9)); /* 使用CSS变量动态控制放大倍数 */
96+
transform-origin: center top; /* 从顶部中心开始缩放,避免顶部被截断 */
97+
gap: 2px; /* 添加2px间距避免拼图块遮挡 */
98+
row-gap: 4px; /* 垂直方向增加额外2px间距 */
99+
100+
/* 修复滚动问题:确保缩放后的内容仍然可以正确滚动 */
101+
position: relative;
102+
z-index: 1;
103+
margin-bottom: 20px; /* 添加底部边距,确保内容不被网格信息挡住 */
104+
}
105+
106+
/* 确保16:9答题卡的网格信息区域不会挡住内容 */
107+
.answer-grid-container[data-aspect-ratio="16:9"] .grid-info {
108+
position: sticky;
109+
bottom: 0;
110+
background: rgba(255, 255, 255, 0.95); /* 使用半透明背景避免完全遮挡 */
111+
z-index: 10;
112+
padding: 12px 16px;
113+
margin-top: auto;
114+
border-top: 1px solid #e2e8f0;
115+
backdrop-filter: blur(5px); /* 添加毛玻璃效果 */
116+
117+
/* 保留原始flex布局 */
118+
display: flex;
119+
justify-content: space-between;
120+
align-items: center;
121+
border-radius: 8px 8px 0 0;
122+
flex-shrink: 0;
123+
}
124+
125+
/* 确保缩放后的网格内容能够正确触发滚动 */
126+
.answer-grid-container[data-aspect-ratio="16:9"]::before {
127+
content: '';
128+
position: absolute;
129+
top: -10px; /* 扩展可滚动区域到顶部 */
130+
left: 0;
131+
right: 0;
132+
bottom: -60px; /* 扩展可滚动区域到底部 */
133+
pointer-events: none;
134+
z-index: -1;
135+
}
136+
137+
/* 16:9方形拼图块的图片样式 - 确保图片在边框内显示 */
138+
.grid-slot[data-aspect-ratio="16:9"] .placed-piece .piece-image {
139+
transform: scale(var(--image-scale-factor, 0.95)); /* 使用CSS变量动态控制图片缩小倍数 */
140+
transform-origin: center;
141+
}
142+
143+
/* 16:9网格槽位虚线边框 */
144+
.grid-slot[data-aspect-ratio="16:9"] {
145+
border-style: dashed;
146+
border-color: #cbd5e1;
147+
box-sizing: border-box; /* 确保边框不增加元素尺寸 */
148+
}
149+
68150
/* 自定义滚动条样式 */
69151
.answer-grid::-webkit-scrollbar {
70152
width: 8px;
@@ -84,7 +166,6 @@
84166
background: #a8a8a8;
85167
}
86168

87-
88169
.grid-slot {
89170
position: relative;
90171
background: white;
@@ -96,8 +177,6 @@
96177
justify-content: center;
97178
overflow: hidden;
98179
border: 2px solid transparent;
99-
/* 确保单元格保持正方形 */
100-
aspect-ratio: 1;
101180
/* 添加最小尺寸防止过度缩小 */
102181
min-height: 60px;
103182
min-width: 60px;
@@ -202,6 +281,8 @@
202281
display: block;
203282
}
204283

284+
285+
205286
.piece-info {
206287
position: absolute;
207288
bottom: 4px;
@@ -657,4 +738,4 @@
657738
white-space: nowrap;
658739
pointer-events: none;
659740
z-index: 3;
660-
}
741+
}

0 commit comments

Comments
 (0)