// 引入形状和颜色数据 import { shapes, colors } from './shapes'; Page({ data: { // 可选择的形状列表 availableShapes: shapes, // 可选择的颜色列表 availableColors: colors, // 已选择的形状列表 selectedShapes: [], // 当前选中的形状类型 currentShapeType: 'circle', // 当前选中的颜色 currentColor: '#FF4444', // 生成的图片 generatedImage: null, // 是否正在生成 isGenerating: false, // 背景选项 backgrounds: [ { name: '纯白', value: '#FFFFFF' }, { name: '米黄', value: '#FAF5E8' }, { name: '淡灰', value: '#F5F5F5' }, ], bgColor: '#FFFFFF', }, onLoad() { console.log('形状绘制页面加载完成'); }, // 选择形状类型 selectShape(e) { const shapeId = e.currentTarget.dataset.shapeId; this.setData({ currentShapeType: shapeId, }); }, // 选择颜色 selectColor(e) { const color = e.currentTarget.dataset.color; this.setData({ currentColor: color, }); }, // 添加形状到选中列表 addShape() { const { currentShapeType, currentColor, selectedShapes } = this.data; const shape = shapes.find((s) => s.id === currentShapeType); if (shape) { const newShape = { id: Date.now(), type: shape.type, name: shape.name, color: currentColor, shapeId: currentShapeType, drawFunction: shape.drawFunction, }; this.setData({ selectedShapes: [...selectedShapes, newShape], }); wx.showToast({ title: `添加了${shape.name}`, icon: 'success', duration: 1000, }); } }, // 删除选中的形状 removeShape(e) { const index = e.currentTarget.dataset.index; const selectedShapes = [...this.data.selectedShapes]; selectedShapes.splice(index, 1); this.setData({ selectedShapes, }); }, // 清空所有形状 clearShapes() { this.setData({ selectedShapes: [], }); }, // 设置背景颜色 setBackground(e) { this.setData({ bgColor: e.detail.value, }); }, // 创建形状的SVG预览 createShapePreview(shape) { const svgContent = ` ${shape.svg} `; const base64 = wx.arrayBufferToBase64( new TextEncoder().encode(svgContent), ); return `data:image/svg+xml;base64,${base64}`; }, // 绘制各种形状的函数 drawCircle(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, size * 0.4, 0, Math.PI * 2); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawSquare(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const squareSize = size * 0.8; const startX = x - squareSize / 2; const startY = y - squareSize / 2; if (filled) ctx.fillRect(startX, startY, squareSize, squareSize); ctx.strokeRect(startX, startY, squareSize, squareSize); ctx.restore(); }, drawRectangle(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const rectWidth = size * 0.9; const rectHeight = size * 0.6; const startX = x - rectWidth / 2; const startY = y - rectHeight / 2; if (filled) ctx.fillRect(startX, startY, rectWidth, rectHeight); ctx.strokeRect(startX, startY, rectWidth, rectHeight); ctx.restore(); }, drawTriangle(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const triangleSize = size * 0.8; ctx.beginPath(); ctx.moveTo(x, y - triangleSize / 2); ctx.lineTo(x - triangleSize / 2, y + triangleSize / 2); ctx.lineTo(x + triangleSize / 2, y + triangleSize / 2); ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawOval(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; ctx.beginPath(); ctx.ellipse(x, y, size * 0.45, size * 0.3, 0, 0, Math.PI * 2); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawDiamond(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const diamondSize = size * 0.4; ctx.beginPath(); ctx.moveTo(x, y - diamondSize); ctx.lineTo(x + diamondSize, y); ctx.lineTo(x, y + diamondSize); ctx.lineTo(x - diamondSize, y); ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawStar(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const outerRadius = size * 0.4; const innerRadius = size * 0.2; ctx.beginPath(); for (let i = 0; i < 10; i++) { const angle = (i * Math.PI) / 5; const radius = i % 2 === 0 ? outerRadius : innerRadius; const pointX = x + Math.cos(angle - Math.PI / 2) * radius; const pointY = y + Math.sin(angle - Math.PI / 2) * radius; if (i === 0) ctx.moveTo(pointX, pointY); else ctx.lineTo(pointX, pointY); } ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawHeart(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const heartSize = size * 0.4; ctx.beginPath(); ctx.moveTo(x, y + heartSize * 0.3); ctx.bezierCurveTo( x - heartSize * 0.5, y - heartSize * 0.3, x - heartSize, y + heartSize * 0.1, x, y + heartSize * 0.8, ); ctx.bezierCurveTo( x + heartSize, y + heartSize * 0.1, x + heartSize * 0.5, y - heartSize * 0.3, x, y + heartSize * 0.3, ); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawHexagon(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const radius = size * 0.4; ctx.beginPath(); for (let i = 0; i < 6; i++) { const angle = (i * Math.PI) / 3; const pointX = x + Math.cos(angle) * radius; const pointY = y + Math.sin(angle) * radius; if (i === 0) ctx.moveTo(pointX, pointY); else ctx.lineTo(pointX, pointY); } ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawPentagon(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const radius = size * 0.4; ctx.beginPath(); for (let i = 0; i < 5; i++) { const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2; const pointX = x + Math.cos(angle) * radius; const pointY = y + Math.sin(angle) * radius; if (i === 0) ctx.moveTo(pointX, pointY); else ctx.lineTo(pointX, pointY); } ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawSemicircle(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const radius = size * 0.4; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI); ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, drawTrapezoid(ctx, x, y, size, color, filled = true) { ctx.save(); if (filled) { ctx.fillStyle = color; } ctx.strokeStyle = '#333'; ctx.lineWidth = 3; const trapezoidSize = size * 0.4; ctx.beginPath(); ctx.moveTo(x - trapezoidSize * 0.6, y - trapezoidSize * 0.5); ctx.lineTo(x + trapezoidSize * 0.6, y - trapezoidSize * 0.5); ctx.lineTo(x + trapezoidSize, y + trapezoidSize * 0.5); ctx.lineTo(x - trapezoidSize, y + trapezoidSize * 0.5); ctx.closePath(); if (filled) ctx.fill(); ctx.stroke(); ctx.restore(); }, // 绘制形状的通用方法 drawShape(ctx, shape, x, y, size, filled = true) { const color = filled ? shape.color : 'transparent'; switch (shape.drawFunction) { case 'drawCircle': this.drawCircle(ctx, x, y, size, color, filled); break; case 'drawSquare': this.drawSquare(ctx, x, y, size, color, filled); break; case 'drawRectangle': this.drawRectangle(ctx, x, y, size, color, filled); break; case 'drawTriangle': this.drawTriangle(ctx, x, y, size, color, filled); break; case 'drawOval': this.drawOval(ctx, x, y, size, color, filled); break; case 'drawDiamond': this.drawDiamond(ctx, x, y, size, color, filled); break; case 'drawStar': this.drawStar(ctx, x, y, size, color, filled); break; case 'drawHeart': this.drawHeart(ctx, x, y, size, color, filled); break; case 'drawHexagon': this.drawHexagon(ctx, x, y, size, color, filled); break; case 'drawPentagon': this.drawPentagon(ctx, x, y, size, color, filled); break; case 'drawSemicircle': this.drawSemicircle(ctx, x, y, size, color, filled); break; case 'drawTrapezoid': this.drawTrapezoid(ctx, x, y, size, color, filled); break; } }, // 生成A4纸张大小的图片 async generateImage() { if (this.data.selectedShapes.length === 0) { wx.showToast({ title: '请先添加形状', icon: 'none', }); return; } this.setData({ isGenerating: true }); try { const query = wx.createSelectorQuery().in(this); query .select('#mainCanvas') .fields({ node: true, size: true }) .exec((res) => { if (!res[0] || !res[0].node) { this.setData({ isGenerating: false }); wx.showToast({ title: 'Canvas 获取失败', icon: 'none', }); return; } const canvas = res[0].node; const ctx = canvas.getContext('2d'); // A4纸张尺寸 (595x842 点,按照72DPI) const dpr = wx.getSystemInfoSync().pixelRatio; const a4Width = 595; const a4Height = 842; canvas.width = a4Width * dpr; canvas.height = a4Height * dpr; ctx.scale(dpr, dpr); // 清空画布并设置背景 ctx.clearRect(0, 0, a4Width, a4Height); ctx.fillStyle = this.data.bgColor; ctx.fillRect(0, 0, a4Width, a4Height); // 绘制标题 this.drawTitle(ctx, a4Width); // 绘制上半部分:带颜色的形状示例 this.drawUpperSection(ctx, a4Width, a4Height); // 绘制分割线 this.drawDividerLine(ctx, a4Width, a4Height * 0.45); // 绘制下半部分:无颜色的练习区域 this.drawLowerSection(ctx, a4Width, a4Height); // 导出图片 wx.canvasToTempFilePath({ canvas: canvas, success: (res) => { this.setData({ generatedImage: res.tempFilePath, isGenerating: false, }); wx.showToast({ title: '图片生成成功', icon: 'success', }); }, fail: (err) => { console.error('导出图片失败:', err); this.setData({ isGenerating: false }); wx.showToast({ title: '图片生成失败', icon: 'none', }); }, }); }); } catch (error) { console.error('生成图片出错:', error); this.setData({ isGenerating: false }); wx.showToast({ title: '生成出错,请重试', icon: 'none', }); } }, // 绘制标题 drawTitle(ctx, canvasWidth) { ctx.save(); ctx.fillStyle = '#333'; ctx.font = 'bold 32px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'top'; ctx.fillText('形状涂色练习', canvasWidth / 2, 30); ctx.font = '20px Arial'; ctx.fillText('给相同的形状涂上相同的颜色', canvasWidth / 2, 75); ctx.restore(); }, // 绘制上半部分:带颜色的形状示例 drawUpperSection(ctx, canvasWidth, canvasHeight) { const { selectedShapes } = this.data; const startY = 120; const sectionHeight = canvasHeight * 0.32; const shapeSize = 60; const cols = Math.min(selectedShapes.length, 6); const rows = Math.ceil(selectedShapes.length / cols); const cellWidth = (canvasWidth - 120) / cols; const cellHeight = sectionHeight / Math.max(rows, 1); selectedShapes.forEach((shape, index) => { const row = Math.floor(index / cols); const col = index % cols; const x = 60 + col * cellWidth + cellWidth / 2; const y = startY + row * cellHeight + cellHeight / 2; this.drawShape(ctx, shape, x, y, shapeSize, true); }); }, // 绘制分割线 drawDividerLine(ctx, canvasWidth, y) { ctx.save(); ctx.strokeStyle = '#333'; ctx.lineWidth = 2; ctx.setLineDash([10, 5]); ctx.beginPath(); ctx.moveTo(60, y); ctx.lineTo(canvasWidth - 60, y); ctx.stroke(); ctx.restore(); }, // 绘制下半部分:无颜色的练习区域 drawLowerSection(ctx, canvasWidth, canvasHeight) { const { selectedShapes } = this.data; const startY = canvasHeight * 0.5; const sectionHeight = canvasHeight * 0.45; const shapeSize = 50; // 计算网格布局 const cols = 8; const rows = 6; const cellWidth = (canvasWidth - 120) / cols; const cellHeight = sectionHeight / rows; // 生成随机形状排列 const practiceShapes = []; const totalShapes = cols * rows; for (let i = 0; i < totalShapes; i++) { const randomShape = selectedShapes[i % selectedShapes.length]; practiceShapes.push({ ...randomShape, color: 'transparent', // 练习区域不填充颜色 }); } // 打乱顺序 for (let i = practiceShapes.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [practiceShapes[i], practiceShapes[j]] = [ practiceShapes[j], practiceShapes[i], ]; } practiceShapes.forEach((shape, index) => { const row = Math.floor(index / cols); const col = index % cols; const x = 60 + col * cellWidth + cellWidth / 2; const y = startY + row * cellHeight + cellHeight / 2; this.drawShape(ctx, shape, x, y, shapeSize, false); }); }, // 保存到相册 saveToAlbum() { if (!this.data.generatedImage) { wx.showToast({ title: '请先生成图片', icon: 'none', }); return; } wx.saveImageToPhotosAlbum({ filePath: this.data.generatedImage, success: () => { wx.showToast({ title: '保存成功', icon: 'success', }); }, fail: (err) => { if (err.errMsg.includes('auth deny')) { this.showAuthGuide(); } else { wx.showToast({ title: '保存失败', icon: 'none', }); } }, }); }, // 显示授权引导 showAuthGuide() { wx.showModal({ title: '权限申请', content: '需要相册访问权限才能保存图片,请在设置中开启权限', success: (res) => { if (res.confirm) { wx.openSetting(); } }, }); }, // 预览生成的图片 previewImage() { if (!this.data.generatedImage) return; wx.previewImage({ current: this.data.generatedImage, urls: [this.data.generatedImage], }); }, });