feat:添加埋点,createPage 重构

This commit is contained in:
R524809
2025-12-09 17:33:58 +08:00
parent e193369478
commit 2c32235568
10 changed files with 1249 additions and 1279 deletions
+1 -1
View File
@@ -60,7 +60,7 @@
"pagePath": "pages/mathIndex/mathIndex", "pagePath": "pages/mathIndex/mathIndex",
"iconPath": "assets/tabBar/icon-math.png", "iconPath": "assets/tabBar/icon-math.png",
"selectedIconPath": "assets/tabBar/icon-math-active.png", "selectedIconPath": "assets/tabBar/icon-math-active.png",
"text": "数" "text": "数感启蒙"
}, },
{ {
"pagePath": "pages/index/index", "pagePath": "pages/index/index",
+4 -4
View File
@@ -2,19 +2,19 @@ import config from './config/config';
const defaultPrintConfig: PrintConfig = config.printHeader as PrintConfig; const defaultPrintConfig: PrintConfig = config.printHeader as PrintConfig;
// app.ts // app.ts
App<IAppOption>({ App<IAppOption>({
globalData: { globalData: {
env: 'release', env: 'release',
printConfig: defaultPrintConfig printConfig: defaultPrintConfig,
}, },
onLaunch() { onLaunch() {
const accountInfo = wx.getAccountInfoSync(); const accountInfo = wx.getAccountInfoSync();
const env = accountInfo.miniProgram.envVersion || 'release'; const env = accountInfo.miniProgram.envVersion || 'release';
this.globalData.env = env; this.globalData.env = env;
if (env !== 'release') { if (env !== 'release') {
const printConfig = wx.getStorageSync('printConfig') || defaultPrintConfig; const printConfig =
wx.getStorageSync('printConfig') || defaultPrintConfig;
this.globalData.printConfig = printConfig; this.globalData.printConfig = printConfig;
} }
}, },
@@ -34,5 +34,5 @@ App<IAppOption>({
setPrintConfig(config: PrintConfig) { setPrintConfig(config: PrintConfig) {
this.globalData.printConfig = config; this.globalData.printConfig = config;
wx.setStorageSync('printConfig', config); wx.setStorageSync('printConfig', config);
} },
}); });
+73 -129
View File
@@ -1,13 +1,9 @@
import { PAPER_SIZE } from '../../constants/colors';
import { checkAndSaveImage } from '../../utils/saveImage';
import { shouldShowShareGuide } from '../../utils/shareGuide';
import AdditionDraw from '../service/additionDraw'; import AdditionDraw from '../service/additionDraw';
import { import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
MATH_FUNCTION_TYPES,
MathFunctionType,
} from '../../constants/mathFunctions';
Page({ Page(
applyMathPageMixin(
{
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -24,6 +20,7 @@ Page({
data: { data: {
pageTitle: '加减法计算', pageTitle: '加减法计算',
subTitle: '通过图形化方式学习加减法运算',
functionId: '', functionId: '',
hasContent: false, hasContent: false,
showShareDialog: false, showShareDialog: false,
@@ -36,19 +33,16 @@ Page({
{ name: '10以内加减法', value: 'addition-subtraction-10' }, { name: '10以内加减法', value: 'addition-subtraction-10' },
], ],
imageType: 'twelve-animals', // 图片类型:twelve-animals 或 fruits imageType: 'twelve-animals', // 图片类型:twelve-animals 或 fruits
} as CanvasDataState & {
currentType: string;
currentTypeName: string;
typeActions: Array<{ name: string; value: string }>;
imageType: string;
}, },
onLoad(options: { id?: string }) { onLoad(options: { id?: string }) {
const functionId = options.id || 'addition-5'; const functionId = options.id || 'addition-5';
this.setData({ functionId }); this.initPageInfo(functionId, '加减法计算');
const functionItem = MATH_FUNCTION_TYPES.find(
(item: MathFunctionType) => item.id === functionId,
);
const pageTitle = functionItem?.title || '加减法计算';
this.setData({ pageTitle });
wx.setNavigationBarTitle({ title: pageTitle });
// 根据 functionId 设置默认类型 // 根据 functionId 设置默认类型
if (functionId === 'addition-5') { if (functionId === 'addition-5') {
@@ -75,59 +69,22 @@ Page({
}, },
onReady() { onReady() {
this.initCanvas(); this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new AdditionDraw(canvas, ctx, options);
}, },
drawServiceOptions: {
/** subTitle: this.data.subTitle,
* 初始化Canvas
*/
initCanvas() {
const query = wx.createSelectorQuery();
query
.select('#canvasWrapper')
.boundingClientRect((rect) => {
if (rect) {
const { width, height } = PAPER_SIZE['A4'];
const boxWidth = rect.width;
const boxHeight = boxWidth / (width / height);
this.boxHeight = boxHeight;
this.boxWidth = boxWidth;
this.setData({ boxWidth, boxHeight });
const canvas = wx
.createSelectorQuery()
.select('#canvasContent');
canvas.fields({ node: true, size: true }).exec((res) => {
if (res[0]) {
const canvasNode = res[0].node;
const ctx = canvasNode.getContext('2d');
const dpr = wx.getSystemInfoSync().pixelRatio;
canvasNode.width = boxWidth * dpr;
canvasNode.height = boxHeight * dpr;
ctx.scale(dpr, dpr);
this.canvas = canvasNode;
this.ctx = ctx;
this.drawService = new AdditionDraw(
canvasNode,
ctx,
{
title: this.data.currentTypeName,
subTitle: '通过图形化方式学习加减法运算',
}, },
); onCanvasReady: () => {
// 初始随机生成 // 初始随机生成
this.onRandom(); this.onRandom();
} },
}); });
}
})
.exec();
}, },
/** /**
@@ -141,7 +98,8 @@ Page({
try { try {
// 更新 Header 的 Title // 更新 Header 的 Title
if (this.drawService) { if (this.drawService) {
this.drawService.options.title = this.data.currentTypeName; this.drawService.options.title =
this.data.currentTypeName;
} }
await this.drawService.draw( await this.drawService.draw(
@@ -168,26 +126,38 @@ Page({
const type = this.data.currentType; const type = this.data.currentType;
// 生成6道题目 // 生成5道题目
for (let i = 0; i < 5; i++) { for (let i = 0; i < 5; i++) {
if (type === 'addition-5') { if (type === 'addition-5') {
// 5以内加法:和 ≤ 5 // 5以内加法:和 ≤ 5
// left >= 1, right >= 1, left + right <= 5 // left >= 1, right >= 1, left + right <= 5
const maxSum = 5; const maxSum = 5;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 4 const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 4
const maxRight = maxSum - left; // 确保 left + right <= 5 const maxRight = maxSum - left; // 确保 left + right <= 5
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right; const result = left + right;
problems.push({ type: 'addition', left, right, result }); problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'addition-10') { } else if (type === 'addition-10') {
// 10以内加法:和 ≤ 10 // 10以内加法:和 ≤ 10
// left >= 1, right >= 1, left + right <= 10 // left >= 1, right >= 1, left + right <= 10
const maxSum = 10; const maxSum = 10;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9 const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10 const maxRight = maxSum - left; // 确保 left + right <= 10
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right; const result = left + right;
problems.push({ type: 'addition', left, right, result }); problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'subtraction-10') { } else if (type === 'subtraction-10') {
// 10以内减法:被减数 ≤ 10 // 10以内减法:被减数 ≤ 10
// left <= 10, left - right = result, result >= 1 // left <= 10, left - right = result, result >= 1
@@ -196,25 +166,44 @@ Page({
const maxRight = left - 1; // 确保 result >= 1 const maxRight = left - 1; // 确保 result >= 1
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right; const result = left - right;
problems.push({ type: 'subtraction', left, right, result }); problems.push({
type: 'subtraction',
left,
right,
result,
});
} else if (type === 'addition-subtraction-10') { } else if (type === 'addition-subtraction-10') {
// 加减法混合 // 加减法混合
if (Math.random() < 0.5) { if (Math.random() < 0.5) {
// 加法:和 ≤ 10 // 加法:和 ≤ 10
const maxSum = 10; const maxSum = 10;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9 const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10 const maxRight = maxSum - left; // 确保 left + right <= 10
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight const right =
Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right; const result = left + right;
problems.push({ type: 'addition', left, right, result }); problems.push({
type: 'addition',
left,
right,
result,
});
} else { } else {
// 减法:被减数 ≤ 10 // 减法:被减数 ≤ 10
const maxLeft = 10; const maxLeft = 10;
const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10 const left =
Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
const maxRight = left - 1; // 确保 result >= 1 const maxRight = left - 1; // 确保 result >= 1
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight const right =
Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right; const result = left - right;
problems.push({ type: 'subtraction', left, right, result }); problems.push({
type: 'subtraction',
left,
right,
result,
});
} }
} }
} }
@@ -223,56 +212,6 @@ Page({
this.drawCanvas(); this.drawCanvas();
}, },
/**
* 导出打印
*/
exportToPrint() {
if (!this.canvas || !this.data.hasContent) {
return;
}
if (shouldShowShareGuide()) {
this.setData({ showShareDialog: true });
return;
}
checkAndSaveImage(this.canvas);
},
/**
* 分享小程序
*/
onShareAppMessage() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
path: `/mathPages/addition/addition?id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
onShareTimeline() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
query: `id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
/** 关闭分享引导弹窗 */
onCloseShareDialog() {
this.setData({ showShareDialog: false });
},
/** 分享成功回调 */
onShareSuccess() {
this.setData({ showShareDialog: false });
if (this.canvas) {
checkAndSaveImage(this.canvas);
}
},
/** 选择类型 */ /** 选择类型 */
onSelectType(event: any) { onSelectType(event: any) {
const { name, value } = event.detail; const { name, value } = event.detail;
@@ -283,4 +222,9 @@ Page({
// 重新生成数据 // 重新生成数据
this.onRandom(); this.onRandom();
}, },
}); },
{
pagePath: 'addition/addition',
},
),
);
+62 -8
View File
@@ -6,6 +6,7 @@ import {
MATH_FUNCTION_TYPES, MATH_FUNCTION_TYPES,
MathFunctionType, MathFunctionType,
} from '../../constants/mathFunctions'; } from '../../constants/mathFunctions';
import tracker from '../../utils/tracker';
/** /**
* Canvas 相关的页面实例属性 * Canvas 相关的页面实例属性
@@ -16,6 +17,7 @@ export interface MathPageCanvasInstance {
boxHeight: number; boxHeight: number;
boxWidth: number; boxWidth: number;
drawService: BaseMathDrawService | null; drawService: BaseMathDrawService | null;
setData(data: any, callback?: () => void): void;
} }
/** /**
@@ -63,11 +65,16 @@ export interface ShareOptions {
pagePath: string; pagePath: string;
} }
const shareConfig = {
title: '涂鸦丫-数学学习涂鸦卡',
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
/** /**
* 获取数学页面的公共方法 * 获取数学页面的公共方法
* 这些方法可以在所有数学页面中复用 * 这些方法可以在所有数学页面中复用
*/ */
export function getMathPageCommonMethods(shareOptions: ShareOptions) { export function getMathPageCommonMethods() {
return { return {
/** /**
* 初始化 Canvas * 初始化 Canvas
@@ -76,6 +83,7 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
this: MathPageCanvasInstance & { data: CanvasDataState }, this: MathPageCanvasInstance & { data: CanvasDataState },
options: InitCanvasOptions, options: InitCanvasOptions,
) { ) {
console.log('initCanvas this', this);
const query = wx.createSelectorQuery(); const query = wx.createSelectorQuery();
query query
.select('#canvasWrapper') .select('#canvasWrapper')
@@ -145,6 +153,9 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
return; return;
} }
// 上报下载埋点
tracker.reportDownload(this.data.pageTitle);
checkAndSaveImage(this.canvas); checkAndSaveImage(this.canvas);
}, },
@@ -154,11 +165,13 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
onShareAppMessage( onShareAppMessage(
this: MathPageCanvasInstance & { data: CanvasDataState }, this: MathPageCanvasInstance & { data: CanvasDataState },
) { ) {
console.log('onShareAppMessage');
// 上报分享埋点
tracker.reportShare(this.data.pageTitle);
return { return {
title: '涂鸦丫-数学学习涂鸦卡', ...shareConfig,
path: `/mathPages/${shareOptions.pagePath}?id=${this.data.functionId}`, query: `id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
}; };
}, },
@@ -168,11 +181,13 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
onShareTimeline( onShareTimeline(
this: MathPageCanvasInstance & { data: CanvasDataState }, this: MathPageCanvasInstance & { data: CanvasDataState },
) { ) {
console.log('onShareTimeline');
// 上报分享埋点
tracker.reportShare(this.data.pageTitle);
return { return {
title: '涂鸦丫-数学学习涂鸦卡', ...shareConfig,
query: `id=${this.data.functionId}`, query: `id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
}; };
}, },
@@ -193,6 +208,8 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
) { ) {
this.setData({ showShareDialog: false }); this.setData({ showShareDialog: false });
if (this.canvas) { if (this.canvas) {
// 上报下载埋点(分享成功后下载)
tracker.reportDownload(this.data.pageTitle);
checkAndSaveImage(this.canvas); checkAndSaveImage(this.canvas);
} }
}, },
@@ -222,3 +239,40 @@ export function getMathPageCommonMethods(shareOptions: ShareOptions) {
}, },
}; };
} }
/**
* 应用数学页面公共方法的辅助函数
* 自动混入公共方法,简化页面代码
* @param pageOptions 页面选项
* @param shareOptions 分享配置选项
* @returns 合并后的页面选项
*/
export function applyMathPageMixin(pageOptions: any) {
const commonMethods = getMathPageCommonMethods();
// 提取 pageOptions 中的 data(如果有)
const pageData = pageOptions.data || {};
// 合并页面选项和公共方法
// 注意:pageOptions 放在后面,这样页面可以覆盖公共方法
const mergedOptions: any = {
...commonMethods,
...pageOptions,
// 处理 data 的合并(需要特殊处理,避免覆盖)
data: {
...pageData,
},
};
return mergedOptions;
}
/**
* 创建数学页面的便捷函数
* 自动应用公共方法并注册为页面
* 页面路径从函数调用栈自动推断(从调用文件路径提取)
* @param pageOptions 页面选项
*/
export function createMathPage(pageOptions: any) {
Page(applyMathPageMixin(pageOptions));
}
+44 -123
View File
@@ -1,14 +1,10 @@
import { PAPER_SIZE } from '../../constants/colors';
import { checkAndSaveImage } from '../../utils/saveImage';
import { shouldShowShareGuide } from '../../utils/shareGuide';
import CountMatchDraw from '../service/countMatchDraw'; import CountMatchDraw from '../service/countMatchDraw';
import NumberColorDraw from '../service/numberColorDraw'; import NumberColorDraw from '../service/numberColorDraw';
import { import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
MATH_FUNCTION_TYPES,
MathFunctionType,
} from '../../constants/mathFunctions';
Page({ Page(
applyMathPageMixin(
{
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -34,19 +30,25 @@ Page({
{ name: '十二生肖', value: 'twelve-animals' }, { name: '十二生肖', value: 'twelve-animals' },
{ name: '水果', value: 'fruits' }, { name: '水果', value: 'fruits' },
], ],
} as CanvasDataState & {
showTypeSelector: boolean;
currentType: string;
currentTypeName: string;
typeActions: Array<{ name: string; value: string }>;
}, },
onLoad(options: { id?: string }) { onLoad(options: { id?: string }) {
const functionId = options.id || 'counting-matching'; const functionId = options.id || 'counting-matching';
this.setData({ functionId }); this.setData({ functionId });
const functionItem = MATH_FUNCTION_TYPES.find( const functionItem =
(item: MathFunctionType) => item.id === functionId, require('../../constants/mathFunctions').MATH_FUNCTION_TYPES.find(
(item: any) => item.id === functionId,
); );
const pageTitle = functionItem?.title || '数一数,连一连'; const pageTitle = functionItem?.title || '数一数,连一连';
this.setData({ pageTitle }); this.setData({ pageTitle });
wx.setNavigationBarTitle({ title: pageTitle }); this.initPageInfo(functionId, pageTitle);
// 根据 functionId 设置不同的类型选择器 // 根据 functionId 设置不同的类型选择器
if (functionId === 'number-coloring') { if (functionId === 'number-coloring') {
@@ -62,72 +64,30 @@ Page({
}, },
onReady() { onReady() {
this.initCanvas(); this.initCanvas({
}, createDrawService: (
canvas: Canvas,
/** ctx: RenderingContext,
* 初始化Canvas options?: Record<string, any>,
*/ ) => {
initCanvas() {
const query = wx.createSelectorQuery();
query
.select('#canvasWrapper')
.boundingClientRect((rect) => {
if (rect) {
const { width, height } = PAPER_SIZE['A4'];
const boxWidth = rect.width;
const boxHeight = boxWidth / (width / height);
this.boxHeight = boxHeight;
this.boxWidth = boxWidth;
this.setData({ boxWidth, boxHeight });
const canvas = wx
.createSelectorQuery()
.select('#canvasContent');
canvas.fields({ node: true, size: true }).exec((res) => {
if (res[0]) {
const canvasNode = res[0].node;
const ctx = canvasNode.getContext('2d');
const dpr = wx.getSystemInfoSync().pixelRatio;
canvasNode.width = boxWidth * dpr;
canvasNode.height = boxHeight * dpr;
ctx.scale(dpr, dpr);
this.canvas = canvasNode;
this.ctx = ctx;
// 根据 functionId 创建不同的绘制服务 // 根据 functionId 创建不同的绘制服务
if (this.data.functionId === 'number-coloring') { if (this.data.functionId === 'number-coloring') {
this.drawService = new NumberColorDraw( return new NumberColorDraw(canvas, ctx, options);
canvasNode,
ctx,
{
title: this.data.pageTitle,
subTitle: '按数字给相应的圆圈涂上颜色',
},
);
} else { } else {
this.drawService = new CountMatchDraw( return new CountMatchDraw(canvas, ctx, options);
canvasNode,
ctx,
{
title: this.data.pageTitle,
subTitle:
'通过连线配对数字和对应的数量图形',
},
);
} }
},
drawServiceOptions: {
subTitle:
this.data.functionId === 'number-coloring'
? '按数字给相应的圆圈涂上颜色'
: '通过连线配对数字和对应的数量图形',
},
onCanvasReady: () => {
// 初始随机生成 // 初始随机生成
this.onRandom(); this.onRandom();
} },
}); });
}
})
.exec();
}, },
/** /**
@@ -179,7 +139,10 @@ Page({
const randomIndex = Math.floor( const randomIndex = Math.floor(
Math.random() * availableNumbers.length, Math.random() * availableNumbers.length,
); );
const number = availableNumbers.splice(randomIndex, 1)[0]; const number = availableNumbers.splice(
randomIndex,
1,
)[0];
numbers.push(number); numbers.push(number);
} }
@@ -196,7 +159,10 @@ Page({
const randomIndex = Math.floor( const randomIndex = Math.floor(
Math.random() * availableNumbers.length, Math.random() * availableNumbers.length,
); );
const number = availableNumbers.splice(randomIndex, 1)[0]; const number = availableNumbers.splice(
randomIndex,
1,
)[0];
leftNumbers.push(number); leftNumbers.push(number);
} }
@@ -216,56 +182,6 @@ Page({
this.drawCanvas(); this.drawCanvas();
}, },
/**
* 导出打印
*/
exportToPrint() {
if (!this.canvas || !this.data.hasContent) {
return;
}
if (shouldShowShareGuide()) {
this.setData({ showShareDialog: true });
return;
}
checkAndSaveImage(this.canvas);
},
/**
* 分享小程序
*/
onShareAppMessage() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
path: `/mathPages/countMatch/countMatch?id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
onShareTimeline() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
query: `id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
/** 关闭分享引导弹窗 */
onCloseShareDialog() {
this.setData({ showShareDialog: false });
},
/** 分享成功回调 */
onShareSuccess() {
this.setData({ showShareDialog: false });
if (this.canvas) {
checkAndSaveImage(this.canvas);
}
},
/** 选择类型 */ /** 选择类型 */
onSelectType(event: any) { onSelectType(event: any) {
const { name, value } = event.detail; const { name, value } = event.detail;
@@ -276,4 +192,9 @@ Page({
// 重新生成数据 // 重新生成数据
this.onRandom(); this.onRandom();
}, },
}); },
{
pagePath: 'countMatch/countMatch',
},
),
);
@@ -1,15 +1,9 @@
import CountingSelectDraw from '../service/countingSelectDraw'; import CountingSelectDraw from '../service/countingSelectDraw';
import { import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
getMathPageCommonMethods,
CanvasDataState,
} from '../common/mathPageMixin';
// 获取公共方法 Page(
const commonMethods = getMathPageCommonMethods({ applyMathPageMixin(
pagePath: 'countingSelect/countingSelect', {
});
Page({
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -31,7 +25,12 @@ Page({
functionId: '', functionId: '',
hasContent: false, hasContent: false,
showShareDialog: false, showShareDialog: false,
} as CanvasDataState, } as CanvasDataState & {
showTypeSelector?: boolean;
currentType?: string;
currentTypeName?: string;
typeActions?: Array<{ name: string; value: any }>;
},
onLoad(options: { id?: string }) { onLoad(options: { id?: string }) {
const functionId = options.id || 'counting-select'; const functionId = options.id || 'counting-select';
@@ -41,7 +40,11 @@ Page({
onReady() { onReady() {
this.initCanvas({ this.initCanvas({
createDrawService: (canvas, ctx, options) => { createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new CountingSelectDraw(canvas, ctx, options); return new CountingSelectDraw(canvas, ctx, options);
}, },
drawServiceOptions: { drawServiceOptions: {
@@ -58,14 +61,20 @@ Page({
* 绘制Canvas内容 * 绘制Canvas内容
*/ */
async drawCanvas() { async drawCanvas() {
if (!this.ctx || !this.drawService || !this.countingSelectData) { if (
!this.ctx ||
!this.drawService ||
!this.countingSelectData
) {
return; return;
} }
try { try {
// 判断是选一选还是填一填模式 // 判断是选一选还是填一填模式
const mode = const mode =
this.data.functionId === 'counting-fill' ? 'fill' : 'select'; this.data.functionId === 'counting-fill'
? 'fill'
: 'select';
await this.drawService.draw(this.countingSelectData, mode); await this.drawService.draw(this.countingSelectData, mode);
this.setData({ hasContent: true }); this.setData({ hasContent: true });
} catch (error) { } catch (error) {
@@ -108,7 +117,8 @@ Page({
const count = Math.floor(Math.random() * 10) + 1; const count = Math.floor(Math.random() * 10) + 1;
// 随机选择图片索引 // 随机选择图片索引
const imageIndex = Math.floor(Math.random() * maxImageIndex) + 1; const imageIndex =
Math.floor(Math.random() * maxImageIndex) + 1;
const problem: { const problem: {
count: number; count: number;
@@ -134,7 +144,8 @@ Page({
// 生成错误答案(与正确答案不同) // 生成错误答案(与正确答案不同)
let wrongAnswer: number; let wrongAnswer: number;
do { do {
wrongAnswer = Math.floor(Math.random() * 10) + 1; wrongAnswer =
Math.floor(Math.random() * 10) + 1;
} while (wrongAnswer === count); } while (wrongAnswer === count);
options.push(wrongAnswer); options.push(wrongAnswer);
} }
@@ -149,13 +160,9 @@ Page({
this.countingSelectData = { problems }; this.countingSelectData = { problems };
}, },
},
// ========== 使用公共方法 ========== {
initCanvas: commonMethods.initCanvas, pagePath: 'countingSelect/countingSelect',
exportToPrint: commonMethods.exportToPrint, },
onShareAppMessage: commonMethods.onShareAppMessage, ),
onShareTimeline: commonMethods.onShareTimeline, );
onCloseShareDialog: commonMethods.onCloseShareDialog,
onShareSuccess: commonMethods.onShareSuccess,
initPageInfo: commonMethods.initPageInfo,
});
@@ -1,16 +1,10 @@
import { getRandomNumberColor } from '../../constants/colors'; import { getRandomNumberColor } from '../../constants/colors';
import MissingNumberDraw from '../service/missingNumberDraw'; import MissingNumberDraw from '../service/missingNumberDraw';
import { import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
getMathPageCommonMethods,
CanvasDataState,
} from '../common/mathPageMixin';
// 获取公共方法 Page(
const commonMethods = getMathPageCommonMethods({ applyMathPageMixin(
pagePath: 'missingNumber/missingNumber', {
});
Page({
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -56,7 +50,11 @@ Page({
onReady() { onReady() {
this.initCanvas({ this.initCanvas({
createDrawService: (canvas, ctx, options) => { createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new MissingNumberDraw(canvas, ctx, options); return new MissingNumberDraw(canvas, ctx, options);
}, },
drawServiceOptions: { drawServiceOptions: {
@@ -129,7 +127,8 @@ Page({
gridCount: 1, gridCount: 1,
}, },
}; };
const { gridCount } = gridMap[maxNumber as keyof typeof gridMap]; const { gridCount } =
gridMap[maxNumber as keyof typeof gridMap];
// 生成多个网格 // 生成多个网格
for (let gridIndex = 0; gridIndex < gridCount; gridIndex++) { for (let gridIndex = 0; gridIndex < gridCount; gridIndex++) {
@@ -156,13 +155,13 @@ Page({
hiddenIndices.add(randomIndex); hiddenIndices.add(randomIndex);
} }
const gridNumbers: (number | null)[] = numbers.map((num, index) => const gridNumbers: (number | null)[] = numbers.map(
hiddenIndices.has(index) ? null : num, (num, index) => (hiddenIndices.has(index) ? null : num),
); );
// 为每个数字分配颜色(包括null位置) // 为每个数字分配颜色(包括null位置)
const gridColors: (string | null)[] = gridNumbers.map((num) => const gridColors: (string | null)[] = gridNumbers.map(
num ? this.getRandomNumberColor() : null, (num) => (num ? this.getRandomNumberColor() : null),
); );
grids.push({ numbers: gridNumbers, colors: gridColors }); grids.push({ numbers: gridNumbers, colors: gridColors });
@@ -181,15 +180,6 @@ Page({
return getRandomNumberColor(); return getRandomNumberColor();
}, },
// ========== 使用公共方法 ==========
initCanvas: commonMethods.initCanvas,
exportToPrint: commonMethods.exportToPrint,
onShareAppMessage: commonMethods.onShareAppMessage,
onShareTimeline: commonMethods.onShareTimeline,
onCloseShareDialog: commonMethods.onCloseShareDialog,
onShareSuccess: commonMethods.onShareSuccess,
initPageInfo: commonMethods.initPageInfo,
/** 选择类型 */ /** 选择类型 */
onSelectType(event: any) { onSelectType(event: any) {
const { name, value } = event.detail; const { name, value } = event.detail;
@@ -200,4 +190,9 @@ Page({
// 重新生成数据 // 重新生成数据
this.onRandom(); this.onRandom();
}, },
}); },
{
pagePath: 'missingNumber/missingNumber',
},
),
);
@@ -1,17 +1,11 @@
import NumberDecomposeDraw from '../service/numberDecomposeDraw'; import NumberDecomposeDraw from '../service/numberDecomposeDraw';
import { import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
getMathPageCommonMethods,
CanvasDataState,
} from '../common/mathPageMixin';
// 获取公共方法
const commonMethods = getMathPageCommonMethods({
pagePath: 'numberDecompose/numberDecompose',
});
type DecomposeMode = 'with-image' | 'decompose' | 'compose'; type DecomposeMode = 'with-image' | 'decompose' | 'compose';
Page({ Page(
applyMathPageMixin(
{
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -67,7 +61,11 @@ Page({
onReady() { onReady() {
this.initCanvas({ this.initCanvas({
createDrawService: (canvas, ctx, options) => { createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new NumberDecomposeDraw(canvas, ctx, options); return new NumberDecomposeDraw(canvas, ctx, options);
}, },
drawServiceOptions: { drawServiceOptions: {
@@ -128,13 +126,17 @@ Page({
let attempts = 0; let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数 const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) { while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++; attempts++;
// 生成总数(2-10 // 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2; const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1) // 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1; const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null // 随机决定显示 part1 还是 part2(另一个为 null
@@ -173,13 +175,17 @@ Page({
let attempts = 0; let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数 const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) { while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++; attempts++;
// 生成总数(2-10 // 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2; const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1) // 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1; const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null // 随机决定显示 part1 还是 part2(另一个为 null
@@ -209,13 +215,17 @@ Page({
let attempts = 0; let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数 const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) { while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++; attempts++;
// 生成总数(2-10 // 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2; const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1) // 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1; const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1; const part2 = whole - part1;
// 生成唯一标识(统一格式:part1 <= part2 // 生成唯一标识(统一格式:part1 <= part2
@@ -242,15 +252,6 @@ Page({
this.decomposeData = { problems, mode }; this.decomposeData = { problems, mode };
}, },
// ========== 使用公共方法 ==========
initCanvas: commonMethods.initCanvas,
exportToPrint: commonMethods.exportToPrint,
onShareAppMessage: commonMethods.onShareAppMessage,
onShareTimeline: commonMethods.onShareTimeline,
onCloseShareDialog: commonMethods.onCloseShareDialog,
onShareSuccess: commonMethods.onShareSuccess,
initPageInfo: commonMethods.initPageInfo,
/** 选择类型 */ /** 选择类型 */
onSelectType(event: any) { onSelectType(event: any) {
const { name, value } = event.detail; const { name, value } = event.detail;
@@ -261,4 +262,9 @@ Page({
// 重新生成数据 // 重新生成数据
this.onRandom(); this.onRandom();
}, },
}); },
{
pagePath: 'numberDecompose/numberDecompose',
},
),
);
+21 -133
View File
@@ -1,13 +1,7 @@
import { PAPER_SIZE } from '../../constants/colors';
import { checkAndSaveImage } from '../../utils/saveImage';
import { shouldShowShareGuide } from '../../utils/shareGuide';
import NumberFindDraw from '../service/numberFindDraw'; import NumberFindDraw from '../service/numberFindDraw';
import { import { createMathPage, CanvasDataState } from '../common/mathPageMixin';
MATH_FUNCTION_TYPES,
MathFunctionType,
} from '../../constants/mathFunctions';
Page({ createMathPage({
canvas: null as Canvas | null, canvas: null as Canvas | null,
ctx: null as RenderingContext | null, ctx: null as RenderingContext | null,
boxHeight: 0, boxHeight: 0,
@@ -19,90 +13,38 @@ Page({
selectedNumber: 0, // 选中的数字 selectedNumber: 0, // 选中的数字
functionId: '', // 功能ID,用于判断标题 functionId: '', // 功能ID,用于判断标题
numberList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], // 数字列表 numberList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], // 数字列表
showShareDialog: false, // 显示分享引导弹窗 hasContent: false,
showShareDialog: false,
} as CanvasDataState & {
selectedNumber: number;
numberList: number[];
}, },
onLoad(options: { id?: string }) { onLoad(options: { id?: string }) {
// 根据id参数判断页面标题
const functionId = options.id || 'number-find'; const functionId = options.id || 'number-find';
this.setData({ this.setData({
functionId, functionId,
}); });
this.initPageInfo(functionId, '看数字,涂一涂');
// 根据functionId设置页面标题
const functionItem = MATH_FUNCTION_TYPES.find(
(item: MathFunctionType) => item.id === functionId,
);
const pageTitle = functionItem?.title || '看数字,涂一涂';
this.setData({
pageTitle,
});
// 设置导航栏标题
wx.setNavigationBarTitle({
title: pageTitle,
});
}, },
onReady() { onReady() {
// 初始化canvas this.initCanvas({
this.initCanvas(); createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new NumberFindDraw(canvas, ctx, options);
}, },
drawServiceOptions: {
/**
* 初始化Canvas
*/
initCanvas() {
const query = wx.createSelectorQuery();
query
.select('#canvasWrapper')
.boundingClientRect((rect) => {
if (rect) {
const { width, height } = PAPER_SIZE['A4'];
const boxWidth = rect.width;
const boxHeight = boxWidth / (width / height);
this.boxHeight = boxHeight;
this.boxWidth = boxWidth;
this.setData({
boxWidth,
boxHeight,
});
// 初始化canvas上下文
const canvas = wx
.createSelectorQuery()
.select('#canvasContent');
canvas.fields({ node: true, size: true }).exec((res) => {
if (res[0]) {
const canvasNode = res[0].node;
const ctx = canvasNode.getContext('2d');
const dpr = wx.getSystemInfoSync().pixelRatio;
canvasNode.width = boxWidth * dpr;
canvasNode.height = boxHeight * dpr;
ctx.scale(dpr, dpr);
this.canvas = canvasNode;
this.ctx = ctx;
// 初始化绘制服务
this.drawService = new NumberFindDraw(
canvasNode,
ctx,
{
title: this.data.pageTitle,
subTitle: '找一找下面相同的数字,涂上颜色', subTitle: '找一找下面相同的数字,涂上颜色',
functionId: this.data.functionId, functionId: this.data.functionId,
}, },
); onCanvasReady: () => {
// 如果还没有选中数字,随机选择一个 // 如果还没有选中数字,随机选择一个
if (this.data.selectedNumber === 0) { if (this.data.selectedNumber === 0) {
const randomNumber = const randomNumber = Math.floor(Math.random() * 10) + 1;
Math.floor(Math.random() * 10) + 1;
this.setData({ this.setData({
selectedNumber: randomNumber, selectedNumber: randomNumber,
}); });
@@ -110,11 +52,8 @@ Page({
// 绘制初始内容 // 绘制初始内容
this.drawCanvas(); this.drawCanvas();
} },
}); });
}
})
.exec();
}, },
/** /**
@@ -128,8 +67,10 @@ Page({
if (this.data.selectedNumber > 0) { if (this.data.selectedNumber > 0) {
try { try {
await this.drawService.draw(this.data.selectedNumber); await this.drawService.draw(this.data.selectedNumber);
this.setData({ hasContent: true });
} catch (error) { } catch (error) {
console.error('绘制失败:', error); console.error('绘制失败:', error);
this.setData({ hasContent: false });
} }
} }
}, },
@@ -155,57 +96,4 @@ Page({
}); });
this.drawCanvas(); this.drawCanvas();
}, },
/**
* 导出打印
*/
exportToPrint() {
if (!this.canvas || this.data.selectedNumber <= 0) {
return;
}
// 检查是否需要显示分享引导
if (shouldShowShareGuide()) {
this.setData({ showShareDialog: true });
return;
}
// 直接下载
checkAndSaveImage(this.canvas);
},
/**
* 分享小程序
*/
onShareAppMessage() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
path: `/mathPages/numberFind/numberFind?id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
onShareTimeline() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
query: `id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
/** 关闭分享引导弹窗 */
onCloseShareDialog() {
this.setData({ showShareDialog: false });
},
/** 分享成功回调 */
onShareSuccess() {
this.setData({ showShareDialog: false });
// 分享成功后直接下载
if (this.canvas) {
checkAndSaveImage(this.canvas);
}
},
}); });
+155
View File
@@ -0,0 +1,155 @@
/**
* 埋点服务
* 提供分享和下载打印事件的上报功能
*/
// 生成唯一 UUID
function generateUUID(): string {
const timestamp = Date.now();
const random = Math.floor(Math.random() * 1000000);
return `${timestamp}-${random}`;
}
// 格式化时间为 yyyy-MM-dd HH:mm
function formatTime(date: Date = new Date()): string {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
/**
* 获取今天的日期字符串(yyyy-MM-dd)
*/
function getTodayString(): string {
const date = new Date();
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* 获取事件计数(当天该事件上报的次数)
* 使用本地存储持久化,每天重置计数
*/
function getEventCount(eventName: string): number {
try {
const storageKey = `tracker_count_${eventName}`;
const dateKey = `tracker_date_${eventName}`;
const today = getTodayString();
// 获取上次记录的日期
const lastDate = wx.getStorageSync(dateKey);
let currentCount = 0;
// 如果是今天,获取上次的计数;否则重置为0
if (lastDate === today) {
currentCount = wx.getStorageSync(storageKey) || 0;
}
// 递增计数
const newCount = currentCount + 1;
// 保存计数和日期
wx.setStorageSync(storageKey, newCount);
wx.setStorageSync(dateKey, today);
return newCount;
} catch (error) {
console.error('获取事件计数失败:', error);
return 1; // 如果存储失败,返回 1
}
}
/**
* 埋点追踪器
*/
class Tracker {
private uuid: string;
private openLog: boolean;
constructor() {
// 初始化时获取或生成 UUID(持久化存储)
this.uuid = this.getOrCreateUUID();
this.openLog = true;
}
/**
* 获取或创建 UUID
*/
private getOrCreateUUID(): string {
const uuidKey = 'tracker_uuid';
let storedUUID = wx.getStorageSync(uuidKey);
if (!storedUUID) {
storedUUID = generateUUID();
wx.setStorageSync(uuidKey, storedUUID);
}
return storedUUID;
}
printLog(tip: string, message?: string | object): void {
if (this.openLog) {
if (message) {
if (typeof message === 'object') {
console.log(tip, ':', JSON.stringify(message, null, 2));
} else {
console.log(tip, ':', message as string);
}
} else {
console.log(tip);
}
}
}
/**
* 上报分享点击事件
* @param pageName 页面名称
*/
reportShare(pageName: string): void {
try {
const time = formatTime();
const count = getEventCount('share_click');
const params = {
count,
time,
uuid: this.uuid,
page_name: pageName,
};
this.printLog('分享事件', params);
wx.reportEvent('share_click', params);
} catch (error) {
console.error('上报分享事件失败:', error);
}
}
/**
* 上报下载打印事件
* @param pageName 页面名称
*/
reportDownload(pageName: string): void {
try {
const time = formatTime();
const count = getEventCount('download');
const params = {
count,
time,
uuid: this.uuid,
page_name: pageName,
};
this.printLog('下载事件', params);
wx.reportEvent('download', params);
} catch (error) {
console.error('上报下载事件失败:', error);
}
}
}
// 创建并导出 tracker 实例
const tracker = new Tracker();
export default tracker;