Files
doodle-mini/miniprogram/core/draw/dailyPracticeDrawHelper.ts
T
2026-05-21 22:44:48 +08:00

142 lines
4.7 KiB
TypeScript

// 每个分区内容区顶部内边距(px)
export const SECTION_PADDING_TOP = 12;
// 每个分区内容区底部内边距(px)
export const SECTION_PADDING_BOTTOM = 10;
// 标题与上一元素的垂直间距(px)
export const HEADER_TITLE_GAP = 32;
// 标题与元信息(如日期、学号等)的间距(px)
export const HEADER_META_GAP = 22;
// 正文内容距标题/元信息顶部的间距(px)
export const CONTENT_TOP_GAP = 14;
// 页脚短语与内容之间的间距(px)
export const FOOTER_PHRASE_GAP = 26;
// 页脚索引与短语之间的间距(px)
export const FOOTER_INDEX_GAP = 22;
// 20 条鼓励语词库
export const ENCOURAGEMENT_PHRASES: ReadonlyArray<string> = [
'书山有路勤为径',
'日拱一卒 不期而至',
'宝剑锋从磨砺出',
'梅花香自苦寒来',
'业精于勤荒于嬉',
'行百里者半九十',
'锲而不舍金石可镂',
'读万卷书行万里路',
'滴水穿石非一日之功',
'天下无难事只怕有心人',
'熟能生巧功到自然成',
'敏而好学不耻下问',
'学而不思则罔',
'思而不学则殆',
'欲速则不达',
'不积跬步无以至千里',
'不积小流无以成江海',
'温故而知新',
'读书破万卷下笔如有神',
'千里之行始于足下',
];
/**
* 随机获取一条鼓励语
*/
export function getRandomEncouragementPhrase(): string {
const idx = Math.floor(Math.random() * ENCOURAGEMENT_PHRASES.length);
return ENCOURAGEMENT_PHRASES[idx];
}
/**
* 绘制统一的每日练习每个 Section 中的 Header
* @param ctx 微信小程序 Canvas 绘制上下文
* @param rect 目标 Section 的位置与宽高信息
* @param title 标题文本,如 “每日打卡” 或 “拼音每日一练”
* @param options.centerMeta 是否将 “月/日/姓名” 元信息行整体水平居中(默认 false,保持原左侧布局)
*/
export function drawSectionHeader(
ctx: RenderingContext,
rect: { x: number; y: number; w: number; h: number },
title: string,
options?: { centerMeta?: boolean },
): void {
const cx = rect.x + rect.w / 2;
const titleY = rect.y + SECTION_PADDING_TOP + 12;
const metaY = titleY + HEADER_TITLE_GAP;
const metaUnderlineY = metaY + 5;
const canvasCtx = ctx as any;
canvasCtx.save();
canvasCtx.fillStyle = '#322E25';
canvasCtx.textAlign = 'center';
canvasCtx.textBaseline = 'middle';
// 字体样式与 dailyCheckinDraw.ts 保持一致
canvasCtx.font = '20px serif';
canvasCtx.fillText(title, cx, titleY);
// 元信息行布局:原始版本以 rect.x 为基准(左侧偏移 40~248,宽 208)
// 当 centerMeta=true 时,整体水平居中到 rect 中部,仅影响调用方显式开启的场景
const META_BLOCK_WIDTH = 208;
const META_BASE_OFFSET = 40;
const metaLeft = options?.centerMeta
? rect.x + (rect.w - META_BLOCK_WIDTH) / 2
: rect.x + META_BASE_OFFSET;
const offset = (n: number) => metaLeft + (n - META_BASE_OFFSET);
canvasCtx.font = '12px serif';
canvasCtx.fillText('月', offset(76), metaY);
canvasCtx.fillText('日', offset(122), metaY);
canvasCtx.fillText('姓名:', offset(176), metaY);
canvasCtx.strokeStyle = '#8E897E';
canvasCtx.lineWidth = 1;
canvasCtx.setLineDash([]);
canvasCtx.beginPath();
canvasCtx.moveTo(offset(40), metaUnderlineY);
canvasCtx.lineTo(offset(68), metaUnderlineY);
canvasCtx.moveTo(offset(85), metaUnderlineY);
canvasCtx.lineTo(offset(113), metaUnderlineY);
canvasCtx.moveTo(offset(195), metaUnderlineY);
canvasCtx.lineTo(offset(248), metaUnderlineY);
canvasCtx.stroke();
canvasCtx.restore();
}
/**
* 绘制统一的每日练习每个 Section 中的 Footer
* @param ctx 微信小程序 Canvas 绘制上下文
* @param rect 目标 Section 的位置与宽高信息
* @param phrase 鼓励语文本
* @param labelText 页面/天数索引文本,如 “- 1 -” 或 “第 1 天”
*/
export function drawSectionFooter(
ctx: RenderingContext,
rect: { x: number; y: number; w: number; h: number },
phrase: string,
labelText: string,
): void {
const cx = rect.x + rect.w / 2;
const phraseY =
rect.y + rect.h - SECTION_PADDING_BOTTOM - FOOTER_PHRASE_GAP;
const indexY = rect.y + rect.h - SECTION_PADDING_BOTTOM - 6;
const canvasCtx = ctx as any;
canvasCtx.save();
canvasCtx.fillStyle = '#3E3A33';
canvasCtx.textAlign = 'center';
canvasCtx.textBaseline = 'middle';
// 字体样式与 dailyCheckinDraw.ts 保持一致
canvasCtx.font = '14px serif';
canvasCtx.fillText(phrase, cx, phraseY);
canvasCtx.font = '12px serif';
canvasCtx.fillText(labelText, cx, indexY);
canvasCtx.restore();
}