feat: 开发时钟连线
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
import type {
|
||||
ClockConnectData,
|
||||
ClockConnectProblem,
|
||||
} from '../generators/clock-connect-generator';
|
||||
import { drawAnalogClock } from '../../clockReading/draw/drawAnalogClock';
|
||||
import { NUMBER_COLORS } from '../../../constants/colors';
|
||||
|
||||
const ROWS = 6;
|
||||
const MARGIN_X = 28;
|
||||
const MARGIN_Y_TOP = 12;
|
||||
const CLOCK_RADIUS = 38 * 1.2; // 放大 1.2 倍
|
||||
const TIME_BOX_W = 70 * 3 * 0.8 * 0.75; // 放大3倍后缩80%,再缩短长度
|
||||
const TIME_BOX_H = 28 * 3 * 0.8; // 放大3倍后缩80%
|
||||
const TIME_BOX_RADIUS = 8 * 3 * 0.8; // 放大3倍后缩80%
|
||||
const TIME_FONT_SIZE = 42 * 0.8; // 字体放大3倍后缩80%
|
||||
const CONTENT_BOTTOM_PADDING = 28;
|
||||
|
||||
const DASH_PATTERN = [7, 5];
|
||||
const BOX_COLOR = '#555555';
|
||||
|
||||
const LINE_POINT_RADIUS = 8;
|
||||
const LINE_POINT_SPACING = 12;
|
||||
const LINE_POINT_COLOR = '#93D333';
|
||||
|
||||
/** 从 NUMBER_COLORS 中随机取一个颜色 */
|
||||
function getRandomColor(): string {
|
||||
const keys = Object.keys(NUMBER_COLORS).map(Number);
|
||||
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||
return NUMBER_COLORS[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* 时钟连线绘制服务:
|
||||
* 左侧 6 个钟表(右侧带连线圆点),右侧 6 个打乱顺序的时间(左侧带连线圆点)
|
||||
* 用户需要将左侧的钟表和右侧对应的时间连线
|
||||
*/
|
||||
export default class ClockConnectDraw extends BaseDrawService {
|
||||
async draw(data: ClockConnectData) {
|
||||
if (!data?.clocks?.length) return;
|
||||
|
||||
this.prepareDraw();
|
||||
await this.drawHeaderAndDivider();
|
||||
this.drawConnectGrid(data.clocks, data.shuffledTimes);
|
||||
this.drawPrintFooter({ mode: 'A' });
|
||||
}
|
||||
|
||||
private drawConnectGrid(
|
||||
clocks: ClockConnectProblem[],
|
||||
shuffledTimes: ClockConnectProblem[],
|
||||
) {
|
||||
const { ctx, canvasWidth, canvasHeight } = this;
|
||||
const contentTop = this.currentY;
|
||||
const availableH = canvasHeight - contentTop - CONTENT_BOTTOM_PADDING;
|
||||
|
||||
const clockBlockH = CLOCK_RADIUS * 2;
|
||||
const totalGridH = ROWS * clockBlockH;
|
||||
const rowGap = Math.max(
|
||||
8,
|
||||
(availableH - totalGridH - MARGIN_Y_TOP * 2) / (ROWS - 1),
|
||||
);
|
||||
const startY = contentTop + MARGIN_Y_TOP;
|
||||
const rowH = clockBlockH + rowGap;
|
||||
|
||||
// 向中间靠拢
|
||||
const leftCenterX = MARGIN_X + CLOCK_RADIUS + 40;
|
||||
const rightCenterX = canvasWidth - MARGIN_X - TIME_BOX_W / 2 - 40;
|
||||
|
||||
for (let i = 0; i < ROWS && i < clocks.length; i++) {
|
||||
const clock = clocks[i];
|
||||
const cellTopY = startY + i * rowH;
|
||||
const clockCy = cellTopY + CLOCK_RADIUS;
|
||||
|
||||
// 绘制左侧钟表
|
||||
drawAnalogClock(ctx, {
|
||||
cx: leftCenterX,
|
||||
cy: clockCy,
|
||||
radius: CLOCK_RADIUS,
|
||||
hour: clock.hour,
|
||||
minute: clock.minute,
|
||||
});
|
||||
|
||||
// 绘制钟表右侧的连线圆点
|
||||
const leftDotX =
|
||||
leftCenterX + CLOCK_RADIUS + LINE_POINT_SPACING + LINE_POINT_RADIUS;
|
||||
ctx.fillStyle = LINE_POINT_COLOR;
|
||||
ctx.beginPath();
|
||||
ctx.arc(leftDotX, clockCy, LINE_POINT_RADIUS, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 绘制右侧时间框
|
||||
const timeItem = shuffledTimes[i];
|
||||
const boxX = rightCenterX - TIME_BOX_W / 2;
|
||||
const boxY = clockCy - TIME_BOX_H / 2;
|
||||
const color = getRandomColor();
|
||||
this.drawTimeBox(boxX, boxY, timeItem.timeText, color);
|
||||
|
||||
// 绘制时间框左侧的连线圆点
|
||||
const rightDotX = boxX - LINE_POINT_SPACING - LINE_POINT_RADIUS;
|
||||
ctx.fillStyle = LINE_POINT_COLOR;
|
||||
ctx.beginPath();
|
||||
ctx.arc(rightDotX, clockCy, LINE_POINT_RADIUS, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
/** 绘制圆角虚线矩形 + 居中彩色时间文本 */
|
||||
private drawTimeBox(x: number, y: number, text: string, color: string) {
|
||||
const { ctx } = this;
|
||||
|
||||
ctx.save();
|
||||
|
||||
ctx.strokeStyle = BOX_COLOR;
|
||||
ctx.lineWidth = 1.8;
|
||||
ctx.setLineDash(DASH_PATTERN);
|
||||
|
||||
this.strokeRoundRect(x, y, TIME_BOX_W, TIME_BOX_H, TIME_BOX_RADIUS);
|
||||
|
||||
ctx.setLineDash([]);
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.font = `bold ${TIME_FONT_SIZE}px "Microsoft Yahei", sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, x + TIME_BOX_W / 2, y + TIME_BOX_H / 2);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private strokeRoundRect(
|
||||
x: number,
|
||||
y: number,
|
||||
w: number,
|
||||
h: number,
|
||||
r: number,
|
||||
) {
|
||||
const { ctx } = this;
|
||||
const radius = Math.min(r, w / 2, h / 2);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + w - radius, y);
|
||||
ctx.arcTo(x + w, y, x + w, y + radius, radius);
|
||||
ctx.lineTo(x + w, y + h - radius);
|
||||
ctx.arcTo(x + w, y + h, x + w - radius, y + h, radius);
|
||||
ctx.lineTo(x + radius, y + h);
|
||||
ctx.arcTo(x, y + h, x, y + h - radius, radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.arcTo(x, y, x + radius, y, radius);
|
||||
ctx.closePath();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user