feat:V2.5.2 图形符号配对

This commit is contained in:
R524809
2025-12-17 10:43:56 +08:00
parent d1a41602a7
commit b534972355
19 changed files with 718 additions and 51 deletions
@@ -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;