feat:V2.5.5方格推理

This commit is contained in:
R524809
2025-12-19 10:34:30 +08:00
parent 99686e30bd
commit 78d98164e5
10 changed files with 651 additions and 4 deletions
@@ -17,8 +17,9 @@
type-actions="{{typeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
<ad-custom unit-id="adunit-89cff2de998f1146"></ad-custom>
</view>
<ad-custom unit-id="adunit-89cff2de998f1146"></ad-custom>
<view class="empty"></view>
</view>
<math-bottom-buttons
@@ -0,0 +1,12 @@
{
"navigationBarTitleText": "方格推理",
"navigationBarBackgroundColor": "#FFD719",
"homeButton": true,
"backgroundColor": "#F6F6F6",
"enablePullDownRefresh": false,
"usingComponents": {
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-bottom-buttons": "../../components/math-bottom-buttons/math-bottom-buttons",
"math-type-selector": "../../components/math-type-selector/math-type-selector"
}
}
@@ -0,0 +1,261 @@
import GridReasoningDraw from '../shared/service/gridReasoningDraw';
import { createFocusPage } from '../shared/common/focusPageMixin';
import {
GridReasoningData,
GridPosition,
} from '../shared/service/gridReasoningDraw';
import { NUMBER_COLORS } from '../../constants/colors';
createFocusPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as GridReasoningDraw | null,
gridData: null as GridReasoningData | null,
data: {
pageTitle: '方格推理',
functionId: '',
hasContent: false,
showShareDialog: false,
currentOperatorType: 'mixed', // 'addition', 'subtraction', 'mixed'
currentOperatorTypeName: '混合运算',
operatorTypeActions: [
{ name: '加法运算', value: 'addition' },
{ name: '减法运算', value: 'subtraction' },
{ name: '混合运算', value: 'mixed' },
],
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'grid-reasoning';
this.setData({
functionId,
});
this.initPageInfo(functionId, '方格推理');
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
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<string>();
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<string>();
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;
},
});
@@ -0,0 +1,31 @@
<view class="page-container">
<view class="wrapper">
<text class="wrapper-title">预览打印效果</text>
<!-- 预览打印效果 -->
<view id="canvasWrapper" class="canvas-wrapper">
<canvas
type="2d"
id="canvasContent"
class="canvas-content"
style="width:{{boxWidth}}px;height:{{boxHeight}}px" />
</view>
<!-- 类型选择器和随机生成按钮 -->
<math-type-selector
current-type-name="{{currentOperatorTypeName}}"
type-actions="{{operatorTypeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
<ad-custom unit-id="adunit-89cff2de998f1146"></ad-custom>
</view>
<view class="empty"></view>
</view>
<math-bottom-buttons
disabled="{{!hasContent}}"
bind:share="onShareAppMessage"
bind:export="exportToPrint" />
<share-guide-popup
show="{{showShareDialog}}"
bind:onClose="onCloseShareDialog"
bind:onShareSuccess="onShareSuccess" />
@@ -0,0 +1 @@
@import '../../base/baseDrawPage.wxss';
@@ -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<string, any>,
) {
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<void> {
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;