From 78d98164e59fbbfafdaf83ca8c0491e88a8a0765 Mon Sep 17 00:00:00 2001 From: R524809 Date: Fri, 19 Dec 2025 10:34:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:V2.5.5=E6=96=B9=E6=A0=BC=E6=8E=A8=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/app.json | 3 +- miniprogram/constants/focusFunctions.ts | 8 + .../focusPages/gridDrawing/gridDrawing.wxml | 3 +- .../gridReasoning/gridReasoning.json | 12 + .../focusPages/gridReasoning/gridReasoning.ts | 261 ++++++++++++++++++ .../gridReasoning/gridReasoning.wxml | 31 +++ .../gridReasoning/gridReasoning.wxss | 1 + .../shared/service/gridReasoningDraw.ts | 226 +++++++++++++++ miniprogram/service/baseDraw.ts | 99 +++++++ project.private.config.json | 11 +- 10 files changed, 651 insertions(+), 4 deletions(-) create mode 100644 miniprogram/focusPages/gridReasoning/gridReasoning.json create mode 100644 miniprogram/focusPages/gridReasoning/gridReasoning.ts create mode 100644 miniprogram/focusPages/gridReasoning/gridReasoning.wxml create mode 100644 miniprogram/focusPages/gridReasoning/gridReasoning.wxss create mode 100644 miniprogram/focusPages/shared/service/gridReasoningDraw.ts diff --git a/miniprogram/app.json b/miniprogram/app.json index b372da1..413296e 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -31,7 +31,8 @@ "shapeSymbol/shapeSymbol", "colorPattern/colorPattern", "matchConnect/matchConnect", - "lineRecognition/lineRecognition" + "lineRecognition/lineRecognition", + "gridReasoning/gridReasoning" ], "independent": false } diff --git a/miniprogram/constants/focusFunctions.ts b/miniprogram/constants/focusFunctions.ts index 9fbe576..da33173 100644 --- a/miniprogram/constants/focusFunctions.ts +++ b/miniprogram/constants/focusFunctions.ts @@ -56,6 +56,14 @@ export const FOCUS_FUNCTION_TYPES: FocusFunctionType[] = [ desc: '认识不同线条,画出颜色对应的线条', icon: '📏', }, + // 方格推理 + { + id: 'grid-reasoning', + page: 'gridReasoning', + title: '方格推理', + desc: '仔细观察,推理出合并方格并连线', + icon: '🧩', + }, // 格子仿画 { id: 'grid-drawing-3x3', diff --git a/miniprogram/focusPages/gridDrawing/gridDrawing.wxml b/miniprogram/focusPages/gridDrawing/gridDrawing.wxml index 49dc28f..bd1d15c 100644 --- a/miniprogram/focusPages/gridDrawing/gridDrawing.wxml +++ b/miniprogram/focusPages/gridDrawing/gridDrawing.wxml @@ -17,8 +17,9 @@ type-actions="{{typeActions}}" bind:select="onSelectType" bind:random="onRandom" /> + - + , + ) => { + return new GridReasoningDraw(canvas, ctx, options); + }, + drawServiceOptions: { + title: this.data.pageTitle, + subTitle: '仔细观察,推理出合并方格并连线', + functionId: this.data.functionId, + }, + onCanvasReady: () => { + // 初始随机生成 + this.onRandom(); + }, + }); + }, + + /** + * 绘制Canvas内容 + */ + async drawCanvas() { + if (!this.ctx || !this.drawService || !this.gridData) { + return; + } + + try { + await this.drawService.draw(this.gridData); + this.setData({ hasContent: true }); + } catch (error) { + console.error('绘制失败:', error); + this.setData({ hasContent: false }); + } + }, + + /** + * 选择运算类型 + */ + onSelectType(event: any) { + const { name, value } = event.detail; + this.setData({ + currentOperatorType: value, + currentOperatorTypeName: name, + }); + // 重新生成题目 + this.onRandom(); + }, + + /** + * 随机生成 + */ + onRandom() { + // 随机选择一种颜色 + const colorKeys = Object.keys(NUMBER_COLORS).map(Number); + const randomColorKey = + colorKeys[Math.floor(Math.random() * colorKeys.length)]; + const selectedColor = NUMBER_COLORS[randomColorKey]; + + const rows: Array<{ + grid1: GridPosition[]; + grid2: GridPosition[]; + operator: '+' | '-'; + result: GridPosition[]; + }> = []; + + const operatorType = this.data.currentOperatorType; + + // 生成5行的数据 + for (let i = 0; i < 5; i++) { + // 根据选中的运算类型选择运算符 + let operator: '+' | '-'; + if (operatorType === 'addition') { + operator = '+'; + } else if (operatorType === 'subtraction') { + operator = '-'; + } else { + // 混合运算:随机选择运算符 + operator = Math.random() > 0.5 ? '+' : '-'; + } + + // 生成第一个网格的涂色位置 + // 如果是减法运算,第一个网格至少要有2个位置 + let grid1Count: number; + if (operator === '-') { + grid1Count = Math.floor(Math.random() * 8) + 2; // 2-9个位置 + } else { + grid1Count = Math.floor(Math.random() * 9) + 1; // 1-9个位置 + } + const grid1Positions = this.generateRandomPositions(grid1Count); + + let grid2Positions: GridPosition[]; + let resultPositions: GridPosition[]; + + if (operator === '+') { + // 加法:生成第二个网格的涂色位置(至少1个) + const grid2Count = Math.floor(Math.random() * 9) + 1; + grid2Positions = this.generateRandomPositions(grid2Count); + + // 结果 = 并集 + resultPositions = this.unionPositions( + grid1Positions, + grid2Positions, + ); + } else { + // 减法:生成第二个网格的涂色位置(必须少于第一个) + const maxGrid2Count = grid1Count - 1; + const grid2Count = + Math.floor(Math.random() * maxGrid2Count) + 1; + grid2Positions = this.generateRandomPositions(grid2Count); + + // 结果 = 差集 + resultPositions = this.subtractPositions( + grid1Positions, + grid2Positions, + ); + } + + rows.push({ + grid1: grid1Positions, + grid2: grid2Positions, + operator: operator as '+' | '-', + result: resultPositions, + }); + } + + // 生成右侧答案选项 + // 右侧展示左侧5行的结果(result),顺序随机 + const answerOptions: Array<{ + positions: GridPosition[]; + rowIndex: number; + }> = []; + + for (let i = 0; i < 5; i++) { + answerOptions.push({ + positions: rows[i].result, + rowIndex: i, + }); + } + + // 随机打乱顺序 + answerOptions.sort(() => Math.random() - 0.5); + + this.gridData = { + rows, + answerOptions, + color: selectedColor, + }; + + this.drawCanvas(); + }, + + /** + * 生成随机位置(不重复) + */ + generateRandomPositions(count: number): GridPosition[] { + const allPositions: GridPosition[] = []; + for (let y = 0; y < 3; y++) { + for (let x = 0; x < 3; x++) { + allPositions.push({ x, y }); + } + } + + // 随机打乱 + const shuffled = [...allPositions].sort(() => Math.random() - 0.5); + + // 返回前count个 + return shuffled.slice(0, count); + }, + + /** + * 计算位置的并集 + */ + unionPositions(pos1: GridPosition[], pos2: GridPosition[]): GridPosition[] { + const set = new Set(); + const result: GridPosition[] = []; + + // 添加第一个数组的位置 + for (const pos of pos1) { + const key = `${pos.x},${pos.y}`; + if (!set.has(key)) { + set.add(key); + result.push(pos); + } + } + + // 添加第二个数组的位置(去重) + for (const pos of pos2) { + const key = `${pos.x},${pos.y}`; + if (!set.has(key)) { + set.add(key); + result.push(pos); + } + } + + return result; + }, + + /** + * 计算位置的差集(pos1 - pos2) + */ + subtractPositions( + pos1: GridPosition[], + pos2: GridPosition[], + ): GridPosition[] { + const set2 = new Set(); + for (const pos of pos2) { + set2.add(`${pos.x},${pos.y}`); + } + + const result: GridPosition[] = []; + for (const pos of pos1) { + const key = `${pos.x},${pos.y}`; + if (!set2.has(key)) { + result.push(pos); + } + } + + return result; + }, +}); diff --git a/miniprogram/focusPages/gridReasoning/gridReasoning.wxml b/miniprogram/focusPages/gridReasoning/gridReasoning.wxml new file mode 100644 index 0000000..f63ead0 --- /dev/null +++ b/miniprogram/focusPages/gridReasoning/gridReasoning.wxml @@ -0,0 +1,31 @@ + + + 预览打印效果 + + + + + + + + + + + + + + diff --git a/miniprogram/focusPages/gridReasoning/gridReasoning.wxss b/miniprogram/focusPages/gridReasoning/gridReasoning.wxss new file mode 100644 index 0000000..ddc41cc --- /dev/null +++ b/miniprogram/focusPages/gridReasoning/gridReasoning.wxss @@ -0,0 +1 @@ +@import '../../base/baseDrawPage.wxss'; diff --git a/miniprogram/focusPages/shared/service/gridReasoningDraw.ts b/miniprogram/focusPages/shared/service/gridReasoningDraw.ts new file mode 100644 index 0000000..d9fea61 --- /dev/null +++ b/miniprogram/focusPages/shared/service/gridReasoningDraw.ts @@ -0,0 +1,226 @@ +/** + * 方格推理绘制服务 + */ + +import { BaseDrawService } from '../../../service/baseDraw'; + +/** + * 3x3网格中的位置坐标 + */ +export interface GridPosition { + x: number; // 0-2 + y: number; // 0-2 +} + +/** + * 方格推理数据 + */ +export interface GridReasoningData { + /** 每行的数据 */ + rows: Array<{ + /** 第一个方格涂色位置 */ + grid1: GridPosition[]; + /** 第二个方格涂色位置 */ + grid2: GridPosition[]; + /** 运算符:+ 或 - */ + operator: '+' | '-'; + /** 结果方格涂色位置 */ + result: GridPosition[]; + }>; + /** 右侧答案选项(随机排列的方格) */ + answerOptions: Array<{ + /** 方格涂色位置 */ + positions: GridPosition[]; + /** 对应的行索引(用于匹配答案) */ + rowIndex: number; + }>; + /** 使用的颜色 */ + color: string; +} + +/** + * 方格推理绘制服务 + */ +class GridReasoningDraw extends BaseDrawService { + gridData: GridReasoningData | null = null; + + constructor( + canvas: Canvas, + ctx: RenderingContext, + options?: Record, + ) { + super(canvas, ctx, options); + } + + /** + * 绘制方格推理内容 + */ + async draw(gridData: GridReasoningData) { + if (!gridData || !gridData.rows || !gridData.answerOptions) { + return; + } + + this.setPrintConfig(); + this.gridData = gridData; + this.clear(); + this.setPaper(); + + // 绘制Header + if (this.headerType !== 'minimal') { + await this.drawHeader(); + } else { + this.drawMiniHeader(); + } + + // 绘制内容区域 + this.drawDivider(); + await this.drawContent({ + canvas: this.canvas, + ctx: this.ctx, + gridData: this.gridData, + canvasWidth: this.canvasWidth, + startY: this.currentY, + }); + } + + /** + * 绘制内容区域 + */ + private async drawContent(params: { + canvas: WechatMiniprogram.Canvas; + ctx: RenderingContext; + gridData: GridReasoningData; + canvasWidth: number; + startY: number; + }): Promise { + const { ctx, gridData, canvasWidth } = params; + let { startY } = params; + + startY = startY + 30; + + const margin = 40; // 左右边距 + const gridSize = 3; // 3x3网格 + const cellSize = 32; // 每个格子的宽度 + const gridWidth = cellSize * gridSize; + const gridHeight = cellSize * gridSize; + + // 左侧区域宽度(两个网格 + 运算符 + 间距) + const gridSpacing = 15; // 两个网格之间的间距 + const operatorWidth = 40; // 运算符宽度 + const dotRadius = 7; // 圆点半径 + const dotSpacing = 20; // 圆点与网格的间距 + + // 右侧区域宽度(答案选项网格) + const rightAreaWidth = gridWidth; + const rightAreaStartX = canvasWidth - margin - rightAreaWidth; + + // 行间距 + const rowSpacing = 38; + + // 打乱答案选项的顺序,实现随机乱序 + const shuffledAnswers = [...gridData.answerOptions].sort( + () => Math.random() - 0.5, + ); + + // 绘制5行 + for (let rowIndex = 0; rowIndex < 5; rowIndex++) { + const rowData = gridData.rows[rowIndex]; + if (!rowData) continue; + + const rowY = startY + rowIndex * (gridHeight + rowSpacing); + + // 绘制左侧第一个网格 + const grid1X = margin; + this.drawGrid( + ctx, + grid1X, + rowY, + cellSize, + gridSize, + rowData.grid1, + gridData.color, + ); + + // 绘制运算符 + const operatorStartX = grid1X + gridWidth + gridSpacing; + const operatorX = operatorStartX + operatorWidth / 2; + const operatorY = rowY + gridHeight / 2; + const operatorSize = 30; // 运算符大小 + this.drawSymbol( + operatorX, + operatorY, + rowData.operator, + operatorSize, + ); + + // 绘制左侧第二个网格 + const grid2X = operatorStartX + operatorWidth + gridSpacing; + this.drawGrid( + ctx, + grid2X, + rowY, + cellSize, + gridSize, + rowData.grid2, + gridData.color, + ); + + // 绘制左侧圆点(垂直居中) + const leftDotX = grid2X + gridWidth + dotSpacing; + const leftDotY = rowY + gridHeight / 2; + this.drawDot(ctx, leftDotX, leftDotY, dotRadius); + + // 绘制右侧答案选项(随机乱序) + const answerData = shuffledAnswers[rowIndex]; + if (answerData) { + // 绘制右侧圆点(垂直居中) + const rightDotX = rightAreaStartX - dotSpacing - dotRadius * 2; + const rightDotY = rowY + gridHeight / 2; + this.drawDot(ctx, rightDotX, rightDotY, dotRadius); + + // 绘制答案网格 + this.drawGrid( + ctx, + rightAreaStartX, + rowY, + cellSize, + gridSize, + answerData.positions, + gridData.color, + ); + } + } + } + + /** + * 绘制3x3网格 + */ + private drawGrid( + ctx: RenderingContext, + x: number, + y: number, + cellSize: number, + gridSize: number, + filledPositions: GridPosition[], + fillColor: string, + ) { + // 绘制网格线 + this.drawGridLines(x, y, cellSize, cellSize, gridSize, gridSize); + + // 绘制填充的格子 + ctx.fillStyle = fillColor; + for (const pos of filledPositions) { + const cellX = x + pos.x * cellSize; + const cellY = y + pos.y * cellSize; + ctx.fillRect(cellX, cellY, cellSize, cellSize); + } + + // 重新绘制网格线(确保在填充色之上) + ctx.strokeStyle = '#000'; + ctx.lineWidth = 1; + ctx.setLineDash([]); + this.drawGridLines(x, y, cellSize, cellSize, gridSize, gridSize); + } +} + +export default GridReasoningDraw; diff --git a/miniprogram/service/baseDraw.ts b/miniprogram/service/baseDraw.ts index 77f8574..76d4c91 100644 --- a/miniprogram/service/baseDraw.ts +++ b/miniprogram/service/baseDraw.ts @@ -282,4 +282,103 @@ export class BaseDrawService { // 重置为实线(避免影响后续绘制) ctx.setLineDash([]); } + + /** + * 绘制符号(使用路径绘制) + * @param x 符号中心X坐标 + * @param y 符号中心Y坐标 + * @param symbol 符号类型:'+', '-', '×', '✓' + * @param size 符号大小 + */ + drawSymbol(x: number, y: number, symbol: string, size: number): void { + const { ctx } = this; + ctx.save(); + ctx.translate(x, y); + + const lineWidth = size * 0.15; // 线条宽度 + const halfSize = size / 2; + const strokeLength = halfSize * 0.7; // 线条长度 + + ctx.strokeStyle = '#000'; + ctx.lineWidth = lineWidth; + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + + switch (symbol) { + case '+': + // 加号:横线和竖线 + ctx.beginPath(); + // 横线 + ctx.moveTo(-strokeLength, 0); + ctx.lineTo(strokeLength, 0); + // 竖线 + ctx.moveTo(0, -strokeLength); + ctx.lineTo(0, strokeLength); + ctx.stroke(); + break; + + case '-': + // 减号:横线 + ctx.beginPath(); + ctx.moveTo(-strokeLength, 0); + ctx.lineTo(strokeLength, 0); + ctx.stroke(); + break; + + case '×': + // 乘号:两条斜线 + ctx.beginPath(); + // 左上到右下 + ctx.moveTo(-strokeLength * 0.7, -strokeLength * 0.7); + ctx.lineTo(strokeLength * 0.7, strokeLength * 0.7); + // 右上到左下 + ctx.moveTo(strokeLength * 0.7, -strokeLength * 0.7); + ctx.lineTo(-strokeLength * 0.7, strokeLength * 0.7); + ctx.stroke(); + break; + + case '✓': + // 对号:勾,整体更大,右侧的线更长 + const checkScale = 1.2; // 对号整体放大1.2倍 + ctx.beginPath(); + const checkStartX = -strokeLength * 0.5 * checkScale; + const checkStartY = -strokeLength * 0.2 * checkScale; + const checkMidX = -strokeLength * 0.1 * checkScale; + const checkMidY = strokeLength * 0.3 * checkScale; + const checkEndX = strokeLength * 1.0 * checkScale; // 增加右侧长度 + const checkEndY = -strokeLength * 0.4 * checkScale; // 稍微向上调整 + ctx.moveTo(checkStartX, checkStartY); + ctx.lineTo(checkMidX, checkMidY); + ctx.lineTo(checkEndX, checkEndY); + ctx.stroke(); + break; + + default: + // 默认绘制加号 + ctx.beginPath(); + ctx.moveTo(-strokeLength, 0); + ctx.lineTo(strokeLength, 0); + ctx.moveTo(0, -strokeLength); + ctx.lineTo(0, strokeLength); + ctx.stroke(); + } + + ctx.restore(); + } + + /** + * 绘制圆点 + */ + drawDot( + ctx: RenderingContext, + x: number, + y: number, + radius: number, + fillColor?: string, + ) { + ctx.fillStyle = fillColor ?? '#93D333'; + ctx.beginPath(); + ctx.arc(x, y, radius, 0, Math.PI * 2); + ctx.fill(); + } } diff --git a/project.private.config.json b/project.private.config.json index 34fed26..a222ea1 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -23,12 +23,19 @@ "condition": { "miniprogram": { "list": [ + { + "name": "focusPages/gridReasoning/gridReasoning", + "pathName": "focusPages/gridReasoning/gridReasoning", + "query": "id=grid-reasoning", + "scene": null, + "launchMode": "default" + }, { "name": "focusPages/lineRecognition/lineRecognition", "pathName": "focusPages/lineRecognition/lineRecognition", "query": "id=line-recognition", - "scene": null, - "launchMode": "default" + "launchMode": "default", + "scene": null }, { "name": "focusPages/matchConnect/matchConnect",