feat:数字学习卡
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
import { PAPER_SIZE } from '../../constants/colors';
|
||||
import { drawMathHeader, drawMathMiniHeader } from './mathHeaderDraw';
|
||||
|
||||
/**
|
||||
* 基础数学绘制服务
|
||||
* 包含Paper设置和Header绘制功能,可被其他绘制服务复用
|
||||
*/
|
||||
export class BaseMathDrawService {
|
||||
headerType: PrintHeader = 'wechat';
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
canvasWidth: number; // 逻辑像素宽度
|
||||
canvasHeight: number; // 逻辑像素高度
|
||||
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
options = options || {};
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦丫小程序',
|
||||
appHint: '在玩耍中学习数学',
|
||||
title: '看数字,涂一涂',
|
||||
subTitle: '找一找下面相同的数字,涂上颜色',
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
this.canvasWidth = 0;
|
||||
this.canvasHeight = 0;
|
||||
this.setPrintConfig();
|
||||
}
|
||||
|
||||
setPrintConfig() {
|
||||
const printConfig = getApp().getPrintConfig();
|
||||
this.headerType = printConfig.header;
|
||||
this.options.appName = printConfig.appName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Paper(逻辑像素,尺寸除以3)
|
||||
*/
|
||||
setPaper() {
|
||||
const { ctx, canvas } = this;
|
||||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||||
let { width, height } = PAPER_SIZE[this.paperSize];
|
||||
console.log('width', width);
|
||||
console.log('height', height);
|
||||
console.log('dpr', dpr);
|
||||
|
||||
this.canvasWidth = width;
|
||||
this.canvasHeight = height;
|
||||
|
||||
// 设置 canvas 为物理像素尺寸(用于高分辨率显示)
|
||||
const physicalWidth = width * dpr;
|
||||
const physicalHeight = height * dpr;
|
||||
canvas.width = physicalWidth;
|
||||
canvas.height = physicalHeight;
|
||||
|
||||
// 重置 transform 并 scale 到逻辑像素
|
||||
ctx.setTransform(1, 0, 0, 1, 0, 0); // 重置 transform
|
||||
ctx.scale(dpr, dpr); // scale 到逻辑像素,后续绘制都使用逻辑像素
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff';
|
||||
// 使用逻辑像素尺寸填充
|
||||
ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除画布
|
||||
*/
|
||||
clear() {
|
||||
const canvas = this.canvas;
|
||||
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制Header(逻辑像素,尺寸除以3)
|
||||
*/
|
||||
async drawHeader() {
|
||||
this.currentX = 24;
|
||||
this.currentY = 24;
|
||||
await drawMathHeader({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
headerType: this.headerType,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
appHint: this.options.appHint || '识字|识图|练字|打印',
|
||||
title: this.options.title || '看数字,涂一涂',
|
||||
subTitle:
|
||||
this.options.subTitle || '找一找下面相同的数字,涂上颜色',
|
||||
},
|
||||
onHeaderDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制迷你Header(逻辑像素,尺寸除以3)
|
||||
*/
|
||||
drawMiniHeader() {
|
||||
drawMathMiniHeader({
|
||||
ctx: this.ctx,
|
||||
canvasWidth: this.canvasWidth,
|
||||
options: {
|
||||
appName: this.options.appName || '涂鸦丫小程序',
|
||||
title: this.options.title || '看数字,涂一涂',
|
||||
},
|
||||
onHeaderDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制分割线(逻辑像素,尺寸除以3)
|
||||
*/
|
||||
drawDivider() {
|
||||
const { ctx, canvasWidth } = this;
|
||||
const dividerY = this.currentY + 10; // 10px间距
|
||||
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(24, dividerY);
|
||||
ctx.lineTo(canvasWidth - 24, dividerY);
|
||||
ctx.stroke();
|
||||
|
||||
this.currentY = dividerY + 10; // 分割线下方10px间距
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
import { getMiniCodeImage, getImage } from '../../utils/index';
|
||||
|
||||
/**
|
||||
* 绘制数学模块页眉的参数接口(尺寸除以3)
|
||||
*/
|
||||
interface DrawMathHeaderParams {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
headerType: PrintHeader;
|
||||
options: {
|
||||
appName: string;
|
||||
appHint: string;
|
||||
title: string;
|
||||
subTitle: string;
|
||||
};
|
||||
onHeaderDrawn?: (currentY: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块完整页眉(尺寸除以3)
|
||||
*/
|
||||
export async function drawMathHeader({
|
||||
canvas,
|
||||
ctx,
|
||||
headerType,
|
||||
options,
|
||||
onHeaderDrawn,
|
||||
}: DrawMathHeaderParams): Promise<void> {
|
||||
const { appName, appHint, title, subTitle } = options;
|
||||
|
||||
let titleX = 108; // 约109.33
|
||||
const titleY = 26; // 约26.67
|
||||
|
||||
const logoX = 24; // 约26.67
|
||||
const logoY = 20; // 20
|
||||
const logoWidth = 65; // 约66.67
|
||||
const logoHeight = 65; // 约66.67
|
||||
|
||||
// 根据 headerType 绘制 Logo 或调整标题位置
|
||||
switch (headerType) {
|
||||
case 'LogoImage': {
|
||||
const image = await getImage(
|
||||
canvas,
|
||||
'/assets/imgs/doodle-logo.png',
|
||||
);
|
||||
ctx.drawImage(image, logoX, logoY, logoWidth, logoHeight);
|
||||
break;
|
||||
}
|
||||
case 'noLogoImage': {
|
||||
titleX = 40;
|
||||
break;
|
||||
}
|
||||
case 'minimal': {
|
||||
titleX = 40;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const image = await getMiniCodeImage(canvas);
|
||||
ctx.drawImage(image, logoX, logoY, logoWidth, logoHeight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制应用名称(字体大小除以3)
|
||||
ctx.font = 'bold 22px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(appName, titleX, titleY);
|
||||
|
||||
// 绘制应用提示(字体大小除以3)
|
||||
ctx.font = '16px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(appHint, titleX, 66);
|
||||
|
||||
// 绘制标题(字体大小除以3)
|
||||
ctx.font = 'bold 22px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(title, 280, titleY);
|
||||
|
||||
// 绘制副标题(字体大小除以3)
|
||||
ctx.font = '16px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(subTitle, 280, 66);
|
||||
|
||||
// 调用回调函数
|
||||
if (onHeaderDrawn) {
|
||||
onHeaderDrawn(100);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块迷你页眉的参数接口(尺寸除以3)
|
||||
*/
|
||||
interface DrawMathMiniHeaderParams {
|
||||
// canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: {
|
||||
appName: string;
|
||||
title: string;
|
||||
};
|
||||
canvasWidth: number; // 逻辑像素宽度(已除以3)
|
||||
onHeaderDrawn?: (currentY: number) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数学模块迷你页眉(尺寸除以3)
|
||||
*/
|
||||
export function drawMathMiniHeader({
|
||||
ctx,
|
||||
options,
|
||||
canvasWidth,
|
||||
onHeaderDrawn,
|
||||
}: DrawMathMiniHeaderParams): void {
|
||||
const { appName, title } = options;
|
||||
const titleY = 46;
|
||||
const centerX = canvasWidth / 2;
|
||||
|
||||
// 字体大小除以3
|
||||
ctx.font = 'bold 24px "Microsoft Yahei"'; // 64/3
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(appName + ' ' + title, centerX, titleY);
|
||||
|
||||
// 调用回调函数,传递除以3后的currentY
|
||||
if (onHeaderDrawn) {
|
||||
onHeaderDrawn(64); // 约66.67
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { BaseMathDrawService } from './baseMathDraw';
|
||||
import { drawNumberPreview } from './numberPreviewDraw';
|
||||
import { drawNumberContent } from './numberContentDraw';
|
||||
import { drawNumberWriteContent } from './numberWriteDraw';
|
||||
|
||||
/**
|
||||
* 数字涂色绘制服务
|
||||
* 组合使用基础绘制服务、预览区域绘制服务和内容区域绘制服务
|
||||
*/
|
||||
class NumberColorDraw extends BaseMathDrawService {
|
||||
selectedNumber: number;
|
||||
functionId: string; // 功能ID,用于判断绘制类型
|
||||
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
super(canvas, ctx, options);
|
||||
this.selectedNumber = 0;
|
||||
this.functionId = options?.functionId || 'number-find';
|
||||
}
|
||||
|
||||
async draw(selectedNumber: number) {
|
||||
if (selectedNumber <= 0 || selectedNumber > 10) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setPrintConfig();
|
||||
this.selectedNumber = selectedNumber;
|
||||
this.clear();
|
||||
this.setPaper();
|
||||
|
||||
// 绘制Header
|
||||
if (this.headerType !== 'minimal') {
|
||||
await this.drawHeader();
|
||||
} else {
|
||||
this.drawMiniHeader();
|
||||
}
|
||||
|
||||
// 绘制预览区域
|
||||
this.drawDivider();
|
||||
await drawNumberPreview({
|
||||
canvas: this.canvas,
|
||||
ctx: this.ctx,
|
||||
selectedNumber: this.selectedNumber,
|
||||
canvasWidth: this.canvasWidth,
|
||||
startY: this.currentY,
|
||||
onDrawn: (currentY) => {
|
||||
this.currentY = currentY;
|
||||
},
|
||||
});
|
||||
|
||||
// 绘制内容区域(根据 functionId 选择不同的绘制方法)
|
||||
this.drawDivider();
|
||||
if (this.functionId === 'number-write') {
|
||||
// 书写类型:绘制书写行
|
||||
drawNumberWriteContent({
|
||||
ctx: this.ctx,
|
||||
selectedNumber: this.selectedNumber,
|
||||
canvasWidth: this.canvasWidth,
|
||||
startY: this.currentY,
|
||||
});
|
||||
} else {
|
||||
// 默认类型(number-find):绘制数字涂色内容
|
||||
drawNumberContent({
|
||||
ctx: this.ctx,
|
||||
selectedNumber: this.selectedNumber,
|
||||
canvasWidth: this.canvasWidth,
|
||||
startY: this.currentY,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NumberColorDraw;
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 绘制内容区域的参数接口
|
||||
*/
|
||||
interface DrawNumberContentParams {
|
||||
ctx: RenderingContext;
|
||||
selectedNumber: number;
|
||||
canvasWidth: number; // 逻辑像素宽度(已除以3)
|
||||
startY: number; // 起始Y坐标
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数字涂色内容区域服务
|
||||
* 随机绘制1-10的数字,尺寸除以3
|
||||
*/
|
||||
export function drawNumberContent({
|
||||
ctx,
|
||||
selectedNumber,
|
||||
canvasWidth,
|
||||
startY,
|
||||
}: DrawNumberContentParams): void {
|
||||
const rows = 6;
|
||||
const radius = 26; // 约26.67
|
||||
const fontSize = 24; // 24
|
||||
|
||||
const startYPos = startY + 10 + radius; // 约47.33
|
||||
const startX1 = 100 + radius; // 约112.22
|
||||
const startX2 = 60 + radius; // 约75.56
|
||||
const spaceWidthFirst = (canvasWidth - startX1 * 2) / (5 - 1);
|
||||
const spaceWidthSecond = (canvasWidth - startX2 * 2) / (6 - 1);
|
||||
const spaceHeight = 18; // 18
|
||||
|
||||
ctx.moveTo(startX1, startYPos);
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const cols = i % 2 === 0 ? 5 : 6;
|
||||
|
||||
for (let j = 0; j < cols; j++) {
|
||||
const y = startYPos + i * (spaceHeight + radius * 2);
|
||||
const x =
|
||||
i % 2 === 0
|
||||
? startX1 + j * spaceWidthFirst
|
||||
: startX2 + j * spaceWidthSecond;
|
||||
|
||||
// 随机生成1-10的数字,但确保包含选中的数字
|
||||
let number: number;
|
||||
if (i === 0 && j === 0) {
|
||||
// 第一个位置固定为选中的数字
|
||||
number = selectedNumber;
|
||||
} else {
|
||||
// 其他位置随机,但增加选中数字出现的概率
|
||||
const random = Math.random();
|
||||
if (random < 0.3) {
|
||||
// 30%概率是选中的数字
|
||||
number = selectedNumber;
|
||||
} else {
|
||||
// 70%概率是其他随机数字
|
||||
number = Math.floor(Math.random() * 10) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制圆形背景(线宽除以3)
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 1; // 约1.33
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// 绘制数字(字体大小除以3)
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = `bold ${fontSize}px "Microsoft Yahei"`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(String(number), x, y, radius * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { getImage } from '../../utils/index';
|
||||
|
||||
/**
|
||||
* 数字到英文单词的映射
|
||||
*/
|
||||
const NUMBER_WORDS: Record<number, string> = {
|
||||
1: 'one',
|
||||
2: 'two',
|
||||
3: 'three',
|
||||
4: 'four',
|
||||
5: 'five',
|
||||
6: 'six',
|
||||
7: 'seven',
|
||||
8: 'eight',
|
||||
9: 'nine',
|
||||
10: 'ten',
|
||||
};
|
||||
|
||||
const NUMBER_COLORS: Record<number, string> = {
|
||||
1: '#9FC558',
|
||||
2: '#CDB984',
|
||||
3: '#B393B8',
|
||||
4: '#ED7E97',
|
||||
5: '#53B5B9',
|
||||
6: '#B3B3B3',
|
||||
7: '#EE8B7A',
|
||||
8: '#F6C644',
|
||||
9: '#99C1F4',
|
||||
10: '#F5D25C',
|
||||
};
|
||||
|
||||
/**
|
||||
* 绘制预览区域的参数接口
|
||||
*/
|
||||
interface DrawNumberPreviewParams {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
selectedNumber: number;
|
||||
canvasWidth: number; // 逻辑像素宽度(已除以3)
|
||||
startY: number; // 起始Y坐标
|
||||
onDrawn?: (currentY: number) => void; // 绘制完成后的回调
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数字预览区域服务
|
||||
* 左侧:animal-number 图片
|
||||
* 右侧:两个框,第一个框是finger图片,第二个框是英文单词
|
||||
* 所有尺寸除以3
|
||||
*/
|
||||
export async function drawNumberPreview({
|
||||
canvas,
|
||||
ctx,
|
||||
selectedNumber,
|
||||
canvasWidth,
|
||||
startY,
|
||||
onDrawn,
|
||||
}: DrawNumberPreviewParams): Promise<void> {
|
||||
// 左侧 animal-number 图片位置
|
||||
const animalImageWidth = 360;
|
||||
const animalImageHeight = 236;
|
||||
const leftMargin = 35;
|
||||
const animalImageX = leftMargin;
|
||||
const animalImageY = startY + 10;
|
||||
|
||||
// 加载并绘制 animal-number 图片
|
||||
try {
|
||||
const animalImagePath = `/mathPages/assets/animal-number/animal-number-${selectedNumber}.png`;
|
||||
const animalImage = await getImage(canvas, animalImagePath);
|
||||
ctx.drawImage(
|
||||
animalImage,
|
||||
animalImageX,
|
||||
animalImageY,
|
||||
animalImageWidth,
|
||||
animalImageHeight,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('加载animal-number图片失败:', error);
|
||||
}
|
||||
|
||||
// 右侧两个框的位置(逻辑像素,除以3)
|
||||
const boxWidth = 130;
|
||||
const boxHeight = 100;
|
||||
const rightMargin = 35;
|
||||
const boxSpacing = 30;
|
||||
const boxX = canvasWidth - rightMargin - boxWidth;
|
||||
|
||||
// 第一个框:finger 图片
|
||||
const fingerBoxY = animalImageY;
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(boxX, fingerBoxY, boxWidth, boxHeight);
|
||||
|
||||
try {
|
||||
const fingerImagePath = `/mathPages/assets/finger/finger-${selectedNumber}.png`;
|
||||
const fingerImage = await getImage(canvas, fingerImagePath);
|
||||
// 计算图片在框内的位置(居中显示)
|
||||
const imagePadding = 10;
|
||||
const imageWidth = boxWidth - imagePadding * 2;
|
||||
const imageHeight = boxHeight - imagePadding * 2;
|
||||
ctx.drawImage(
|
||||
fingerImage,
|
||||
boxX + imagePadding,
|
||||
fingerBoxY + imagePadding,
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('加载finger图片失败:', error);
|
||||
}
|
||||
|
||||
// 第二个框:英文单词
|
||||
const wordBoxY = fingerBoxY + boxHeight + boxSpacing;
|
||||
ctx.strokeRect(boxX, wordBoxY, boxWidth, boxHeight);
|
||||
|
||||
// 绘制英文单词(字体大小除以3)
|
||||
const word = NUMBER_WORDS[selectedNumber] || '';
|
||||
const color = NUMBER_COLORS[selectedNumber] || '#000';
|
||||
ctx.fillStyle = color;
|
||||
// 推荐使用系统内常见的圆体或幼圆体字体,更适合儿童视觉风格
|
||||
ctx.font =
|
||||
'bold 32px "HarmonyOS Sans", "PingFang SC", "YouYuan", "Microsoft Yahei", sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(
|
||||
word,
|
||||
boxX + boxWidth / 2,
|
||||
wordBoxY + boxHeight / 2,
|
||||
boxWidth - 20,
|
||||
);
|
||||
|
||||
// 更新 currentY 为预览区域底部
|
||||
const finalY =
|
||||
Math.max(animalImageY + animalImageHeight, wordBoxY + boxHeight) + 10;
|
||||
|
||||
if (onDrawn) {
|
||||
onDrawn(finalY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 绘制书写行内容区域的参数接口
|
||||
*/
|
||||
interface DrawNumberWriteContentParams {
|
||||
ctx: RenderingContext;
|
||||
selectedNumber: number;
|
||||
canvasWidth: number; // 逻辑像素宽度
|
||||
startY: number; // 起始Y坐标
|
||||
}
|
||||
|
||||
/**
|
||||
* 绘制数字书写内容区域服务
|
||||
* 绘制三条书写行,每条行有三条线(上下实线,中间虚线)
|
||||
* 第一行和第二行有数字(第一个黑色实线,后续灰色虚线),第三行空白
|
||||
*/
|
||||
export function drawNumberWriteContent({
|
||||
ctx,
|
||||
selectedNumber,
|
||||
canvasWidth,
|
||||
startY,
|
||||
}: DrawNumberWriteContentParams): void {
|
||||
const lineSpacing = 30; // 行间距(三条线之间的间距)
|
||||
const rowHeight = lineSpacing * 2; // 每行的高度(两条实线之间的距离)
|
||||
const rowMargin = 24; // 行与行之间的间距
|
||||
const leftMargin = 24; // 左边距
|
||||
const rightMargin = 24; // 右边距
|
||||
const numberIndent = selectedNumber === 10 ? 12 : 24; // 数字缩进(避免贴边)
|
||||
const numberSpacing = 90; // 数字之间的间距
|
||||
const numberCount = 6; // 每行数字的数量(第一个实线 + 6个虚线)
|
||||
const fontSize = 76; // 数字字体大小(调整为行高的83%,避免超出上下边线)
|
||||
|
||||
// 计算每行的起始Y坐标
|
||||
const rowStartY = startY + 20; // 顶部间距
|
||||
|
||||
// 绘制三条书写行
|
||||
for (let rowIndex = 0; rowIndex < 5; rowIndex++) {
|
||||
const rowY = rowStartY + rowIndex * (rowHeight + rowMargin);
|
||||
const topLineY = rowY;
|
||||
const middleLineY = rowY + lineSpacing;
|
||||
const bottomLineY = rowY + rowHeight;
|
||||
|
||||
// 绘制上实线(蓝色)
|
||||
ctx.strokeStyle = '#4A90E2'; // 蓝色
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([]); // 实线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(leftMargin, topLineY);
|
||||
ctx.lineTo(canvasWidth - rightMargin, topLineY);
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制中间虚线(橙色)
|
||||
ctx.strokeStyle = '#FF8C42'; // 橙色
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([12, 12]); // 虚线模式
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(leftMargin, middleLineY);
|
||||
ctx.lineTo(canvasWidth - rightMargin, middleLineY);
|
||||
ctx.stroke();
|
||||
ctx.setLineDash([]); // 恢复实线模式
|
||||
|
||||
// 绘制下实线(蓝色)
|
||||
ctx.strokeStyle = '#4A90E2'; // 蓝色
|
||||
ctx.lineWidth = 1;
|
||||
ctx.setLineDash([]); // 实线
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(leftMargin, bottomLineY);
|
||||
ctx.lineTo(canvasWidth - rightMargin, bottomLineY);
|
||||
ctx.stroke();
|
||||
|
||||
// 第一行和第二行绘制数字
|
||||
if (rowIndex < 3) {
|
||||
const centerY = rowY + lineSpacing; // 数字中心Y坐标(中间虚线位置)
|
||||
|
||||
for (let i = 0; i < numberCount; i++) {
|
||||
const numberX = leftMargin + numberIndent + i * numberSpacing;
|
||||
|
||||
if (i === 0) {
|
||||
// 第一个数字:黑色实线
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.font = `bold ${fontSize}px "Microsoft Yahei"`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(String(selectedNumber), numberX, centerY);
|
||||
} else {
|
||||
// 后续数字:灰色虚线(使用灰色填充配合透明度模拟虚线效果)
|
||||
const savedAlpha = ctx.globalAlpha;
|
||||
ctx.globalAlpha = 0.6; // 设置透明度模拟虚线效果
|
||||
ctx.fillStyle = '#999999'; // 灰色
|
||||
ctx.font = `${fontSize}px "Microsoft Yahei"`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(String(selectedNumber), numberX, centerY);
|
||||
ctx.globalAlpha = savedAlpha; // 恢复透明度
|
||||
}
|
||||
}
|
||||
}
|
||||
// 第三行不绘制数字(空白行)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user