diff --git a/miniprogram/app.json b/miniprogram/app.json index a7888d3..5d35ad5 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -14,7 +14,8 @@ "numberFind/numberFind", "countMatch/countMatch", "addition/addition", - "missingNumber/missingNumber" + "missingNumber/missingNumber", + "compare/compare" ], "independent": false } diff --git a/miniprogram/constants/mathFunctions.ts b/miniprogram/constants/mathFunctions.ts index bb7e5f0..11ecbdb 100644 --- a/miniprogram/constants/mathFunctions.ts +++ b/miniprogram/constants/mathFunctions.ts @@ -72,8 +72,9 @@ export const MATH_FUNCTION_TYPES: MathFunctionType[] = [ }, { id: 'compare', + page: 'compare', title: '数一数,比大小', - desc: '数一数,比较数量,在⭕️中填入>、<、=', + desc: '比较数量大小,在 ⭕️ 中填入>、<、=', icon: '⚖️', }, { diff --git a/miniprogram/mathPages/compare/compare.json b/miniprogram/mathPages/compare/compare.json new file mode 100644 index 0000000..90335ca --- /dev/null +++ b/miniprogram/mathPages/compare/compare.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", + "math-bottom-buttons": "../components/math-bottom-buttons/math-bottom-buttons" + } +} diff --git a/miniprogram/mathPages/compare/compare.less b/miniprogram/mathPages/compare/compare.less new file mode 100644 index 0000000..12602d2 --- /dev/null +++ b/miniprogram/mathPages/compare/compare.less @@ -0,0 +1 @@ +@import '../common/mathPage.less'; \ No newline at end of file diff --git a/miniprogram/mathPages/compare/compare.ts b/miniprogram/mathPages/compare/compare.ts new file mode 100644 index 0000000..293327f --- /dev/null +++ b/miniprogram/mathPages/compare/compare.ts @@ -0,0 +1,132 @@ +import CompareDraw from '../service/compareDraw'; +import { + getMathPageCommonMethods, + CanvasDataState, +} from '../common/mathPageMixin'; + +// 获取公共方法 +const commonMethods = getMathPageCommonMethods({ + pagePath: 'compare/compare', +}); + +Page({ + canvas: null as Canvas | null, + ctx: null as RenderingContext | null, + boxHeight: 0, + boxWidth: 0, + drawService: null as CompareDraw | null, + compareData: null as { + problems: Array<{ + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }>; + } | null, + + data: { + pageTitle: '数一数,比大小', + subTitle: '数一数,比较数量,在⭕️中填入>、<、=', + functionId: '', + hasContent: false, + showShareDialog: false, + } as CanvasDataState, + + onLoad(options: { id?: string }) { + const functionId = options.id || 'compare'; + this.initPageInfo(functionId, '数一数,比大小'); + }, + + onReady() { + this.initCanvas({ + createDrawService: (canvas, ctx, options) => { + return new CompareDraw(canvas, ctx, options); + }, + drawServiceOptions: { + subTitle: this.data.subTitle, + }, + onCanvasReady: () => { + // Canvas 初始化完成后,生成初始数据 + this.onRandom(); + }, + }); + }, + + /** + * 绘制Canvas内容 + */ + async drawCanvas() { + if (!this.ctx || !this.drawService || !this.compareData) { + return; + } + + try { + await this.drawService.draw(this.compareData); + this.setData({ hasContent: true }); + } catch (error) { + console.error('绘制失败:', error); + this.setData({ hasContent: false }); + } + }, + + /** + * 随机生成 + */ + onRandom() { + this.generateCompareData(); + this.drawCanvas(); + }, + + /** + * 生成比较数据 + */ + generateCompareData() { + const problems: Array<{ + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }> = []; + + // 生成6道题目 + for (let i = 0; i < 12; i++) { + // 随机选择图片类型 + const imageType: 'fruits' | 'twelve-animals' = + Math.random() < 0.5 ? 'fruits' : 'twelve-animals'; + + // 根据图片类型确定最大索引 + const maxImageIndex = imageType === 'fruits' ? 22 : 12; + + // 生成左右两边的数量(1-10) + const leftCount = Math.floor(Math.random() * 10) + 1; + const rightCount = Math.floor(Math.random() * 10) + 1; + + // 随机选择图片索引 + const leftImageIndex = + Math.floor(Math.random() * maxImageIndex) + 1; + const rightImageIndex = + Math.floor(Math.random() * maxImageIndex) + 1; + + problems.push({ + leftCount, + rightCount, + leftImageIndex, + rightImageIndex, + imageType, + }); + } + + this.compareData = { problems }; + }, + + // ========== 使用公共方法 ========== + initCanvas: commonMethods.initCanvas, + exportToPrint: commonMethods.exportToPrint, + onShareAppMessage: commonMethods.onShareAppMessage, + onShareTimeline: commonMethods.onShareTimeline, + onCloseShareDialog: commonMethods.onCloseShareDialog, + onShareSuccess: commonMethods.onShareSuccess, + initPageInfo: commonMethods.initPageInfo, +}); diff --git a/miniprogram/mathPages/compare/compare.wxml b/miniprogram/mathPages/compare/compare.wxml new file mode 100644 index 0000000..5d3cbdb --- /dev/null +++ b/miniprogram/mathPages/compare/compare.wxml @@ -0,0 +1,37 @@ + + + 预览打印效果 + + + + + + + + + + 随机生成 + + + + + + + diff --git a/miniprogram/mathPages/service/compareContentDraw.ts b/miniprogram/mathPages/service/compareContentDraw.ts new file mode 100644 index 0000000..d744b25 --- /dev/null +++ b/miniprogram/mathPages/service/compareContentDraw.ts @@ -0,0 +1,330 @@ +import { getImage } from '../../utils/index'; + +interface DrawCompareContentParams { + canvas: WechatMiniprogram.Canvas; + ctx: RenderingContext; + compareData: { + problems: Array<{ + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }>; + }; + canvasWidth: number; + startY: number; +} + +/** + * 绘制圆角矩形(虚线边框) + */ +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([]); // 重置为实线 +} + +/** + * 绘制数一数比大小内容区域 + * 两列布局,每列左右两个虚线框,中间圆圈 + */ +export async function drawCompareContent({ + canvas, + ctx, + compareData, + canvasWidth, + startY, +}: DrawCompareContentParams): Promise { + const { problems } = compareData; + + // 布局参数 + const leftMargin = 30; + const rightMargin = 30; + const topMargin = 20; + const rowSpacing = 114; // 行之间的间距 + const columnSpacing = 40; // 两列之间的间距(可调节) + + // 计算每列的宽度(减去边距和列间距) + const availableWidth = + canvasWidth - leftMargin - rightMargin - columnSpacing; + const columnWidth = availableWidth / 2; + + // 框的尺寸 + const boxWidth = 100; + const boxHeight = 98; + const borderRadius = 12; + const circleRadius = 18; // 圆圈半径 + + // 框与圆圈之间的间距 + const boxToCircleSpacing = 12; + + // 计算左侧列和右侧列的起始X位置 + const leftColumnStartX = leftMargin; + const rightColumnStartX = leftMargin + columnWidth + columnSpacing; + + // 计算每列内部的布局(左框 | 圆圈 | 右框,居中) + const totalWidthPerColumn = + boxWidth + + boxToCircleSpacing + + circleRadius * 2 + + boxToCircleSpacing + + boxWidth; + const columnPadding = (columnWidth - totalWidthPerColumn) / 2; + + let currentY = startY + topMargin; + + // 固定绘制6行 + const totalRows = 6; + for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) { + const rowY = currentY + rowIndex * rowSpacing; + + // 绘制左侧列 + const leftProblemIndex = rowIndex * 2; + if (leftProblemIndex < problems.length) { + await drawCompareProblem( + ctx, + canvas, + problems[leftProblemIndex], + leftColumnStartX + columnPadding, + rowY, + boxWidth, + boxHeight, + borderRadius, + circleRadius, + boxToCircleSpacing, + ); + } + + // 绘制右侧列 + const rightProblemIndex = rowIndex * 2 + 1; + if (rightProblemIndex < problems.length) { + await drawCompareProblem( + ctx, + canvas, + problems[rightProblemIndex], + rightColumnStartX + columnPadding, + rowY, + boxWidth, + boxHeight, + borderRadius, + circleRadius, + boxToCircleSpacing, + ); + } + } +} + +/** + * 绘制一个比较问题(左框 | 圆圈 | 右框) + */ +async function drawCompareProblem( + ctx: RenderingContext, + canvas: WechatMiniprogram.Canvas, + problem: { + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }, + startX: number, + startY: number, + boxWidth: number, + boxHeight: number, + borderRadius: number, + circleRadius: number, + boxToCircleSpacing: number, +) { + const { + leftCount, + rightCount, + leftImageIndex, + rightImageIndex, + imageType, + } = problem; + + // 图片配置 + const imageConfig = { + 'twelve-animals': { + folder: 'twelve-animals', + maxIndex: 12, + }, + fruits: { + folder: 'fruits', + maxIndex: 22, + }, + }; + + const config = imageConfig[imageType] || imageConfig['twelve-animals']; + + // 计算左框位置 + const leftBoxX = startX; + const leftBoxY = startY; + + // 计算圆圈位置 + const circleX = leftBoxX + boxWidth + boxToCircleSpacing + circleRadius; + const circleY = startY + boxHeight / 2; + + // 计算右框位置 + const rightBoxX = circleX + circleRadius + boxToCircleSpacing; + const rightBoxY = startY; + + // 绘制左框 + ctx.strokeStyle = '#999'; + ctx.lineWidth = 1; + drawRoundedRect( + ctx, + leftBoxX, + leftBoxY, + boxWidth, + boxHeight, + borderRadius, + true, + ); + + // 绘制左框内的图片 + await drawImagesInBox( + ctx, + canvas, + leftCount, + leftImageIndex, + config.folder, + leftBoxX, + leftBoxY, + boxWidth, + boxHeight, + ); + + // 绘制实心圆圈 + ctx.fillStyle = '#fff'; + ctx.strokeStyle = '#333'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.arc(circleX, circleY, circleRadius, 0, Math.PI * 2); + ctx.fill(); // 先填充白色 + ctx.stroke(); // 再绘制边框 + + // 绘制右框 + ctx.strokeStyle = '#999'; + ctx.lineWidth = 1; + drawRoundedRect( + ctx, + rightBoxX, + rightBoxY, + boxWidth, + boxHeight, + borderRadius, + true, + ); + + // 绘制右框内的图片 + await drawImagesInBox( + ctx, + canvas, + rightCount, + rightImageIndex, + config.folder, + rightBoxX, + rightBoxY, + boxWidth, + boxHeight, + ); +} + +/** + * 在框内绘制多张图片 + */ +async function drawImagesInBox( + ctx: RenderingContext, + canvas: WechatMiniprogram.Canvas, + count: number, + imageIndex: number, + folder: string, + boxX: number, + boxY: number, + boxWidth: number, + boxHeight: number, +) { + const padding = 8; // 框内边距 + const availableWidth = boxWidth - padding * 2; + const availableHeight = boxHeight - padding * 2; + + // 根据数量确定每行的图片数和图片大小 + let imagesPerRow: number; + let imageSize: number; + + if (count <= 4) { + imagesPerRow = count <= 2 ? count : 2; + imageSize = + Math.min(availableWidth / imagesPerRow, availableHeight / 2) - 4; + } else if (count <= 6) { + imagesPerRow = 3; + imageSize = Math.min(availableWidth / 3, availableHeight / 2) - 4; + } else { + imagesPerRow = 3; + imageSize = Math.min(availableWidth / 3, availableHeight / 3) - 4; + } + + const rows = Math.ceil(count / imagesPerRow); + const imageSpacing = + (availableWidth - imageSize * imagesPerRow) / (imagesPerRow + 1); + const rowSpacing = + rows > 1 ? (availableHeight - imageSize * rows) / (rows + 1) : 0; + + // 加载图片 + let boxImage: any = null; + try { + const imagePath = `/mathPages/assets/${folder}/${imageIndex}.png`; + boxImage = await getImage(canvas, imagePath); + } catch (error) { + console.error(`加载${folder}/${imageIndex}图片失败:`, error); + return; + } + + // 绘制图片 + for (let i = 0; i < count; i++) { + const row = Math.floor(i / imagesPerRow); + const col = i % imagesPerRow; + + const imageX = + boxX + padding + imageSpacing + col * (imageSize + imageSpacing); + const imageY = + boxY + + padding + + (rows > 1 ? rowSpacing : (availableHeight - imageSize) / 2) + + row * (imageSize + (rows > 1 ? rowSpacing : 0)); + + if (boxImage) { + // 计算图片高度(等比例缩放) + // @ts-ignore - 微信小程序图片对象有 width 和 height 属性 + const scaledHeight = (boxImage.height / boxImage.width) * imageSize; + + ctx.drawImage(boxImage, imageX, imageY, imageSize, scaledHeight); + } + } +} diff --git a/miniprogram/mathPages/service/compareDraw.ts b/miniprogram/mathPages/service/compareDraw.ts new file mode 100644 index 0000000..db2e5ef --- /dev/null +++ b/miniprogram/mathPages/service/compareDraw.ts @@ -0,0 +1,65 @@ +import { BaseMathDrawService } from './baseMathDraw'; +import { drawCompareContent } from './compareContentDraw'; + +/** + * 数一数比大小绘制服务 + * 组合使用基础绘制服务和内容区域绘制服务 + */ +class CompareDraw extends BaseMathDrawService { + compareData: { + problems: Array<{ + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }>; + } | null; + + constructor( + canvas: Canvas, + ctx: RenderingContext, + options?: Record, + ) { + super(canvas, ctx, options); + this.compareData = null; + } + + async draw(compareData: { + problems: Array<{ + leftCount: number; + rightCount: number; + leftImageIndex: number; + rightImageIndex: number; + imageType: 'fruits' | 'twelve-animals'; + }>; + }) { + if (!compareData || !compareData.problems) { + return; + } + + this.setPrintConfig(); + this.compareData = compareData; + this.clear(); + this.setPaper(); + + // 绘制Header + if (this.headerType !== 'minimal') { + await this.drawHeader(); + } else { + this.drawMiniHeader(); + } + + // 绘制内容区域(数一数比大小) + this.drawDivider(); + await drawCompareContent({ + canvas: this.canvas, + ctx: this.ctx, + compareData: this.compareData, + canvasWidth: this.canvasWidth, + startY: this.currentY, + }); + } +} + +export default CompareDraw; diff --git a/project.private.config.json b/project.private.config.json index 5ae1791..9734f26 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -23,12 +23,19 @@ "condition": { "miniprogram": { "list": [ + { + "name": "mathPages/compare/compare", + "pathName": "mathPages/compare/compare", + "query": "id=compare", + "scene": null, + "launchMode": "default" + }, { "name": "mathPages/missingNumber/missingNumber", "pathName": "mathPages/missingNumber/missingNumber", "query": "id=missing-number", - "scene": null, - "launchMode": "default" + "launchMode": "default", + "scene": null }, { "name": "mathPages/addition/addition",