276 lines
8.4 KiB
TypeScript
276 lines
8.4 KiB
TypeScript
/**
|
|
* 格子仿画绘制服务
|
|
* 合并了基础绘制和内容绘制逻辑
|
|
*/
|
|
|
|
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
|
import { GridCell, GridConfig } from '../types/gridTypes';
|
|
|
|
/**
|
|
* 格子组数据(一组包含一个填充的网格和一个空的网格)
|
|
*/
|
|
export interface GridGroup {
|
|
/** 填充的格子数据 */
|
|
filledCells: GridCell[];
|
|
/** 空的格子(用于仿画) */
|
|
emptyCells: GridCell[]; // 始终为空数组,仅用于标识
|
|
}
|
|
|
|
/**
|
|
* 格子仿画数据
|
|
*/
|
|
export interface GridDrawingData {
|
|
config: GridConfig;
|
|
/** 格子组数组,每组包含一个填充的网格和一个空的网格 */
|
|
groups: GridGroup[];
|
|
/** 每行展示的组数 */
|
|
groupsPerRow: number;
|
|
/** 总行数 */
|
|
totalRows: number;
|
|
}
|
|
|
|
/**
|
|
* 格子仿画绘制服务
|
|
*/
|
|
class GridDraw extends BaseDrawService {
|
|
gridData: GridDrawingData | null = null;
|
|
|
|
constructor(
|
|
canvas: Canvas,
|
|
ctx: RenderingContext,
|
|
options?: Record<string, any>,
|
|
) {
|
|
super(canvas, ctx, options);
|
|
}
|
|
|
|
/**
|
|
* 绘制格子仿画内容
|
|
*/
|
|
async draw(gridData: GridDrawingData) {
|
|
if (!gridData || !gridData.groups || gridData.groups.length === 0) {
|
|
return;
|
|
}
|
|
|
|
this.gridData = gridData;
|
|
this.prepareDraw();
|
|
await this.drawHeaderAndDivider();
|
|
await this.drawGridContent({
|
|
canvas: this.canvas,
|
|
ctx: this.ctx,
|
|
gridData: this.gridData,
|
|
canvasWidth: this.canvasWidth,
|
|
startY: this.currentY,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 绘制网格内容
|
|
*/
|
|
private async drawGridContent(params: {
|
|
canvas: WechatMiniprogram.Canvas;
|
|
ctx: RenderingContext;
|
|
gridData: GridDrawingData;
|
|
canvasWidth: number;
|
|
startY: number;
|
|
}): Promise<void> {
|
|
const { ctx, gridData, canvasWidth } = params;
|
|
let { startY } = params;
|
|
const { config, groups, groupsPerRow, totalRows } = gridData;
|
|
const { rows, cols } = config;
|
|
|
|
startY = startY + 30;
|
|
|
|
// 计算网格尺寸
|
|
const margin = 40; // 左右边距
|
|
const gridSpacing = 20; // 填充网格和空网格之间的间距
|
|
const groupSpacing = 40; // 两组之间的间距(一行中两组格子仿画中间)
|
|
const rowSpacing = 50; // 行之间的间距
|
|
const dividerWidth = 1; // 分割线宽度
|
|
|
|
// 计算每组网格的尺寸
|
|
const availableWidth = canvasWidth - margin * 2;
|
|
const totalGridsPerRow = groupsPerRow * 2; // 每组包含填充和空两个网格
|
|
const gridWidth =
|
|
(availableWidth -
|
|
(groupsPerRow - 1) * groupSpacing -
|
|
(totalGridsPerRow - 1) * gridSpacing) /
|
|
totalGridsPerRow;
|
|
const cellSize = Math.floor(gridWidth / cols);
|
|
const actualGridWidth = cellSize * cols;
|
|
const gridHeight = cellSize * rows;
|
|
|
|
const firstRowStartY = startY + 30; // 顶部间距
|
|
const lastRowEndY =
|
|
firstRowStartY +
|
|
totalRows * gridHeight +
|
|
(totalRows - 1) * rowSpacing;
|
|
|
|
// 计算第一行的起始X位置(居中)
|
|
const rowTotalWidth =
|
|
totalGridsPerRow * actualGridWidth +
|
|
(totalGridsPerRow - 1) * gridSpacing +
|
|
(groupsPerRow - 1) * groupSpacing;
|
|
const rowStartX = margin + (availableWidth - rowTotalWidth) / 2;
|
|
|
|
// 计算中间垂直分割线的X位置(两组之间的位置)
|
|
const middleDividerX =
|
|
groupsPerRow > 1
|
|
? rowStartX +
|
|
actualGridWidth +
|
|
gridSpacing +
|
|
actualGridWidth +
|
|
groupSpacing / 2
|
|
: null;
|
|
|
|
// 遍历每一行
|
|
for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
|
|
const rowStartIndex = rowIndex * groupsPerRow;
|
|
const rowGroups = groups.slice(
|
|
rowStartIndex,
|
|
rowStartIndex + groupsPerRow,
|
|
);
|
|
|
|
const currentY =
|
|
firstRowStartY + rowIndex * (gridHeight + rowSpacing);
|
|
let currentX = rowStartX;
|
|
|
|
// 遍历当前行的每一组
|
|
for (
|
|
let groupIndex = 0;
|
|
groupIndex < rowGroups.length;
|
|
groupIndex++
|
|
) {
|
|
const group = rowGroups[groupIndex];
|
|
|
|
// 绘制填充的网格(左侧)
|
|
this.drawSingleGrid({
|
|
ctx,
|
|
gridStartX: currentX,
|
|
gridStartY: currentY,
|
|
gridWidth: actualGridWidth,
|
|
gridHeight,
|
|
cellSize,
|
|
rows,
|
|
cols,
|
|
cells: group.filledCells,
|
|
config,
|
|
});
|
|
|
|
currentX += actualGridWidth + gridSpacing;
|
|
|
|
// 绘制空的网格(右侧)
|
|
this.drawSingleGrid({
|
|
ctx,
|
|
gridStartX: currentX,
|
|
gridStartY: currentY,
|
|
gridWidth: actualGridWidth,
|
|
gridHeight,
|
|
cellSize,
|
|
rows,
|
|
cols,
|
|
cells: [], // 空网格
|
|
config,
|
|
});
|
|
|
|
currentX += actualGridWidth;
|
|
|
|
// 如果不是最后一组,添加组间距
|
|
if (groupIndex < rowGroups.length - 1) {
|
|
currentX += groupSpacing;
|
|
}
|
|
}
|
|
|
|
// 如果不是最后一行,绘制水平分割线
|
|
if (rowIndex < totalRows - 1) {
|
|
const lineY = currentY + gridHeight + rowSpacing / 2;
|
|
ctx.strokeStyle = '#999';
|
|
ctx.lineWidth = dividerWidth;
|
|
ctx.setLineDash([4, 4]); // 虚线
|
|
ctx.beginPath();
|
|
ctx.moveTo(margin, lineY);
|
|
ctx.lineTo(canvasWidth - margin, lineY);
|
|
ctx.stroke();
|
|
ctx.setLineDash([]); // 重置为实线
|
|
}
|
|
}
|
|
|
|
// 绘制中间垂直分割线(贯穿所有行)
|
|
if (middleDividerX !== null && groupsPerRow > 1) {
|
|
ctx.strokeStyle = '#999';
|
|
ctx.lineWidth = dividerWidth;
|
|
ctx.setLineDash([4, 4]); // 虚线
|
|
ctx.beginPath();
|
|
ctx.moveTo(middleDividerX, firstRowStartY);
|
|
ctx.lineTo(middleDividerX, lastRowEndY);
|
|
ctx.stroke();
|
|
ctx.setLineDash([]); // 重置为实线
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 绘制单个网格
|
|
*/
|
|
private drawSingleGrid(params: {
|
|
ctx: RenderingContext;
|
|
gridStartX: number;
|
|
gridStartY: number;
|
|
gridWidth: number;
|
|
gridHeight: number;
|
|
cellSize: number;
|
|
rows: number;
|
|
cols: number;
|
|
cells: GridCell[];
|
|
config: GridConfig;
|
|
}): void {
|
|
const {
|
|
ctx,
|
|
gridStartX,
|
|
gridStartY,
|
|
gridWidth,
|
|
gridHeight,
|
|
cellSize,
|
|
rows,
|
|
cols,
|
|
cells,
|
|
config,
|
|
} = params;
|
|
|
|
// 绘制网格线
|
|
ctx.strokeStyle = config.gridLineColor || '#999';
|
|
ctx.lineWidth = config.gridLineWidth || 1;
|
|
ctx.setLineDash([]); // 实线
|
|
|
|
// 绘制垂直线
|
|
for (let i = 0; i <= cols; i++) {
|
|
const x = gridStartX + i * cellSize;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, gridStartY);
|
|
ctx.lineTo(x, gridStartY + gridHeight);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制水平线
|
|
for (let i = 0; i <= rows; i++) {
|
|
const y = gridStartY + i * cellSize;
|
|
ctx.beginPath();
|
|
ctx.moveTo(gridStartX, y);
|
|
ctx.lineTo(gridStartX + gridWidth, y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// 绘制填充的格子
|
|
for (const cell of cells) {
|
|
if (cell.x >= 0 && cell.x < cols && cell.y >= 0 && cell.y < rows) {
|
|
const x = gridStartX + cell.x * cellSize;
|
|
const y = gridStartY + cell.y * cellSize;
|
|
|
|
// 填充颜色
|
|
ctx.fillStyle = cell.color;
|
|
ctx.fillRect(x + 1, y + 1, cellSize - 2, cellSize - 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export default GridDraw;
|