feat:架构代码优化

This commit is contained in:
R524809
2025-12-11 13:02:44 +08:00
parent 2c32235568
commit 3c0e2af10d
89 changed files with 2774 additions and 2816 deletions
+162
View File
@@ -0,0 +1,162 @@
# mathPages 目录重构方案
## 当前目录结构
```
mathPages/
├── addition/ # 页面
├── compare/ # 页面
├── countingSelect/ # 页面
├── countMatch/ # 页面
├── missingNumber/ # 页面
├── numberDecompose/ # 页面
├── numberFind/ # 页面
├── assets/ # 静态资源
├── common/ # 公共文件(mixin等)
├── components/ # 组件
└── service/ # 服务(Draw相关)
```
## 问题
- 页面和服务放在同一级,不易区分
- 目录较多,查找页面不够直观
- 公共服务散落在不同目录
## 推荐方案:使用 `shared/` 目录
### 重构后的目录结构
```
mathPages/
├── addition/ # 页面
├── compare/ # 页面
├── countingSelect/ # 页面
├── countMatch/ # 页面
├── missingNumber/ # 页面
├── numberDecompose/ # 页面
├── numberFind/ # 页面
├── assets/ # 静态资源
└── shared/ # 公共服务目录(新建)
├── service/ # 服务(从 service/ 移动)
├── common/ # 公共文件(从 common/ 移动)
└── components/ # 组件(从 components/ 移动)
```
### 优点
1.**语义清晰**`shared/` 明确表示共享代码
2.**结构清晰**:页面和公共服务分离,一目了然
3.**易于维护**:所有公共服务集中在一个目录下
4.**符合常见实践**`shared/` 在很多项目中都有使用
5.**扩展性好**:未来可以添加 `shared/utils/``shared/types/`
### 需要修改的导入路径
#### 页面文件中的导入
**之前:**
```typescript
import AdditionDraw from '../service/additionDraw';
import { createMathPage } from '../common/mathPageMixin';
import MathBottomButtons from '../components/math-bottom-buttons/math-bottom-buttons';
```
**之后:**
```typescript
import AdditionDraw from '../shared/service/additionDraw';
import { createMathPage } from '../shared/common/mathPageMixin';
import MathBottomButtons from '../shared/components/math-bottom-buttons/math-bottom-buttons';
```
#### service 文件之间的导入
**之前:**
```typescript
import { BaseDrawService } from './baseMathDraw';
```
**之后:**
```typescript
import { BaseDrawService } from './baseMathDraw';
// 或者如果跨目录
import { BaseDrawService } from '../baseMathDraw';
```
## 备选方案:使用 `lib/` 目录
如果不想用 `shared/`,也可以考虑 `lib/`
```
mathPages/
├── addition/
├── compare/
├── ...
├── assets/
└── lib/ # 公共服务目录
├── service/
├── common/
└── components/
```
**优点:**
- 简洁
- 表示库文件
**缺点:**
- `lib/` 通常用于第三方库或编译产物
- 语义上不如 `shared/` 清晰
## 备选方案:使用 `core/` 目录
```
mathPages/
├── addition/
├── compare/
├── ...
├── assets/
└── core/ # 核心功能目录
├── service/
├── common/
└── components/
```
**优点:**
- 表示核心功能
**缺点:**
- `core/` 通常用于框架核心,不太适合业务代码
## 迁移步骤
1. **创建 `shared/` 目录**
2. **移动目录**
- `service/``shared/service/`
- `common/``shared/common/`
- `components/``shared/components/`
3. **批量更新导入路径**
- 所有页面文件中的 `../service/``../shared/service/`
- 所有页面文件中的 `../common/``../shared/common/`
- 所有页面文件中的 `../components/``../shared/components/`
- 检查 `shared/` 内部文件的相互导入
4. **更新配置文件**
- 检查是否有路径配置需要更新(如 `app.json` 中的组件路径)
5. **测试验证**
- 确保所有页面功能正常
- 确保导入路径正确
## 推荐使用 `shared/` 目录
综合考虑,**推荐使用 `shared/` 目录**,因为:
- 语义最清晰
- 符合常见实践
- 易于理解和维护
+2 -2
View File
@@ -6,7 +6,7 @@
"enablePullDownRefresh": false,
"usingComponents": {
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-type-selector": "../components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-type-selector": "../shared/components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
+1 -1
View File
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
+213 -224
View File
@@ -1,230 +1,219 @@
import AdditionDraw from '../service/additionDraw';
import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
import AdditionDraw from '../shared/service/additionDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
Page(
applyMathPageMixin(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as AdditionDraw | null,
calculationData: null as {
problems: Array<{
type: 'addition' | 'subtraction';
left: number;
right: number;
result: number;
}>;
} | null,
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as AdditionDraw | null,
calculationData: null as {
problems: Array<{
type: 'addition' | 'subtraction';
left: number;
right: number;
result: number;
}>;
} | null,
data: {
pageTitle: '加减法计算',
subTitle: '通过图形化方式学习加减法运算',
functionId: '',
hasContent: false,
showShareDialog: false,
currentType: 'addition-5', // 'addition-5', 'addition-10', 'subtraction-10', 'addition-subtraction-10'
currentTypeName: '5以内加法',
typeActions: [
{ name: '5以内加法', value: 'addition-5' },
{ name: '10以内加法', value: 'addition-10' },
{ name: '10以内减法', value: 'subtraction-10' },
{ name: '10以内加减法', value: 'addition-subtraction-10' },
],
imageType: 'twelve-animals', // 图片类型:twelve-animals 或 fruits
} as CanvasDataState & {
currentType: string;
currentTypeName: string;
typeActions: Array<{ name: string; value: string }>;
imageType: string;
data: {
pageTitle: '加减法计算',
subTitle: '通过图形化方式学习加减法运算',
functionId: '',
hasContent: false,
showShareDialog: false,
currentMode: 'addition-5', // 'addition-5', 'addition-10', 'subtraction-10', 'addition-subtraction-10'
currentModeName: '5以内加法',
typeActions: [
{ name: '5以内加法', value: 'addition-5' },
{ name: '10以内加法', value: 'addition-10' },
{ name: '10以内减法', value: 'subtraction-10' },
{ name: '10以内加减法', value: 'addition-subtraction-10' },
],
imageType: 'twelve-animals', // 图片类型:twelve-animals 或 fruits
} as CanvasDataState & {
currentMode: string;
currentModeName: string;
typeActions: Array<{ name: string; value: string }>;
imageType: string;
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'addition-5';
this.initPageInfo(functionId, '加减法计算');
// 根据 functionId 设置默认类型
if (functionId === 'addition-5') {
this.setData({
currentMode: 'addition-5',
currentModeName: '5以内加法',
});
} else if (functionId === 'addition-10') {
this.setData({
currentMode: 'addition-10',
currentModeName: '10以内加法',
});
} else if (functionId === 'subtraction-10') {
this.setData({
currentMode: 'subtraction-10',
currentModeName: '10以内减法',
});
} else if (functionId === 'addition-subtraction-10') {
this.setData({
currentMode: 'addition-subtraction-10',
currentModeName: '10以内加减法',
});
}
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new AdditionDraw(canvas, ctx, options);
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'addition-5';
this.initPageInfo(functionId, '加减法计算');
// 根据 functionId 设置默认类型
if (functionId === 'addition-5') {
this.setData({
currentType: 'addition-5',
currentTypeName: '5以内加法',
});
} else if (functionId === 'addition-10') {
this.setData({
currentType: 'addition-10',
currentTypeName: '10以内加法',
});
} else if (functionId === 'subtraction-10') {
this.setData({
currentType: 'subtraction-10',
currentTypeName: '10以内减法',
});
} else if (functionId === 'addition-subtraction-10') {
this.setData({
currentType: 'addition-subtraction-10',
currentTypeName: '10以内加减法',
});
}
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new AdditionDraw(canvas, ctx, options);
},
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onCanvasReady: () => {
// 初始随机生成
this.onRandom();
},
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.calculationData) {
return;
}
try {
// 更新 Header 的 Title
if (this.drawService) {
this.drawService.options.title =
this.data.currentTypeName;
}
await this.drawService.draw(
this.calculationData,
this.data.currentType,
);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
const problems: Array<{
type: 'addition' | 'subtraction';
left: number;
right: number;
result: number;
}> = [];
const type = this.data.currentType;
// 生成5道题目
for (let i = 0; i < 5; i++) {
if (type === 'addition-5') {
// 5以内加法:和 ≤ 5
// left >= 1, right >= 1, left + right <= 5
const maxSum = 5;
const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 4
const maxRight = maxSum - left; // 确保 left + right <= 5
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'addition-10') {
// 10以内加法:和 ≤ 10
// left >= 1, right >= 1, left + right <= 10
const maxSum = 10;
const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'subtraction-10') {
// 10以内减法:被减数 ≤ 10
// left <= 10, left - right = result, result >= 1
const maxLeft = 10;
const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
const maxRight = left - 1; // 确保 result >= 1
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right;
problems.push({
type: 'subtraction',
left,
right,
result,
});
} else if (type === 'addition-subtraction-10') {
// 加减法混合
if (Math.random() < 0.5) {
// 加法:和 ≤ 10
const maxSum = 10;
const left =
Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10
const right =
Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else {
// 减法:被减数 ≤ 10
const maxLeft = 10;
const left =
Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
const maxRight = left - 1; // 确保 result >= 1
const right =
Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right;
problems.push({
type: 'subtraction',
left,
right,
result,
});
}
}
}
this.calculationData = { problems };
this.drawCanvas();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentType: value,
currentTypeName: name,
});
// 重新生成数据
onCanvasReady: () => {
// 初始随机生成
this.onRandom();
},
},
{
pagePath: 'addition/addition',
},
),
);
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.calculationData) {
return;
}
try {
// 更新 Header 的 Title
if (this.drawService) {
this.drawService.options.title = this.data.currentModeName;
}
await this.drawService.draw(
this.calculationData,
this.data.currentMode,
);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
const problems: Array<{
type: 'addition' | 'subtraction';
left: number;
right: number;
result: number;
}> = [];
const type = this.data.currentMode;
// 生成5道题目
for (let i = 0; i < 5; i++) {
if (type === 'addition-5') {
// 5以内加法:和 ≤ 5
// left >= 1, right >= 1, left + right <= 5
const maxSum = 5;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 4
const maxRight = maxSum - left; // 确保 left + right <= 5
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'addition-10') {
// 10以内加法:和 ≤ 10
// left >= 1, right >= 1, left + right <= 10
const maxSum = 10;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else if (type === 'subtraction-10') {
// 10以内减法:被减数 ≤ 10
// left <= 10, left - right = result, result >= 1
const maxLeft = 10;
const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
const maxRight = left - 1; // 确保 result >= 1
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right;
problems.push({
type: 'subtraction',
left,
right,
result,
});
} else if (type === 'addition-subtraction-10') {
// 加减法混合
if (Math.random() < 0.5) {
// 加法:和 ≤ 10
const maxSum = 10;
const left = Math.floor(Math.random() * (maxSum - 1)) + 1; // 1 到 9
const maxRight = maxSum - left; // 确保 left + right <= 10
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left + right;
problems.push({
type: 'addition',
left,
right,
result,
});
} else {
// 减法:被减数 ≤ 10
const maxLeft = 10;
const left = Math.floor(Math.random() * maxLeft) + 1; // 1 到 10
const maxRight = left - 1; // 确保 result >= 1
const right = Math.floor(Math.random() * maxRight) + 1; // 1 到 maxRight
const result = left - right;
problems.push({
type: 'subtraction',
left,
right,
result,
});
}
}
}
this.calculationData = { problems };
this.drawCanvas();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentMode: value,
currentModeName: name,
});
// 重新生成数据
this.onRandom();
},
});
+1 -1
View File
@@ -13,7 +13,7 @@
<!-- 类型选择器和随机生成按钮 -->
<math-type-selector
current-type-name="{{currentTypeName}}"
current-type-name="{{currentModeName}}"
type-actions="{{typeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
@@ -1,278 +0,0 @@
import { PAPER_SIZE } from '../../constants/colors';
import { checkAndSaveImage } from '../../utils/saveImage';
import { shouldShowShareGuide } from '../../utils/shareGuide';
import { BaseMathDrawService } from '../service/baseMathDraw';
import {
MATH_FUNCTION_TYPES,
MathFunctionType,
} from '../../constants/mathFunctions';
import tracker from '../../utils/tracker';
/**
* Canvas 相关的页面实例属性
*/
export interface MathPageCanvasInstance {
canvas: Canvas | null;
ctx: RenderingContext | null;
boxHeight: number;
boxWidth: number;
drawService: BaseMathDrawService | null;
setData(data: any, callback?: () => void): void;
}
/**
* Canvas 数据状态接口
*/
export interface CanvasDataState {
hasContent: boolean;
showShareDialog: boolean;
boxWidth: number;
boxHeight: number;
functionId: string;
pageTitle: string;
subTitle?: string;
}
/**
* Canvas 初始化选项
*/
export interface InitCanvasOptions {
/**
* 创建绘制服务的工厂函数
*/
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => BaseMathDrawService;
/**
* 绘制服务的选项
*/
drawServiceOptions?: Record<string, any>;
/**
* Canvas 初始化完成后回调
*/
onCanvasReady?: () => void;
}
/**
* 分享配置选项
*/
export interface ShareOptions {
/**
* 页面路径(相对于 mathPages 目录,例如:'missingNumber/missingNumber'
*/
pagePath: string;
}
const shareConfig = {
title: '涂鸦丫-数学学习涂鸦卡',
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
/**
* 获取数学页面的公共方法
* 这些方法可以在所有数学页面中复用
*/
export function getMathPageCommonMethods() {
return {
/**
* 初始化 Canvas
*/
initCanvas(
this: MathPageCanvasInstance & { data: CanvasDataState },
options: InitCanvasOptions,
) {
console.log('initCanvas this', this);
const query = wx.createSelectorQuery();
query
.select('#canvasWrapper')
.boundingClientRect((rect) => {
if (!rect) return;
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;
// 创建绘制服务
const drawServiceOptions = {
title: this.data.pageTitle,
subTitle: this.data.subTitle || '',
...options.drawServiceOptions,
};
this.drawService = options.createDrawService(
canvasNode,
ctx,
drawServiceOptions,
);
// 执行初始化完成回调
if (options.onCanvasReady) {
options.onCanvasReady.call(this);
}
}
});
})
.exec();
},
/**
* 导出打印
*/
exportToPrint(
this: MathPageCanvasInstance & { data: CanvasDataState },
) {
if (!this.canvas || !this.data.hasContent) {
return;
}
if (shouldShowShareGuide()) {
this.setData({ showShareDialog: true });
return;
}
// 上报下载埋点
tracker.reportDownload(this.data.pageTitle);
checkAndSaveImage(this.canvas);
},
/**
* 分享小程序
*/
onShareAppMessage(
this: MathPageCanvasInstance & { data: CanvasDataState },
) {
console.log('onShareAppMessage');
// 上报分享埋点
tracker.reportShare(this.data.pageTitle);
return {
...shareConfig,
query: `id=${this.data.functionId}`,
};
},
/**
* 分享到朋友圈
*/
onShareTimeline(
this: MathPageCanvasInstance & { data: CanvasDataState },
) {
console.log('onShareTimeline');
// 上报分享埋点
tracker.reportShare(this.data.pageTitle);
return {
...shareConfig,
query: `id=${this.data.functionId}`,
};
},
/**
* 关闭分享引导弹窗
*/
onCloseShareDialog(
this: MathPageCanvasInstance & { data: CanvasDataState },
) {
this.setData({ showShareDialog: false });
},
/**
* 分享成功回调
*/
onShareSuccess(
this: MathPageCanvasInstance & { data: CanvasDataState },
) {
this.setData({ showShareDialog: false });
if (this.canvas) {
// 上报下载埋点(分享成功后下载)
tracker.reportDownload(this.data.pageTitle);
checkAndSaveImage(this.canvas);
}
},
/**
* 初始化页面信息(从 functionId 获取标题等信息)
*/
initPageInfo(
this: MathPageCanvasInstance & { data: CanvasDataState },
functionId: string,
defaultTitle?: string,
) {
const functionItem = MATH_FUNCTION_TYPES.find(
(item: MathFunctionType) => item.id === functionId,
);
const title = functionItem?.title || defaultTitle || '';
const desc = functionItem?.desc || '';
this.setData({
pageTitle: title,
subTitle: desc,
functionId,
});
wx.setNavigationBarTitle({ title });
},
};
}
/**
* 应用数学页面公共方法的辅助函数
* 自动混入公共方法,简化页面代码
* @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));
}
+1 -1
View File
@@ -7,6 +7,6 @@
"usingComponents": {
"toy-button": "../../ui/button/button",
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
+1 -1
View File
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
+10 -18
View File
@@ -1,15 +1,12 @@
import CompareDraw from '../service/compareDraw';
import CompareDraw from '../shared/service/compareDraw';
import {
getMathPageCommonMethods,
createMathPage,
CanvasDataState,
} from '../common/mathPageMixin';
} from '../shared/common/mathPageMixin';
// 获取公共方
const commonMethods = getMathPageCommonMethods({
pagePath: 'compare/compare',
});
// 获取公共方
Page({
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
@@ -40,7 +37,11 @@ Page({
onReady() {
this.initCanvas({
createDrawService: (canvas, ctx, options) => {
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new CompareDraw(canvas, ctx, options);
},
drawServiceOptions: {
@@ -120,13 +121,4 @@ Page({
this.compareData = { problems };
},
// ========== 使用公共方法 ==========
initCanvas: commonMethods.initCanvas,
exportToPrint: commonMethods.exportToPrint,
onShareAppMessage: commonMethods.onShareAppMessage,
onShareTimeline: commonMethods.onShareTimeline,
onCloseShareDialog: commonMethods.onCloseShareDialog,
onShareSuccess: commonMethods.onShareSuccess,
initPageInfo: commonMethods.initPageInfo,
});
@@ -1,7 +0,0 @@
{
"component": true,
"usingComponents": {
"toy-button": "../../../ui/button/button",
"van-action-sheet": "../../../miniprogram_npm/@vant/weapp/action-sheet/index"
}
}
@@ -6,7 +6,7 @@
"enablePullDownRefresh": false,
"usingComponents": {
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-type-selector": "../components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-type-selector": "../shared/components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
+181 -187
View File
@@ -1,200 +1,194 @@
import CountMatchDraw from '../service/countMatchDraw';
import NumberColorDraw from '../service/numberColorDraw';
import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
import CountMatchDraw from '../shared/service/countMatchDraw';
import NumberColorDraw from '../shared/service/numberColorDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
Page(
applyMathPageMixin(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as CountMatchDraw | NumberColorDraw | null,
matchData: null as {
leftNumbers: number[];
rightNumbers: number[];
} | null,
colorData: null as {
numbers: number[];
} | null,
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as CountMatchDraw | NumberColorDraw | null,
matchData: null as {
leftNumbers: number[];
rightNumbers: number[];
} | null,
colorData: null as {
numbers: number[];
} | null,
data: {
pageTitle: '数一数,连一连',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true, // 控制是否显示类型选择器
currentType: 'twelve-animals', // 'twelve-animals' 或 'fruits' 或 'circle' 或 'caterpillar'
currentTypeName: '十二生肖',
data: {
pageTitle: '数一数,连一连',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true, // 控制是否显示类型选择器
currentMode: 'twelve-animals', // 'twelve-animals' 或 'fruits' 或 'circle' 或 'caterpillar'
currentModeName: '十二生肖',
typeActions: [
{ name: '十二生肖', value: 'twelve-animals' },
{ name: '水果', value: 'fruits' },
],
} as CanvasDataState & {
showTypeSelector: boolean;
currentMode: string;
currentModeName: string;
typeActions: Array<{ name: string; value: string }>;
},
onLoad(options: { id?: string; mode?: string }) {
const functionId = options.id || 'counting-matching';
this.initPageInfo(functionId, '数一数,连一连');
// 根据 functionId 设置不同的类型选择器
if (functionId === 'number-coloring') {
const currentMode = options.mode || 'caterpillar';
this.setData({
currentMode: currentMode,
currentModeName:
currentMode === 'caterpillar' ? '毛毛虫' : '圆圈',
typeActions: [
{ name: '毛毛虫', value: 'caterpillar' },
{ name: '圆圈', value: 'circle' },
],
});
} else {
const currentMode = options.mode || 'twelve-animals';
this.setData({
currentMode,
currentModeName:
currentMode === 'twelve-animals' ? '十二生肖' : '水果',
typeActions: [
{ name: '十二生肖', value: 'twelve-animals' },
{ name: '水果', value: 'fruits' },
],
} as CanvasDataState & {
showTypeSelector: boolean;
currentType: string;
currentTypeName: string;
typeActions: Array<{ name: string; value: string }>;
},
});
}
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'counting-matching';
this.setData({ functionId });
const functionItem =
require('../../constants/mathFunctions').MATH_FUNCTION_TYPES.find(
(item: any) => item.id === functionId,
);
const pageTitle = functionItem?.title || '数一数,连一连';
this.setData({ pageTitle });
this.initPageInfo(functionId, pageTitle);
// 根据 functionId 设置不同的类型选择器
if (functionId === 'number-coloring') {
this.setData({
currentType: 'caterpillar',
currentTypeName: '毛毛虫',
typeActions: [
{ name: '毛毛虫', value: 'caterpillar' },
{ name: '圆圈', value: 'circle' },
],
});
}
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
// 根据 functionId 创建不同的绘制服务
if (this.data.functionId === 'number-coloring') {
return new NumberColorDraw(canvas, ctx, options);
} else {
return new CountMatchDraw(canvas, ctx, options);
}
},
drawServiceOptions: {
subTitle:
this.data.functionId === 'number-coloring'
? '按数字给相应的圆圈涂上颜色'
: '通过连线配对数字和对应的数量图形',
},
onCanvasReady: () => {
// 初始随机生成
this.onRandom();
},
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService) {
return;
}
try {
if (this.data.functionId === 'number-coloring') {
if (!this.colorData) {
return;
}
await (this.drawService as NumberColorDraw).draw(
this.colorData,
this.data.currentType,
);
} else {
if (!this.matchData) {
return;
}
await (this.drawService as CountMatchDraw).draw(
this.matchData,
this.data.currentType,
);
}
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
// 根据 functionId 创建不同的绘制服务
if (this.data.functionId === 'number-coloring') {
// 按数字涂颜色模式:生成6个随机数字(1-10)
const availableNumbers = Array.from(
{ length: 10 },
(_, i) => i + 1,
);
const numbers: number[] = [];
for (let i = 0; i < 6; i++) {
const randomIndex = Math.floor(
Math.random() * availableNumbers.length,
);
const number = availableNumbers.splice(
randomIndex,
1,
)[0];
numbers.push(number);
}
this.colorData = { numbers };
return new NumberColorDraw(canvas, ctx, options);
} else {
// 数一数连一连模式:生成5个不同的数字(1-10)
const availableNumbers = Array.from(
{ length: 10 },
(_, i) => i + 1,
);
const leftNumbers: number[] = [];
for (let i = 0; i < 5; i++) {
const randomIndex = Math.floor(
Math.random() * availableNumbers.length,
);
const number = availableNumbers.splice(
randomIndex,
1,
)[0];
leftNumbers.push(number);
}
// 复制数字数组并打乱顺序,作为右侧显示的数字
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],
];
}
this.matchData = { leftNumbers, rightNumbers };
return new CountMatchDraw(canvas, ctx, options);
}
this.drawCanvas();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentType: value,
currentTypeName: name,
});
// 重新生成数据
drawServiceOptions: {
subTitle:
this.data.functionId === 'number-coloring'
? '按数字给相应的圆圈涂上颜色'
: '通过连线配对数字和对应的数量图形',
},
onCanvasReady: () => {
// 初始随机生成
this.onRandom();
},
},
{
pagePath: 'countMatch/countMatch',
},
),
);
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService) {
return;
}
try {
if (this.data.functionId === 'number-coloring') {
if (!this.colorData) {
return;
}
await (this.drawService as NumberColorDraw).draw(
this.colorData,
this.data.currentMode,
);
} else {
if (!this.matchData) {
return;
}
await (this.drawService as CountMatchDraw).draw(
this.matchData,
this.data.currentMode,
);
}
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
if (this.data.functionId === 'number-coloring') {
// 按数字涂颜色模式:生成6个随机数字(1-10)
const availableNumbers = Array.from(
{ length: 10 },
(_, i) => i + 1,
);
const numbers: number[] = [];
for (let i = 0; i < 6; i++) {
const randomIndex = Math.floor(
Math.random() * availableNumbers.length,
);
const number = availableNumbers.splice(randomIndex, 1)[0];
numbers.push(number);
}
this.colorData = { numbers };
} else {
// 数一数连一连模式:生成5个不同的数字(1-10)
const availableNumbers = Array.from(
{ length: 10 },
(_, i) => i + 1,
);
const leftNumbers: number[] = [];
for (let i = 0; i < 5; i++) {
const randomIndex = Math.floor(
Math.random() * availableNumbers.length,
);
const number = availableNumbers.splice(randomIndex, 1)[0];
leftNumbers.push(number);
}
// 复制数字数组并打乱顺序,作为右侧显示的数字
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],
];
}
this.matchData = { leftNumbers, rightNumbers };
}
this.drawCanvas();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentMode: value,
currentModeName: name,
});
// 重新生成数据
this.onRandom();
},
});
@@ -14,7 +14,7 @@
<!-- 类型选择器和随机生成按钮 -->
<math-type-selector
wx:if="{{showTypeSelector}}"
current-type-name="{{currentTypeName}}"
current-type-name="{{currentModeName}}"
type-actions="{{typeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
@@ -7,6 +7,6 @@
"usingComponents": {
"toy-button": "../../ui/button/button",
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
@@ -1,168 +1,151 @@
import CountingSelectDraw from '../service/countingSelectDraw';
import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
import CountingSelectDraw from '../shared/service/countingSelectDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
Page(
applyMathPageMixin(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as CountingSelectDraw | null,
countingSelectData: null as {
problems: Array<{
count: number; // 图片数量(正确答案)
imageIndex: number; // 图片索引
imageType: 'fruits' | 'twelve-animals'; // 图片类型
options?: number[]; // 三个数字选项(选一选模式需要)
correctIndex?: number; // 正确答案在options中的索引(选一选模式需要)
}>;
} | null,
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as CountingSelectDraw | null,
countingSelectData: null as {
problems: Array<{
count: number; // 图片数量(正确答案)
imageIndex: number; // 图片索引
imageType: 'fruits' | 'twelve-animals'; // 图片类型
options?: number[]; // 三个数字选项(选一选模式需要)
correctIndex?: number; // 正确答案在options中的索引(选一选模式需要)
}>;
} | null,
data: {
pageTitle: '数一数,选一选',
subTitle: '数出物品数量,从多个选项中选择正确答案',
functionId: '',
hasContent: false,
showShareDialog: false,
} as CanvasDataState & {
showTypeSelector?: boolean;
currentType?: string;
currentTypeName?: string;
typeActions?: Array<{ name: string; value: any }>;
data: {
pageTitle: '数一数,选一选',
subTitle: '数出物品数量,从多个选项中选择正确答案',
functionId: '',
hasContent: false,
showShareDialog: false,
} as CanvasDataState,
onLoad(options: { id?: string }) {
const functionId = options.id || 'counting-select';
// 初始化页面信息(会从 mathFunctions.ts 读取 title 和 desc
this.initPageInfo(functionId, '数一数,选一选');
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new CountingSelectDraw(canvas, ctx, options);
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'counting-select';
// 初始化页面信息(会从 mathFunctions.ts 读取 title 和 desc
this.initPageInfo(functionId, '数一数,选一选');
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new CountingSelectDraw(canvas, ctx, options);
},
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
});
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (
!this.ctx ||
!this.drawService ||
!this.countingSelectData
) {
return;
}
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.countingSelectData) {
return;
}
try {
// 判断是选一选还是填一填模式
const mode =
this.data.functionId === 'counting-fill'
? 'fill'
: 'select';
await this.drawService.draw(this.countingSelectData, mode);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
try {
// 判断是选一选还是填一填模式
const mode =
this.data.functionId === 'counting-fill' ? 'fill' : 'select';
await this.drawService.draw(this.countingSelectData, mode);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
this.generateCountingSelectData();
this.drawCanvas();
},
/**
* 随机生成
*/
onRandom() {
this.generateCountingSelectData();
this.drawCanvas();
},
/**
* 生成数一数选一选/填一填数据
*/
generateCountingSelectData() {
const isFillMode = this.data.functionId === 'counting-fill';
const problems: Array<{
count: number;
imageIndex: number;
imageType: 'fruits' | 'twelve-animals';
options?: number[];
correctIndex?: number;
}> = [];
/**
* 生成数一数选一选/填一填数据
*/
generateCountingSelectData() {
const isFillMode = this.data.functionId === 'counting-fill';
const problems: Array<{
count: number;
imageIndex: number;
imageType: 'fruits' | 'twelve-animals';
options?: number[];
correctIndex?: number;
}> = [];
// 生成9道题目
for (let i = 0; i < 9; i++) {
// 随机选择图片类型
const imageType: 'fruits' | 'twelve-animals' =
Math.random() < 0.5 ? 'fruits' : 'twelve-animals';
// 生成9道题目
for (let i = 0; i < 9; i++) {
// 随机选择图片类型
const imageType: 'fruits' | 'twelve-animals' =
Math.random() < 0.5 ? 'fruits' : 'twelve-animals';
// 根据图片类型确定最大索引
const maxImageIndex = imageType === 'fruits' ? 22 : 12;
// 根据图片类型确定最大索引
const maxImageIndex = imageType === 'fruits' ? 22 : 12;
// 生成图片数量(1-10
const count = Math.floor(Math.random() * 10) + 1;
// 生成图片数量(1-10
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: {
count: number;
imageIndex: number;
imageType: 'fruits' | 'twelve-animals';
options?: number[];
correctIndex?: number;
} = {
count,
imageIndex,
imageType,
};
const problem: {
count: number;
imageIndex: number;
imageType: 'fruits' | 'twelve-animals';
options?: number[];
correctIndex?: number;
} = {
count,
imageIndex,
imageType,
};
// 选一选模式:生成三个选项
if (!isFillMode) {
const options: number[] = [];
const correctIndex = Math.floor(Math.random() * 3);
// 选一选模式:生成三个选项
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 wrongAnswer: number;
do {
wrongAnswer =
Math.floor(Math.random() * 10) + 1;
} while (wrongAnswer === count);
options.push(wrongAnswer);
}
}
problem.options = options;
problem.correctIndex = correctIndex;
for (let j = 0; j < 3; j++) {
if (j === correctIndex) {
options.push(count); // 正确答案
} else {
// 生成错误答案(与正确答案不同)
let wrongAnswer: number;
do {
wrongAnswer = Math.floor(Math.random() * 10) + 1;
} while (wrongAnswer === count);
options.push(wrongAnswer);
}
problems.push(problem);
}
this.countingSelectData = { problems };
},
},
{
pagePath: 'countingSelect/countingSelect',
},
),
);
problem.options = options;
problem.correctIndex = correctIndex;
}
problems.push(problem);
}
this.countingSelectData = { problems };
},
});
@@ -6,7 +6,7 @@
"enablePullDownRefresh": false,
"usingComponents": {
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-type-selector": "../components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-type-selector": "../shared/components/math-type-selector/math-type-selector",
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
@@ -1,198 +1,198 @@
import { getRandomNumberColor } from '../../constants/colors';
import MissingNumberDraw from '../service/missingNumberDraw';
import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
import MissingNumberDraw from '../shared/service/missingNumberDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
Page(
applyMathPageMixin(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as MissingNumberDraw | null,
missingNumberData: null as {
grids: Array<{
numbers: (number | null)[];
colors: (string | null)[];
}>;
maxNumber: number;
} | null,
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as MissingNumberDraw | null,
missingNumberData: null as {
grids: Array<{
numbers: (number | null)[];
colors: (string | null)[];
}>;
maxNumber: number;
} | null,
data: {
pageTitle: '填上缺少的数字',
subTitle: '在数字序列中找出并填写缺失的数字',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true,
currentType: 10,
currentTypeName: '10以内',
typeActions: [
{ name: '10以内', value: 10 },
{ name: '20以内', value: 20 },
{ name: '40以内', value: 40 },
{ name: '50以内', value: 50 },
{ name: '80以内', value: 80 },
{ name: '100以内', value: 100 },
{ name: '120以内', value: 120 },
],
} as CanvasDataState & {
showTypeSelector: boolean;
currentType: number;
currentTypeName: string;
typeActions: Array<{ name: string; value: number }>;
data: {
pageTitle: '填上缺少的数字',
subTitle: '在数字序列中找出并填写缺失的数字',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true,
currentMode: 10,
currentModeName: '10以内',
typeActions: [
{ name: '10以内', value: 10 },
{ name: '20以内', value: 20 },
{ name: '40以内', value: 40 },
{ name: '50以内', value: 50 },
{ name: '80以内', value: 80 },
{ name: '100以内', value: 100 },
{ name: '120以内', value: 120 },
],
} as CanvasDataState & {
showTypeSelector: boolean;
currentMode: number;
currentModeName: string;
typeActions: Array<{ name: string; value: number }>;
},
onLoad(options: { id?: string; mode?: number }) {
const functionId = options.id || 'missing-number';
this.initPageInfo(functionId, '填上缺少的数字');
this.setData({
currentMode: options.mode || 10,
currentModeName: options.mode ? `${options.mode}以内` : '10以内',
});
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new MissingNumberDraw(canvas, ctx, options);
},
onLoad(options: { id?: string }) {
const functionId = options.id || 'missing-number';
this.initPageInfo(functionId, '填上缺少的数字');
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new MissingNumberDraw(canvas, ctx, options);
},
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.missingNumberData) {
return;
}
try {
await this.drawService.draw(
this.missingNumberData,
String(this.data.currentType),
);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
const maxNumber = this.data.currentType;
this.generateMissingNumberData(maxNumber);
this.drawCanvas();
},
/**
* 生成缺失数字数据
*/
generateMissingNumberData(maxNumber: number) {
const grids: Array<{
numbers: (number | null)[];
colors: (string | null)[];
}> = [];
const gridMap = {
10: {
gridCount: 3,
},
20: {
gridCount: 2,
},
40: {
gridCount: 2,
},
50: {
gridCount: 2,
},
80: {
gridCount: 1,
},
100: {
gridCount: 1,
},
120: {
gridCount: 1,
},
};
const { gridCount } =
gridMap[maxNumber as keyof typeof gridMap];
// 生成多个网格
for (let gridIndex = 0; gridIndex < gridCount; gridIndex++) {
let startNumber = 1;
let endNumber = maxNumber;
const actualNumbersPerGrid = endNumber - startNumber + 1;
const numbers: number[] = Array.from(
{ length: actualNumbersPerGrid },
(_, i) => startNumber + i,
);
// 随机隐藏一部分数字(隐藏40-60%)
const hideCount = Math.floor(
actualNumbersPerGrid * (0.4 + Math.random() * 0.2),
);
const hiddenIndices = new Set<number>();
while (hiddenIndices.size < hideCount) {
const randomIndex = Math.floor(
Math.random() * actualNumbersPerGrid,
);
hiddenIndices.add(randomIndex);
}
const gridNumbers: (number | null)[] = numbers.map(
(num, index) => (hiddenIndices.has(index) ? null : num),
);
// 为每个数字分配颜色(包括null位置)
const gridColors: (string | null)[] = gridNumbers.map(
(num) => (num ? this.getRandomNumberColor() : null),
);
grids.push({ numbers: gridNumbers, colors: gridColors });
}
this.missingNumberData = {
grids,
maxNumber,
};
},
/**
* 获取随机数字颜色
*/
getRandomNumberColor(): string {
return getRandomNumberColor();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentType: value,
currentTypeName: name,
});
// 重新生成数据
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
},
{
pagePath: 'missingNumber/missingNumber',
},
),
);
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.missingNumberData) {
return;
}
try {
await this.drawService.draw(
this.missingNumberData,
String(this.data.currentMode),
);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
const maxNumber = this.data.currentMode;
this.generateMissingNumberData(maxNumber);
this.drawCanvas();
},
/**
* 生成缺失数字数据
*/
generateMissingNumberData(maxNumber: number) {
const grids: Array<{
numbers: (number | null)[];
colors: (string | null)[];
}> = [];
const gridMap = {
10: {
gridCount: 3,
},
20: {
gridCount: 2,
},
40: {
gridCount: 2,
},
50: {
gridCount: 2,
},
80: {
gridCount: 1,
},
100: {
gridCount: 1,
},
120: {
gridCount: 1,
},
};
const { gridCount } = gridMap[maxNumber as keyof typeof gridMap];
// 生成多个网格
for (let gridIndex = 0; gridIndex < gridCount; gridIndex++) {
let startNumber = 1;
let endNumber = maxNumber;
const actualNumbersPerGrid = endNumber - startNumber + 1;
const numbers: number[] = Array.from(
{ length: actualNumbersPerGrid },
(_, i) => startNumber + i,
);
// 随机隐藏一部分数字(隐藏40-60%)
const hideCount = Math.floor(
actualNumbersPerGrid * (0.4 + Math.random() * 0.2),
);
const hiddenIndices = new Set<number>();
while (hiddenIndices.size < hideCount) {
const randomIndex = Math.floor(
Math.random() * actualNumbersPerGrid,
);
hiddenIndices.add(randomIndex);
}
const gridNumbers: (number | null)[] = numbers.map((num, index) =>
hiddenIndices.has(index) ? null : num,
);
// 为每个数字分配颜色(包括null位置)
const gridColors: (string | null)[] = gridNumbers.map((num) =>
num ? this.getRandomNumberColor() : null,
);
grids.push({ numbers: gridNumbers, colors: gridColors });
}
this.missingNumberData = {
grids,
maxNumber,
};
},
/**
* 获取随机数字颜色
*/
getRandomNumberColor(): string {
return getRandomNumberColor();
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentMode: value,
currentModeName: name,
});
// 重新生成数据
this.onRandom();
},
});
@@ -14,7 +14,7 @@
<!-- 类型选择器和随机生成按钮 -->
<math-type-selector
wx:if="{{showTypeSelector}}"
current-type-name="{{currentTypeName}}"
current-type-name="{{currentModeName}}"
type-actions="{{typeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
@@ -6,7 +6,7 @@
"enablePullDownRefresh": false,
"usingComponents": {
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons",
"math-type-selector": "../components/math-type-selector/math-type-selector"
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons",
"math-type-selector": "../shared/components/math-type-selector/math-type-selector"
}
}
@@ -1 +1 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
@@ -1,270 +1,300 @@
import NumberDecomposeDraw from '../service/numberDecomposeDraw';
import { applyMathPageMixin, CanvasDataState } from '../common/mathPageMixin';
import NumberDecomposeDraw from '../shared/service/numberDecomposeDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
type DecomposeMode = 'with-image' | 'decompose' | 'compose';
Page(
applyMathPageMixin(
{
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as NumberDecomposeDraw | null,
decomposeData: null as {
problems: Array<{
whole: number | null; // 总数null表示组合模式需要填写)
part1: number | null; // 第一个部分(null表示需要填写
part2: number | null; // 第二个部分(null表示需要填写)
imageIndex?: number; // 图片索引(有图片模式需要)
imageType?: 'fruits' | 'twelve-animals'; // 图片类型
}>;
mode: DecomposeMode;
} | null,
createMathPage({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
drawService: null as NumberDecomposeDraw | null,
decomposeData: null as {
problems: Array<{
whole: number | null; // 总数(null表示组合模式需要填写)
part1: number | null; // 第一个部分(null表示需要填写)
part2: number | null; // 第二个部分null表示需要填写)
imageIndex?: number; // 图片索引(有图片模式需要
imageType?: 'fruits' | 'twelve-animals'; // 图片类型
}>;
mode: DecomposeMode;
} | null,
data: {
pageTitle: '10以内数的分与合',
subTitle: '学习数的分解与组合',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true,
currentType: 'with-image',
currentTypeName: '有图片模式',
typeActions: [
{ name: '有图片模式', value: 'with-image' },
{ name: '分模式', value: 'decompose' },
{ name: '组合模式', value: 'compose' },
],
} as CanvasDataState & {
showTypeSelector: boolean;
currentType: DecomposeMode;
currentTypeName: string;
typeActions: Array<{ name: string; value: DecomposeMode }>;
data: {
pageTitle: '10以内数的分与合',
subTitle: '学习数的分解与组合',
functionId: '',
hasContent: false,
showShareDialog: false,
showTypeSelector: true,
currentMode: 'with-image',
currentModeName: '有图片模式',
typeActions: [
{ name: '有图片模式', value: 'with-image' },
{ name: '分模式', value: 'decompose' },
{ name: '组合模式', value: 'compose' },
],
maxNumber: 10, // 最大数字:10 或 20
} as CanvasDataState & {
showTypeSelector: boolean;
currentMode: DecomposeMode;
currentModeName: string;
typeActions: Array<{ name: string; value: DecomposeMode }>;
maxNumber: number;
},
onLoad(options: { id?: string; mode?: DecomposeMode }) {
const functionId = options.id || 'number-decompose';
const is20Within = functionId === 'number-decompose-20';
const maxNumber = is20Within ? 20 : 10;
// 20以内只有两种模式,10以内有三种模式
let defaultMode: DecomposeMode;
let typeActions: Array<{ name: string; value: DecomposeMode }>;
let defaultTypeName: string;
if (is20Within) {
// 20以内:只有分模式和组合模式
defaultMode = options.mode || 'decompose';
typeActions = [
{ name: '20以内的分解', value: 'decompose' },
{ name: '20以内的组合', value: 'compose' },
];
defaultTypeName =
defaultMode === 'decompose' ? '20以内的分解' : '20以内的组合';
} else {
// 10以内:有图片模式、分模式、组合模式
defaultMode = options.mode || 'with-image';
typeActions = [
{ name: '有图片模式', value: 'with-image' },
{ name: '分模式', value: 'decompose' },
{ name: '组合模式', value: 'compose' },
];
defaultTypeName =
defaultMode === 'with-image'
? '有图片模式'
: defaultMode === 'decompose'
? '分模式'
: '组合模式';
}
this.setData({
currentMode: defaultMode,
currentModeName: defaultTypeName,
typeActions,
maxNumber,
});
this.initPageInfo(
functionId,
is20Within ? '20以内数的分与合' : '10以内数的分与合',
);
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new NumberDecomposeDraw(canvas, ctx, options);
},
onLoad(options: { id?: string; mode?: DecomposeMode }) {
const functionId = options.id || 'number-decompose';
const mode = options.mode || 'with-image';
const currentTypeName =
mode === 'with-image'
? '有图片模式'
: mode === 'decompose'
? '分模式'
: '组合模式';
this.setData({
currentType: mode,
currentTypeName,
});
this.initPageInfo(functionId, '10以内数的分与合');
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onReady() {
this.initCanvas({
createDrawService: (
canvas: Canvas,
ctx: RenderingContext,
options?: Record<string, any>,
) => {
return new NumberDecomposeDraw(canvas, ctx, options);
},
drawServiceOptions: {
subTitle: this.data.subTitle,
},
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.decomposeData) {
return;
}
try {
await this.drawService.draw(this.decomposeData);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
this.generateDecomposeData();
this.drawCanvas();
},
/**
* 生成分解数据(确保不重复)
*/
generateDecomposeData() {
const mode = this.data.currentType;
const problems: Array<{
whole: number | null;
part1: number | null;
part2: number | null;
imageIndex?: number;
imageType?: 'fruits' | 'twelve-animals';
}> = [];
// 用于去重的 Set,存储题目唯一标识
// 格式:对于分解模式 "whole:part1:part2:showPart1",对于组合模式 "part1:part2"
const usedKeys = new Set<string>();
if (mode === 'with-image') {
// 有图片模式:9个题目,一行三列,总共三行
const problemCount = 9;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++;
// 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1)
const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null
const showPart1 = Math.random() < 0.5;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${whole}:${minPart}:${maxPart}:${showPart1}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
// 随机选择图片类型和索引
const imageType: 'fruits' | 'twelve-animals' =
Math.random() < 0.5 ? 'fruits' : 'twelve-animals';
const maxImageIndex = imageType === 'fruits' ? 22 : 12;
const imageIndex =
Math.floor(Math.random() * maxImageIndex) + 1;
problems.push({
whole,
part1: showPart1 ? part1 : null,
part2: showPart1 ? null : part2,
imageIndex,
imageType,
});
}
} else if (mode === 'decompose') {
// 分模式:15个题目,一行3个,总共5行
const problemCount = 15;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++;
// 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1)
const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null
const showPart1 = Math.random() < 0.5;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${whole}:${minPart}:${maxPart}:${showPart1}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
problems.push({
whole,
part1: showPart1 ? part1 : null,
part2: showPart1 ? null : part2,
});
}
} else if (mode === 'compose') {
// 组合模式:15个题目,一行3个,总共5行
const problemCount = 15;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (
problems.length < problemCount &&
attempts < maxAttempts
) {
attempts++;
// 生成总数(2-10
const whole = Math.floor(Math.random() * 9) + 2;
// 随机选择一个部分(1 到 whole-1)
const part1 =
Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${minPart}:${maxPart}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
// 组合模式:两个部分都显示,根节点为 null
problems.push({
whole: null, // 根节点需要填写
part1,
part2,
});
}
}
this.decomposeData = { problems, mode };
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentType: value,
currentTypeName: name,
});
// 重新生成数据
onCanvasReady: () => {
// Canvas 初始化完成后,生成初始数据
this.onRandom();
},
},
{
pagePath: 'numberDecompose/numberDecompose',
},
),
);
});
},
/**
* 绘制Canvas内容
*/
async drawCanvas() {
if (!this.ctx || !this.drawService || !this.decomposeData) {
return;
}
try {
await this.drawService.draw(this.decomposeData);
this.setData({ hasContent: true });
} catch (error) {
console.error('绘制失败:', error);
this.setData({ hasContent: false });
}
},
/**
* 随机生成
*/
onRandom() {
this.generateDecomposeData();
this.drawCanvas();
},
/**
* 生成分解数据(确保不重复)
*/
generateDecomposeData() {
const mode = this.data.currentMode;
const maxNumber = this.data.maxNumber;
const is20Within = maxNumber === 20;
const problems: Array<{
whole: number | null;
part1: number | null;
part2: number | null;
imageIndex?: number;
imageType?: 'fruits' | 'twelve-animals';
}> = [];
// 用于去重的 Set,存储题目唯一标识
// 格式:对于分解模式 "whole:part1:part2:showPart1",对于组合模式 "part1:part2"
const usedKeys = new Set<string>();
// 确定根节点的数字范围
const minWhole = is20Within ? 11 : 2; // 20以内从11开始,10以内从2开始
const maxWhole = maxNumber;
if (mode === 'with-image') {
// 有图片模式:9个题目,一行三列,总共三行(仅10以内)
const problemCount = 9;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) {
attempts++;
// 生成总数(2-10
const whole =
Math.floor(Math.random() * (maxWhole - minWhole + 1)) +
minWhole;
// 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null
const showPart1 = Math.random() < 0.5;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${whole}:${minPart}:${maxPart}:${showPart1}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
// 随机选择图片类型和索引
const imageType: 'fruits' | 'twelve-animals' =
Math.random() < 0.5 ? 'fruits' : 'twelve-animals';
const maxImageIndex = imageType === 'fruits' ? 22 : 12;
const imageIndex =
Math.floor(Math.random() * maxImageIndex) + 1;
problems.push({
whole,
part1: showPart1 ? part1 : null,
part2: showPart1 ? null : part2,
imageIndex,
imageType,
});
}
} else if (mode === 'decompose') {
// 分模式:15个题目,一行3个,总共5行
const problemCount = 15;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) {
attempts++;
// 生成总数(2-10 或 11-20
const whole =
Math.floor(Math.random() * (maxWhole - minWhole + 1)) +
minWhole;
// 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 随机决定显示 part1 还是 part2(另一个为 null
const showPart1 = Math.random() < 0.5;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${whole}:${minPart}:${maxPart}:${showPart1}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
problems.push({
whole,
part1: showPart1 ? part1 : null,
part2: showPart1 ? null : part2,
});
}
} else if (mode === 'compose') {
// 组合模式:15个题目,一行3个,总共5行
const problemCount = 15;
let attempts = 0;
const maxAttempts = problemCount * 50; // 最大尝试次数
while (problems.length < problemCount && attempts < maxAttempts) {
attempts++;
// 生成总数(2-10 或 11-20
const whole =
Math.floor(Math.random() * (maxWhole - minWhole + 1)) +
minWhole;
// 随机选择一个部分(1 到 whole-1)
const part1 = Math.floor(Math.random() * (whole - 1)) + 1;
const part2 = whole - part1;
// 生成唯一标识(统一格式:part1 <= part2
const minPart = Math.min(part1, part2);
const maxPart = Math.max(part1, part2);
const key = `${minPart}:${maxPart}`;
// 检查是否已存在
if (usedKeys.has(key)) {
continue;
}
usedKeys.add(key);
// 组合模式:两个部分都显示,根节点为 null
problems.push({
whole: null, // 根节点需要填写
part1,
part2,
});
}
}
this.decomposeData = { problems, mode };
},
/** 选择类型 */
onSelectType(event: any) {
const { name, value } = event.detail;
this.setData({
currentMode: value,
currentModeName: name,
});
// 重新生成数据
this.onRandom();
},
});
@@ -14,7 +14,7 @@
<!-- 类型选择器和随机生成按钮 -->
<math-type-selector
wx:if="{{showTypeSelector}}"
current-type-name="{{currentTypeName}}"
current-type-name="{{currentModeName}}"
type-actions="{{typeActions}}"
bind:select="onSelectType"
bind:random="onRandom" />
@@ -7,6 +7,6 @@
"usingComponents": {
"toy-button": "../../ui/button/button",
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons"
"math-bottom-buttons": "../shared/components/math-bottom-buttons/math-bottom-buttons"
}
}
@@ -1,4 +1,4 @@
@import '../common/mathPage.less';
@import '../shared/common/mathPage.less';
/* 数字选择区域 */
.number-selection-area {
@@ -1,5 +1,8 @@
import NumberFindDraw from '../service/numberFindDraw';
import { createMathPage, CanvasDataState } from '../common/mathPageMixin';
import NumberFindDraw from '../shared/service/numberFindDraw';
import {
createMathPage,
CanvasDataState,
} from '../shared/common/mathPageMixin';
createMathPage({
canvas: null as Canvas | null,
@@ -1,140 +0,0 @@
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];
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 = 25;
this.currentY = 25;
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) => {
console.log('drawMiniHeader currentY', currentY);
this.currentY = currentY;
},
});
}
/**
* 绘制分割线(逻辑像素,尺寸除以3)
*/
drawDivider() {
const { ctx, canvasWidth } = this;
const dividerY = this.currentY;
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(24, dividerY);
ctx.lineTo(canvasWidth - 24, dividerY);
ctx.stroke();
this.currentY = dividerY + 10; // 分割线下方10px间距
}
}
@@ -1,129 +0,0 @@
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 = 25; // 约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(110);
}
}
/**
* 绘制数学模块迷你页眉的参数接口(尺寸除以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(66); // 约66.67
}
}
@@ -0,0 +1,97 @@
/**
* 数学页面专用的 Mixin
* 基于通用 pageMixin,提供数学模块特定的配置
*/
import {
getPageCommonMethods,
applyPageMixin,
createPage,
CanvasDataState,
PageCanvasInstance,
InitCanvasOptions,
ShareOptions,
PageCommonMethodsConfig,
} from '../../../base/pageMixin';
import {
MATH_FUNCTION_TYPES,
MathFunctionType,
} from '../../../constants/mathFunctions';
/**
* 数学模块的页面实例类型(向后兼容)
*/
export type MathPageCanvasInstance = PageCanvasInstance;
/**
* 数学模块的分享配置
*/
const mathShareConfig = {
title: '涂鸦丫-数学学习涂鸦卡',
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
/**
* 数学模块的页面信息查找函数
*/
function mathPageInfoLookup(functionId: string) {
const functionItem = MATH_FUNCTION_TYPES.find(
(item: MathFunctionType) => item.id === functionId,
);
if (functionItem) {
return {
title: functionItem.title,
desc: functionItem.desc,
};
}
return undefined;
}
/**
* 获取数学页面的公共方法
* 这些方法可以在所有数学页面中复用
* @deprecated 使用 getMathPageCommonMethods() 即可,会自动应用数学模块配置
*/
export function getMathPageCommonMethods() {
const config: PageCommonMethodsConfig = {
shareConfig: mathShareConfig,
pageInfoLookup: mathPageInfoLookup,
};
return getPageCommonMethods(config);
}
/**
* 应用数学页面公共方法的辅助函数
* 自动混入公共方法,简化页面代码
* @param pageOptions 页面选项
* @returns 合并后的页面选项
* @deprecated 使用 applyMathPageMixin() 即可,会自动应用数学模块配置
*/
export function applyMathPageMixin(pageOptions: any) {
const config: PageCommonMethodsConfig = {
shareConfig: mathShareConfig,
pageInfoLookup: mathPageInfoLookup,
};
return applyPageMixin(pageOptions, config);
}
/**
* 创建数学页面的便捷函数
* 自动应用公共方法并注册为页面
* @param pageOptions 页面选项
*/
export function createMathPage(pageOptions: any) {
const config: PageCommonMethodsConfig = {
shareConfig: mathShareConfig,
pageInfoLookup: mathPageInfoLookup,
};
createPage(pageOptions, config);
}
// 导出类型,保持向后兼容
export type { CanvasDataState, InitCanvasOptions, ShareOptions };
@@ -1,6 +1,6 @@
{
"component": true,
"usingComponents": {
"toy-button": "../../../ui/button/button"
"toy-button": "../../../../ui/button/button"
}
}
@@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"toy-button": "../../../../ui/button/button",
"van-action-sheet": "../../../../miniprogram_npm/@vant/weapp/action-sheet/index"
}
}
@@ -1,4 +1,4 @@
import { getImage } from '../../utils/index';
import { getImage } from '../../../utils/index';
interface DrawAdditionContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawAdditionContent } from './additionContentDraw';
/**
*
* 使
*/
class AdditionDraw extends BaseMathDrawService {
class AdditionDraw extends BaseDrawService {
calculationData: {
problems: Array<{
type: 'addition' | 'subtraction';
@@ -1,4 +1,4 @@
import { getImage } from '../../utils/index';
import { getImage } from '../../../utils/index';
interface DrawCompareContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawCompareContent } from './compareContentDraw';
/**
*
* 使
*/
class CompareDraw extends BaseMathDrawService {
class CompareDraw extends BaseDrawService {
compareData: {
problems: Array<{
leftCount: number;
@@ -1,5 +1,5 @@
import { getImage } from '../../utils/index';
import { getRandomNumberColor } from '../../constants/colors';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
interface DrawCountMatchContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawCountMatchContent } from './countMatchContentDraw';
/**
*
* 使
*/
class CountMatchDraw extends BaseMathDrawService {
class CountMatchDraw extends BaseDrawService {
matchData: {
leftNumbers: number[];
rightNumbers: number[]; // 打乱顺序后的数字数组
@@ -1,5 +1,5 @@
import { getImage } from '../../utils/index';
import { getRandomNumberColor } from '../../constants/colors';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
interface DrawCountingSelectContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,4 +1,4 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawCountingSelectContent } from './countingSelectContentDraw';
/**
@@ -6,7 +6,7 @@ import { drawCountingSelectContent } from './countingSelectContentDraw';
* 使
* 'select' 'fill'
*/
class CountingSelectDraw extends BaseMathDrawService {
class CountingSelectDraw extends BaseDrawService {
countingData: {
problems: Array<{
count: number;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawMissingNumberContent } from './missingNumberContentDraw';
/**
*
* 使
*/
class MissingNumberDraw extends BaseMathDrawService {
class MissingNumberDraw extends BaseDrawService {
missingNumberData: {
grids: Array<{
numbers: (number | null)[];
@@ -1,4 +1,4 @@
import { getNumberColors } from '../../constants/colors';
import { getNumberColors } from '../../../constants/colors';
interface DrawNumberColorContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawNumberColorContent } from './numberColorContentDraw';
/**
*
* 使
*/
class NumberColorDraw extends BaseMathDrawService {
class NumberColorDraw extends BaseDrawService {
colorData: {
numbers: number[];
} | null;
@@ -1,5 +1,5 @@
import { getImage } from '../../utils/index';
import { getRandomNumberColor } from '../../constants/colors';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
interface DrawNumberDecomposeContentParams {
canvas: WechatMiniprogram.Canvas;
@@ -1,11 +1,11 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawNumberDecomposeContent } from './numberDecomposeContentDraw';
/**
* 10
* 使
*/
class NumberDecomposeDraw extends BaseMathDrawService {
class NumberDecomposeDraw extends BaseDrawService {
decomposeData: {
problems: Array<{
whole: number | null;
@@ -1,4 +1,4 @@
import { BaseMathDrawService } from './baseMathDraw';
import { BaseDrawService } from '../../../service/baseDraw';
import { drawNumberPreview } from './numberPreviewDraw';
import { drawNumberContent } from './numberContentDraw';
import { drawNumberWriteContent } from './numberWriteDraw';
@@ -7,7 +7,7 @@ import { drawNumberWriteContent } from './numberWriteDraw';
*
* 使
*/
class NumberFindDraw extends BaseMathDrawService {
class NumberFindDraw extends BaseDrawService {
selectedNumber: number;
functionId: string; // 功能ID,用于判断绘制类型
@@ -1,4 +1,4 @@
import { getImage } from '../../utils/index';
import { getImage } from '../../../utils/index';
/**
*