feat:V2.5.3 图形符号配对
This commit is contained in:
@@ -27,7 +27,8 @@
|
|||||||
"pages": [
|
"pages": [
|
||||||
"gridDrawing/gridDrawing",
|
"gridDrawing/gridDrawing",
|
||||||
"shape/shape",
|
"shape/shape",
|
||||||
"positionColoring/positionColoring"
|
"positionColoring/positionColoring",
|
||||||
|
"shapeSymbol/shapeSymbol"
|
||||||
],
|
],
|
||||||
"independent": false
|
"independent": false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,48 @@ export const WATER_COLORS = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数字颜色映射(柔和12色)
|
||||||
|
* 用于数学页面的数字显示和涂色
|
||||||
|
*/
|
||||||
|
export const NUMBER_COLORS: Record<number, string> = {
|
||||||
|
1: '#ED6D50', // 柔和红色
|
||||||
|
2: '#F9A857', // 柔和橙色
|
||||||
|
3: '#FFE176', // 柔和黄色
|
||||||
|
4: '#B2D94B', // 柔和黄绿色
|
||||||
|
5: '#53D1B6', // 柔和绿色
|
||||||
|
6: '#6CD2EA', // 柔和青色
|
||||||
|
7: '#79A7ED', // 柔和蓝色
|
||||||
|
8: '#9C98DE', // 柔和紫色
|
||||||
|
9: '#F2A4C0', // 柔和粉色
|
||||||
|
10: '#FC9B6C', // 柔和橙红色
|
||||||
|
11: '#BC8F71', // 柔和棕色
|
||||||
|
12: '#666666', // 柔和黑灰
|
||||||
|
13: '#FF2222', // 鲜艳大红
|
||||||
|
14: '#FF9900', // 鲜艳橙色
|
||||||
|
15: '#FFD700', // 鲜艳金黄
|
||||||
|
16: '#32CD32', // 鲜艳亮绿
|
||||||
|
17: '#21C5E4', // 鲜艳青色
|
||||||
|
18: '#3777FF', // 鲜艳蓝色
|
||||||
|
19: '#9D27E3', // 鲜艳紫色
|
||||||
|
20: '#FF40A0', // 鲜艳玫粉
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有数字颜色数组
|
||||||
|
*/
|
||||||
|
export function getNumberColors(): string[] {
|
||||||
|
return Object.values(NUMBER_COLORS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机获取一个数字颜色
|
||||||
|
*/
|
||||||
|
export function getRandomNumberColor(): string {
|
||||||
|
const colors = getNumberColors();
|
||||||
|
return colors[Math.floor(Math.random() * colors.length)];
|
||||||
|
}
|
||||||
|
|
||||||
export const PAPER_SIZE = {
|
export const PAPER_SIZE = {
|
||||||
A4: {
|
A4: {
|
||||||
// width: 2480,
|
// width: 2480,
|
||||||
|
|||||||
@@ -16,6 +16,14 @@ export const FOCUS_FUNCTION_TYPES: FocusFunctionType[] = [
|
|||||||
desc: '识别形状,涂一涂',
|
desc: '识别形状,涂一涂',
|
||||||
icon: '🔍',
|
icon: '🔍',
|
||||||
},
|
},
|
||||||
|
// 图形符号配对
|
||||||
|
{
|
||||||
|
id: 'shape-symbol',
|
||||||
|
page: 'shapeSymbol',
|
||||||
|
title: '图形符号配对',
|
||||||
|
desc: '根据图形画对应的符号',
|
||||||
|
icon: '🔗',
|
||||||
|
},
|
||||||
// 方位涂涂乐
|
// 方位涂涂乐
|
||||||
{
|
{
|
||||||
id: 'position-coloring',
|
id: 'position-coloring',
|
||||||
|
|||||||
@@ -110,37 +110,3 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
|
|||||||
icon: '🔢',
|
icon: '🔢',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
|
||||||
* 数字颜色映射(柔和12色)
|
|
||||||
* 用于数学页面的数字显示和涂色
|
|
||||||
*/
|
|
||||||
export const NUMBER_COLORS: Record<number, string> = {
|
|
||||||
1: '#ED6D50', // 柔和红色
|
|
||||||
2: '#F9A857', // 柔和橙色
|
|
||||||
3: '#FFE176', // 柔和黄色
|
|
||||||
4: '#B2D94B', // 柔和黄绿色
|
|
||||||
5: '#53D1B6', // 柔和绿色
|
|
||||||
6: '#6CD2EA', // 柔和青色
|
|
||||||
7: '#79A7ED', // 柔和蓝色
|
|
||||||
8: '#9C98DE', // 柔和紫色
|
|
||||||
9: '#F2A4C0', // 柔和粉色
|
|
||||||
10: '#FC9B6C', // 柔和橙红色
|
|
||||||
11: '#BC8F71', // 柔和棕色
|
|
||||||
12: '#666666', // 柔和黑灰
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有数字颜色数组
|
|
||||||
*/
|
|
||||||
export function getNumberColors(): string[] {
|
|
||||||
return Object.values(NUMBER_COLORS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 随机获取一个数字颜色
|
|
||||||
*/
|
|
||||||
export function getRandomNumberColor(): string {
|
|
||||||
const colors = getNumberColors();
|
|
||||||
return colors[Math.floor(Math.random() * colors.length)];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "图形符号配对",
|
||||||
|
"navigationBarBackgroundColor": "#FFD719",
|
||||||
|
"homeButton": true,
|
||||||
|
"backgroundColor": "#F6F6F6",
|
||||||
|
"enablePullDownRefresh": false,
|
||||||
|
"usingComponents": {
|
||||||
|
"toy-button": "../../ui/button/button",
|
||||||
|
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||||
|
"math-bottom-buttons": "../../components/math-bottom-buttons/math-bottom-buttons"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
import ShapeSymbolDraw from '../shared/service/shapeSymbolDraw';
|
||||||
|
import { createFocusPage } from '../shared/common/focusPageMixin';
|
||||||
|
import { ShapeSymbolData } from '../shared/service/shapeSymbolDraw';
|
||||||
|
import { SHAPE_SYMBOL_SHAPES } from '../shared/shapes/shapeSymbolShapes';
|
||||||
|
import { getRandomNumberColor } from '../../constants/colors';
|
||||||
|
|
||||||
|
createFocusPage({
|
||||||
|
canvas: null as Canvas | null,
|
||||||
|
ctx: null as RenderingContext | null,
|
||||||
|
boxHeight: 0,
|
||||||
|
boxWidth: 0,
|
||||||
|
drawService: null as ShapeSymbolDraw | null,
|
||||||
|
gridData: null as ShapeSymbolData | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
pageTitle: '图形符号配对',
|
||||||
|
functionId: '',
|
||||||
|
hasContent: false,
|
||||||
|
showShareDialog: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
onLoad(options: { id?: string }) {
|
||||||
|
const functionId = options.id || 'shape-symbol';
|
||||||
|
this.setData({
|
||||||
|
functionId,
|
||||||
|
});
|
||||||
|
this.initPageInfo(functionId, '图形符号配对');
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
this.initCanvas({
|
||||||
|
createDrawService: (
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) => {
|
||||||
|
return new ShapeSymbolDraw(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 });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机生成
|
||||||
|
*/
|
||||||
|
onRandom() {
|
||||||
|
// 从9种图形中随机选择4个
|
||||||
|
const shuffled = [...SHAPE_SYMBOL_SHAPES].sort(
|
||||||
|
() => Math.random() - 0.5,
|
||||||
|
);
|
||||||
|
const selectedShapes = shuffled.slice(0, 4);
|
||||||
|
|
||||||
|
// 符号类型:加号、减号、乘号、对号
|
||||||
|
const symbols = ['+', '-', '×', '✓'];
|
||||||
|
// 随机打乱符号顺序
|
||||||
|
const shuffledSymbols = [...symbols].sort(() => Math.random() - 0.5);
|
||||||
|
|
||||||
|
// 创建图形和符号的映射关系(按位置对应),并为每个图形分配不同的颜色
|
||||||
|
const shapeColorMap = new Map<string, string>(); // 存储每个图形ID对应的颜色
|
||||||
|
const usedColors = new Set<string>(); // 记录已使用的颜色,确保4个图形颜色不同
|
||||||
|
const legendMapping: Array<{
|
||||||
|
shapeId: string;
|
||||||
|
symbol: string;
|
||||||
|
color: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const shape = selectedShapes[i];
|
||||||
|
|
||||||
|
// 为每个图形分配颜色,确保4个图形颜色都不相同
|
||||||
|
let color: string;
|
||||||
|
if (shapeColorMap.has(shape.id)) {
|
||||||
|
// 如果这个图形已经分配过颜色,使用之前的颜色
|
||||||
|
color = shapeColorMap.get(shape.id)!;
|
||||||
|
} else {
|
||||||
|
// 生成新颜色,确保不与已使用的颜色重复
|
||||||
|
do {
|
||||||
|
color = getRandomNumberColor();
|
||||||
|
} while (usedColors.has(color));
|
||||||
|
shapeColorMap.set(shape.id, color);
|
||||||
|
usedColors.add(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
legendMapping.push({
|
||||||
|
shapeId: shape.id,
|
||||||
|
symbol: shuffledSymbols[i],
|
||||||
|
color: color,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成练习区域的数据(6行7列,每两行一组)
|
||||||
|
const practiceRows: Array<
|
||||||
|
Array<{ shapeId: string | null; symbol: string | null }>
|
||||||
|
> = [];
|
||||||
|
for (let groupIndex = 0; groupIndex < 4; groupIndex++) {
|
||||||
|
// 每组第一行:随机展示图形
|
||||||
|
const shapeRow: Array<{
|
||||||
|
shapeId: string | null;
|
||||||
|
symbol: string | null;
|
||||||
|
}> = [];
|
||||||
|
for (let col = 0; col < 8; col++) {
|
||||||
|
// 随机选择一个图形(从选中的4个中选)
|
||||||
|
const randomShape =
|
||||||
|
selectedShapes[
|
||||||
|
Math.floor(Math.random() * selectedShapes.length)
|
||||||
|
];
|
||||||
|
shapeRow.push({ shapeId: randomShape.id, symbol: null });
|
||||||
|
}
|
||||||
|
practiceRows.push(shapeRow);
|
||||||
|
|
||||||
|
// 每组第二行:空着,让小朋友涂符号
|
||||||
|
const symbolRow: Array<{
|
||||||
|
shapeId: string | null;
|
||||||
|
symbol: string | null;
|
||||||
|
}> = [];
|
||||||
|
for (let col = 0; col < 8; col++) {
|
||||||
|
symbolRow.push({ shapeId: null, symbol: null });
|
||||||
|
}
|
||||||
|
practiceRows.push(symbolRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.gridData = {
|
||||||
|
legendMapping,
|
||||||
|
practiceRows,
|
||||||
|
shapeColorMap: Object.fromEntries(shapeColorMap), // 转换为普通对象传递给绘制服务
|
||||||
|
};
|
||||||
|
|
||||||
|
this.drawCanvas();
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 随机生成按钮 -->
|
||||||
|
<view class="random-button-area">
|
||||||
|
<toy-button
|
||||||
|
class="random-button"
|
||||||
|
type="primary"
|
||||||
|
bind:click="onRandom"
|
||||||
|
width="100%"
|
||||||
|
height="80rpx"
|
||||||
|
icon="refresh"
|
||||||
|
icon-class-prefix="toy-icon">
|
||||||
|
随机生成
|
||||||
|
</toy-button>
|
||||||
|
</view>
|
||||||
|
</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';
|
||||||
@@ -121,14 +121,7 @@ class PositionColoringDraw extends BaseDrawService {
|
|||||||
|
|
||||||
// 绘制虚线分隔
|
// 绘制虚线分隔
|
||||||
const dividerY = referenceGridY + gridHeight + 30;
|
const dividerY = referenceGridY + gridHeight + 30;
|
||||||
ctx.strokeStyle = '#999';
|
this.drawDashedDivider(dividerY, margin);
|
||||||
ctx.lineWidth = 1;
|
|
||||||
ctx.setLineDash([4, 4]); // 虚线
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(margin, dividerY);
|
|
||||||
ctx.lineTo(canvasWidth - margin, dividerY);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]); // 重置为实线
|
|
||||||
|
|
||||||
// 绘制任务区域(三行三列)
|
// 绘制任务区域(三行三列)
|
||||||
const taskStartY = dividerY + 30;
|
const taskStartY = dividerY + 30;
|
||||||
|
|||||||
@@ -0,0 +1,265 @@
|
|||||||
|
/**
|
||||||
|
* 图形符号配对绘制服务
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { BaseDrawService } from '../../../service/baseDraw';
|
||||||
|
import { drawShapeSymbolShape } from '../shapes/shapeSymbolShapes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图形符号配对数据
|
||||||
|
*/
|
||||||
|
export interface ShapeSymbolData {
|
||||||
|
/** 图例映射(4个图形和符号的对应关系) */
|
||||||
|
legendMapping: Array<{
|
||||||
|
shapeId: string;
|
||||||
|
symbol: string;
|
||||||
|
color: string;
|
||||||
|
}>;
|
||||||
|
/** 练习区域的行数据(6行,每两行一组) */
|
||||||
|
practiceRows: Array<
|
||||||
|
Array<{ shapeId: string | null; symbol: string | null }>
|
||||||
|
>;
|
||||||
|
/** 图形颜色映射 */
|
||||||
|
shapeColorMap: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图形符号配对绘制服务
|
||||||
|
*/
|
||||||
|
class ShapeSymbolDraw extends BaseDrawService {
|
||||||
|
gridData: ShapeSymbolData | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
super(canvas, ctx, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图形符号配对内容
|
||||||
|
*/
|
||||||
|
async draw(gridData: ShapeSymbolData) {
|
||||||
|
if (!gridData || !gridData.legendMapping || !gridData.practiceRows) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setPrintConfig();
|
||||||
|
this.gridData = gridData;
|
||||||
|
this.clear();
|
||||||
|
this.setPaper();
|
||||||
|
|
||||||
|
// 绘制Header
|
||||||
|
if (this.headerType !== 'minimal') {
|
||||||
|
await this.drawHeader();
|
||||||
|
} else {
|
||||||
|
this.drawMiniHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制内容区域
|
||||||
|
this.drawDivider();
|
||||||
|
this.drawContent({
|
||||||
|
ctx: this.ctx,
|
||||||
|
gridData: this.gridData,
|
||||||
|
canvasWidth: this.canvasWidth,
|
||||||
|
startY: this.currentY,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制内容区域
|
||||||
|
*/
|
||||||
|
private drawContent(params: {
|
||||||
|
ctx: RenderingContext;
|
||||||
|
gridData: ShapeSymbolData;
|
||||||
|
canvasWidth: number;
|
||||||
|
startY: number;
|
||||||
|
}): void {
|
||||||
|
const { ctx, gridData, canvasWidth } = params;
|
||||||
|
let { startY } = params;
|
||||||
|
|
||||||
|
startY = startY + 25;
|
||||||
|
|
||||||
|
const margin = 40; // 左右边距
|
||||||
|
const legendCols = 4; // 图例列数
|
||||||
|
const legendRows = 2; // 图例行数
|
||||||
|
const cellSize = 65; // 每个格子的大小
|
||||||
|
const gridWidth = cellSize * legendCols;
|
||||||
|
const gridHeight = cellSize * legendRows;
|
||||||
|
|
||||||
|
// 绘制参考网格(上面的九宫格)
|
||||||
|
const legendStartX =
|
||||||
|
margin + (canvasWidth - margin * 2 - gridWidth) / 2; // 居中
|
||||||
|
const legendStartY = startY;
|
||||||
|
|
||||||
|
// 绘制参考网格的网格线
|
||||||
|
this.drawGridLines(
|
||||||
|
ctx,
|
||||||
|
legendStartX,
|
||||||
|
legendStartY,
|
||||||
|
gridWidth,
|
||||||
|
gridHeight,
|
||||||
|
cellSize,
|
||||||
|
legendCols,
|
||||||
|
legendRows,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制图例内容
|
||||||
|
// 优化常量设置,减少重复计算,增强可维护性
|
||||||
|
// const legendColSpacing = 24; // 列间距(px),如需调整只此一处
|
||||||
|
// const legendCellHeight = cellSize; // 图例格子的高度,当前与cellSize一致
|
||||||
|
|
||||||
|
gridData.legendMapping.forEach((mapping, index: number) => {
|
||||||
|
if (!mapping) return;
|
||||||
|
|
||||||
|
const shapeX = legendStartX + index * cellSize + cellSize / 2; // 图片居中,假设图片大小50x50
|
||||||
|
const shapeY = legendStartY + cellSize / 2;
|
||||||
|
|
||||||
|
const shapeSize = 42;
|
||||||
|
const shapeColor = mapping.color || '#000';
|
||||||
|
drawShapeSymbolShape(
|
||||||
|
ctx,
|
||||||
|
mapping.shapeId,
|
||||||
|
shapeX,
|
||||||
|
shapeY,
|
||||||
|
shapeSize,
|
||||||
|
shapeColor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 第二行:绘制符号(使用 emoji)
|
||||||
|
const symbolX = legendStartX + index * cellSize + cellSize / 2; // 图片居中,假设图片大小50x50
|
||||||
|
const symbolY = legendStartY + cellSize + cellSize / 2 + 5;
|
||||||
|
this.drawSymbol(ctx, symbolX, symbolY, mapping.symbol, 30);
|
||||||
|
});
|
||||||
|
|
||||||
|
const dividerY = legendStartY + legendRows * cellSize + 30;
|
||||||
|
this.drawDashedDivider(dividerY, margin);
|
||||||
|
|
||||||
|
// 绘制练习区域(六行七列,使用网格线,确保正方形格子)
|
||||||
|
const practiceStartY = dividerY + 30;
|
||||||
|
const practiceCols = 8;
|
||||||
|
const practiceRows = 8;
|
||||||
|
const practiceCellSize = 60; // 练习格子大小(正方形)
|
||||||
|
const practiceGridWidth = practiceCellSize * practiceCols;
|
||||||
|
const practiceGridHeight = practiceCellSize * practiceRows;
|
||||||
|
|
||||||
|
// 计算练习区域起始X位置(居中)
|
||||||
|
const practiceStartX =
|
||||||
|
margin + (canvasWidth - margin * 2 - practiceGridWidth) / 2;
|
||||||
|
|
||||||
|
// 绘制练习区域网格线(使用 drawGridLines)
|
||||||
|
this.drawGridLines(
|
||||||
|
ctx,
|
||||||
|
practiceStartX,
|
||||||
|
practiceStartY,
|
||||||
|
practiceGridWidth,
|
||||||
|
practiceGridHeight,
|
||||||
|
practiceCellSize,
|
||||||
|
practiceCols,
|
||||||
|
practiceRows,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 绘制练习内容
|
||||||
|
for (let row = 0; row < practiceRows; row++) {
|
||||||
|
const practiceRow = gridData.practiceRows[row];
|
||||||
|
if (!practiceRow) continue;
|
||||||
|
|
||||||
|
const rowY = practiceStartY + row * practiceCellSize;
|
||||||
|
const cellCenterY = rowY + practiceCellSize / 2;
|
||||||
|
|
||||||
|
for (let col = 0; col < practiceCols; col++) {
|
||||||
|
const cell = practiceRow[col];
|
||||||
|
if (!cell) continue;
|
||||||
|
|
||||||
|
const colX = practiceStartX + col * practiceCellSize;
|
||||||
|
const cellCenterX = colX + practiceCellSize / 2;
|
||||||
|
|
||||||
|
// 如果第一行(图形行),绘制图形(使用相同颜色,无边框,居中)
|
||||||
|
if (row % 2 === 0 && cell.shapeId) {
|
||||||
|
const shapeSize = 42;
|
||||||
|
const shapeColor =
|
||||||
|
gridData.shapeColorMap[cell.shapeId] || '#000';
|
||||||
|
drawShapeSymbolShape(
|
||||||
|
ctx,
|
||||||
|
cell.shapeId,
|
||||||
|
cellCenterX,
|
||||||
|
cellCenterY,
|
||||||
|
shapeSize,
|
||||||
|
shapeColor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 第二行(符号行)留空,让小朋友涂符号
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制符号(使用 emoji 文字)
|
||||||
|
*/
|
||||||
|
private drawSymbol(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
symbol: string,
|
||||||
|
fontSize: number,
|
||||||
|
): void {
|
||||||
|
ctx.save();
|
||||||
|
|
||||||
|
// 符号映射:将符号转换为 emoji
|
||||||
|
const symbolMap: Record<string, string> = {
|
||||||
|
'+': '➕',
|
||||||
|
'-': '➖',
|
||||||
|
'×': '✖️',
|
||||||
|
'✓': '✔️',
|
||||||
|
};
|
||||||
|
|
||||||
|
const emojiSymbol = symbolMap[symbol] || symbol;
|
||||||
|
|
||||||
|
ctx.font = `bold ${fontSize}px Arial`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.fillStyle = '#000';
|
||||||
|
ctx.fillText(emojiSymbol, x, y);
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制网格线
|
||||||
|
*/
|
||||||
|
private drawGridLines(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
gridStartX: number,
|
||||||
|
gridStartY: number,
|
||||||
|
gridWidth: number,
|
||||||
|
gridHeight: number,
|
||||||
|
cellSize: number,
|
||||||
|
legendCols: number,
|
||||||
|
legendRows: number,
|
||||||
|
): void {
|
||||||
|
ctx.strokeStyle = '#000';
|
||||||
|
ctx.lineWidth = 1;
|
||||||
|
ctx.setLineDash([]); // 实线
|
||||||
|
|
||||||
|
// 绘制垂直线
|
||||||
|
for (let i = 0; i <= legendCols; i++) {
|
||||||
|
const x = gridStartX + i * cellSize;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, gridStartY);
|
||||||
|
ctx.lineTo(x, gridStartY + gridHeight);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绘制水平线
|
||||||
|
for (let i = 0; i <= legendRows; i++) {
|
||||||
|
const y = gridStartY + i * cellSize;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(gridStartX, y);
|
||||||
|
ctx.lineTo(gridStartX + gridWidth, y);
|
||||||
|
ctx.stroke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShapeSymbolDraw;
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* 图形符号配对专用的图形定义
|
||||||
|
* 所有图形保持大小一致
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ShapeSymbolShape {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SHAPE_SYMBOL_SHAPES: ShapeSymbolShape[] = [
|
||||||
|
{ id: 'circle', name: '圆形' },
|
||||||
|
{ id: 'square', name: '正方形' },
|
||||||
|
{ id: 'triangle', name: '三角形' },
|
||||||
|
{ id: 'diamond', name: '菱形' },
|
||||||
|
{ id: 'trapezoid', name: '梯形' },
|
||||||
|
{ id: 'ellipse', name: '椭圆' },
|
||||||
|
{ id: 'star', name: '五角星' },
|
||||||
|
{ id: 'pentagon', name: '五边形' },
|
||||||
|
{ id: 'heart', name: '心形' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图形(无边框,仅填充)
|
||||||
|
* 所有图形保持相同的大小
|
||||||
|
*/
|
||||||
|
export function drawShapeSymbolShape(
|
||||||
|
ctx: RenderingContext,
|
||||||
|
shapeId: string,
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
size: number,
|
||||||
|
fillColor: string,
|
||||||
|
): void {
|
||||||
|
ctx.save();
|
||||||
|
ctx.translate(x, y);
|
||||||
|
|
||||||
|
const radius = size / 2;
|
||||||
|
|
||||||
|
ctx.fillStyle = fillColor;
|
||||||
|
|
||||||
|
switch (shapeId) {
|
||||||
|
case 'circle':
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'square':
|
||||||
|
ctx.fillRect(-radius, -radius, size, size);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'triangle':
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -radius);
|
||||||
|
ctx.lineTo(-radius, radius);
|
||||||
|
ctx.lineTo(radius, radius);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'diamond':
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(0, -radius);
|
||||||
|
ctx.lineTo(radius, 0);
|
||||||
|
ctx.lineTo(0, radius);
|
||||||
|
ctx.lineTo(-radius, 0);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'trapezoid':
|
||||||
|
ctx.beginPath();
|
||||||
|
const topWidth = radius * 0.6;
|
||||||
|
ctx.moveTo(-topWidth, -radius * 0.7);
|
||||||
|
ctx.lineTo(topWidth, -radius * 0.7);
|
||||||
|
ctx.lineTo(radius, radius * 0.7);
|
||||||
|
ctx.lineTo(-radius, radius * 0.7);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ellipse':
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.ellipse(0, 0, radius * 0.8, radius * 0.6, 0, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'star':
|
||||||
|
// 五角星
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const angle1 = (i * 2 * Math.PI) / 5 - Math.PI / 2;
|
||||||
|
const angle2 = ((i + 0.5) * 2 * Math.PI) / 5 - Math.PI / 2;
|
||||||
|
const r1 = radius;
|
||||||
|
const r2 = radius * 0.4;
|
||||||
|
const x1 = Math.cos(angle1) * r1;
|
||||||
|
const y1 = Math.sin(angle1) * r1;
|
||||||
|
const x2 = Math.cos(angle2) * r2;
|
||||||
|
const y2 = Math.sin(angle2) * r2;
|
||||||
|
if (i === 0) {
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
} else {
|
||||||
|
ctx.lineTo(x1, y1);
|
||||||
|
}
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pentagon':
|
||||||
|
ctx.beginPath();
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2;
|
||||||
|
const px = Math.cos(angle) * radius;
|
||||||
|
const py = Math.sin(angle) * radius;
|
||||||
|
if (i === 0) ctx.moveTo(px, py);
|
||||||
|
else ctx.lineTo(px, py);
|
||||||
|
}
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'heart':
|
||||||
|
// 爱心路径(参考 shapes.ts 中的 SVG 路径)
|
||||||
|
// SVG路径:M50,35 C35,20 20,35 25,50 C30,65 50,80 50,80 C50,80 70,65 75,50 C80,35 65,20 50,35Z
|
||||||
|
// SVG中心为(50,50),高度约为60(从y=20到y=80)
|
||||||
|
// 使用scale=60使心形高度约为size,与其他图形保持一致
|
||||||
|
const scale = 60;
|
||||||
|
function tx(px: number) {
|
||||||
|
return ((px - 50) / scale) * size;
|
||||||
|
}
|
||||||
|
function ty(py: number) {
|
||||||
|
return ((py - 50) / scale) * size;
|
||||||
|
}
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(tx(50), ty(35));
|
||||||
|
ctx.bezierCurveTo(tx(35), ty(20), tx(20), ty(35), tx(25), ty(50));
|
||||||
|
ctx.bezierCurveTo(tx(30), ty(65), tx(50), ty(80), tx(50), ty(80));
|
||||||
|
ctx.bezierCurveTo(tx(50), ty(80), tx(70), ty(65), tx(75), ty(50));
|
||||||
|
ctx.bezierCurveTo(tx(80), ty(35), tx(65), ty(20), tx(50), ty(35));
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// 默认绘制圆形
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(0, 0, radius, 0, Math.PI * 2);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getRandomNumberColor } from '../../constants/mathFunctions';
|
import { getRandomNumberColor } from '../../constants/colors';
|
||||||
import MissingNumberDraw from '../shared/service/missingNumberDraw';
|
import MissingNumberDraw from '../shared/service/missingNumberDraw';
|
||||||
import {
|
import {
|
||||||
createMathPage,
|
createMathPage,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getImage } from '../../../utils/index';
|
import { getImage } from '../../../utils/index';
|
||||||
import { getRandomNumberColor } from '../../../constants/mathFunctions';
|
import { getRandomNumberColor } from '../../../constants/colors';
|
||||||
|
|
||||||
interface DrawCountMatchContentParams {
|
interface DrawCountMatchContentParams {
|
||||||
canvas: WechatMiniprogram.Canvas;
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getImage } from '../../../utils/index';
|
import { getImage } from '../../../utils/index';
|
||||||
import { getRandomNumberColor } from '../../../constants/mathFunctions';
|
import { getRandomNumberColor } from '../../../constants/colors';
|
||||||
|
|
||||||
interface DrawCountingSelectContentParams {
|
interface DrawCountingSelectContentParams {
|
||||||
canvas: WechatMiniprogram.Canvas;
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getNumberColors } from '../../../constants/mathFunctions';
|
import { getNumberColors } from '../../../constants/colors';
|
||||||
|
|
||||||
interface DrawNumberColorContentParams {
|
interface DrawNumberColorContentParams {
|
||||||
canvas: WechatMiniprogram.Canvas;
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getImage } from '../../../utils/index';
|
import { getImage } from '../../../utils/index';
|
||||||
import { getRandomNumberColor } from '../../../constants/mathFunctions';
|
import { getRandomNumberColor } from '../../../constants/colors';
|
||||||
|
|
||||||
interface DrawNumberDecomposeContentParams {
|
interface DrawNumberDecomposeContentParams {
|
||||||
canvas: WechatMiniprogram.Canvas;
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { getImage } from '../../../utils/index';
|
import { getImage } from '../../../utils/index';
|
||||||
import { NUMBER_COLORS } from '../../../constants/mathFunctions';
|
import { NUMBER_COLORS } from '../../../constants/colors';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 数字到英文单词的映射
|
* 数字到英文单词的映射
|
||||||
|
|||||||
@@ -146,4 +146,29 @@ export class BaseDrawService {
|
|||||||
|
|
||||||
this.currentY = dividerY + 10; // 分割线下方10px间距
|
this.currentY = dividerY + 10; // 分割线下方10px间距
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制虚线分割线(逻辑像素,尺寸除以3)
|
||||||
|
* @param y 分割线的Y坐标
|
||||||
|
* @param margin 左右边距,默认为40
|
||||||
|
* @param color 线条颜色,默认为'#999'
|
||||||
|
* @param lineWidth 线条宽度,默认为1
|
||||||
|
*/
|
||||||
|
drawDashedDivider(
|
||||||
|
y: number,
|
||||||
|
margin: number = 40,
|
||||||
|
color: string = '#999',
|
||||||
|
lineWidth: number = 1,
|
||||||
|
) {
|
||||||
|
const { ctx, canvasWidth } = this;
|
||||||
|
|
||||||
|
ctx.strokeStyle = color;
|
||||||
|
ctx.lineWidth = lineWidth;
|
||||||
|
ctx.setLineDash([4, 4]); // 虚线
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(margin, y);
|
||||||
|
ctx.lineTo(canvasWidth - margin, y);
|
||||||
|
ctx.stroke();
|
||||||
|
ctx.setLineDash([]); // 重置为实线
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,12 +23,19 @@
|
|||||||
"condition": {
|
"condition": {
|
||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "focusPages/shapeSymbol/shapeSymbol",
|
||||||
|
"pathName": "focusPages/shapeSymbol/shapeSymbol",
|
||||||
|
"query": "id=shape-symbol",
|
||||||
|
"scene": null,
|
||||||
|
"launchMode": "default"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "focusPages/positionColoring/positionColoring",
|
"name": "focusPages/positionColoring/positionColoring",
|
||||||
"pathName": "focusPages/positionColoring/positionColoring",
|
"pathName": "focusPages/positionColoring/positionColoring",
|
||||||
"query": "id=position-coloring",
|
"query": "id=position-coloring",
|
||||||
"scene": null,
|
"launchMode": "default",
|
||||||
"launchMode": "default"
|
"scene": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pages/focusIndex/focusIndex",
|
"name": "pages/focusIndex/focusIndex",
|
||||||
|
|||||||
Reference in New Issue
Block a user