diff --git a/miniprogram/base/baseDrawPage.less b/miniprogram/base/baseDrawPage.less deleted file mode 100644 index e0ca03b..0000000 --- a/miniprogram/base/baseDrawPage.less +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 数学页面公共样式 - */ -page { - background-color: #f6f6f6; -} - -.page-container { - background-color: #f6f6f6; - padding: 0 24rpx; - box-sizing: border-box; - padding-bottom: 160rpx; // 为底部按钮预留空间 - - .empty { - height: 50rpx; - } -} - -.wrapper { - background-color: #ffffff; - border-radius: 20rpx; - padding: 36rpx 24rpx; - box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15); - margin: 30rpx 0; - display: flex; - flex-direction: column; - - .wrapper-title { - font-size: 32rpx; - color: #141414; - margin-bottom: 36rpx; - font-weight: bold; - text-align: center; - } - - .canvas-wrapper { - display: flex; - justify-content: center; - align-items: center; - min-height: 400rpx; - background: #f8f9fa; - border-radius: 12rpx; - border: 2rpx dashed #dee2e6; - } - - .canvas-content { - max-width: 100%; - border-radius: 8rpx; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); - overflow: hidden; - } - - .random-button-area { - margin-top: 40rpx; - display: flex; - align-items: center; - gap: 20rpx; - - .type-selector { - flex: 1; // 占据剩余空间 - height: 80rpx; - background-color: #fff; - border: 2rpx solid #e5e5e5; - border-radius: 16rpx; - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 24rpx; - box-sizing: border-box; - - .type-selector-text { - font-size: 28rpx; - color: #333; - } - - .type-selector-arrow { - font-size: 20rpx; - color: #999; - } - } - - .random-button { - flex: 2; - } - } -} - -/* 底部按钮区域 */ -.bottom-btn-box { - position: fixed; - left: 0; - bottom: 0; - width: 100%; - height: 180rpx; - display: flex; - justify-content: space-between; - background-color: #fff; - box-shadow: 0px -3.9rpx 5.2rpx rgba(0, 0, 0, 0.15); - padding: 24rpx; - padding-bottom: env(safe-area-inset-bottom); - box-sizing: border-box; - border-radius: 16rpx 16rpx 0 0; - z-index: 2; -} \ No newline at end of file diff --git a/miniprogram/base/baseDrawPage.wxss b/miniprogram/base/baseDrawPage.wxss new file mode 100644 index 0000000..f9f6206 --- /dev/null +++ b/miniprogram/base/baseDrawPage.wxss @@ -0,0 +1,104 @@ +/** + * 数学页面公共样式 + */ +page { + background-color: #f6f6f6; +} + +.page-container { + background-color: #f6f6f6; + padding: 0 24rpx; + box-sizing: border-box; + padding-bottom: 160rpx; /* 为底部按钮预留空间 */ +} + +.page-container .empty { + height: 50rpx; +} + +.wrapper { + background-color: #ffffff; + border-radius: 20rpx; + padding: 36rpx 24rpx; + box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15); + margin: 30rpx 0; + display: flex; + flex-direction: column; +} + +.wrapper .wrapper-title { + font-size: 32rpx; + color: #141414; + margin-bottom: 36rpx; + font-weight: bold; + text-align: center; +} + +.wrapper .canvas-wrapper { + display: flex; + justify-content: center; + align-items: center; + min-height: 400rpx; + background: #f8f9fa; + border-radius: 12rpx; + border: 2rpx dashed #dee2e6; +} + +.wrapper .canvas-content { + max-width: 100%; + border-radius: 8rpx; + box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1); + overflow: hidden; +} + +.wrapper .random-button-area { + margin-top: 40rpx; + display: flex; + align-items: center; + gap: 20rpx; +} + +.wrapper .random-button-area .type-selector { + flex: 1; /* 占据剩余空间 */ + height: 80rpx; + background-color: #fff; + border: 2rpx solid #e5e5e5; + border-radius: 16rpx; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 24rpx; + box-sizing: border-box; +} + +.wrapper .random-button-area .type-selector .type-selector-text { + font-size: 28rpx; + color: #333; +} + +.wrapper .random-button-area .type-selector .type-selector-arrow { + font-size: 20rpx; + color: #999; +} + +.wrapper .random-button-area .random-button { + flex: 2; +} + +/* 底部按钮区域 */ +.bottom-btn-box { + position: fixed; + left: 0; + bottom: 0; + width: 100%; + height: 180rpx; + display: flex; + justify-content: space-between; + background-color: #fff; + box-shadow: 0px -3.9rpx 5.2rpx rgba(0, 0, 0, 0.15); + padding: 24rpx; + padding-bottom: env(safe-area-inset-bottom); + box-sizing: border-box; + border-radius: 16rpx 16rpx 0 0; + z-index: 2; +} diff --git a/miniprogram/focusPages/gridDrawing/gridDrawing.ts b/miniprogram/focusPages/gridDrawing/gridDrawing.ts index 6210c16..81039b5 100644 --- a/miniprogram/focusPages/gridDrawing/gridDrawing.ts +++ b/miniprogram/focusPages/gridDrawing/gridDrawing.ts @@ -1,11 +1,12 @@ -import GridDraw from '../shared/service/gridDraw'; +import GridDraw, { GridGroup } from '../shared/service/gridDraw'; import { createFocusPage } from '../shared/common/focusPageMixin'; import { GridDrawingData } from '../shared/service/gridDraw'; import { generateCompletePattern } from '../shared/utils/gridUtils'; -import { getRandom3x3Shape } from '../shared/shapes/gridShapes3x3'; -import { getRandom5x5Shape } from '../shared/shapes/gridShapes5x5'; -import { getRandom7x7Shape } from '../shared/shapes/gridShapes7x7'; -import { ShapeTemplate } from '../shared/types/gridTypes'; +import { ALL_3X3_SHAPES } from '../shared/shapes/gridShapes3x3'; +import { ALL_5X5_SHAPES } from '../shared/shapes/gridShapes5x5'; +import { ALL_7X7_SHAPES } from '../shared/shapes/gridShapes7x7'; +import { ShapeTemplate, GridCell } from '../shared/types/gridTypes'; +import { GridConfig } from '../shared/types/gridTypes'; createFocusPage({ canvas: null as Canvas | null, @@ -79,34 +80,158 @@ createFocusPage({ } }, + /** + * 检查两个图案是否相同(通过比较格子的位置和颜色) + */ + arePatternsEqual(cells1: GridCell[], cells2: GridCell[]): boolean { + if (cells1.length !== cells2.length) { + return false; + } + + // 将格子转换为字符串键进行比较 + const pattern1 = cells1 + .map((c) => `${c.x},${c.y},${c.color}`) + .sort() + .join('|'); + const pattern2 = cells2 + .map((c) => `${c.x},${c.y},${c.color}`) + .sort() + .join('|'); + + return pattern1 === pattern2; + }, + + /** + * 生成不重复的图案组 + */ + generateUniqueGroups( + allShapes: ShapeTemplate[], + count: number, + config: GridConfig, + ): GridGroup[] { + const groups: GridGroup[] = []; + const usedPatterns = new Set(); // 用于存储已使用的图案签名 + + // 如果需要的数量超过可用形状数量,允许重复使用 + const maxUniqueShapes = Math.min(count, allShapes.length); + const availableShapes = [...allShapes]; // 复制数组以便打乱 + + // 打乱数组顺序 + for (let i = availableShapes.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [availableShapes[i], availableShapes[j]] = [ + availableShapes[j], + availableShapes[i], + ]; + } + + // 生成不重复的图案 + for (let i = 0; i < count; i++) { + const shapeIndex = i % availableShapes.length; + const shape = availableShapes[shapeIndex]; + const cells = generateCompletePattern(shape); + + // 生成图案签名 + const patternSignature = cells + .map((c) => `${c.x},${c.y},${c.color}`) + .sort() + .join('|'); + + // 如果图案已存在,尝试下一个形状 + if (usedPatterns.has(patternSignature)) { + // 如果所有形状都用过了,允许重复 + if (usedPatterns.size >= maxUniqueShapes) { + // 使用当前形状,即使重复 + groups.push({ + filledCells: cells, + emptyCells: [], + }); + continue; + } else { + // 尝试找到未使用的形状 + let found = false; + for (let j = 0; j < availableShapes.length; j++) { + const nextShape = availableShapes[j]; + const nextCells = generateCompletePattern(nextShape); + const nextSignature = nextCells + .map((c) => `${c.x},${c.y},${c.color}`) + .sort() + .join('|'); + + if (!usedPatterns.has(nextSignature)) { + groups.push({ + filledCells: nextCells, + emptyCells: [], + }); + usedPatterns.add(nextSignature); + found = true; + break; + } + } + + if (!found) { + // 如果找不到未使用的,使用当前形状 + groups.push({ + filledCells: cells, + emptyCells: [], + }); + } + } + } else { + // 图案未使用,添加到结果中 + groups.push({ + filledCells: cells, + emptyCells: [], + }); + usedPatterns.add(patternSignature); + } + } + + return groups; + }, + /** * 随机生成 */ onRandom() { const mode = this.data.currentMode; - let shape: ShapeTemplate; + let allShapes: ShapeTemplate[]; let config: GridConfig; + let groupsPerRow: number; + let totalRows: number; // 根据模式选择形状和配置 if (mode === '3x3') { - shape = getRandom3x3Shape(); + allShapes = ALL_3X3_SHAPES; config = { rows: 3, cols: 3 }; + groupsPerRow = 2; // 每行2组 + totalRows = 3; // 总共3行 } else if (mode === '5x5') { - shape = getRandom5x5Shape(); + allShapes = ALL_5X5_SHAPES; config = { rows: 5, cols: 5 }; + groupsPerRow = 2; + totalRows = 2; // 5x5可以展示2行 } else { // 7x7 - shape = getRandom7x7Shape(); + allShapes = ALL_7X7_SHAPES; config = { rows: 7, cols: 7 }; + groupsPerRow = 1; // 7x7每行1组 + totalRows = 2; // 总共2行 } - // 生成完整的格子数据 - const cells = generateCompletePattern(shape); + // 生成不重复的图案组 + const totalGroups = groupsPerRow * totalRows; + const groups = this.generateUniqueGroups( + allShapes, + totalGroups, + config, + ); this.gridData = { config, - cells, - name: shape.name, + groups, + groupsPerRow, + totalRows, }; this.drawCanvas(); diff --git a/miniprogram/focusPages/gridDrawing/gridDrawing.wxss b/miniprogram/focusPages/gridDrawing/gridDrawing.wxss new file mode 100644 index 0000000..ddc41cc --- /dev/null +++ b/miniprogram/focusPages/gridDrawing/gridDrawing.wxss @@ -0,0 +1 @@ +@import '../../base/baseDrawPage.wxss'; diff --git a/miniprogram/focusPages/shared/service/gridDraw.ts b/miniprogram/focusPages/shared/service/gridDraw.ts index 8474716..f77c9ca 100644 --- a/miniprogram/focusPages/shared/service/gridDraw.ts +++ b/miniprogram/focusPages/shared/service/gridDraw.ts @@ -6,13 +6,27 @@ import { BaseDrawService } from '../../../service/baseDraw'; import { GridCell, GridConfig } from '../types/gridTypes'; +/** + * 格子组数据(一组包含一个填充的网格和一个空的网格) + */ +export interface GridGroup { + /** 填充的格子数据 */ + filledCells: GridCell[]; + /** 空的格子(用于仿画) */ + emptyCells: GridCell[]; // 始终为空数组,仅用于标识 +} + /** * 格子仿画数据 */ export interface GridDrawingData { config: GridConfig; - cells: GridCell[]; - name?: string; + /** 格子组数组,每组包含一个填充的网格和一个空的网格 */ + groups: GridGroup[]; + /** 每行展示的组数 */ + groupsPerRow: number; + /** 总行数 */ + totalRows: number; } /** @@ -33,7 +47,7 @@ class GridDraw extends BaseDrawService { * 绘制格子仿画内容 */ async draw(gridData: GridDrawingData) { - if (!gridData || !gridData.cells) { + if (!gridData || !gridData.groups || gridData.groups.length === 0) { return; } @@ -71,19 +85,139 @@ class GridDraw extends BaseDrawService { startY: number; }): Promise { const { ctx, gridData, canvasWidth, startY } = params; - const { config, cells } = gridData; + const { config, groups, groupsPerRow, totalRows } = gridData; const { rows, cols } = config; // 计算网格尺寸 const margin = 40; // 左右边距 + const groupSpacing = 20; // 组之间的间距 + const rowSpacing = 30; // 行之间的间距 + const dividerWidth = 2; // 分割线宽度 + + // 计算每组网格的尺寸 const availableWidth = canvasWidth - margin * 2; - const cellSize = Math.floor(availableWidth / cols); + const totalGroupsPerRow = groupsPerRow * 2; // 每组包含填充和空两个网格 + const groupWidth = + (availableWidth - + (groupsPerRow - 1) * dividerWidth - + (totalGroupsPerRow - 1) * groupSpacing) / + totalGroupsPerRow; + const cellSize = Math.floor(groupWidth / cols); const gridWidth = cellSize * cols; const gridHeight = cellSize * rows; - // 计算网格起始位置(居中) - const gridStartX = margin + (availableWidth - gridWidth) / 2; - const gridStartY = startY + 30; // 顶部间距 + let currentY = startY + 30; // 顶部间距 + + // 遍历每一行 + for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { + const rowStartIndex = rowIndex * groupsPerRow; + const rowGroups = groups.slice( + rowStartIndex, + rowStartIndex + groupsPerRow, + ); + + // 计算当前行的起始X位置(居中) + const rowTotalWidth = + totalGroupsPerRow * gridWidth + + (totalGroupsPerRow - 1) * groupSpacing + + (groupsPerRow - 1) * dividerWidth; + const rowStartX = margin + (availableWidth - rowTotalWidth) / 2; + + let currentX = rowStartX; + + // 遍历当前行的每一组 + for ( + let groupIndex = 0; + groupIndex < rowGroups.length; + groupIndex++ + ) { + const group = rowGroups[groupIndex]; + + // 绘制填充的网格(左侧) + this.drawSingleGrid({ + ctx, + gridStartX: currentX, + gridStartY: currentY, + gridWidth, + gridHeight, + cellSize, + rows, + cols, + cells: group.filledCells, + config, + }); + + currentX += gridWidth + groupSpacing; + + // 绘制空的网格(右侧) + this.drawSingleGrid({ + ctx, + gridStartX: currentX, + gridStartY: currentY, + gridWidth, + gridHeight, + cellSize, + rows, + cols, + cells: [], // 空网格 + config, + }); + + currentX += gridWidth; + + // 如果不是最后一组,绘制垂直分割线 + if (groupIndex < rowGroups.length - 1) { + ctx.strokeStyle = '#000000'; + ctx.lineWidth = dividerWidth; + ctx.beginPath(); + ctx.moveTo(currentX, currentY); + ctx.lineTo(currentX, currentY + gridHeight); + ctx.stroke(); + currentX += dividerWidth; + } + } + + currentY += gridHeight + rowSpacing; + + // 如果不是最后一行,绘制水平分割线 + if (rowIndex < totalRows - 1) { + ctx.strokeStyle = '#000000'; + ctx.lineWidth = dividerWidth; + ctx.beginPath(); + ctx.moveTo(margin, currentY - rowSpacing / 2); + ctx.lineTo(canvasWidth - margin, currentY - rowSpacing / 2); + ctx.stroke(); + } + } + } + + /** + * 绘制单个网格 + */ + private drawSingleGrid(params: { + ctx: RenderingContext; + gridStartX: number; + gridStartY: number; + gridWidth: number; + gridHeight: number; + cellSize: number; + rows: number; + cols: number; + cells: GridCell[]; + config: GridConfig; + }): void { + const { + ctx, + gridStartX, + gridStartY, + gridWidth, + gridHeight, + cellSize, + rows, + cols, + cells, + config, + } = params; // 绘制网格线 ctx.strokeStyle = config.gridLineColor || '#000000'; diff --git a/miniprogram/mathPages/addition/addition.less b/miniprogram/mathPages/addition/addition.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/addition/addition.less +++ b/miniprogram/mathPages/addition/addition.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/compare/compare.less b/miniprogram/mathPages/compare/compare.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/compare/compare.less +++ b/miniprogram/mathPages/compare/compare.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/countMatch/countMatch.less b/miniprogram/mathPages/countMatch/countMatch.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/countMatch/countMatch.less +++ b/miniprogram/mathPages/countMatch/countMatch.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/countingSelect/countingSelect.less b/miniprogram/mathPages/countingSelect/countingSelect.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/countingSelect/countingSelect.less +++ b/miniprogram/mathPages/countingSelect/countingSelect.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/missingNumber/missingNumber.less b/miniprogram/mathPages/missingNumber/missingNumber.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/missingNumber/missingNumber.less +++ b/miniprogram/mathPages/missingNumber/missingNumber.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/numberDecompose/numberDecompose.less b/miniprogram/mathPages/numberDecompose/numberDecompose.less index f0ebb3c..e2b2a7b 100644 --- a/miniprogram/mathPages/numberDecompose/numberDecompose.less +++ b/miniprogram/mathPages/numberDecompose/numberDecompose.less @@ -1 +1 @@ -@import '../../base/baseDrawPage.less'; \ No newline at end of file +@import '../../base/baseDrawPage.wxss'; \ No newline at end of file diff --git a/miniprogram/mathPages/numberFind/numberFind.less b/miniprogram/mathPages/numberFind/numberFind.less index f9d5a8d..80d49ed 100644 --- a/miniprogram/mathPages/numberFind/numberFind.less +++ b/miniprogram/mathPages/numberFind/numberFind.less @@ -1,4 +1,4 @@ -@import '../../base/baseDrawPage.less'; +@import '../../base/baseDrawPage.wxss'; /* 数字选择区域 */ .number-selection-area { diff --git a/project.private.config.json b/project.private.config.json index 596629d..112bdf2 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -23,12 +23,19 @@ "condition": { "miniprogram": { "list": [ + { + "name": "focusPages/gridDrawing/gridDrawing", + "pathName": "focusPages/gridDrawing/gridDrawing", + "query": "id=grid-drawing", + "launchMode": "default", + "scene": null + }, { "name": "pages/shape/index", "pathName": "pages/shape/index", "query": "", - "scene": null, - "launchMode": "default" + "launchMode": "default", + "scene": null }, { "name": "pages/index/index", @@ -113,13 +120,6 @@ "query": "", "launchMode": "default", "scene": null - }, - { - "name": "pages/buttonTest/buttonTest", - "pathName": "pages/buttonTest/buttonTest", - "query": "", - "launchMode": "default", - "scene": null } ] }