// 引入形状和颜色数据 import { shapes, colors } from './shapes'; import { WATER_COLORS } from '../../constants/colors'; Page({ data: { // 可选择的形状列表 availableShapes: shapes, // 已选择的形状列表 selectedShapes: [], // 基础12色 basic12Colors: WATER_COLORS.basic12, // 已使用的颜色索引(确保不重复) usedColorIndexes: [], // 生成的图片 generatedImage: null, // 是否正在生成 isGenerating: false, // 背景选项 backgrounds: [ { name: '纯白', value: '#FFFFFF' }, { name: '米黄', value: '#FAF5E8' }, { name: '淡灰', value: '#F5F5F5' }, ], bgColor: '#FFFFFF', // 弹窗控制 showShapeSelector: false, showColorPicker: false, // 当前编辑的形状索引 currentEditingIndex: -1, currentEditingColor: '', // 临时选择的形状ID列表(弹窗中的选择状态) tempSelectedShapeIds: [], }, onLoad() { console.log('形状绘制页面加载完成'); // 为可选形状生成SVG内容 const shapesWithSVG = this.data.availableShapes.map((shape) => ({ ...shape, svgContent: this.createSVGElement(shape, 'transparent', '#333', 50), })); // 初始化临时选择列表 this.setData({ availableShapes: shapesWithSVG, tempSelectedShapeIds: this.data.selectedShapes.map( (shape) => shape.id, ), }); }, // 生成带颜色的SVG字符串 generateColoredSVG(shape, fillColor = '#666', borderColor = '#333') { if (!shape || !shape.svg) return ''; let svg = shape.svg; // 替换stroke属性 svg = svg.replace(/stroke="[^"]*"/g, `stroke="${borderColor}"`); // 处理fill属性 if (fillColor && fillColor !== 'transparent') { svg = svg.replace(/fill="[^"]*"/g, `fill="${fillColor}"`); } // 确保stroke-width存在 if (!svg.includes('stroke-width')) { svg = svg.replace( /(<(?:circle|rect|polygon|ellipse|path|line)[^>]*?)>/g, '$1 stroke-width="2">', ); } return svg; }, // 创建SVG的base64数据URL createSVGElement(shape, fillColor, borderColor, size = 60) { const coloredSVG = this.generateColoredSVG( shape, fillColor, borderColor, ); const svgContent = `${coloredSVG}`; // 转换为base64 const base64 = wx.arrayBufferToBase64( new TextEncoder().encode(svgContent), ); // 可选:调试日志(发布时移除) // console.log(`Creating SVG for ${shape.name}:`, { svgContent }); return `data:image/svg+xml;base64,${base64}`; }, // 打开形状选择器 openShapeSelector() { this.setData({ showShapeSelector: true, // 将当前已选择的形状ID设置到临时列表中 tempSelectedShapeIds: this.data.selectedShapes.map( (shape) => shape.shapeId, ), }); }, // 关闭形状选择器 closeShapeSelector() { this.setData({ showShapeSelector: false, }); }, // 切换形状选择状态 toggleShape(e) { const shapeId = e.currentTarget.dataset.shapeId; const { tempSelectedShapeIds } = this.data; if (tempSelectedShapeIds.includes(shapeId)) { // 取消选择 const newTempSelected = tempSelectedShapeIds.filter( (id) => id !== shapeId, ); this.setData({ tempSelectedShapeIds: newTempSelected, }); } else { // 添加选择(最多6个) if (tempSelectedShapeIds.length < 6) { this.setData({ tempSelectedShapeIds: [...tempSelectedShapeIds, shapeId], }); } else { wx.showToast({ title: '最多只能选择6个形状', icon: 'none', duration: 2000, }); } } }, // 确认形状选择 confirmShapeSelection() { const { tempSelectedShapeIds, availableShapes, selectedShapes } = this.data; // 构建新的选择列表 const newSelectedShapes = []; const usedColorIndexes = []; tempSelectedShapeIds.forEach((shapeId, index) => { const shape = availableShapes.find((s) => s.id === shapeId); if (shape) { // 查找是否已存在该形状 const existingShape = selectedShapes.find( (s) => s.shapeId === shapeId, ); if (existingShape) { // 保持原有颜色,更新SVG内容 const updatedShape = { ...existingShape, svgContent: this.createSVGElement( shape, existingShape.fillColor, '#333', 60, ), }; newSelectedShapes.push(updatedShape); const colorIndex = this.data.basic12Colors.findIndex( (c) => c.hex === existingShape.fillColor, ); if (colorIndex !== -1) { usedColorIndexes.push(colorIndex); } } else { // 新形状,分配随机颜色 const randomColor = this.getRandomUnusedColor(usedColorIndexes); const newShape = { id: Date.now() + index, shapeId: shape.id, name: shape.name, fillColor: randomColor.hex, colorName: randomColor.name, drawFunction: shape.drawFunction, svgContent: this.createSVGElement( shape, randomColor.hex, '#333', 60, ), }; newSelectedShapes.push(newShape); } } }); this.setData( { selectedShapes: newSelectedShapes, usedColorIndexes, showShapeSelector: false, }, () => { // 自动生成新的打印纸 if (newSelectedShapes.length > 0) { this.generateImage(); } }, ); wx.showToast({ title: `已选择${newSelectedShapes.length}个形状`, icon: 'success', duration: 1500, }); }, // 获取随机未使用的颜色 getRandomUnusedColor(usedIndexes) { const { basic12Colors } = this.data; const availableIndexes = []; for (let i = 0; i < basic12Colors.length; i++) { if (!usedIndexes.includes(i)) { availableIndexes.push(i); } } if (availableIndexes.length === 0) { // 如果所有颜色都被使用,则重新开始 const randomIndex = Math.floor( Math.random() * basic12Colors.length, ); usedIndexes.push(randomIndex); return basic12Colors[randomIndex]; } const randomIndex = availableIndexes[ Math.floor(Math.random() * availableIndexes.length) ]; usedIndexes.push(randomIndex); return basic12Colors[randomIndex]; }, // 删除形状 deleteShape(e) { const index = parseInt(e.currentTarget.dataset.index); const selectedShapes = [...this.data.selectedShapes]; const deletedShape = selectedShapes[index]; // 释放颜色 const colorIndex = this.data.basic12Colors.findIndex( (c) => c.hex === deletedShape.fillColor, ); const usedColorIndexes = this.data.usedColorIndexes.filter( (i) => i !== colorIndex, ); selectedShapes.splice(index, 1); this.setData( { selectedShapes, usedColorIndexes, }, () => { // 如果还有形状,自动重新生成 if (selectedShapes.length > 0) { this.generateImage(); } else { // 清空生成的图片 this.setData({ generatedImage: null, }); } }, ); wx.showToast({ title: '已删除形状', icon: 'success', duration: 1000, }); }, // 打开颜色选择器 openColorPicker(e) { const index = parseInt(e.currentTarget.dataset.index); const shape = this.data.selectedShapes[index]; this.setData({ showColorPicker: true, currentEditingIndex: index, currentEditingColor: shape.fillColor, }); }, // 关闭颜色选择器 closeColorPicker() { this.setData({ showColorPicker: false, currentEditingIndex: -1, currentEditingColor: '', }); }, // 颜色改变事件 onColorChange(e) { const { color } = e.detail; const { currentEditingIndex, selectedShapes, basic12Colors } = this.data; if (currentEditingIndex >= 0) { const newSelectedShapes = [...selectedShapes]; const currentShape = newSelectedShapes[currentEditingIndex]; const shapeDefinition = this.data.availableShapes.find( (s) => s.id === currentShape.shapeId, ); const colorInfo = basic12Colors.find((c) => c.hex === color) || WATER_COLORS.extended24.find((c) => c.hex === color) || { name: '自定义色', hex: color, }; newSelectedShapes[currentEditingIndex] = { ...currentShape, fillColor: color, colorName: colorInfo.name, svgContent: this.createSVGElement( shapeDefinition, color, '#333', 60, ), }; this.setData( { selectedShapes: newSelectedShapes, showColorPicker: false, currentEditingIndex: -1, }, () => { // 自动重新生成 this.generateImage(); }, ); wx.showToast({ title: '颜色已更新', icon: 'success', duration: 1000, }); } }, // 设置背景颜色 setBackground(e) { this.setData( { bgColor: e.detail.value, }, () => { // 如果有形状,自动重新生成 if (this.data.selectedShapes.length > 0) { this.generateImage(); } }, ); }, // 生成练习页图片 async generateImage() { if (this.data.selectedShapes.length === 0) { wx.showToast({ title: '请先选择形状', icon: 'none', }); return; } this.setData({ isGenerating: true }); try { const canvas = await this.getCanvas(); const ctx = canvas.getContext('2d'); // A4纸张尺寸 (595x842 像素,72 DPI) const width = 595; const height = 842; // 设置画布尺寸 canvas.width = width; canvas.height = height; // 设置背景 ctx.fillStyle = this.data.bgColor; ctx.fillRect(0, 0, width, height); // 绘制标题 this.drawTitle(ctx, width); // 绘制有颜色的示例部分(上半部) this.drawColoredExamples(ctx, width, height); // 绘制分隔线 this.drawDivider(ctx, width, height); // 绘制练习区域(下半部,无颜色) this.drawPracticeArea(ctx, width, height); // 导出图片 wx.canvasToTempFilePath({ canvas: canvas, fileType: 'png', quality: 1, success: (res) => { this.setData({ generatedImage: res.tempFilePath, isGenerating: false, }); wx.showToast({ title: '生成完成', icon: 'success', }); }, fail: (error) => { console.error('导出图片失败:', error); this.setData({ isGenerating: false }); wx.showToast({ title: '生成失败,请重试', icon: 'none', }); }, }); } catch (error) { console.error('生成图片失败:', error); this.setData({ isGenerating: false }); wx.showToast({ title: '生成失败,请重试', icon: 'none', }); } }, // 获取Canvas实例 getCanvas() { return new Promise((resolve) => { const query = wx.createSelectorQuery().in(this); query .select('#mainCanvas') .fields({ node: true, size: true }) .exec((res) => { const canvas = res[0].node; resolve(canvas); }); }); }, // 绘制标题 drawTitle(ctx, width) { ctx.save(); ctx.fillStyle = '#333'; ctx.font = 'bold 24px Arial'; ctx.textAlign = 'center'; ctx.fillText('形状涂色练习', width / 2, 40); ctx.font = '16px Arial'; ctx.fillText('请为下面的形状涂上颜色', width / 2, 70); ctx.restore(); }, // 绘制有颜色的示例 drawColoredExamples(ctx, width, height) { const { selectedShapes } = this.data; const startY = 100; const shapeSize = 80; const cols = 3; const spacing = width / (cols + 1); selectedShapes.forEach((shape, index) => { const row = Math.floor(index / cols); const col = index % cols; const x = (col + 1) * spacing; const y = startY + row * (shapeSize + 60); // 绘制形状 this.drawShapeOnCanvas(ctx, shape, x, y, shapeSize, true); // 绘制形状名称 ctx.save(); ctx.fillStyle = '#333'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText(shape.name, x, y + shapeSize / 2 + 25); ctx.restore(); }); }, // 绘制分隔线 drawDivider(ctx, width, height) { const y = height / 2; ctx.save(); ctx.strokeStyle = '#ccc'; ctx.lineWidth = 2; ctx.setLineDash([10, 5]); ctx.beginPath(); ctx.moveTo(50, y); ctx.lineTo(width - 50, y); ctx.stroke(); // 分隔线文字 ctx.fillStyle = '#666'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText('练习区域(请涂色)', width / 2, y + 20); ctx.restore(); }, // 绘制练习区域 drawPracticeArea(ctx, width, height) { const { selectedShapes } = this.data; const startY = height / 2 + 60; const shapeSize = 80; const cols = 3; const spacing = width / (cols + 1); selectedShapes.forEach((shape, index) => { const row = Math.floor(index / cols); const col = index % cols; const x = (col + 1) * spacing; const y = startY + row * (shapeSize + 60); // 绘制形状(无填充色,仅轮廓) this.drawShapeOnCanvas(ctx, shape, x, y, shapeSize, false); // 绘制形状名称 ctx.save(); ctx.fillStyle = '#333'; ctx.font = '14px Arial'; ctx.textAlign = 'center'; ctx.fillText(shape.name, x, y + shapeSize / 2 + 25); ctx.restore(); }); }, // 在Canvas上绘制形状 drawShapeOnCanvas(ctx, shape, x, y, size, filled) { const color = filled ? shape.fillColor : 'transparent'; switch (shape.drawFunction) { case 'drawCircle': this.drawCircle(ctx, x, y, size, color, '#333', filled); break; case 'drawSquare': this.drawSquare(ctx, x, y, size, color, '#333', filled); break; case 'drawRectangle': this.drawRectangle(ctx, x, y, size, color, '#333', filled); break; case 'drawTriangle': this.drawTriangle(ctx, x, y, size, color, '#333', filled); break; case 'drawOval': this.drawOval(ctx, x, y, size, color, '#333', filled); break; case 'drawDiamond': this.drawDiamond(ctx, x, y, size, color, '#333', filled); break; case 'drawStar': this.drawStar(ctx, x, y, size, color, '#333', filled); break; case 'drawHeart': this.drawHeart(ctx, x, y, size, color, '#333', filled); break; case 'drawHexagon': this.drawHexagon(ctx, x, y, size, color, '#333', filled); break; case 'drawPentagon': this.drawPentagon(ctx, x, y, size, color, '#333', filled); break; case 'drawSemicircle': this.drawSemicircle(ctx, x, y, size, color, '#333', filled); break; case 'drawTrapezoid': this.drawTrapezoid(ctx, x, y, size, color, '#333', filled); break; case 'drawCylinder': this.drawCylinder(ctx, x, y, size, color, '#333', filled); break; case 'drawCube': this.drawCube(ctx, x, y, size, color, '#333', filled); break; case 'drawSphere': this.drawSphere(ctx, x, y, size, color, '#333', filled); break; case 'drawCone': this.drawCone(ctx, x, y, size, color, '#333', filled); break; } }, // 绘制各种形状的函数(更新参数以支持边框色) drawCircle(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); ctx.beginPath(); ctx.arc(x, y, size * 0.4, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawSquare(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const squareSize = size * 0.8; const startX = x - squareSize / 2; const startY = y - squareSize / 2; if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fillRect(startX, startY, squareSize, squareSize); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.strokeRect(startX, startY, squareSize, squareSize); ctx.restore(); }, drawRectangle(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const rectWidth = size * 0.9; const rectHeight = size * 0.6; const startX = x - rectWidth / 2; const startY = y - rectHeight / 2; if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fillRect(startX, startY, rectWidth, rectHeight); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.strokeRect(startX, startY, rectWidth, rectHeight); ctx.restore(); }, drawTriangle(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawOval(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); ctx.beginPath(); ctx.ellipse(x, y, size * 0.45, size * 0.3, 0, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawDiamond(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawStar(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawHeart(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawHexagon(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawPentagon(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawSemicircle(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const radius = size * 0.4; ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI); ctx.closePath(); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, drawTrapezoid(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); 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 && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); ctx.restore(); }, // === 3D形状绘制函数 === // 绘制圆柱体 drawCylinder(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const radius = size * 0.3; const height = size * 0.6; // 绘制主体 if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fillRect(x - radius, y - height / 2, radius * 2, height); } // 绘制主体边框 ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(x - radius, y - height / 2); ctx.lineTo(x - radius, y + height / 2); ctx.moveTo(x + radius, y - height / 2); ctx.lineTo(x + radius, y + height / 2); ctx.stroke(); // 绘制顶部椭圆 ctx.beginPath(); ctx.ellipse(x, y - height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.stroke(); // 绘制底部椭圆 ctx.beginPath(); ctx.ellipse(x, y + height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.stroke(); ctx.restore(); }, // 绘制正方体 drawCube(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const cubeSize = size * 0.4; const offset = cubeSize * 0.3; // 3D偏移量 // 绘制后面(右侧面) if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.globalAlpha = 0.6; // 较暗 ctx.fillRect( x - cubeSize / 2 + offset, y - cubeSize / 2 - offset, cubeSize, cubeSize, ); ctx.globalAlpha = 1; } ctx.strokeStyle = borderColor; ctx.lineWidth = 2; ctx.strokeRect( x - cubeSize / 2 + offset, y - cubeSize / 2 - offset, cubeSize, cubeSize, ); // 绘制前面(正面) if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fillRect( x - cubeSize / 2, y - cubeSize / 2, cubeSize, cubeSize, ); } ctx.strokeRect(x - cubeSize / 2, y - cubeSize / 2, cubeSize, cubeSize); // 绘制连接线 ctx.beginPath(); // 左上角连接线 ctx.moveTo(x - cubeSize / 2, y - cubeSize / 2); ctx.lineTo(x - cubeSize / 2 + offset, y - cubeSize / 2 - offset); // 右上角连接线 ctx.moveTo(x + cubeSize / 2, y - cubeSize / 2); ctx.lineTo(x + cubeSize / 2 + offset, y - cubeSize / 2 - offset); // 右下角连接线 ctx.moveTo(x + cubeSize / 2, y + cubeSize / 2); ctx.lineTo(x + cubeSize / 2 + offset, y + cubeSize / 2 - offset); ctx.stroke(); ctx.restore(); }, // 绘制球体 drawSphere(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const radius = size * 0.4; // 绘制主体圆形 ctx.beginPath(); ctx.arc(x, y, radius, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { // 创建径向渐变模拟3D效果 const gradient = ctx.createRadialGradient( x - radius * 0.3, y - radius * 0.3, 0, x, y, radius, ); gradient.addColorStop(0, '#ffffff'); gradient.addColorStop(0.3, fillColor); gradient.addColorStop(1, '#000000'); ctx.fillStyle = gradient; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); // 绘制纬线 ctx.strokeStyle = borderColor; ctx.lineWidth = 1; ctx.globalAlpha = 0.5; // 水平纬线 ctx.beginPath(); ctx.ellipse(x, y, radius, radius * 0.3, 0, 0, Math.PI * 2); ctx.stroke(); // 垂直经线 ctx.beginPath(); ctx.ellipse(x, y, radius * 0.3, radius, 0, 0, Math.PI * 2); ctx.stroke(); ctx.restore(); }, // 绘制圆锥体 drawCone(ctx, x, y, size, fillColor, borderColor, filled = true) { ctx.save(); const radius = size * 0.3; const height = size * 0.6; // 绘制圆锥主体 ctx.beginPath(); ctx.moveTo(x, y - height / 2); // 顶点 ctx.lineTo(x - radius, y + height / 2); // 左下 ctx.lineTo(x + radius, y + height / 2); // 右下 ctx.closePath(); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.strokeStyle = borderColor; ctx.lineWidth = 3; ctx.stroke(); // 绘制底部椭圆 ctx.beginPath(); ctx.ellipse(x, y + height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2); if (filled && fillColor !== 'transparent') { ctx.fillStyle = fillColor; ctx.fill(); } ctx.stroke(); ctx.restore(); }, // 保存到相册 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) => { console.error('保存失败:', err); if (err.errMsg.includes('auth')) { wx.showModal({ title: '提示', content: '需要授权访问相册才能保存图片', confirmText: '去授权', success: (res) => { if (res.confirm) { wx.openSetting(); } }, }); } else { wx.showToast({ title: '保存失败', icon: 'none', }); } }, }); }, // 预览图片 previewImage() { if (this.data.generatedImage) { wx.previewImage({ urls: [this.data.generatedImage], current: this.data.generatedImage, }); } }, });