feat: 增加字母笔顺图片
@@ -5,6 +5,13 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"glyphs": [
|
||||||
|
{
|
||||||
|
"icon_id": "47465822",
|
||||||
|
"name": "task-o",
|
||||||
|
"font_class": "task-o",
|
||||||
|
"unicode": "e62d",
|
||||||
|
"unicode_decimal": 58925
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon_id": "47422029",
|
"icon_id": "47422029",
|
||||||
"name": "refresh",
|
"name": "refresh",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
type LetterFontSizes,
|
type LetterFontSizes,
|
||||||
} from '../../shared/draw/drawTools';
|
} from '../../shared/draw/drawTools';
|
||||||
|
|
||||||
const SLOTS_PER_ROW = 6;
|
const SLOTS_PER_ROW = 7;
|
||||||
const PAIR_GAP = 25;
|
const PAIR_GAP = 25;
|
||||||
|
|
||||||
type SingleLineData = Extract<
|
type SingleLineData = Extract<
|
||||||
@@ -26,11 +26,11 @@ type SingleLineData = Extract<
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 单字母逐行绘制:
|
* 单字母逐行绘制:
|
||||||
* 13 行四线三格横跨整页宽度,每行划分为 6 组位置。
|
* 13 行四线三格横跨整页宽度,每行按 7 组位置预留空间。
|
||||||
* 前 4 组展示大小写字母(Aa),后 2 组空白供用户书写。
|
* 前 4 组展示大小写字母(Aa),其余位置留空供用户书写。
|
||||||
* 颜色递进:黑色 → 浅红 → 半透明浅红 → 虚线字体+半透明浅红。
|
* 颜色递进:黑色 → 浅红 → 半透明浅红 → 虚线字体+半透明浅红。
|
||||||
*/
|
*/
|
||||||
export default class SingleLineDraw extends BaseDrawService {
|
export default class HalfLetterDraw extends BaseDrawService {
|
||||||
private letterSizes: LetterFontSizes =
|
private letterSizes: LetterFontSizes =
|
||||||
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ import singleLetterDraw from './singleLetterDraw';
|
|||||||
import CasePairingDraw from './casePairingDraw';
|
import CasePairingDraw from './casePairingDraw';
|
||||||
import TwoColumnDraw from './twoColumnDraw';
|
import TwoColumnDraw from './twoColumnDraw';
|
||||||
import UpperLowerDraw from './upperLowerDraw';
|
import UpperLowerDraw from './upperLowerDraw';
|
||||||
import SingleLineDraw from './singleLineDraw';
|
import HalfLetterDraw from './halfLetterDraw';
|
||||||
import TripleDraw from './tripleDraw';
|
import TripleDraw from './tripleDraw';
|
||||||
import DailyCheckinDraw from './dailyCheckinDraw';
|
import DailyCheckinDraw from './dailyCheckinDraw';
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export default class LetterTracingDraw extends BaseDrawService {
|
|||||||
'letter-tracing-upper-lower': UpperLowerDraw,
|
'letter-tracing-upper-lower': UpperLowerDraw,
|
||||||
'letter-tracing-case-pairing': CasePairingDraw,
|
'letter-tracing-case-pairing': CasePairingDraw,
|
||||||
'letter-tracing-two-column': TwoColumnDraw,
|
'letter-tracing-two-column': TwoColumnDraw,
|
||||||
'letter-tracing-half': SingleLineDraw,
|
'letter-tracing-half': HalfLetterDraw,
|
||||||
'letter-tracing-three': TripleDraw,
|
'letter-tracing-three': TripleDraw,
|
||||||
'letter-tracing-daily-checkin': DailyCheckinDraw,
|
'letter-tracing-daily-checkin': DailyCheckinDraw,
|
||||||
}[data.mode];
|
}[data.mode];
|
||||||
|
|||||||
@@ -4,13 +4,18 @@ import type {
|
|||||||
LetterTracingData,
|
LetterTracingData,
|
||||||
HighlightGrid,
|
HighlightGrid,
|
||||||
} from '../generators/letter-tracing-generator';
|
} from '../generators/letter-tracing-generator';
|
||||||
import { fontFamilyOf } from '../../shared/data/fontProfiles';
|
import {
|
||||||
|
fontFamilyOf,
|
||||||
|
PRINT_CLEARLY_BOLD,
|
||||||
|
PRINT_CLEARLY_DASHED,
|
||||||
|
} from '../../shared/data/fontProfiles';
|
||||||
import {
|
import {
|
||||||
calcLetterFontSizes,
|
calcLetterFontSizes,
|
||||||
drawFourLineGrid,
|
drawFourLineGrid,
|
||||||
drawLetterInFourLineGrid,
|
drawLetterInFourLineGrid,
|
||||||
FOUR_LINE_GRID_H,
|
FOUR_LINE_GRID_H,
|
||||||
getLetterFontSize,
|
getLetterFontSize,
|
||||||
|
loadLetterFont,
|
||||||
type LetterFontSizes,
|
type LetterFontSizes,
|
||||||
} from '../../shared/draw/drawTools';
|
} from '../../shared/draw/drawTools';
|
||||||
|
|
||||||
@@ -27,29 +32,37 @@ type SingleLetterData = Extract<
|
|||||||
export default class SingleLetterDraw extends BaseDrawService {
|
export default class SingleLetterDraw extends BaseDrawService {
|
||||||
private letterSizes: LetterFontSizes =
|
private letterSizes: LetterFontSizes =
|
||||||
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
calcLetterFontSizes(FOUR_LINE_GRID_H);
|
||||||
|
private dashedSizes: LetterFontSizes = calcLetterFontSizes(
|
||||||
|
FOUR_LINE_GRID_H,
|
||||||
|
PRINT_CLEARLY_DASHED,
|
||||||
|
);
|
||||||
private get letterFace(): string {
|
private get letterFace(): string {
|
||||||
return fontFamilyOf(this.letterSizes._profile);
|
return fontFamilyOf(this.letterSizes._profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
async draw(data: SingleLetterData) {
|
async draw(data: SingleLetterData) {
|
||||||
this.prepareDraw();
|
this.prepareDraw();
|
||||||
await this.drawHeaderAndDivider();
|
await Promise.all([
|
||||||
|
this.drawHeaderAndDivider(),
|
||||||
|
loadLetterFont(PRINT_CLEARLY_BOLD),
|
||||||
|
loadLetterFont(PRINT_CLEARLY_DASHED),
|
||||||
|
]);
|
||||||
await this.drawContent(data);
|
await this.drawContent(data);
|
||||||
this.drawPrintFooter();
|
this.drawPrintFooter();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async drawContent(data: SingleLetterData) {
|
private async drawContent(data: SingleLetterData) {
|
||||||
const { teaching, highlightGrid, traceImgPath } = data;
|
const { teaching, highlightGrid } = data;
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
|
|
||||||
// ── 上区:left:78 top:120 w:451 h:180 ──
|
// ── 上区:left:78 top:120 w:451 h:180 ──
|
||||||
const TOP_X = 78;
|
const TOP_X = 78;
|
||||||
const TOP_Y = 120;
|
const TOP_Y = 110;
|
||||||
|
|
||||||
const letterImgX = TOP_X + 15;
|
const letterImgX = TOP_X + 15;
|
||||||
const letterImgY = TOP_Y + 9;
|
const letterImgY = TOP_Y + 9;
|
||||||
const letterImgW = 180;
|
const letterImgW = 180;
|
||||||
const letterImgH = 103.5;
|
const letterImgH = 130;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const letterImg = (await getImage(
|
const letterImg = (await getImage(
|
||||||
@@ -85,8 +98,8 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
this.drawSingleLetterSentence(
|
this.drawSingleLetterSentence(
|
||||||
teaching.sentence,
|
teaching.sentence,
|
||||||
teaching.upper,
|
teaching.upper,
|
||||||
TOP_X,
|
letterImgX + letterImgW / 2,
|
||||||
TOP_Y + 9 + 120,
|
TOP_Y + 16 + 120,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 右侧配图
|
// 右侧配图
|
||||||
@@ -142,13 +155,7 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
ctx.strokeRect(TR_X, TR_Y, TR_W, TR_H);
|
ctx.strokeRect(TR_X, TR_Y, TR_W, TR_H);
|
||||||
|
|
||||||
this.drawHighlightSection(highlightGrid, HL_X, HL_Y);
|
this.drawHighlightSection(highlightGrid, HL_X, HL_Y);
|
||||||
await this.drawTraceItSection(
|
this.drawTraceItSection(teaching.upper, teaching.lower, TR_X, TR_Y);
|
||||||
traceImgPath,
|
|
||||||
teaching.upper,
|
|
||||||
teaching.lower,
|
|
||||||
TR_X,
|
|
||||||
TR_Y,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ── 下区:4 行四线三格 + 字母临摹(倒置直角三角形)──
|
// ── 下区:4 行四线三格 + 字母临摹(倒置直角三角形)──
|
||||||
// 每行:left:24 w:547 h:43,top 分别 524.5 / 590.5 / 656.5 / 722.5
|
// 每行:left:24 w:547 h:43,top 分别 524.5 / 590.5 / 656.5 / 722.5
|
||||||
@@ -159,21 +166,27 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
// 8 → 6 → 3 → 1 组(每组为 Upper+Lower),左对齐,形成倒置直角三角形
|
// 8 → 6 → 3 → 1 组(每组为 Upper+Lower),左对齐,形成倒置直角三角形
|
||||||
const groupsPerLine = [8, 6, 3, 1];
|
const groupsPerLine = [8, 6, 3, 1];
|
||||||
const slotW = lineW / groupsPerLine[0]; // 以第一行的 8 组为基准
|
const slotW = lineW / groupsPerLine[0]; // 以第一行的 8 组为基准
|
||||||
|
const lineStyles = [
|
||||||
|
{ color: 'rgba(26, 26, 26, 1)', alpha: 1, dashed: false },
|
||||||
|
{ color: '#FF8E8E', alpha: 1, dashed: false },
|
||||||
|
{ color: '#FF8E8E', alpha: 0.5, dashed: false },
|
||||||
|
{ color: '#FF8E8E', alpha: 0.5, dashed: true },
|
||||||
|
] as const;
|
||||||
|
|
||||||
for (let r = 0; r < lineTops.length; r++) {
|
for (let r = 0; r < lineTops.length; r++) {
|
||||||
const y = lineTops[r];
|
const y = lineTops[r];
|
||||||
drawFourLineGrid(ctx, lineX, y, lineW, FOUR_LINE_GRID_H);
|
drawFourLineGrid(ctx, lineX, y, lineW, FOUR_LINE_GRID_H);
|
||||||
|
|
||||||
const n = groupsPerLine[r] ?? 0;
|
const n = groupsPerLine[r] ?? 0;
|
||||||
|
const style = lineStyles[r] ?? lineStyles[lineStyles.length - 1];
|
||||||
for (let i = 0; i < n; i++) {
|
for (let i = 0; i < n; i++) {
|
||||||
const alpha = n <= 1 ? 1 : 1 - (i / (n - 1)) * 0.7;
|
|
||||||
this.drawUpperLowerPairInFourLines(
|
this.drawUpperLowerPairInFourLines(
|
||||||
teaching.upper,
|
teaching.upper,
|
||||||
teaching.lower,
|
teaching.lower,
|
||||||
lineX + i * slotW,
|
lineX + i * slotW,
|
||||||
y,
|
y,
|
||||||
slotW,
|
slotW,
|
||||||
alpha,
|
style,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,26 +201,32 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
w: number,
|
w: number,
|
||||||
alpha: number,
|
style: {
|
||||||
|
color: string;
|
||||||
|
alpha: number;
|
||||||
|
dashed: boolean;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const sizes = this.letterSizes;
|
const sizes = style.dashed ? this.dashedSizes : this.letterSizes;
|
||||||
|
const fontFamily = fontFamilyOf(
|
||||||
|
style.dashed ? PRINT_CLEARLY_DASHED : sizes._profile,
|
||||||
|
);
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.globalAlpha = Math.max(0, Math.min(1, alpha));
|
ctx.globalAlpha = Math.max(0, Math.min(1, style.alpha));
|
||||||
|
|
||||||
const pairGap = 3;
|
const pairGap = 3;
|
||||||
|
|
||||||
const upperFs = getLetterFontSize(upper, sizes);
|
const upperFs = getLetterFontSize(upper, sizes);
|
||||||
ctx.font = `${upperFs}px ${this.letterFace}`;
|
ctx.font = `${upperFs}px ${fontFamily}`;
|
||||||
const upperW = ctx.measureText(upper).width;
|
const upperW = ctx.measureText(upper).width;
|
||||||
|
|
||||||
const lowerFs = getLetterFontSize(lower, sizes);
|
const lowerFs = getLetterFontSize(lower, sizes);
|
||||||
ctx.font = `${lowerFs}px ${this.letterFace}`;
|
ctx.font = `${lowerFs}px ${fontFamily}`;
|
||||||
const lowerW = ctx.measureText(lower).width;
|
const lowerW = ctx.measureText(lower).width;
|
||||||
|
|
||||||
const totalPairW = upperW + pairGap + lowerW;
|
const totalPairW = upperW + pairGap + lowerW;
|
||||||
const pairStartX = x + (w - totalPairW) / 2;
|
const pairStartX = x + (w - totalPairW) / 2;
|
||||||
const color = 'rgba(26, 26, 26, 1)';
|
|
||||||
|
|
||||||
drawLetterInFourLineGrid(
|
drawLetterInFourLineGrid(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -216,7 +235,7 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
y,
|
y,
|
||||||
FOUR_LINE_GRID_H,
|
FOUR_LINE_GRID_H,
|
||||||
sizes,
|
sizes,
|
||||||
{ fontFamily: this.letterFace, color },
|
{ fontFamily, color: style.color },
|
||||||
);
|
);
|
||||||
drawLetterInFourLineGrid(
|
drawLetterInFourLineGrid(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -225,7 +244,7 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
y,
|
y,
|
||||||
FOUR_LINE_GRID_H,
|
FOUR_LINE_GRID_H,
|
||||||
sizes,
|
sizes,
|
||||||
{ fontFamily: this.letterFace, color },
|
{ fontFamily, color: style.color },
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
@@ -234,15 +253,20 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
private drawSingleLetterSentence(
|
private drawSingleLetterSentence(
|
||||||
sentence: string,
|
sentence: string,
|
||||||
letter: string,
|
letter: string,
|
||||||
x: number,
|
centerX: number,
|
||||||
y: number,
|
y: number,
|
||||||
) {
|
) {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
ctx.font = `36px ${this.letterFace}`;
|
ctx.font = `bold 40px ${this.letterFace}`;
|
||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.textBaseline = 'top';
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
let cx = x;
|
let totalWidth = 0;
|
||||||
|
for (let i = 0; i < sentence.length; i++) {
|
||||||
|
totalWidth += ctx.measureText(sentence[i]).width;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cx = centerX - totalWidth / 2;
|
||||||
for (let i = 0; i < sentence.length; i++) {
|
for (let i = 0; i < sentence.length; i++) {
|
||||||
const ch = sentence[i];
|
const ch = sentence[i];
|
||||||
ctx.fillStyle =
|
ctx.fillStyle =
|
||||||
@@ -302,8 +326,7 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async drawTraceItSection(
|
private drawTraceItSection(
|
||||||
traceImgPath: string,
|
|
||||||
upper: string,
|
upper: string,
|
||||||
lower: string,
|
lower: string,
|
||||||
sx: number,
|
sx: number,
|
||||||
@@ -357,37 +380,39 @@ export default class SingleLetterDraw extends BaseDrawService {
|
|||||||
ctx.lineTo(lx + lw, lines[3]);
|
ctx.lineTo(lx + lw, lines[3]);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
// 笔顺图
|
const gridH = lines[3] - lines[0];
|
||||||
const imgX = 337,
|
const boldSizes = calcLetterFontSizes(gridH, PRINT_CLEARLY_BOLD);
|
||||||
imgY = 365,
|
const boldFace = fontFamilyOf(PRINT_CLEARLY_BOLD);
|
||||||
imgW = 159,
|
const pairGap = 20;
|
||||||
imgH = 80;
|
|
||||||
try {
|
const upperFs = getLetterFontSize(upper, boldSizes);
|
||||||
const img = (await getImage(
|
ctx.font = `${upperFs}px ${boldFace}`;
|
||||||
this.canvas,
|
const upperW = ctx.measureText(upper).width;
|
||||||
traceImgPath,
|
|
||||||
)) as WechatMiniprogram.Image;
|
const lowerFs = getLetterFontSize(lower, boldSizes);
|
||||||
const aspect = (img.width || 1) / (img.height || 1);
|
ctx.font = `${lowerFs}px ${boldFace}`;
|
||||||
let dw = imgW;
|
const lowerW = ctx.measureText(lower).width;
|
||||||
let dh = dw / aspect;
|
|
||||||
if (dh > imgH) {
|
const totalW = upperW + pairGap + lowerW;
|
||||||
dh = imgH;
|
const startX = lx + (lw - totalW) / 2;
|
||||||
dw = dh * aspect;
|
|
||||||
}
|
drawLetterInFourLineGrid(
|
||||||
ctx.drawImage(
|
ctx,
|
||||||
img,
|
upper,
|
||||||
imgX + (imgW - dw) / 2,
|
startX + upperW / 2,
|
||||||
imgY + (imgH - dh) / 2,
|
lines[0],
|
||||||
dw,
|
gridH,
|
||||||
dh,
|
boldSizes,
|
||||||
);
|
{ fontFamily: boldFace, color: '#222' },
|
||||||
} catch {
|
);
|
||||||
const fs = Math.min(imgH * 0.6, imgW * 0.18);
|
drawLetterInFourLineGrid(
|
||||||
ctx.font = `bold ${fs}px ${this.letterFace}`;
|
ctx,
|
||||||
ctx.fillStyle = '#222';
|
lower,
|
||||||
ctx.textAlign = 'center';
|
startX + upperW + pairGap + lowerW / 2,
|
||||||
ctx.textBaseline = 'middle';
|
lines[0],
|
||||||
ctx.fillText(`${upper} ${lower}`, imgX + imgW / 2, imgY + imgH / 2);
|
gridH,
|
||||||
}
|
boldSizes,
|
||||||
|
{ fontFamily: boldFace, color: '#222' },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import {
|
|||||||
type LetterFontSizes,
|
type LetterFontSizes,
|
||||||
} from '../../shared/draw/drawTools';
|
} from '../../shared/draw/drawTools';
|
||||||
|
|
||||||
// 每行可容纳的格子数(用于三字母精练模板,一行展示 6 个位置)
|
// 每行按 7 个位置预留空间,当前字母绘制占用其中前几组
|
||||||
const SLOTS_PER_ROW = 6;
|
const SLOTS_PER_ROW = 7;
|
||||||
// 大小写字母配对间的间隔(px,字母间距)
|
// 大小写字母配对间的间隔(px,字母间距)
|
||||||
const PAIR_GAP = 25;
|
const PAIR_GAP = 25;
|
||||||
// 同一字母区域内相邻两行四线三格之间的间距(px)
|
// 同一字母区域内相邻两行四线三格之间的间距(px)
|
||||||
|
|||||||
@@ -317,9 +317,7 @@ export function generateLetterTracing(
|
|||||||
const upper = key;
|
const upper = key;
|
||||||
const lower = key.toLowerCase();
|
const lower = key.toLowerCase();
|
||||||
|
|
||||||
const hasLetterAsset = key === 'A' || key === 'B';
|
const letterImgPath = `/englishPages/shared/assets/letter/${key}.png`;
|
||||||
const letterFile = hasLetterAsset ? key : 'A';
|
|
||||||
const letterImgPath = `/englishPages/shared/assets/letter/${letterFile}.png`;
|
|
||||||
|
|
||||||
const wordImgPath = `/englishPages/shared/assets/letterImgs/Apple.png`;
|
const wordImgPath = `/englishPages/shared/assets/letterImgs/Apple.png`;
|
||||||
|
|
||||||
@@ -361,9 +359,15 @@ export function generateLetterTracing(
|
|||||||
if (mode === 'letter-tracing-two-column') {
|
if (mode === 'letter-tracing-two-column') {
|
||||||
const leftLetters = LETTERS_UPPER.slice(0, 13);
|
const leftLetters = LETTERS_UPPER.slice(0, 13);
|
||||||
const rightLetters = LETTERS_UPPER.slice(13);
|
const rightLetters = LETTERS_UPPER.slice(13);
|
||||||
|
const twoColumnRepetitions = 4;
|
||||||
const toRows = (letters: string[]) =>
|
const toRows = (letters: string[]) =>
|
||||||
letters.map((L) =>
|
letters.map((L) =>
|
||||||
rowForChar(L, repetitions, fadePattern, L.toLowerCase()),
|
rowForChar(
|
||||||
|
L,
|
||||||
|
twoColumnRepetitions,
|
||||||
|
fadePattern,
|
||||||
|
L.toLowerCase(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
mode: 'letter-tracing-two-column',
|
mode: 'letter-tracing-two-column',
|
||||||
@@ -450,11 +454,7 @@ export function mergeLetterTracingConfig(
|
|||||||
'A-M') as AlphabetHalf,
|
'A-M') as AlphabetHalf,
|
||||||
tripleLetters: overrides.tripleLetters ??
|
tripleLetters: overrides.tripleLetters ??
|
||||||
base.tripleLetters ?? ['A', 'B', 'C'],
|
base.tripleLetters ?? ['A', 'B', 'C'],
|
||||||
dailyLetters: overrides.dailyLetters ?? base.dailyLetters ?? [
|
dailyLetters: overrides.dailyLetters ??
|
||||||
'A',
|
base.dailyLetters ?? ['A', 'B', 'C', 'D'],
|
||||||
'B',
|
|
||||||
'C',
|
|
||||||
'D',
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ const MODES = [
|
|||||||
title: '默认字帖',
|
title: '默认字帖',
|
||||||
subtitle: '配图、例句与描红',
|
subtitle: '配图、例句与描红',
|
||||||
difficulty: 1,
|
difficulty: 1,
|
||||||
tags: ['字母描红', '英语启蒙', '看图描红'],
|
tags: ['字母', '描红', '看图描红'],
|
||||||
sortOrder: 42,
|
sortOrder: 42,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ const MODES = [
|
|||||||
title: '基础描红',
|
title: '基础描红',
|
||||||
subtitle: 'Uppercase / Lowercase 总览',
|
subtitle: 'Uppercase / Lowercase 总览',
|
||||||
difficulty: 1,
|
difficulty: 1,
|
||||||
tags: ['字母描红', '英语启蒙', '字母总览'],
|
tags: ['字母', '描红', '字母总览'],
|
||||||
sortOrder: 44,
|
sortOrder: 44,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -37,7 +37,7 @@ const MODES = [
|
|||||||
title: '大小写对照',
|
title: '大小写对照',
|
||||||
subtitle: '半组字母左大写右小写',
|
subtitle: '半组字母左大写右小写',
|
||||||
difficulty: 1,
|
difficulty: 1,
|
||||||
tags: ['字母描红', '英语启蒙', '大小写练习'],
|
tags: ['字母', '描红', '大小写练习'],
|
||||||
sortOrder: 45,
|
sortOrder: 45,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -46,7 +46,7 @@ const MODES = [
|
|||||||
title: '两列描红',
|
title: '两列描红',
|
||||||
subtitle: '左 A–M、右 N–Z 配对描红',
|
subtitle: '左 A–M、右 N–Z 配对描红',
|
||||||
difficulty: 1,
|
difficulty: 1,
|
||||||
tags: ['字母描红', '英语启蒙', '两列练习'],
|
tags: ['字母', '描红', '两列练习'],
|
||||||
sortOrder: 43,
|
sortOrder: 43,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -55,7 +55,7 @@ const MODES = [
|
|||||||
title: '13字母半表',
|
title: '13字母半表',
|
||||||
subtitle: '每行一个字母,13 字母半表',
|
subtitle: '每行一个字母,13 字母半表',
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
tags: ['字母描红', '英语启蒙', '单字母逐行'],
|
tags: ['字母', '描红', '半表练习'],
|
||||||
sortOrder: 47,
|
sortOrder: 47,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -64,25 +64,26 @@ const MODES = [
|
|||||||
title: '三字母精练',
|
title: '三字母精练',
|
||||||
subtitle: '每页聚焦 3 个字母深度书写',
|
subtitle: '每页聚焦 3 个字母深度书写',
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
tags: ['字母描红', '英语启蒙', '三字母精练'],
|
tags: ['字母', '描红', '三字母精练'],
|
||||||
sortOrder: 46,
|
sortOrder: 46,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'letter-tracing-daily-checkin',
|
id: 'letter-tracing-daily-checkin',
|
||||||
icon: 'draw-o',
|
icon: 'task-o',
|
||||||
title: '每日打卡',
|
title: '每日打卡',
|
||||||
subtitle: '四宫格每日字母打卡练习',
|
subtitle: '四宫格每日字母打卡练习',
|
||||||
difficulty: 2,
|
difficulty: 2,
|
||||||
tags: ['字母描红', '英语启蒙', '每日打卡'],
|
tags: ['字母', '描红', '每日打卡'],
|
||||||
sortOrder: 48,
|
sortOrder: 48,
|
||||||
},
|
},
|
||||||
] as const satisfies ReadonlyArray<ModeDefinition>;
|
] as const satisfies ReadonlyArray<ModeDefinition>;
|
||||||
|
|
||||||
type Mode = (typeof MODES)[number];
|
type Mode = (typeof MODES)[number];
|
||||||
|
|
||||||
const MODE_BY_ID = Object.fromEntries(
|
const MODE_BY_ID = Object.fromEntries(MODES.map((m) => [m.id, m])) as Record<
|
||||||
MODES.map((m) => [m.id, m]),
|
string,
|
||||||
) as Record<string, Mode>;
|
Mode
|
||||||
|
>;
|
||||||
|
|
||||||
/** 页面渲染用:模式选择器列表 */
|
/** 页面渲染用:模式选择器列表 */
|
||||||
export const LETTER_TRACING_MODE_OPTIONS = MODES;
|
export const LETTER_TRACING_MODE_OPTIONS = MODES;
|
||||||
|
|||||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 8.4 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 15 KiB |
@@ -24,16 +24,16 @@
|
|||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
{
|
{
|
||||||
"name": "supportPages/debug/debug",
|
"name": "englishPages/letterTracing/letterTracing",
|
||||||
"pathName": "supportPages/debug/debug",
|
"pathName": "englishPages/letterTracing/letterTracing",
|
||||||
"query": "",
|
"query": "",
|
||||||
"scene": null,
|
"scene": null,
|
||||||
"launchMode": "default"
|
"launchMode": "default"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "englishPages/letterTracing/letterTracing",
|
"name": "supportPages/debug/debug",
|
||||||
"pathName": "englishPages/letterTracing/letterTracing",
|
"pathName": "supportPages/debug/debug",
|
||||||
"query": "id=letter-tracing-daily-checkin",
|
"query": "",
|
||||||
"launchMode": "default",
|
"launchMode": "default",
|
||||||
"scene": null
|
"scene": null
|
||||||
},
|
},
|
||||||
|
|||||||