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",