diff --git a/miniprogram/app.json b/miniprogram/app.json
index 151c1a8..9ec599c 100644
--- a/miniprogram/app.json
+++ b/miniprogram/app.json
@@ -10,7 +10,7 @@
{
"root": "mathPages",
"name": "mathPages",
- "pages": ["numberColor/numberColor"],
+ "pages": ["numberFind/numberFind", "countMatch/countMatch"],
"independent": false
}
],
diff --git a/miniprogram/constants/mathFunctions.ts b/miniprogram/constants/mathFunctions.ts
index 31f8a6a..91c8ba9 100644
--- a/miniprogram/constants/mathFunctions.ts
+++ b/miniprogram/constants/mathFunctions.ts
@@ -9,26 +9,28 @@ export interface MathFunctionType {
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
{
id: 'number-find',
- page: 'numberColor',
- title: '看数字,涂一涂',
- desc: '看数字,找一找下面相同的数字,涂上颜色',
+ page: 'numberFind',
+ title: '找数字,涂一涂',
+ desc: '找一找下面相同的数字,涂上颜色',
icon: '🔍',
},
+ {
+ id: 'number-coloring',
+ page: 'countMatch',
+ title: '按数字,涂颜色',
+ desc: '根据数字给相应的圆圈涂色,巩固数字与数量的认知',
+ icon: '🎨',
+ },
{
id: 'number-write',
- page: 'numberColor',
+ page: 'numberFind',
title: '看数字,写一写',
desc: '看数字,按笔画顺序写一写',
icon: '✏️',
},
- {
- id: 'number-coloring',
- title: '数字涂色卡',
- desc: '一边写数字、一边学数数,数字和卡通图片一一对应',
- icon: '🎨',
- },
{
id: 'counting-matching',
+ page: 'countMatch',
title: '数一数,连一连',
desc: '通过连线配对数字和对应的数量图形',
icon: '🔗',
diff --git a/miniprogram/mathPages/assets/fruits/1.png b/miniprogram/mathPages/assets/fruits/1.png
new file mode 100644
index 0000000..4e08518
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/1.png differ
diff --git a/miniprogram/mathPages/assets/fruits/10.png b/miniprogram/mathPages/assets/fruits/10.png
new file mode 100644
index 0000000..2f58b1f
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/10.png differ
diff --git a/miniprogram/mathPages/assets/fruits/11.png b/miniprogram/mathPages/assets/fruits/11.png
new file mode 100644
index 0000000..f8fe214
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/11.png differ
diff --git a/miniprogram/mathPages/assets/fruits/12.png b/miniprogram/mathPages/assets/fruits/12.png
new file mode 100644
index 0000000..5d99cfe
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/12.png differ
diff --git a/miniprogram/mathPages/assets/fruits/13.png b/miniprogram/mathPages/assets/fruits/13.png
new file mode 100644
index 0000000..1a31aed
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/13.png differ
diff --git a/miniprogram/mathPages/assets/fruits/14.png b/miniprogram/mathPages/assets/fruits/14.png
new file mode 100644
index 0000000..873e624
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/14.png differ
diff --git a/miniprogram/mathPages/assets/fruits/15.png b/miniprogram/mathPages/assets/fruits/15.png
new file mode 100644
index 0000000..34fc0c3
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/15.png differ
diff --git a/miniprogram/mathPages/assets/fruits/16.png b/miniprogram/mathPages/assets/fruits/16.png
new file mode 100644
index 0000000..d48f3d9
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/16.png differ
diff --git a/miniprogram/mathPages/assets/fruits/17.png b/miniprogram/mathPages/assets/fruits/17.png
new file mode 100644
index 0000000..dfb8798
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/17.png differ
diff --git a/miniprogram/mathPages/assets/fruits/18.png b/miniprogram/mathPages/assets/fruits/18.png
new file mode 100644
index 0000000..c05ca6d
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/18.png differ
diff --git a/miniprogram/mathPages/assets/fruits/19.png b/miniprogram/mathPages/assets/fruits/19.png
new file mode 100644
index 0000000..a69e258
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/19.png differ
diff --git a/miniprogram/mathPages/assets/fruits/2.png b/miniprogram/mathPages/assets/fruits/2.png
new file mode 100644
index 0000000..445cff5
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/2.png differ
diff --git a/miniprogram/mathPages/assets/fruits/20.png b/miniprogram/mathPages/assets/fruits/20.png
new file mode 100644
index 0000000..492b3ac
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/20.png differ
diff --git a/miniprogram/mathPages/assets/fruits/21.png b/miniprogram/mathPages/assets/fruits/21.png
new file mode 100644
index 0000000..fcca685
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/21.png differ
diff --git a/miniprogram/mathPages/assets/fruits/22.png b/miniprogram/mathPages/assets/fruits/22.png
new file mode 100644
index 0000000..917ceb8
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/22.png differ
diff --git a/miniprogram/mathPages/assets/fruits/3.png b/miniprogram/mathPages/assets/fruits/3.png
new file mode 100644
index 0000000..141d3cd
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/3.png differ
diff --git a/miniprogram/mathPages/assets/fruits/4.png b/miniprogram/mathPages/assets/fruits/4.png
new file mode 100644
index 0000000..8ced475
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/4.png differ
diff --git a/miniprogram/mathPages/assets/fruits/5.png b/miniprogram/mathPages/assets/fruits/5.png
new file mode 100644
index 0000000..885011b
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/5.png differ
diff --git a/miniprogram/mathPages/assets/fruits/6.png b/miniprogram/mathPages/assets/fruits/6.png
new file mode 100644
index 0000000..761c03d
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/6.png differ
diff --git a/miniprogram/mathPages/assets/fruits/7.png b/miniprogram/mathPages/assets/fruits/7.png
new file mode 100644
index 0000000..cba3cab
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/7.png differ
diff --git a/miniprogram/mathPages/assets/fruits/8.png b/miniprogram/mathPages/assets/fruits/8.png
new file mode 100644
index 0000000..051d4cb
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/8.png differ
diff --git a/miniprogram/mathPages/assets/fruits/9.png b/miniprogram/mathPages/assets/fruits/9.png
new file mode 100644
index 0000000..9328d40
Binary files /dev/null and b/miniprogram/mathPages/assets/fruits/9.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/1.png b/miniprogram/mathPages/assets/twelve-animals/1.png
new file mode 100644
index 0000000..11bbfa0
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/1.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/10.png b/miniprogram/mathPages/assets/twelve-animals/10.png
new file mode 100644
index 0000000..49e871d
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/10.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/11.png b/miniprogram/mathPages/assets/twelve-animals/11.png
new file mode 100644
index 0000000..f6aeeee
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/11.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/12.png b/miniprogram/mathPages/assets/twelve-animals/12.png
new file mode 100644
index 0000000..5af7c0c
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/12.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/2.png b/miniprogram/mathPages/assets/twelve-animals/2.png
new file mode 100644
index 0000000..e14464a
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/2.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/3.png b/miniprogram/mathPages/assets/twelve-animals/3.png
new file mode 100644
index 0000000..2e3de4e
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/3.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/4.png b/miniprogram/mathPages/assets/twelve-animals/4.png
new file mode 100644
index 0000000..035d957
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/4.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/5.png b/miniprogram/mathPages/assets/twelve-animals/5.png
new file mode 100644
index 0000000..78fcd34
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/5.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/6.png b/miniprogram/mathPages/assets/twelve-animals/6.png
new file mode 100644
index 0000000..740042a
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/6.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/7.png b/miniprogram/mathPages/assets/twelve-animals/7.png
new file mode 100644
index 0000000..90f200f
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/7.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/8.png b/miniprogram/mathPages/assets/twelve-animals/8.png
new file mode 100644
index 0000000..53cd8ed
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/8.png differ
diff --git a/miniprogram/mathPages/assets/twelve-animals/9.png b/miniprogram/mathPages/assets/twelve-animals/9.png
new file mode 100644
index 0000000..09baf73
Binary files /dev/null and b/miniprogram/mathPages/assets/twelve-animals/9.png differ
diff --git a/miniprogram/mathPages/countMatch/countMatch.json b/miniprogram/mathPages/countMatch/countMatch.json
new file mode 100644
index 0000000..d0f0279
--- /dev/null
+++ b/miniprogram/mathPages/countMatch/countMatch.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "数一数,连一连",
+ "navigationBarBackgroundColor": "#FFD719",
+ "homeButton": true,
+ "backgroundColor": "#F6F6F6",
+ "enablePullDownRefresh": false,
+ "usingComponents": {
+ "toy-button": "../../ui/button/button",
+ "share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
+ "van-action-sheet": "../../miniprogram_npm/@vant/weapp/action-sheet/index"
+ }
+}
diff --git a/miniprogram/mathPages/countMatch/countMatch.less b/miniprogram/mathPages/countMatch/countMatch.less
new file mode 100644
index 0000000..c0ad96f
--- /dev/null
+++ b/miniprogram/mathPages/countMatch/countMatch.less
@@ -0,0 +1,106 @@
+page {
+ background-color: #f6f6f6;
+}
+
+.page-container {
+ background-color: #f6f6f6;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+ padding-bottom: 160rpx; // 为底部按钮预留空间
+
+ .empty {
+ height: 50rpx;
+ }
+}
+
+.wrapper {
+ background-color: #ffffff;
+ border-radius: 20rpx;
+ padding: 36rpx 24rpx;
+ box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15);
+ margin: 30rpx 0;
+ display: flex;
+ flex-direction: column;
+
+ .wrapper-title {
+ font-size: 32rpx;
+ color: #141414;
+ margin-bottom: 36rpx;
+ font-weight: bold;
+ text-align: center;
+ }
+
+ .canvas-wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-height: 400rpx;
+ background: #f8f9fa;
+ border-radius: 12rpx;
+ border: 2rpx dashed #dee2e6;
+ }
+
+ .canvas-content {
+ max-width: 100%;
+ border-radius: 8rpx;
+ box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
+ overflow: hidden;
+ }
+
+ .random-button-area {
+ margin-top: 40rpx;
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+
+ .type-selector {
+ flex: 1; // 占据剩余空间
+ height: 80rpx;
+ background-color: #fff;
+ border: 2rpx solid #e5e5e5;
+ border-radius: 16rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0 24rpx;
+ box-sizing: border-box;
+
+ .type-selector-text {
+ font-size: 28rpx;
+ color: #333;
+ }
+
+ .type-selector-arrow {
+ font-size: 20rpx;
+ color: #999;
+ }
+ }
+
+ .random-button {
+ flex: 2;
+ }
+ }
+}
+
+// /* ActionSheet 自定义样式 */
+// .custom-action-sheet {
+// border-radius: 16rpx 16rpx 0 0 !important;
+// }
+
+/* 底部按钮区域 */
+.bottom-btn-box {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ height: 180rpx;
+ display: flex;
+ justify-content: space-between;
+ background-color: #fff;
+ box-shadow: 0px -3.9rpx 5.2rpx rgba(0, 0, 0, 0.15);
+ padding: 24rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+ box-sizing: border-box;
+ border-radius: 16rpx 16rpx 0 0;
+ z-index: 2;
+}
\ No newline at end of file
diff --git a/miniprogram/mathPages/countMatch/countMatch.ts b/miniprogram/mathPages/countMatch/countMatch.ts
new file mode 100644
index 0000000..e3fd5a4
--- /dev/null
+++ b/miniprogram/mathPages/countMatch/countMatch.ts
@@ -0,0 +1,292 @@
+import { PAPER_SIZE } from '../../constants/colors';
+import { checkAndSaveImage } from '../../utils/saveImage';
+import { shouldShowShareGuide } from '../../utils/shareGuide';
+import CountMatchDraw from '../service/countMatchDraw';
+import NumberColorDraw from '../service/numberColorDraw';
+import {
+ MATH_FUNCTION_TYPES,
+ MathFunctionType,
+} from '../../constants/mathFunctions';
+
+Page({
+ 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: false,
+ currentType: 'twelve-animals', // 'twelve-animals' 或 'fruits' 或 'circle' 或 'caterpillar'
+ currentTypeName: '十二生肖',
+ typeActions: [
+ { name: '十二生肖', value: 'twelve-animals' },
+ { name: '水果', value: 'fruits' },
+ ],
+ },
+
+ onLoad(options: { id?: string }) {
+ const functionId = options.id || 'counting-matching';
+ this.setData({ functionId });
+
+ const functionItem = MATH_FUNCTION_TYPES.find(
+ (item: MathFunctionType) => item.id === functionId,
+ );
+
+ const pageTitle = functionItem?.title || '数一数,连一连';
+ this.setData({ pageTitle });
+ wx.setNavigationBarTitle({ title: pageTitle });
+
+ // 根据 functionId 设置不同的类型选择器
+ if (functionId === 'number-coloring') {
+ this.setData({
+ currentType: 'caterpillar',
+ currentTypeName: '毛毛虫',
+ typeActions: [
+ { name: '毛毛虫', value: 'caterpillar' },
+ { name: '圆圈', value: 'circle' },
+ ],
+ });
+ }
+ },
+
+ onReady() {
+ this.initCanvas();
+ },
+
+ /**
+ * 初始化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;
+
+ // 根据 functionId 创建不同的绘制服务
+ if (this.data.functionId === 'number-coloring') {
+ this.drawService = new NumberColorDraw(
+ canvasNode,
+ ctx,
+ {
+ title: this.data.pageTitle,
+ subTitle:
+ '根据数字给相应的圆圈涂色,巩固数字与数量的认知',
+ },
+ );
+ } else {
+ this.drawService = new CountMatchDraw(
+ canvasNode,
+ ctx,
+ {
+ title: this.data.pageTitle,
+ subTitle:
+ '通过连线配对数字和对应的数量图形',
+ },
+ );
+ }
+
+ // 初始随机生成
+ this.onRandom();
+ }
+ });
+ }
+ })
+ .exec();
+ },
+
+ /**
+ * 绘制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() {
+ 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();
+ },
+
+ /**
+ * 导出打印
+ */
+ 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);
+ }
+ },
+
+ /** 显示类型选择器 */
+ onShowTypeSelector() {
+ this.setData({ showTypeSelector: true });
+ },
+
+ /** 关闭类型选择器 */
+ onCloseTypeSelector() {
+ console.log('onCloseTypeSelector');
+ this.setData({ showTypeSelector: false });
+ },
+
+ /** 选择类型 */
+ onSelectType(event: any) {
+ const { name, value } = event.detail;
+ this.setData({
+ currentType: value,
+ currentTypeName: name,
+ showTypeSelector: false,
+ });
+ // 重新生成数据
+ this.onRandom();
+ },
+});
diff --git a/miniprogram/mathPages/countMatch/countMatch.wxml b/miniprogram/mathPages/countMatch/countMatch.wxml
new file mode 100644
index 0000000..604a209
--- /dev/null
+++ b/miniprogram/mathPages/countMatch/countMatch.wxml
@@ -0,0 +1,69 @@
+
+
+ 预览打印效果
+
+
+
+
+
+
+
+
+
+ {{currentTypeName}}
+ ▼
+
+
+ 随机生成
+
+
+
+
+
+
+
+
+ 分享
+
+
+ 下载打印
+
+
+
diff --git a/miniprogram/mathPages/numberColor/numberColor.json b/miniprogram/mathPages/numberFind/numberFind.json
similarity index 100%
rename from miniprogram/mathPages/numberColor/numberColor.json
rename to miniprogram/mathPages/numberFind/numberFind.json
diff --git a/miniprogram/mathPages/numberColor/numberColor.less b/miniprogram/mathPages/numberFind/numberFind.less
similarity index 100%
rename from miniprogram/mathPages/numberColor/numberColor.less
rename to miniprogram/mathPages/numberFind/numberFind.less
diff --git a/miniprogram/mathPages/numberColor/numberColor.ts b/miniprogram/mathPages/numberFind/numberFind.ts
similarity index 95%
rename from miniprogram/mathPages/numberColor/numberColor.ts
rename to miniprogram/mathPages/numberFind/numberFind.ts
index 4dba43f..400e651 100644
--- a/miniprogram/mathPages/numberColor/numberColor.ts
+++ b/miniprogram/mathPages/numberFind/numberFind.ts
@@ -1,7 +1,7 @@
import { PAPER_SIZE } from '../../constants/colors';
import { checkAndSaveImage } from '../../utils/saveImage';
import { shouldShowShareGuide } from '../../utils/shareGuide';
-import NumberColorDraw from '../service/numberColorDraw';
+import NumberFindDraw from '../service/numberFindDraw';
import {
MATH_FUNCTION_TYPES,
MathFunctionType,
@@ -12,7 +12,7 @@ Page({
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
- drawService: null as NumberColorDraw | null,
+ drawService: null as NumberFindDraw | null,
data: {
pageTitle: '看数字,涂一涂', // 默认标题
@@ -89,7 +89,7 @@ Page({
this.ctx = ctx;
// 初始化绘制服务
- this.drawService = new NumberColorDraw(
+ this.drawService = new NumberFindDraw(
canvasNode,
ctx,
{
@@ -180,7 +180,7 @@ Page({
onShareAppMessage() {
return {
title: '涂鸦丫-数学学习涂鸦卡',
- path: `/mathPages/numberColor/numberColor?id=${this.data.functionId}`,
+ path: `/mathPages/numberFind/numberFind?id=${this.data.functionId}`,
imageUrl:
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
diff --git a/miniprogram/mathPages/numberColor/numberColor.wxml b/miniprogram/mathPages/numberFind/numberFind.wxml
similarity index 100%
rename from miniprogram/mathPages/numberColor/numberColor.wxml
rename to miniprogram/mathPages/numberFind/numberFind.wxml
diff --git a/miniprogram/mathPages/service/baseMathDraw.ts b/miniprogram/mathPages/service/baseMathDraw.ts
index 5823630..8f618b5 100644
--- a/miniprogram/mathPages/service/baseMathDraw.ts
+++ b/miniprogram/mathPages/service/baseMathDraw.ts
@@ -52,9 +52,6 @@ export class BaseMathDrawService {
const { ctx, canvas } = this;
const { pixelRatio: dpr } = wx.getWindowInfo();
let { width, height } = PAPER_SIZE[this.paperSize];
- console.log('width', width);
- console.log('height', height);
- console.log('dpr', dpr);
this.canvasWidth = width;
this.canvasHeight = height;
@@ -87,8 +84,8 @@ export class BaseMathDrawService {
* 绘制Header(逻辑像素,尺寸除以3)
*/
async drawHeader() {
- this.currentX = 24;
- this.currentY = 24;
+ this.currentX = 25;
+ this.currentY = 25;
await drawMathHeader({
canvas: this.canvas,
ctx: this.ctx,
@@ -101,6 +98,7 @@ export class BaseMathDrawService {
this.options.subTitle || '找一找下面相同的数字,涂上颜色',
},
onHeaderDrawn: (currentY) => {
+ console.log('onHeaderDrawn currentY', currentY);
this.currentY = currentY;
},
});
@@ -118,6 +116,7 @@ export class BaseMathDrawService {
title: this.options.title || '看数字,涂一涂',
},
onHeaderDrawn: (currentY) => {
+ console.log('drawMiniHeader currentY', currentY);
this.currentY = currentY;
},
});
@@ -128,7 +127,7 @@ export class BaseMathDrawService {
*/
drawDivider() {
const { ctx, canvasWidth } = this;
- const dividerY = this.currentY + 10; // 10px间距
+ const dividerY = this.currentY;
ctx.strokeStyle = '#000';
ctx.lineWidth = 1;
diff --git a/miniprogram/mathPages/service/countMatchContentDraw.ts b/miniprogram/mathPages/service/countMatchContentDraw.ts
new file mode 100644
index 0000000..963b732
--- /dev/null
+++ b/miniprogram/mathPages/service/countMatchContentDraw.ts
@@ -0,0 +1,293 @@
+import { getImage } from '../../utils/index';
+
+interface DrawCountMatchContentParams {
+ canvas: WechatMiniprogram.Canvas;
+ ctx: RenderingContext;
+ matchData: {
+ leftNumbers: number[];
+ rightNumbers: number[]; // 打乱顺序后的数字数组
+ };
+ canvasWidth: number;
+ startY: number;
+ imageType?: string; // 'twelve-animals' 或 'fruits'
+}
+
+/**
+ * 绘制圆角矩形(虚线边框)
+ */
+function drawRoundedRect(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ radius: number,
+ isDashed: boolean = true,
+) {
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+
+ if (isDashed) {
+ ctx.setLineDash([6 / 3, 6 / 3]); // 约2px的虚线
+ } else {
+ ctx.setLineDash([]);
+ }
+ ctx.stroke();
+ ctx.setLineDash([]); // 重置为实线
+}
+
+/**
+ * 绘制数一数连一连内容区域
+ * 左侧显示数字(虚线圆角框),右侧显示对应数量的图片(虚线圆角框)
+ * 支持根据 imageType 选择不同的图片文件夹(twelve-animals 或 fruits)
+ */
+export async function drawCountMatchContent({
+ canvas,
+ ctx,
+ matchData,
+ canvasWidth,
+ startY,
+ imageType = 'twelve-animals',
+}: DrawCountMatchContentParams): Promise {
+ const { leftNumbers, rightNumbers } = matchData;
+
+ // 根据图片类型确定图片目录和最大索引
+ const imageConfig = {
+ 'twelve-animals': {
+ folder: 'twelve-animals',
+ maxIndex: 12,
+ },
+ fruits: {
+ folder: 'fruits',
+ maxIndex: 22,
+ },
+ };
+
+ const config =
+ imageConfig[imageType as keyof typeof imageConfig] ||
+ imageConfig['twelve-animals'];
+ const leftMargin = 24;
+ const rightMargin = 24;
+ const itemSpacing = 140;
+ const LeftBoxWidth = 120;
+ const rightBoxWidth = 250;
+ const boxHeight = 110;
+ const borderRadius = 12;
+ const imageSpacing = 5; // 图片之间的水平间距(同一行内)
+ const rowSpacing = 10; // 行之间的垂直间距(两行之间)
+ const imagesPerRow = 5; // 每行最多5张图片
+ const maxImages = 10; // 最多10张图片
+ const padding = 10; // 框内边距
+ const startYPos = startY + 20;
+ const linePointRadius = 8;
+ const linePointSpacing = 12;
+
+ /**
+ * 根据图片数量动态计算图片宽度
+ * @param count 图片数量
+ * @returns 图片宽度
+ */
+ const getImageWidth = (count: number): number => {
+ if (count <= 2) {
+ return 90;
+ } else if (count <= 3) {
+ return 70;
+ } else if (count <= 4) {
+ return 55;
+ } else {
+ return 40;
+ }
+ };
+
+ // 计算左侧和右侧的起始X位置
+ const leftBoxX = leftMargin;
+ const rightBoxX = canvasWidth - rightMargin - rightBoxWidth;
+
+ // 绘制左侧数字框
+ for (let i = 0; i < leftNumbers.length; i++) {
+ const boxY = startYPos + i * itemSpacing;
+ const number = leftNumbers[i];
+
+ // 绘制虚线圆角矩形框
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ leftBoxX,
+ boxY,
+ LeftBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ // 绘制数字
+ ctx.fillStyle = '#000';
+ ctx.font = `bold ${56}px "Microsoft Yahei"`;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(
+ String(number),
+ leftBoxX + LeftBoxWidth / 2,
+ boxY + boxHeight / 2,
+ );
+
+ // 绘制左侧框右侧的连线圆点(直径14,半径7)
+ const leftCircleX =
+ leftBoxX + LeftBoxWidth + linePointSpacing + linePointRadius;
+ const leftCircleY = boxY + boxHeight / 2;
+ ctx.fillStyle = '#93D333';
+ ctx.beginPath();
+ ctx.arc(leftCircleX, leftCircleY, linePointRadius, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // 为每个框生成唯一的图片索引,确保不同框使用不同的图片
+ const availableImageIndices = Array.from(
+ { length: config.maxIndex },
+ (_, i) => i + 1,
+ );
+ const boxImageIndices: number[] = [];
+ for (let i = 0; i < rightNumbers.length; i++) {
+ const randomIndex = Math.floor(
+ Math.random() * availableImageIndices.length,
+ );
+ const imageIndex = availableImageIndices.splice(randomIndex, 1)[0];
+ boxImageIndices.push(imageIndex);
+ }
+
+ // 绘制右侧图片框
+ for (let i = 0; i < rightNumbers.length; i++) {
+ const boxY = startYPos + i * itemSpacing;
+ const number = rightNumbers[i]; // 这个数字决定了要绘制多少张图片
+ const boxImageIndex = boxImageIndices[i]; // 这个框使用的图片索引(所有图片都一样)
+
+ // 绘制虚线圆角矩形框
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ // 计算要绘制的图片数量(最多10张)
+ const imageCount = Math.min(number, maxImages);
+
+ // 根据图片数量动态计算图片宽度
+ const imageWidth = getImageWidth(imageCount);
+
+ // 加载当前框使用的图片(只加载一次,用于计算高度和绘制)
+ let boxImage: any = null;
+ try {
+ const imagePath = `/mathPages/assets/${config.folder}/${boxImageIndex}.png`;
+ boxImage = await getImage(canvas, imagePath);
+ } catch (error) {
+ console.error(
+ `加载${config.folder}/${boxImageIndex}图片失败:`,
+ error,
+ );
+ }
+
+ // 计算图片高度(等比例缩放)
+ let imageHeight = imageWidth; // 默认高度
+ if (boxImage) {
+ // @ts-ignore - 微信小程序图片对象有 width 和 height 属性
+ imageHeight = (boxImage.height / boxImage.width) * imageWidth;
+ }
+
+ // 计算每行的图片数量
+ const imagesInFirstRow = Math.min(imageCount, imagesPerRow);
+ const imagesInSecondRow =
+ imageCount > imagesPerRow ? imageCount - imagesPerRow : 0;
+
+ // 计算图片的总高度(考虑行数)
+ const rowCount = imagesInSecondRow > 0 ? 2 : 1;
+
+ const totalImageHeight =
+ rowCount * imageHeight + (rowCount - 1) * rowSpacing;
+ // 垂直居中:框顶部 + 内边距 + (框高度 - 上下内边距 - 图片总高度) / 2
+ const startImageY =
+ boxY + padding + (boxHeight - padding * 2 - totalImageHeight) / 2;
+
+ // 计算每行的起始X位置(用于居中)
+ const firstRowImageCount = imagesInFirstRow;
+ const firstRowWidth =
+ firstRowImageCount * imageWidth +
+ (firstRowImageCount - 1) * imageSpacing;
+ const firstRowStartX =
+ rightBoxX +
+ padding +
+ (rightBoxWidth - padding * 2 - firstRowWidth) / 2;
+
+ const secondRowImageCount = imagesInSecondRow;
+ const secondRowWidth =
+ secondRowImageCount * imageWidth +
+ (secondRowImageCount - 1) * imageSpacing;
+ const secondRowStartX =
+ rightBoxX +
+ padding +
+ (rightBoxWidth - padding * 2 - secondRowWidth) / 2;
+
+ // 绘制图片(同一个框内使用同一张图片)
+ for (let imgIndex = 0; imgIndex < imageCount; imgIndex++) {
+ const row = Math.floor(imgIndex / imagesPerRow);
+ const col = imgIndex % imagesPerRow;
+
+ // 计算图片的X位置(根据行数选择不同的起始位置)
+ const rowStartX = row === 0 ? firstRowStartX : secondRowStartX;
+ const imageX = rowStartX + col * (imageWidth + imageSpacing);
+
+ // 计算图片的Y位置
+ const imageY = startImageY + row * (imageHeight + rowSpacing);
+
+ if (boxImage) {
+ // 计算图片高度(等比例缩放)
+ // @ts-ignore - 微信小程序图片对象有 width 和 height 属性
+ const scaledHeight =
+ (boxImage.height / boxImage.width) * imageWidth;
+
+ ctx.drawImage(
+ boxImage,
+ imageX,
+ imageY,
+ imageWidth,
+ scaledHeight,
+ );
+ } else {
+ // 如果图片加载失败,绘制一个小圆点作为备用
+ ctx.fillStyle = '#ccc';
+ ctx.beginPath();
+ ctx.arc(
+ imageX + imageWidth / 2,
+ imageY + imageHeight / 2,
+ imageWidth / 4,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+ }
+ }
+
+ // 绘制右侧框左侧的连线圆点(直径14,半径7)
+ const rightCircleX = rightBoxX - linePointSpacing - linePointRadius; // 间距15 + 半径linePointRadius
+ const rightCircleY = boxY + boxHeight / 2;
+ ctx.fillStyle = '#93D333';
+ ctx.beginPath();
+ ctx.arc(rightCircleX, rightCircleY, linePointRadius, 0, Math.PI * 2);
+ ctx.fill();
+ }
+}
diff --git a/miniprogram/mathPages/service/countMatchDraw.ts b/miniprogram/mathPages/service/countMatchDraw.ts
new file mode 100644
index 0000000..6089c46
--- /dev/null
+++ b/miniprogram/mathPages/service/countMatchDraw.ts
@@ -0,0 +1,56 @@
+import { BaseMathDrawService } from './baseMathDraw';
+import { drawCountMatchContent } from './countMatchContentDraw';
+
+/**
+ * 数一数连一连绘制服务
+ * 组合使用基础绘制服务和内容区域绘制服务
+ */
+class CountMatchDraw extends BaseMathDrawService {
+ matchData: {
+ leftNumbers: number[];
+ rightNumbers: number[]; // 打乱顺序后的数字数组
+ } | null;
+
+ constructor(
+ canvas: Canvas,
+ ctx: RenderingContext,
+ options?: Record,
+ ) {
+ super(canvas, ctx, options);
+ this.matchData = null;
+ }
+
+ async draw(
+ matchData: { leftNumbers: number[]; rightNumbers: number[] },
+ imageType: string = 'twelve-animals',
+ ) {
+ if (!matchData || !matchData.leftNumbers || !matchData.rightNumbers) {
+ return;
+ }
+
+ this.setPrintConfig();
+ this.matchData = matchData;
+ this.clear();
+ this.setPaper();
+
+ // 绘制Header
+ if (this.headerType !== 'minimal') {
+ await this.drawHeader();
+ } else {
+ this.drawMiniHeader();
+ }
+
+ // 绘制内容区域(数一数连一连)
+ this.drawDivider();
+ await drawCountMatchContent({
+ canvas: this.canvas,
+ ctx: this.ctx,
+ matchData: this.matchData,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ imageType,
+ });
+ }
+}
+
+export default CountMatchDraw;
diff --git a/miniprogram/mathPages/service/mathHeaderDraw.ts b/miniprogram/mathPages/service/mathHeaderDraw.ts
index 9d4c5bd..9a0b900 100644
--- a/miniprogram/mathPages/service/mathHeaderDraw.ts
+++ b/miniprogram/mathPages/service/mathHeaderDraw.ts
@@ -29,7 +29,7 @@ export async function drawMathHeader({
const { appName, appHint, title, subTitle } = options;
let titleX = 108; // 约109.33
- const titleY = 26; // 约26.67
+ const titleY = 25; // 约26.67
const logoX = 24; // 约26.67
const logoY = 20; // 20
@@ -85,7 +85,7 @@ export async function drawMathHeader({
// 调用回调函数
if (onHeaderDrawn) {
- onHeaderDrawn(100);
+ onHeaderDrawn(110);
}
}
@@ -124,6 +124,6 @@ export function drawMathMiniHeader({
// 调用回调函数,传递除以3后的currentY
if (onHeaderDrawn) {
- onHeaderDrawn(64); // 约66.67
+ onHeaderDrawn(66); // 约66.67
}
}
diff --git a/miniprogram/mathPages/service/numberColorContentDraw.ts b/miniprogram/mathPages/service/numberColorContentDraw.ts
new file mode 100644
index 0000000..4d2cf14
--- /dev/null
+++ b/miniprogram/mathPages/service/numberColorContentDraw.ts
@@ -0,0 +1,476 @@
+/**
+ * 数字颜色映射(使用基础12色)
+ * 由于只有10个数字,使用全部12种颜色中的11种(跳过黑色,因为黑色不适合作为数字颜色)
+ */
+const NUMBER_COLORS: Record = {
+ 1: '#ED6D50', // 柔和红色
+ 2: '#F9A857', // 柔和橙色
+ 3: '#FFE176', // 柔和黄色
+ 4: '#B2D94B', // 柔和黄绿色
+ 5: '#53D1B6', // 柔和绿色
+ 6: '#6CD2EA', // 柔和青色
+ 7: '#79A7ED', // 柔和蓝色
+ 8: '#9C98DE', // 柔和紫色
+ 9: '#F2A4C0', // 柔和粉色
+ 10: '#FC9B6C', // 柔和橙红色
+ 11: '#BC8F71', // 柔和棕色
+ 12: '#666666', // 柔和黑灰
+};
+
+interface DrawNumberColorContentParams {
+ canvas: WechatMiniprogram.Canvas;
+ ctx: RenderingContext;
+ colorData: {
+ numbers: number[];
+ };
+ canvasWidth: number;
+ startY: number;
+ patternType?: string; // 'circle' 或 'caterpillar'
+}
+
+/**
+ * 绘制圆角矩形(虚线边框)
+ */
+function drawRoundedRect(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ radius: number,
+ isDashed: boolean = true,
+) {
+ ctx.beginPath();
+ ctx.moveTo(x + radius, y);
+ ctx.lineTo(x + width - radius, y);
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+ ctx.lineTo(x + width, y + height - radius);
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+ ctx.lineTo(x + radius, y + height);
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+ ctx.lineTo(x, y + radius);
+ ctx.quadraticCurveTo(x, y, x + radius, y);
+ ctx.closePath();
+
+ if (isDashed) {
+ ctx.setLineDash([6, 6]); // 虚线
+ } else {
+ ctx.setLineDash([]);
+ }
+ ctx.stroke();
+ ctx.setLineDash([]); // 重置为实线
+}
+
+/**
+ * 绘制毛毛虫头部
+ */
+function drawCaterpillarHead(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ size: number,
+ color: string,
+) {
+ const headRadius = 18; // 固定头部半径
+ const headCenterX = x + headRadius;
+ const headCenterY = y + size / 2;
+
+ // 绘制头部(圆形)
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(headCenterX, headCenterY, headRadius, 0, Math.PI * 2);
+ ctx.fill();
+
+ // 绘制头部圆圈实线边框
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1;
+ ctx.setLineDash([]); // 确保是实线
+ ctx.beginPath();
+ ctx.arc(headCenterX, headCenterY, headRadius, 0, Math.PI * 2);
+ ctx.stroke();
+
+ // 绘制触角(在头部圆圈的正上方,带弧度的弯曲)
+ const antennaLength = headRadius * 0.6;
+ const antennaStartY = headCenterY - headRadius; // 头部圆圈的正上方
+ const antennaOffset = headRadius * 0.3; // 触角左右偏移距离
+
+ // 左侧触角:从顶部左侧开始,先向上,然后向左弯曲(带弧度)
+ const leftAntennaStartX = headCenterX - antennaOffset;
+ const leftAntennaStartY = antennaStartY;
+ const leftAntennaMidY = leftAntennaStartY - antennaLength * 0.5; // 向上延伸的中点
+ const leftAntennaEndX = leftAntennaStartX - antennaLength * 0.4; // 向左弯曲
+ const leftAntennaEndY = leftAntennaMidY - antennaLength * 0.3; // 向上并向左
+
+ // 右侧触角:从顶部右侧开始,先向上,然后向右弯曲(带弧度)
+ const rightAntennaStartX = headCenterX + antennaOffset;
+ const rightAntennaStartY = antennaStartY;
+ const rightAntennaMidY = rightAntennaStartY - antennaLength * 0.5; // 向上延伸的中点
+ const rightAntennaEndX = rightAntennaStartX + antennaLength * 0.4; // 向右弯曲
+ const rightAntennaEndY = rightAntennaMidY - antennaLength * 0.3; // 向上并向右
+
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1.5;
+ ctx.lineCap = 'round'; // 圆角端点
+
+ // 绘制左侧触角(使用二次贝塞尔曲线实现平滑弧度)
+ ctx.beginPath();
+ ctx.moveTo(leftAntennaStartX, leftAntennaStartY);
+ // 使用二次贝塞尔曲线:起点、控制点、终点
+ // 控制点在中间位置,使曲线平滑向左弯曲
+ const leftControlX = leftAntennaStartX - antennaLength * 0.2;
+ const leftControlY = leftAntennaMidY;
+ ctx.quadraticCurveTo(
+ leftControlX,
+ leftControlY,
+ leftAntennaEndX,
+ leftAntennaEndY,
+ );
+ ctx.stroke();
+
+ // 绘制右侧触角(使用二次贝塞尔曲线实现平滑弧度)
+ ctx.beginPath();
+ ctx.moveTo(rightAntennaStartX, rightAntennaStartY);
+ // 使用二次贝塞尔曲线:起点、控制点、终点
+ // 控制点在中间位置,使曲线平滑向右弯曲
+ const rightControlX = rightAntennaStartX + antennaLength * 0.2;
+ const rightControlY = rightAntennaMidY;
+ ctx.quadraticCurveTo(
+ rightControlX,
+ rightControlY,
+ rightAntennaEndX,
+ rightAntennaEndY,
+ );
+ ctx.stroke();
+
+ // 绘制触角末端的小圆点
+ ctx.fillStyle = '#000';
+ ctx.beginPath();
+ ctx.arc(leftAntennaEndX, leftAntennaEndY, 2, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(rightAntennaEndX, rightAntennaEndY, 2, 0, Math.PI * 2);
+ ctx.fill();
+
+ // 绘制眼睛
+ const eyeSize = headRadius * 0.15;
+ const eyeOffsetX = headRadius * 0.25;
+ const eyeOffsetY = headRadius * 0.22;
+ ctx.fillStyle = '#fff';
+ ctx.beginPath();
+ ctx.arc(
+ headCenterX - eyeOffsetX - 2,
+ headCenterY - eyeOffsetY,
+ eyeSize,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(
+ headCenterX + eyeOffsetX - 2,
+ headCenterY - eyeOffsetY,
+ eyeSize,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+
+ // 绘制眼珠
+ ctx.fillStyle = '#000';
+ ctx.beginPath();
+ ctx.arc(
+ headCenterX - eyeOffsetX - 2,
+ headCenterY - eyeOffsetY,
+ eyeSize * 0.6,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+ ctx.beginPath();
+ ctx.arc(
+ headCenterX + eyeOffsetX - 2,
+ headCenterY - eyeOffsetY,
+ eyeSize * 0.6,
+ 0,
+ Math.PI * 2,
+ );
+ ctx.fill();
+
+ // 绘制嘴巴(微笑)
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1.5;
+ ctx.beginPath();
+ ctx.arc(
+ headCenterX - 2,
+ headCenterY + headRadius * 0.22,
+ headRadius * 0.3,
+ 0,
+ Math.PI,
+ );
+ ctx.stroke();
+}
+
+/**
+ * 绘制毛毛虫身体(带弧度的圆圈)
+ */
+function drawCaterpillarBody(
+ ctx: RenderingContext,
+ startX: number,
+ startY: number,
+ count: number,
+ bodyRadius: number,
+ color: string,
+) {
+ const spacing = bodyRadius * 2; // 身体圆圈之间的间距(稍微重叠)
+ const waveAmplitude = bodyRadius * 0.4; // 波浪幅度
+
+ for (let i = 0; i < count; i++) {
+ // 计算每个身体圆圈的位置(带弧度,形成弯曲效果)
+ const baseX = startX + i * spacing;
+ // 使用正弦函数创建波浪效果,使身体有弧度
+ const waveOffset = Math.sin((i * Math.PI) / 2.5) * waveAmplitude;
+ const bodyX = baseX;
+ const bodyY = startY + waveOffset;
+
+ // 绘制身体圆圈
+ ctx.fillStyle = color;
+ ctx.beginPath();
+ ctx.arc(bodyX, bodyY, bodyRadius, 0, Math.PI * 2);
+ ctx.fill();
+
+ // 绘制边框(可选,根据设计需求)
+ ctx.strokeStyle = '#000';
+ ctx.lineWidth = 1;
+ ctx.stroke();
+ }
+}
+
+/**
+ * 绘制圆圈模式的内容
+ */
+function drawCirclePattern(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ number: number,
+ numberColor: string,
+ isFirstRow: boolean,
+) {
+ const totalCircles = 10;
+ const circleRadius = 16; // 固定半径
+ const circleSpacing = 6; // 固定间距
+
+ // 计算所有圆圈的总宽度
+ const totalWidth =
+ totalCircles * circleRadius * 2 + (totalCircles - 1) * circleSpacing;
+
+ // 计算水平居中位置
+ const startX = x + (width - totalWidth) / 2 + circleRadius;
+
+ // 计算垂直居中位置
+ const centerY = y + height / 2;
+
+ // 绘制10个虚线圆圈(一行)
+ for (let i = 0; i < totalCircles; i++) {
+ const circleX = startX + i * (circleRadius * 2 + circleSpacing);
+
+ // 绘制虚线圆圈
+ ctx.strokeStyle = numberColor;
+ ctx.lineWidth = 1;
+ ctx.setLineDash([3, 3]);
+ ctx.beginPath();
+ ctx.arc(circleX, centerY, circleRadius, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+
+ // 第一行:根据数字数量涂色
+ if (isFirstRow && i < number) {
+ ctx.fillStyle = numberColor;
+ ctx.beginPath();
+ ctx.arc(circleX, centerY, circleRadius - 1, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+}
+
+/**
+ * 绘制毛毛虫模式的内容
+ */
+function drawCaterpillarPattern(
+ ctx: RenderingContext,
+ x: number,
+ y: number,
+ width: number,
+ height: number,
+ number: number,
+ numberColor: string,
+ isFirstRow: boolean,
+) {
+ const padding = 15;
+ const headSize = 45; // 头部区域高度(用于垂直居中)
+ const headRadius = 18; // 固定头部半径
+ const bodyRadius = 16; // 固定身体半径
+
+ if (isFirstRow) {
+ // 第一行:绘制完整的毛毛虫(头部+身体)
+ const headX = x + padding;
+ const headY = y + (height - headSize) / 2;
+
+ // 绘制头部
+ drawCaterpillarHead(ctx, headX, headY, headSize, numberColor);
+
+ // 计算头部的实际宽度(头部半径 * 2)
+ const headWidth = headRadius * 2; // 头部的实际宽度 = 36
+
+ // 绘制身体(从头部右侧边缘开始,留一点间距)
+ const bodyStartX = headX + headWidth + 17; // 头部右边缘 + 5px间距
+ const bodyStartY = headY + headSize / 2; // 与头部中心Y对齐
+ drawCaterpillarBody(
+ ctx,
+ bodyStartX,
+ bodyStartY,
+ number,
+ bodyRadius,
+ numberColor,
+ );
+ } else {
+ // 其他行:只绘制头部
+ const headX = x + padding;
+ const headY = y + (height - headSize) / 2;
+ drawCaterpillarHead(ctx, headX, headY, headSize, numberColor);
+ }
+}
+
+/**
+ * 绘制按数字涂颜色内容区域
+ */
+export async function drawNumberColorContent({
+ canvas,
+ ctx,
+ colorData,
+ canvasWidth,
+ startY,
+ patternType = 'circle',
+}: DrawNumberColorContentParams): Promise {
+ const { numbers } = colorData;
+
+ const leftMargin = 24;
+ const rightMargin = 24;
+ const itemSpacing = 115; // 行间距
+ const leftBoxWidth = 100;
+ const rightBoxWidth = 420;
+ const boxHeight = 80;
+ const borderRadius = 12;
+ const startYPos = startY + 20;
+
+ // 获取所有可用颜色值
+ const availableColors = Object.values(NUMBER_COLORS);
+
+ // 为每一行随机分配颜色,确保不重复
+ const shuffledColors = [...availableColors];
+ // Fisher-Yates 洗牌算法
+ for (let i = shuffledColors.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [shuffledColors[i], shuffledColors[j]] = [
+ shuffledColors[j],
+ shuffledColors[i],
+ ];
+ }
+
+ // 为每一行分配颜色(确保不重复)
+ // 如果行数超过颜色数量,从剩余颜色中随机选择,但确保相邻行不同
+ const rowColors: string[] = [];
+
+ for (let i = 0; i < numbers.length; i++) {
+ if (i < shuffledColors.length) {
+ // 前 N 行(N <= 颜色数量)使用不同的颜色
+ rowColors.push(shuffledColors[i]);
+ } else {
+ // 如果行数超过颜色数量,从剩余颜色中选择(确保与上一行不同)
+ const remainingColors = shuffledColors.filter(
+ (color) => color !== rowColors[i - 1],
+ );
+ const randomColor =
+ remainingColors[
+ Math.floor(Math.random() * remainingColors.length)
+ ];
+ rowColors.push(randomColor);
+ }
+ }
+
+ // 计算左侧和右侧的起始X位置
+ const leftBoxX = leftMargin;
+ const rightBoxX = canvasWidth - rightMargin - rightBoxWidth;
+
+ // 绘制每一行
+ for (let i = 0; i < numbers.length; i++) {
+ const boxY = startYPos + i * itemSpacing;
+ const number = numbers[i];
+ const numberColor = rowColors[i]; // 使用随机分配的颜色
+ const isFirstRow = i === 0;
+
+ // 绘制左侧数字框(虚线)
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ leftBoxX,
+ boxY,
+ leftBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ // 绘制数字(带颜色)
+ ctx.fillStyle = numberColor;
+ ctx.font = `bold ${56}px "Microsoft Yahei"`;
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(
+ String(number),
+ leftBoxX + leftBoxWidth / 2,
+ boxY + boxHeight / 2,
+ );
+
+ // 绘制右侧内容框(虚线)
+ ctx.strokeStyle = '#999';
+ ctx.lineWidth = 1;
+ drawRoundedRect(
+ ctx,
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ boxHeight,
+ borderRadius,
+ true,
+ );
+
+ // 根据模式绘制右侧内容
+ if (patternType === 'circle') {
+ drawCirclePattern(
+ ctx,
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ boxHeight,
+ number,
+ numberColor,
+ isFirstRow,
+ );
+ } else if (patternType === 'caterpillar') {
+ drawCaterpillarPattern(
+ ctx,
+ rightBoxX,
+ boxY,
+ rightBoxWidth,
+ boxHeight,
+ number,
+ numberColor,
+ isFirstRow,
+ );
+ }
+ }
+}
diff --git a/miniprogram/mathPages/service/numberColorDraw.ts b/miniprogram/mathPages/service/numberColorDraw.ts
index 58b41be..b8af2cc 100644
--- a/miniprogram/mathPages/service/numberColorDraw.ts
+++ b/miniprogram/mathPages/service/numberColorDraw.ts
@@ -1,15 +1,14 @@
import { BaseMathDrawService } from './baseMathDraw';
-import { drawNumberPreview } from './numberPreviewDraw';
-import { drawNumberContent } from './numberContentDraw';
-import { drawNumberWriteContent } from './numberWriteDraw';
+import { drawNumberColorContent } from './numberColorContentDraw';
/**
- * 数字涂色绘制服务
- * 组合使用基础绘制服务、预览区域绘制服务和内容区域绘制服务
+ * 按数字涂颜色绘制服务
+ * 组合使用基础绘制服务和内容区域绘制服务
*/
class NumberColorDraw extends BaseMathDrawService {
- selectedNumber: number;
- functionId: string; // 功能ID,用于判断绘制类型
+ colorData: {
+ numbers: number[];
+ } | null;
constructor(
canvas: Canvas,
@@ -17,17 +16,19 @@ class NumberColorDraw extends BaseMathDrawService {
options?: Record,
) {
super(canvas, ctx, options);
- this.selectedNumber = 0;
- this.functionId = options?.functionId || 'number-find';
+ this.colorData = null;
}
- async draw(selectedNumber: number) {
- if (selectedNumber <= 0 || selectedNumber > 10) {
+ async draw(
+ colorData: { numbers: number[] },
+ patternType: string = 'circle', // 'circle' 或 'caterpillar'
+ ) {
+ if (!colorData || !colorData.numbers) {
return;
}
this.setPrintConfig();
- this.selectedNumber = selectedNumber;
+ this.colorData = colorData;
this.clear();
this.setPaper();
@@ -38,38 +39,16 @@ class NumberColorDraw extends BaseMathDrawService {
this.drawMiniHeader();
}
- // 绘制预览区域
+ // 绘制内容区域(按数字涂颜色)
this.drawDivider();
- await drawNumberPreview({
+ await drawNumberColorContent({
canvas: this.canvas,
ctx: this.ctx,
- selectedNumber: this.selectedNumber,
+ colorData: this.colorData,
canvasWidth: this.canvasWidth,
startY: this.currentY,
- onDrawn: (currentY) => {
- this.currentY = currentY;
- },
+ patternType,
});
-
- // 绘制内容区域(根据 functionId 选择不同的绘制方法)
- this.drawDivider();
- if (this.functionId === 'number-write') {
- // 书写类型:绘制书写行
- drawNumberWriteContent({
- ctx: this.ctx,
- selectedNumber: this.selectedNumber,
- canvasWidth: this.canvasWidth,
- startY: this.currentY,
- });
- } else {
- // 默认类型(number-find):绘制数字涂色内容
- drawNumberContent({
- ctx: this.ctx,
- selectedNumber: this.selectedNumber,
- canvasWidth: this.canvasWidth,
- startY: this.currentY,
- });
- }
}
}
diff --git a/miniprogram/mathPages/service/numberFindDraw.ts b/miniprogram/mathPages/service/numberFindDraw.ts
new file mode 100644
index 0000000..0d9cd0c
--- /dev/null
+++ b/miniprogram/mathPages/service/numberFindDraw.ts
@@ -0,0 +1,76 @@
+import { BaseMathDrawService } from './baseMathDraw';
+import { drawNumberPreview } from './numberPreviewDraw';
+import { drawNumberContent } from './numberContentDraw';
+import { drawNumberWriteContent } from './numberWriteDraw';
+
+/**
+ * 数字涂色绘制服务
+ * 组合使用基础绘制服务、预览区域绘制服务和内容区域绘制服务
+ */
+class NumberFindDraw extends BaseMathDrawService {
+ selectedNumber: number;
+ functionId: string; // 功能ID,用于判断绘制类型
+
+ constructor(
+ canvas: Canvas,
+ ctx: RenderingContext,
+ options?: Record,
+ ) {
+ super(canvas, ctx, options);
+ this.selectedNumber = 0;
+ this.functionId = options?.functionId || 'number-find';
+ }
+
+ async draw(selectedNumber: number) {
+ if (selectedNumber <= 0 || selectedNumber > 10) {
+ return;
+ }
+
+ this.setPrintConfig();
+ this.selectedNumber = selectedNumber;
+ this.clear();
+ this.setPaper();
+
+ // 绘制Header
+ if (this.headerType !== 'minimal') {
+ await this.drawHeader();
+ } else {
+ this.drawMiniHeader();
+ }
+
+ // 绘制预览区域
+ this.drawDivider();
+ await drawNumberPreview({
+ canvas: this.canvas,
+ ctx: this.ctx,
+ selectedNumber: this.selectedNumber,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ onDrawn: (currentY) => {
+ this.currentY = currentY;
+ },
+ });
+
+ // 绘制内容区域(根据 functionId 选择不同的绘制方法)
+ this.drawDivider();
+ if (this.functionId === 'number-write') {
+ // 书写类型:绘制书写行
+ drawNumberWriteContent({
+ ctx: this.ctx,
+ selectedNumber: this.selectedNumber,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ });
+ } else {
+ // 默认类型(number-find):绘制数字涂色内容
+ drawNumberContent({
+ ctx: this.ctx,
+ selectedNumber: this.selectedNumber,
+ canvasWidth: this.canvasWidth,
+ startY: this.currentY,
+ });
+ }
+ }
+}
+
+export default NumberFindDraw;
diff --git a/project.private.config.json b/project.private.config.json
index ca0e6d8..c6f27d4 100644
--- a/project.private.config.json
+++ b/project.private.config.json
@@ -24,9 +24,9 @@
"miniprogram": {
"list": [
{
- "name": "mathPages/numberColor/numberColor",
- "pathName": "mathPages/numberColor/numberColor",
- "query": "id=number-write",
+ "name": "mathPages/countMatch/countMatch",
+ "pathName": "mathPages/countMatch/countMatch",
+ "query": "id=number-coloring",
"scene": null,
"launchMode": "default"
},
@@ -37,6 +37,27 @@
"launchMode": "default",
"scene": null
},
+ {
+ "name": "mathPages/countMatch/countMatch",
+ "pathName": "mathPages/countMatch/countMatch",
+ "query": "id=counting-matching",
+ "launchMode": "default",
+ "scene": null
+ },
+ {
+ "name": "mathPages/numberFind/numberFind",
+ "pathName": "mathPages/numberFind/numberFind",
+ "query": "id=number-write",
+ "launchMode": "default",
+ "scene": null
+ },
+ {
+ "name": "pages/mathIndex/mathIndex",
+ "pathName": "pages/mathIndex/mathIndex",
+ "query": "",
+ "launchMode": "default",
+ "scene": null
+ },
{
"name": "pages/copyBook/copyBook",
"pathName": "pages/copyBook/copyBook",