feat:V2.5.3 图形符号配对
This commit is contained in:
@@ -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;
|
||||
Reference in New Issue
Block a user