import { BaseDrawService } from '../../core/draw/baseDraw'; import NumberFindDraw from '../shared/service/numberFindDraw'; import CountMatchDraw from '../shared/service/countMatchDraw'; import NumberColorDraw from '../shared/service/numberColorDraw'; import AdditionDraw from '../shared/service/additionDraw'; import MissingNumberDraw from '../shared/service/missingNumberDraw'; import CompareDraw from '../shared/service/compareDraw'; import CountingSelectDraw from '../shared/service/countingSelectDraw'; import NumberDecomposeDraw from '../shared/service/numberDecomposeDraw'; import NumberSortDraw from '../shared/service/numberSortDraw'; import NumberObjectMatchDraw from '../shared/service/numberObjectMatchDraw'; import MakeTenDraw from '../shared/service/makeTenDraw'; import BreakTenDraw from '../shared/service/breakTenDraw'; import FlatTenDraw from '../shared/service/flatTenDraw'; import OneDigitAdditionDraw from '../shared/service/oneDigitAdditionDraw'; import CalculationPracticeDraw from '../shared/service/calculationPracticeDraw'; import MultiplicationTableDraw from '../shared/service/multiplicationTableDraw'; import { getRandomNumberColor, getNumberColors, NUMBER_COLORS } from '../../constants/colors'; // ─── Types ─── export interface MathTypeAction { value: string; label: string; } export interface MathTypeConfig { id: string; title: string; subTitle: string; icon: string; actionsTitle?: string; actions?: MathTypeAction[]; defaultMode?: string; hasNumberGrid?: boolean; getTitle?: (mode: string) => string; getSubTitle?: (mode: string) => string; createDrawService: ( canvas: Canvas, ctx: RenderingContext, options?: Record, ) => BaseDrawService; generateData: (mode?: string, extra?: any) => any; drawArgs?: (data: any, mode: string) => any[]; /** 需要根据 functionId 决定不同的 actions/DrawService 的复合类型 */ variants?: Record) => BaseDrawService; }>; } // ─── Data Generators ─── function generateNumberFindData(_mode?: string, extra?: any) { const selectedNumber = extra?.selectedNumber || Math.floor(Math.random() * 10) + 1; return { selectedNumber }; } function generateCountMatchData(mode?: string) { const availableNumbers = Array.from({ length: 10 }, (_, i) => i + 1); const leftNumbers: number[] = []; const pool = [...availableNumbers]; for (let i = 0; i < 5; i++) { const idx = Math.floor(Math.random() * pool.length); leftNumbers.push(pool.splice(idx, 1)[0]); } const rightNumbers = [...leftNumbers]; for (let i = rightNumbers.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [rightNumbers[i], rightNumbers[j]] = [rightNumbers[j], rightNumbers[i]]; } return { leftNumbers, rightNumbers, _drawMode: mode || 'twelve-animals' }; } function generateNumberColorData(mode?: string) { const availableNumbers = Array.from({ length: 10 }, (_, i) => i + 1); const numbers: number[] = []; const pool = [...availableNumbers]; for (let i = 0; i < 6; i++) { const idx = Math.floor(Math.random() * pool.length); numbers.push(pool.splice(idx, 1)[0]); } return { numbers, _drawMode: mode || 'caterpillar' }; } function generateAdditionData(mode?: string) { const type = mode || 'addition-5'; const problems: Array<{ type: 'addition' | 'subtraction'; left: number; right: number; result: number }> = []; for (let i = 0; i < 5; i++) { if (type === 'addition-5') { const left = Math.floor(Math.random() * 4) + 1; const right = Math.floor(Math.random() * (5 - left)) + 1; problems.push({ type: 'addition', left, right, result: left + right }); } else if (type === 'addition-10') { const left = Math.floor(Math.random() * 9) + 1; const right = Math.floor(Math.random() * (10 - left)) + 1; problems.push({ type: 'addition', left, right, result: left + right }); } else if (type === 'subtraction-10') { const left = Math.floor(Math.random() * 10) + 1; const right = Math.floor(Math.random() * (left - 1)) + 1; problems.push({ type: 'subtraction', left, right, result: left - right }); } else { if (Math.random() < 0.5) { const left = Math.floor(Math.random() * 9) + 1; const right = Math.floor(Math.random() * (10 - left)) + 1; problems.push({ type: 'addition', left, right, result: left + right }); } else { const left = Math.floor(Math.random() * 10) + 1; const right = Math.floor(Math.random() * (left - 1)) + 1; problems.push({ type: 'subtraction', left, right, result: left - right }); } } } return { problems }; } function generateMissingNumberData(mode?: string) { const maxNumber = parseInt(mode || '10') || 10; const gridMap: Record = { 10: 3, 20: 2, 40: 2, 50: 2, 80: 1, 100: 1, 120: 1 }; const gridCount = gridMap[maxNumber] || 1; const grids: Array<{ numbers: (number | null)[]; colors: (string | null)[] }> = []; for (let g = 0; g < gridCount; g++) { const total = maxNumber; const numbers = Array.from({ length: total }, (_, i) => i + 1); const hideCount = Math.floor(total * (0.4 + Math.random() * 0.2)); const hidden = new Set(); while (hidden.size < hideCount) hidden.add(Math.floor(Math.random() * total)); const gridNumbers: (number | null)[] = numbers.map((n, i) => hidden.has(i) ? null : n); const gridColors: (string | null)[] = gridNumbers.map((n) => n ? getRandomNumberColor() : null); grids.push({ numbers: gridNumbers, colors: gridColors }); } return { grids, maxNumber }; } function generateCompareData() { const problems: Array<{ leftCount: number; rightCount: number; leftImageIndex: number; rightImageIndex: number; imageType: 'fruits' | 'twelve-animals'; }> = []; for (let i = 0; i < 12; i++) { const imageType: 'fruits' | 'twelve-animals' = Math.random() < 0.5 ? 'fruits' : 'twelve-animals'; const maxIdx = imageType === 'fruits' ? 22 : 12; problems.push({ leftCount: Math.floor(Math.random() * 10) + 1, rightCount: Math.floor(Math.random() * 10) + 1, leftImageIndex: Math.floor(Math.random() * maxIdx) + 1, rightImageIndex: Math.floor(Math.random() * maxIdx) + 1, imageType, }); } return { problems }; } function generateCountingSelectData(_mode?: string, extra?: any) { const isFillMode = extra?.functionId === 'counting-fill'; const problems: Array<{ count: number; imageIndex: number; imageType: 'fruits' | 'twelve-animals'; options?: number[]; correctIndex?: number; }> = []; for (let i = 0; i < 9; i++) { const imageType: 'fruits' | 'twelve-animals' = Math.random() < 0.5 ? 'fruits' : 'twelve-animals'; const maxIdx = imageType === 'fruits' ? 22 : 12; const count = Math.floor(Math.random() * 10) + 1; const problem: any = { count, imageIndex: Math.floor(Math.random() * maxIdx) + 1, imageType }; if (!isFillMode) { const options: number[] = []; const correctIndex = Math.floor(Math.random() * 3); for (let j = 0; j < 3; j++) { if (j === correctIndex) { options.push(count); } else { let w; do { w = Math.floor(Math.random() * 10) + 1; } while (w === count); options.push(w); } } problem.options = options; problem.correctIndex = correctIndex; } problems.push(problem); } return { problems }; } function generateNumberDecomposeData(mode?: string, extra?: any) { const decomposeMode = mode || 'with-image'; const maxNumber = extra?.maxNumber || 10; const is20 = maxNumber === 20; const minWhole = is20 ? 11 : 2; const maxWhole = maxNumber; const problems: Array<{ whole: number | null; part1: number | null; part2: number | null; imageIndex?: number; imageType?: 'fruits' | 'twelve-animals'; }> = []; const usedKeys = new Set(); const problemCount = decomposeMode === 'with-image' ? 9 : 15; let attempts = 0; while (problems.length < problemCount && attempts < problemCount * 50) { attempts++; const whole = Math.floor(Math.random() * (maxWhole - minWhole + 1)) + minWhole; const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part2 = whole - part1; if (decomposeMode === 'compose') { const key = `${Math.min(part1, part2)}:${Math.max(part1, part2)}`; if (usedKeys.has(key)) continue; usedKeys.add(key); problems.push({ whole: null, part1, part2 }); } else { const showPart1 = Math.random() < 0.5; const key = `${whole}:${Math.min(part1, part2)}:${Math.max(part1, part2)}:${showPart1}`; if (usedKeys.has(key)) continue; usedKeys.add(key); const problem: any = { whole, part1: showPart1 ? part1 : null, part2: showPart1 ? null : part2, }; if (decomposeMode === 'with-image') { const imgType: 'fruits' | 'twelve-animals' = Math.random() < 0.5 ? 'fruits' : 'twelve-animals'; problem.imageIndex = Math.floor(Math.random() * (imgType === 'fruits' ? 22 : 12)) + 1; problem.imageType = imgType; } problems.push(problem); } } return { problems, mode: decomposeMode }; } function generateNumberSortData() { const availableColors = getNumberColors(); const groups: Array<{ numbers: Array<{ number: number; color: string; angle: number }> }> = []; for (let i = 0; i < 6; i++) { const pool = Array.from({ length: 9 }, (_, i) => i + 1); const selected: number[] = []; for (let j = 0; j < 4; j++) { const idx = Math.floor(Math.random() * pool.length); selected.push(pool.splice(idx, 1)[0]); } const shuffled = [...selected]; for (let j = shuffled.length - 1; j > 0; j--) { const k = Math.floor(Math.random() * (j + 1)); [shuffled[j], shuffled[k]] = [shuffled[k], shuffled[j]]; } groups.push({ numbers: shuffled.map((num) => ({ number: num, color: NUMBER_COLORS[num as keyof typeof NUMBER_COLORS] || availableColors[Math.floor(Math.random() * availableColors.length)], angle: ((Math.random() - 0.5) * 60 * Math.PI) / 180, })), }); } return { groups }; } function generateNumberObjectMatchData() { const fruitsIndices = Array.from({ length: 22 }, (_, i) => i + 1); const animalsIndices = Array.from({ length: 12 }, (_, i) => i + 1); const allImages = [ ...fruitsIndices.map((idx) => ({ imageIndex: idx, folder: 'fruits' as const })), ...animalsIndices.map((idx) => ({ imageIndex: idx, folder: 'twelve-animals' as const })), ]; const selectedImages = [...allImages].sort(() => Math.random() - 0.5).slice(0, 4); const gridImages = Array.from({ length: 20 }, () => { const img = selectedImages[Math.floor(Math.random() * selectedImages.length)]; return { ...img, offsetX: (Math.random() - 0.5) * 30, offsetY: (Math.random() - 0.5) * 30 }; }); const bottomImages = [...selectedImages]; const countMap = new Map(); gridImages.forEach((it) => { const key = `${it.folder}-${it.imageIndex}`; countMap.set(key, (countMap.get(key) || 0) + 1); }); const imageCounts = selectedImages.map((img) => countMap.get(`${img.folder}-${img.imageIndex}`) || 0); const bottomNumbers = [...imageCounts].sort(() => Math.random() - 0.5); return { gridImages, bottomImages, bottomNumbers }; } function generateMakeTenData() { const problems: Array<{ left: number; right: number; result: number }> = []; const used = new Set(); let attempts = 0; while (problems.length < 9 && attempts < 1000) { attempts++; const bigFirst = Math.random() < 0.5; let left: number, right: number; if (bigFirst) { left = Math.floor(Math.random() * 4) + 6; const minR = Math.max(1, 11 - left); const maxR = Math.min(left - 1, 20 - left); if (maxR < minR) continue; right = Math.floor(Math.random() * (maxR - minR + 1)) + minR; } else { right = Math.floor(Math.random() * 4) + 6; const minL = Math.max(1, 11 - right); const maxL = Math.min(right - 1, 20 - right); if (maxL < minL) continue; left = Math.floor(Math.random() * (maxL - minL + 1)) + minL; } const key = `${left},${right}`; if (used.has(key)) continue; used.add(key); problems.push({ left, right, result: left + right }); } return { problems }; } function generateBreakTenData() { const problems: Array<{ minuend: number; subtrahend: number; result: number }> = []; const used = new Set(); let attempts = 0; while (problems.length < 9 && attempts < 1000) { attempts++; const minuend = Math.floor(Math.random() * 10) + 11; const subtrahend = Math.floor(Math.random() * 9) + 1; if (minuend <= subtrahend) continue; const key = `${minuend},${subtrahend}`; if (used.has(key)) continue; used.add(key); problems.push({ minuend, subtrahend, result: minuend - subtrahend }); } return { problems }; } function generateFlatTenData() { const allValid: Array<{ minuend: number; subtrahend: number; result: number }> = []; for (let m = 11; m < 20; m++) { const ones = m % 10; for (let s = 1; s <= 10; s++) { if (s > ones && m > s) allValid.push({ minuend: m, subtrahend: s, result: m - s }); } } const shuffled = [...allValid]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return { problems: shuffled.slice(0, Math.min(9, shuffled.length)) }; } function generateBorrowTenData(mode?: string) { let maxMinuend = 50; if (mode === 'within-30') maxMinuend = 30; else if (mode === 'within-100') maxMinuend = 100; const byMinuend = new Map>(); for (let m = 20; m <= maxMinuend; m++) { const ones = m % 10; for (let s = 1; s <= 10; s++) { if (ones < s && m > s) { if (!byMinuend.has(m)) byMinuend.set(m, []); byMinuend.get(m)!.push({ minuend: m, subtrahend: s, result: m - s }); } } } const minuends = Array.from(byMinuend.keys()); for (let i = minuends.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [minuends[i], minuends[j]] = [minuends[j], minuends[i]]; } const problems: Array<{ minuend: number; subtrahend: number; result: number }> = []; const target = 9; if (minuends.length >= target) { for (let i = 0; i < target; i++) { const arr = byMinuend.get(minuends[i])!; problems.push(arr[Math.floor(Math.random() * arr.length)]); } } else { const per = Math.floor(target / minuends.length); const rem = target % minuends.length; for (let i = 0; i < minuends.length; i++) { const arr = [...byMinuend.get(minuends[i])!]; for (let k = arr.length - 1; k > 0; k--) { const j = Math.floor(Math.random() * (k + 1)); [arr[k], arr[j]] = [arr[j], arr[k]]; } const count = per + (i < rem ? 1 : 0); for (let j = 0; j < Math.min(count, arr.length); j++) problems.push(arr[j]); } } for (let i = problems.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [problems[i], problems[j]] = [problems[j], problems[i]]; } return { problems }; } function generateOneDigitAdditionData() { const problems: Array<{ left: number; right: number; result: number }> = []; const used = new Set(); let attempts = 0; while (problems.length < 6 && attempts < 1000) { attempts++; const left = Math.floor(Math.random() * 9) + 1; const right = Math.floor(Math.random() * (10 - left)) + 1; const key = `${left},${right}`; if (used.has(key)) continue; used.add(key); problems.push({ left, right, result: left + right }); } return { problems }; } function generateCalculationPracticeData(mode?: string, extra?: any) { const opType = extra?.operationType || 'addition'; let maxValue = 10; if (mode === 'within-20') maxValue = 20; else if (mode === 'within-50') maxValue = 50; else if (mode === 'within-100') maxValue = 100; const problems: Array<{ left: number; operator: '+' | '-'; right: number; result: number }> = []; const used = new Set(); let attempts = 0; while (problems.length < 33 && attempts < 2000) { attempts++; let left: number, right: number, operator: '+' | '-', result: number; const doAdd = opType === 'addition' ? true : opType === 'subtraction' ? false : Math.random() < 0.5; if (doAdd) { left = Math.floor(Math.random() * (maxValue - 1)) + 1; right = Math.floor(Math.random() * (maxValue - left)) + 1; operator = '+'; result = left + right; } else { left = Math.floor(Math.random() * maxValue) + 1; right = Math.floor(Math.random() * left) + 1; operator = '-'; result = left - right; } const key = `${left},${operator},${right}`; if (used.has(key)) continue; used.add(key); problems.push({ left, operator, right, result }); } return { problems }; } function generateMultiplicationTableData() { return {}; } // ─── Registry ─── export const MATH_TYPE_CONFIGS: MathTypeConfig[] = [ { id: 'number-find', title: '看数字,涂一涂', subTitle: '找一找下面相同的数字,涂上颜色', icon: '🔍', hasNumberGrid: true, createDrawService: (canvas, ctx, opts) => new NumberFindDraw(canvas, ctx, opts), generateData: generateNumberFindData, }, { id: 'counting-matching', title: '数一数,连一连', subTitle: '通过连线配对数字和对应的数量图形', icon: '🔗', actionsTitle: '选择图片', actions: [ { value: 'twelve-animals', label: '十二生肖' }, { value: 'fruits', label: '水果' }, ], defaultMode: 'twelve-animals', createDrawService: (canvas, ctx, opts) => new CountMatchDraw(canvas, ctx, opts), generateData: generateCountMatchData, }, { id: 'number-coloring', title: '按数字涂颜色', subTitle: '按数字给相应的圆圈涂上颜色', icon: '🎨', actionsTitle: '选择样式', actions: [ { value: 'caterpillar', label: '毛毛虫' }, { value: 'circle', label: '圆圈' }, ], defaultMode: 'caterpillar', createDrawService: (canvas, ctx, opts) => new NumberColorDraw(canvas, ctx, opts), generateData: generateNumberColorData, }, { id: 'addition', title: '图形加减法', subTitle: '通过图形化方式学习加减法运算', icon: '➕', actionsTitle: '选择类型', actions: [ { value: 'addition-5', label: '5以内加法' }, { value: 'addition-10', label: '10以内加法' }, { value: 'subtraction-10', label: '10以内减法' }, { value: 'addition-subtraction-10', label: '10以内加减法' }, ], defaultMode: 'addition-5', createDrawService: (canvas, ctx, opts) => new AdditionDraw(canvas, ctx, opts), generateData: generateAdditionData, }, { id: 'missing-number', title: '填缺少的数字', subTitle: '在数字序列中找出并填写缺失的数字', icon: '❓', actionsTitle: '选择范围', actions: [ { value: '10', label: '10以内' }, { value: '20', label: '20以内' }, { value: '40', label: '40以内' }, { value: '50', label: '50以内' }, { value: '80', label: '80以内' }, { value: '100', label: '100以内' }, { value: '120', label: '120以内' }, ], defaultMode: '10', createDrawService: (canvas, ctx, opts) => new MissingNumberDraw(canvas, ctx, opts), generateData: generateMissingNumberData, }, { id: 'compare', title: '数一数,比大小', subTitle: '数一数,比较数量,在⭕️中填入>、<、=', icon: '⚖️', createDrawService: (canvas, ctx, opts) => new CompareDraw(canvas, ctx, opts), generateData: generateCompareData, }, { id: 'counting-select', title: '数一数,选一选', subTitle: '数出物品数量,从多个选项中选择正确答案', icon: '🎯', createDrawService: (canvas, ctx, opts) => new CountingSelectDraw(canvas, ctx, opts), generateData: generateCountingSelectData, }, { id: 'number-decompose', title: '10以内分与合', subTitle: '学习数的分解与组合', icon: '🌳', actionsTitle: '选择模式', actions: [ { value: 'with-image', label: '有图片模式' }, { value: 'decompose', label: '分模式' }, { value: 'compose', label: '组合模式' }, ], defaultMode: 'with-image', createDrawService: (canvas, ctx, opts) => new NumberDecomposeDraw(canvas, ctx, opts), generateData: (mode) => generateNumberDecomposeData(mode, { maxNumber: 10 }), }, { id: 'number-decompose-20', title: '20以内分与合', subTitle: '学习20以内数的分解与组合', icon: '🌲', actionsTitle: '选择模式', actions: [ { value: 'decompose', label: '20以内的分解' }, { value: 'compose', label: '20以内的组合' }, ], defaultMode: 'decompose', createDrawService: (canvas, ctx, opts) => new NumberDecomposeDraw(canvas, ctx, opts), generateData: (mode) => generateNumberDecomposeData(mode, { maxNumber: 20 }), }, { id: 'number-sort', title: '数字排序', subTitle: '数字排序,写出正确顺序', icon: '🔢', createDrawService: (canvas, ctx, opts) => new NumberSortDraw(canvas, ctx, opts), generateData: generateNumberSortData, }, { id: 'number-object-match', title: '数物对应', subTitle: '数一数图片数量并对应数字', icon: '🧮', actionsTitle: '选择模式', actions: [ { value: 'match', label: '连线' }, { value: 'fill', label: '填写' }, ], defaultMode: 'match', createDrawService: (canvas, ctx, opts) => new NumberObjectMatchDraw(canvas, ctx, opts), generateData: generateNumberObjectMatchData, }, { id: 'make-ten', title: '凑十法练习', subTitle: '通过凑十法学习20以内进位加法', icon: '🔟', createDrawService: (canvas, ctx, opts) => new MakeTenDraw(canvas, ctx, opts), generateData: generateMakeTenData, }, { id: 'break-ten', title: '破十法练习', subTitle: '通过破十法学习20以内退位减法', icon: '💥', createDrawService: (canvas, ctx, opts) => new BreakTenDraw(canvas, ctx, opts), generateData: generateBreakTenData, }, { id: 'flat-ten', title: '平十法练习', subTitle: '通过平十法学习退位减法', icon: '📐', createDrawService: (canvas, ctx, opts) => new FlatTenDraw(canvas, ctx, opts), generateData: generateFlatTenData, }, { id: 'borrow-ten', title: '借十法练习', subTitle: '拆大数,借出10,减小数,加剩数', icon: '🏦', actionsTitle: '选择范围', actions: [ { value: 'within-30', label: '30以内' }, { value: 'within-50', label: '50以内' }, { value: 'within-100', label: '100以内' }, ], defaultMode: 'within-50', createDrawService: (canvas, ctx, opts) => new BreakTenDraw(canvas, ctx, { ...opts, mnemonic: '拆大数,借出10,减小数,加剩数' }), generateData: generateBorrowTenData, }, { id: 'one-digit-addition', title: '一位数加法', subTitle: '通过圆点学习一位数加法运算', icon: '⚫', createDrawService: (canvas, ctx, opts) => new OneDigitAdditionDraw(canvas, ctx, opts), generateData: generateOneDigitAdditionData, }, { id: 'practice-addition', title: '加法口算', subTitle: '加法计算练习题', icon: '📝', actionsTitle: '选择范围', actions: [ { value: 'within-10', label: '10以内' }, { value: 'within-20', label: '20以内' }, { value: 'within-50', label: '50以内' }, { value: 'within-100', label: '100以内' }, ], defaultMode: 'within-10', createDrawService: (canvas, ctx, opts) => new CalculationPracticeDraw(canvas, ctx, opts), generateData: (mode) => generateCalculationPracticeData(mode, { operationType: 'addition' }), }, { id: 'practice-subtraction', title: '减法口算', subTitle: '减法计算练习题', icon: '📝', actionsTitle: '选择范围', actions: [ { value: 'within-10', label: '10以内' }, { value: 'within-20', label: '20以内' }, { value: 'within-50', label: '50以内' }, { value: 'within-100', label: '100以内' }, ], defaultMode: 'within-10', createDrawService: (canvas, ctx, opts) => new CalculationPracticeDraw(canvas, ctx, opts), generateData: (mode) => generateCalculationPracticeData(mode, { operationType: 'subtraction' }), }, { id: 'practice-mixed', title: '混合口算', subTitle: '加减法混合计算练习题', icon: '📝', actionsTitle: '选择范围', actions: [ { value: 'within-10', label: '10以内' }, { value: 'within-20', label: '20以内' }, { value: 'within-50', label: '50以内' }, { value: 'within-100', label: '100以内' }, ], defaultMode: 'within-10', createDrawService: (canvas, ctx, opts) => new CalculationPracticeDraw(canvas, ctx, opts), generateData: (mode) => generateCalculationPracticeData(mode, { operationType: 'mixed' }), }, { id: 'multiplication-table', title: '九九乘法表', subTitle: '九九乘法表', icon: '✖️', createDrawService: (canvas, ctx, opts) => new MultiplicationTableDraw(canvas, ctx, opts), generateData: generateMultiplicationTableData, }, ]; /** * 通过路由 ID 查找类型配置 * 处理别名映射:counting-fill → counting-select, number-object-fill → number-object-match 等 */ export function findTypeByRouteId( routeId: string, ): { typeConfig: MathTypeConfig; mode?: string; extra?: Record } | null { const direct = MATH_TYPE_CONFIGS.find((t) => t.id === routeId); if (direct) return { typeConfig: direct }; // 别名映射 const aliases: Record }> = { 'counting-fill': { typeId: 'counting-select', extra: { functionId: 'counting-fill' } }, 'number-object-fill': { typeId: 'number-object-match', extra: { defaultMode: 'fill' } }, 'number-write': { typeId: 'number-find', extra: { functionId: 'number-write' } }, 'number-find': { typeId: 'number-find' }, 'addition-5': { typeId: 'addition' }, 'addition-10': { typeId: 'addition', extra: { defaultMode: 'addition-10' } }, 'subtraction-10': { typeId: 'addition', extra: { defaultMode: 'subtraction-10' } }, 'addition-subtraction-10': { typeId: 'addition', extra: { defaultMode: 'addition-subtraction-10' } }, }; const alias = aliases[routeId]; if (alias) { const cfg = MATH_TYPE_CONFIGS.find((t) => t.id === alias.typeId); if (cfg) return { typeConfig: cfg, mode: alias.extra?.defaultMode, extra: alias.extra }; } return { typeConfig: MATH_TYPE_CONFIGS[0] }; }