diff --git a/miniprogram/pages/debug/debug.wxml b/miniprogram/pages/debug/debug.wxml index 036cb7a..d0e3fe2 100644 --- a/miniprogram/pages/debug/debug.wxml +++ b/miniprogram/pages/debug/debug.wxml @@ -68,4 +68,17 @@ 确定 + + + + + diff --git a/miniprogram/pages/shapePrint/index.js b/miniprogram/pages/shapePrint/index.js index d90d2e9..318f4f8 100644 --- a/miniprogram/pages/shapePrint/index.js +++ b/miniprogram/pages/shapePrint/index.js @@ -1,58 +1,92 @@ -// 引入形状数据 +// 引入形状和颜色数据 import { shapes, colors } from './shapes'; Page({ data: { - shapeCards: [], - columns: 3, - bgColor: '#FFFFFF', + // 可选择的形状列表 + availableShapes: shapes, + // 可选择的颜色列表 + availableColors: colors, + // 已选择的形状列表 + selectedShapes: [], + // 当前选中的形状类型 + currentShapeType: 'circle', + // 当前选中的颜色 + currentColor: '#FF4444', + // 生成的图片 generatedImage: null, + // 是否正在生成 isGenerating: false, + // 背景选项 backgrounds: [ - { name: '纯白', value: '#FFFFFF', color: '#f2f2f2' }, - { name: '米黄', value: '#FAF5E8', color: '#d4c5a0' }, - { name: '淡灰', value: '#F5F5F5', color: '#cccccc' }, - { name: '无背景', value: 'transparent', color: '#000000' }, + { name: '纯白', value: '#FFFFFF' }, + { name: '米黄', value: '#FAF5E8' }, + { name: '淡灰', value: '#F5F5F5' }, ], + bgColor: '#FFFFFF', }, onLoad() { - // 初始化示例卡片 - const initialCards = [ - this.createShapeCard('square', '#FF5252'), - this.createShapeCard('diamond', '#69F0AE'), - this.createShapeCard('cylinder', '#448AFF'), - this.createShapeCard('cube', '#FFD740'), - this.createShapeCard('circle', '#E040FB'), - this.createShapeCard('sphere', '#FAF5E8'), - // this.createShapeCard('triangle', '#FF9800'), - ]; - this.setData({ shapeCards: initialCards }); + console.log('形状绘制页面加载完成'); }, - createShapeCard(shapeId, fillColor) { - const shape = shapes.find((s) => s.id === shapeId); - return { - shape: shapeId, - fillColor, - svgData: this.generateSvgData(shape.svg, fillColor), - name: shape.name, - }; - }, - - // SVG Base64生成器 - generateSvgData(svgContent, fillColor) { - const styledSvg = svgContent.replace(/var\(--fill-color\)/g, fillColor); - const base64 = wx.arrayBufferToBase64( - new TextEncoder().encode(styledSvg), - ); - return `data:image/svg+xml;base64,${base64}`; - }, - - // 设置列数 - setColumns(e) { + // 选择形状类型 + selectShape(e) { + const shapeId = e.currentTarget.dataset.shapeId; this.setData({ - columns: e.detail.value, + 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: [], }); }, @@ -63,121 +97,506 @@ Page({ }); }, - // 生成拼接图片 + // 创建形状的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 cardCount = this.data.shapeCards.length; - const cardsPerRow = this.data.columns; - const canvasWidth = 750; // 以rpx为单位的宽度设计 - const cardSize = canvasWidth / cardsPerRow; - const rowCount = Math.ceil(cardCount / cardsPerRow); - const canvasHeight = cardSize * rowCount; + const query = wx.createSelectorQuery().in(this); - // 创建离屏Canvas - const offscreenCanvas = wx.createOffscreenCanvas({ - type: '2d', - width: canvasWidth, - height: canvasHeight, - }); + 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 ctx = offscreenCanvas.getContext('2d'); + const canvas = res[0].node; + const ctx = canvas.getContext('2d'); - // 填充背景 - if (this.data.bgColor !== 'transparent') { - ctx.fillStyle = this.data.bgColor; - ctx.fillRect(0, 0, canvasWidth, canvasHeight); - } + // A4纸张尺寸 (595x842 点,按照72DPI) + const dpr = wx.getSystemInfoSync().pixelRatio; + const a4Width = 595; + const a4Height = 842; - // 批量加载图片的Promise数组 - const loadPromises = this.data.shapeCards.map((card) => { - return new Promise((resolve) => { - wx.getImageInfo({ - src: card.svgData, + 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) => { - resolve({ - path: res.path, - card, + this.setData({ + generatedImage: res.tempFilePath, + isGenerating: false, + }); + wx.showToast({ + title: '图片生成成功', + icon: 'success', }); }, - fail: () => { - resolve(null); + fail: (err) => { + console.error('导出图片失败:', err); + this.setData({ isGenerating: false }); + wx.showToast({ + title: '图片生成失败', + icon: 'none', + }); }, }); }); - }); - - // 等待所有图片加载完成 - const images = await Promise.all(loadPromises); - - // 绘制所有卡片 - images.forEach((imgInfo, index) => { - if (!imgInfo) return; - - const row = Math.floor(index / cardsPerRow); - const col = index % cardsPerRow; - const x = col * cardSize + cardSize / 2; - const y = row * cardSize + cardSize / 2; - - // 保存当前绘制状态 - ctx.save(); - - // 添加卡片投影效果 - ctx.shadowColor = 'rgba(0, 0, 0, 0.2)'; - ctx.shadowBlur = 8; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 3; - - // 绘制卡片SVG - ctx.drawImage( - imgInfo.path, - x - (cardSize * 0.8) / 2, - y - (cardSize * 0.8) / 2, - cardSize * 0.8, - cardSize * 0.8, - ); - - // 恢复绘制状态 - ctx.restore(); - }); - - // 将Canvas转换为临时图片路径 - wx.canvasToTempFilePath({ - canvas: offscreenCanvas, - success: (res) => { - this.setData({ - generatedImage: res.tempFilePath, - isGenerating: false, - }); - }, - fail: () => { - this.setData({ isGenerating: false }); - wx.showToast({ title: '图片生成失败', icon: 'none' }); - }, - }); - } catch (e) { + } catch (error) { + console.error('生成图片出错:', error); this.setData({ isGenerating: false }); - console.error(e); - wx.showToast({ title: '生成出错,请重试', icon: 'none' }); + 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) return; + if (!this.data.generatedImage) { + wx.showToast({ + title: '请先生成图片', + icon: 'none', + }); + return; + } wx.saveImageToPhotosAlbum({ filePath: this.data.generatedImage, success: () => { - wx.showToast({ title: '保存成功', icon: 'success' }); + wx.showToast({ + title: '保存成功', + icon: 'success', + }); }, fail: (err) => { - if (err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') { + if (err.errMsg.includes('auth deny')) { this.showAuthGuide(); } else { - wx.showToast({ title: '保存失败', icon: 'none' }); + wx.showToast({ + title: '保存失败', + icon: 'none', + }); } }, }); @@ -187,7 +606,7 @@ Page({ showAuthGuide() { wx.showModal({ title: '权限申请', - content: '需要相册访问权限才能保存图片,请开启权限', + content: '需要相册访问权限才能保存图片,请在设置中开启权限', success: (res) => { if (res.confirm) { wx.openSetting(); @@ -196,8 +615,8 @@ Page({ }); }, - // 预览大图 - previewFullImage() { + // 预览生成的图片 + previewImage() { if (!this.data.generatedImage) return; wx.previewImage({ diff --git a/miniprogram/pages/shapePrint/index.less b/miniprogram/pages/shapePrint/index.less index 3b5fa5d..5bc908d 100644 --- a/miniprogram/pages/shapePrint/index.less +++ b/miniprogram/pages/shapePrint/index.less @@ -1,84 +1,538 @@ .container { padding: 20rpx; - background-color: #f5f7fa; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; +} + +.header { + text-align: center; + margin-bottom: 30rpx; + padding: 30rpx; + background: rgba(255, 255, 255, 0.95); + border-radius: 25rpx; + box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15); + + .title { + font-size: 40rpx; + font-weight: bold; + color: #333; + display: block; + margin-bottom: 10rpx; + } + + .subtitle { + font-size: 26rpx; + color: #666; + display: block; + } +} + +.section { + background: rgba(255, 255, 255, 0.95); + border-radius: 25rpx; + padding: 30rpx; + margin-bottom: 25rpx; + box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15); + backdrop-filter: blur(10rpx); +} + +.section-title { + font-size: 32rpx; + font-weight: bold; + color: #333; + margin-bottom: 25rpx; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 2rpx solid #f0f0f0; + padding-bottom: 15rpx; +} + +.clear-btn { + background: linear-gradient(45deg, #ff6b6b, #ee5a24); + color: white; + border: none; + padding: 15rpx 25rpx; + border-radius: 20rpx; + font-size: 24rpx; + box-shadow: 0 4rpx 15rpx rgba(255, 107, 107, 0.4); +} + +/* 形状选择器 */ +.shape-selector { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200rpx, 1fr)); + gap: 20rpx; +} + +.shape-option { + height: 140rpx; + border: 3rpx solid #e0e0e0; + border-radius: 20rpx; display: flex; flex-direction: column; + align-items: center; + justify-content: center; + background: linear-gradient(145deg, #ffffff, #f0f0f0); + transition: all 0.3s ease; + cursor: pointer; + box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); + + &.active { + border-color: #667eea; + background: linear-gradient(145deg, #e3f2fd, #bbdefb); + transform: translateY(-5rpx); + box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.3); + + .shape-name { + color: #667eea; + font-weight: bold; + } + } } -.card-container { - display: flex; - flex-wrap: wrap; - justify-content: space-around; - padding: 20rpx; - background: white; - border-radius: 20rpx; - margin-bottom: 30rpx; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); -} - -.shape-card { - width: 200rpx; - height: 200rpx; - margin: 15rpx; - position: relative; +.shape-preview-box { + width: 60rpx; + height: 60rpx; + margin-bottom: 10rpx; display: flex; align-items: center; justify-content: center; - border-radius: 16rpx; - background: white; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08); } -.shape-image { - width: 140rpx; - height: 140rpx; +.shape-icon { + width: 50rpx; + height: 50rpx; + border: 2rpx solid #666; + + &.shape-icon-circle { + border-radius: 50%; + background: transparent; + } + + &.shape-icon-square { + border-radius: 5rpx; + background: transparent; + } + + &.shape-icon-rectangle { + width: 60rpx; + height: 40rpx; + border-radius: 5rpx; + background: transparent; + } + + &.shape-icon-triangle { + width: 0; + height: 0; + border: none; + border-left: 25rpx solid transparent; + border-right: 25rpx solid transparent; + border-bottom: 45rpx solid #666; + background: transparent; + } + + &.shape-icon-oval { + border-radius: 50%; + width: 60rpx; + height: 40rpx; + background: transparent; + } + + &.shape-icon-diamond { + width: 0; + height: 0; + border: none; + border-left: 25rpx solid transparent; + border-right: 25rpx solid transparent; + border-top: 25rpx solid transparent; + border-bottom: 25rpx solid #666; + background: transparent; + transform: rotate(45deg); + } + + &.shape-icon-star { + position: relative; + width: 0; + height: 0; + border: none; + border-left: 25rpx solid transparent; + border-right: 25rpx solid transparent; + border-bottom: 18rpx solid #666; + background: transparent; + transform: rotate(35deg); + + &::before { + content: ''; + position: absolute; + left: -25rpx; + top: -12rpx; + width: 0; + height: 0; + border-left: 25rpx solid transparent; + border-right: 25rpx solid transparent; + border-bottom: 18rpx solid #666; + transform: rotate(-70deg); + } + } + + &.shape-icon-heart { + position: relative; + width: 40rpx; + height: 35rpx; + border: none; + background: #666; + transform: rotate(-45deg); + + &::before, + &::after { + content: ''; + position: absolute; + width: 20rpx; + height: 30rpx; + background: #666; + border-radius: 20rpx 20rpx 0 0; + transform: rotate(-45deg); + transform-origin: 0 100%; + } + + &::before { + left: 20rpx; + } + + &::after { + top: -15rpx; + transform: rotate(45deg); + transform-origin: 100% 100%; + } + } + + &.shape-icon-hexagon { + width: 40rpx; + height: 22rpx; + background: #666; + border: none; + position: relative; + + &::before, + &::after { + content: ''; + position: absolute; + width: 0; + height: 0; + border-left: 20rpx solid transparent; + border-right: 20rpx solid transparent; + } + + &::before { + bottom: 100%; + border-bottom: 11rpx solid #666; + } + + &::after { + top: 100%; + border-top: 11rpx solid #666; + } + } + + &.shape-icon-pentagon { + position: relative; + width: 0; + height: 0; + border: none; + border-left: 25rpx solid transparent; + border-right: 25rpx solid transparent; + border-bottom: 30rpx solid #666; + background: transparent; + + &::before { + content: ''; + position: absolute; + left: -18rpx; + top: 30rpx; + width: 36rpx; + height: 15rpx; + background: #666; + } + } + + &.shape-icon-semicircle { + width: 50rpx; + height: 25rpx; + border: 2rpx solid #666; + border-bottom: none; + border-radius: 50rpx 50rpx 0 0; + background: transparent; + } + + &.shape-icon-trapezoid { + width: 0; + height: 0; + border: none; + border-left: 15rpx solid transparent; + border-right: 15rpx solid transparent; + border-bottom: 40rpx solid #666; + background: transparent; + position: relative; + + &::before { + content: ''; + position: absolute; + left: -25rpx; + top: 40rpx; + width: 50rpx; + height: 2rpx; + background: #666; + } + } } -.color-box { - position: absolute; - bottom: 10rpx; - right: 10rpx; - width: 30rpx; - height: 30rpx; - border-radius: 6rpx; - border: 1rpx solid #eee; +.shape-name { + font-size: 24rpx; + color: #666; + text-align: center; } -.control-panel { +/* 颜色选择器 */ +.color-selector { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(120rpx, 1fr)); + gap: 20rpx; +} + +.color-option { + height: 100rpx; + border-radius: 20rpx; + border: 3rpx solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + justify-content: center; + position: relative; + box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); + + &.active { + border-color: #667eea; + transform: scale(1.05); + box-shadow: 0 0 0 6rpx rgba(102, 126, 234, 0.3); + } + + .color-name { + color: white; + font-size: 22rpx; + font-weight: bold; + text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.5); + } +} + +/* 添加按钮 */ +.add-btn { + width: 100%; + height: 90rpx; + font-size: 32rpx; + border-radius: 20rpx; + background: linear-gradient(45deg, #667eea, #764ba2); + border: none; + color: white; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.4); + + .add-icon { + font-size: 40rpx; + margin-right: 10rpx; + font-weight: bold; + } +} + +/* 已选择的形状 */ +.selected-shapes { display: flex; flex-direction: column; - gap: 20rpx; - padding: 30rpx; - background: white; - border-radius: 20rpx; - margin-bottom: 30rpx; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); + gap: 15rpx; } -.bg-picker { +.selected-shape { + display: flex; + align-items: center; + padding: 20rpx; + background: linear-gradient(145deg, #f8f9fa, #e9ecef); + border-radius: 20rpx; + border: 2rpx solid #e0e0e0; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1); + + &:active { + background: linear-gradient(145deg, #ffebee, #ffcdd2); + border-color: #ff5722; + transform: scale(0.98); + } +} + +.shape-preview { + width: 40rpx; + height: 40rpx; + border-radius: 50%; + margin-right: 20rpx; + border: 2rpx solid #333; + box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2); +} + +.shape-details { + flex: 1; + display: flex; + flex-direction: column; + gap: 5rpx; +} + +.shape-info { + font-size: 28rpx; + color: #333; + font-weight: 500; +} + +.color-info { + font-size: 22rpx; + color: #666; + font-family: monospace; +} + +.remove-hint { + font-size: 20rpx; + color: #999; + font-style: italic; +} + +/* 设置项 */ +.setting-item { + margin-bottom: 30rpx; + + &:last-child { + margin-bottom: 0; + } +} + +.setting-label { + font-size: 28rpx; + color: #333; + margin-bottom: 15rpx; + display: block; + font-weight: 500; +} + +.bg-radio-group { display: flex; flex-wrap: wrap; + gap: 25rpx; + margin-top: 15rpx; +} + +.bg-option { + display: flex; align-items: center; - gap: 20rpx; - padding: 20rpx; - background: white; - border-radius: 20rpx; - margin-bottom: 30rpx; - box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); + gap: 10rpx; + padding: 10rpx 15rpx; + border-radius: 15rpx; + background: rgba(0, 0, 0, 0.05); + + text { + font-size: 26rpx; + color: #666; + } } -.label { - font-weight: bold; +.feature-intro { + margin-top: 30rpx; + padding: 25rpx; + background: linear-gradient(145deg, #f0f8ff, #e6f3ff); + border-radius: 15rpx; + border-left: 5rpx solid #667eea; +} + +.intro-title { + font-size: 26rpx; color: #333; + font-weight: bold; + display: block; + margin-bottom: 15rpx; } +.intro-text { + font-size: 24rpx; + color: #666; + display: block; + margin-bottom: 8rpx; + padding-left: 15rpx; + + &:last-child { + margin-bottom: 0; + } +} + +/* 操作按钮 */ +.generate-btn { + width: 100%; + height: 90rpx; + font-size: 32rpx; + border-radius: 20rpx; + margin-bottom: 20rpx; + background: linear-gradient(45deg, #4CAF50, #45a049); + border: none; + color: white; + box-shadow: 0 8rpx 25rpx rgba(76, 175, 80, 0.4); + + &:disabled { + background: #ccc; + box-shadow: none; + } +} + +.save-btn { + width: 100%; + height: 90rpx; + font-size: 32rpx; + border-radius: 20rpx; + background: linear-gradient(45deg, #ff9800, #f57c00); + border: none; + color: white; + box-shadow: 0 8rpx 25rpx rgba(255, 152, 0, 0.4); +} + +/* 隐藏的画布 */ +.hidden-canvas { + position: fixed; + top: -9999rpx; + left: -9999rpx; + width: 595px; + height: 842px; +} + +/* 预览图片 */ .preview-image { width: 100%; - height: 600rpx; - background-color: white; + max-height: 600rpx; border-radius: 20rpx; + border: 3rpx solid #e0e0e0; + background-color: white; + cursor: pointer; + box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15); + transition: transform 0.3s ease; + + &:active { + transform: scale(0.98); + } +} + +.preview-hint { + display: block; + text-align: center; + font-size: 24rpx; + color: #666; margin-top: 20rpx; - box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1); - border: 1rpx solid #eee; + padding: 15rpx; + background: rgba(0, 0, 0, 0.05); + border-radius: 15rpx; } \ No newline at end of file diff --git a/miniprogram/pages/shapePrint/index.wxml b/miniprogram/pages/shapePrint/index.wxml index 2bd8678..f1e2265 100644 --- a/miniprogram/pages/shapePrint/index.wxml +++ b/miniprogram/pages/shapePrint/index.wxml @@ -1,62 +1,147 @@ - - - - + + + 形状涂色练习生成器 + 为小朋友制作涂色练习页 + + + + + 选择形状 + + wx:for="{{availableShapes}}" + wx:key="id" + class="shape-option {{currentShapeType === item.id ? 'active' : ''}}" + data-shape-id="{{item.id}}" + bindtap="selectShape"> + + + + + {{item.name}} + - - - - 布局列数: {{columns}} + + + 选择颜色 + + + {{item.name}} + + + + + + + + + + + + 已选择的形状 ({{selectedShapes.length}}) + + + + + + + {{item.name}} + {{item.color}} + + 长按删除 + + + + + + + 打印设置 + + + 背景颜色: + + + + + + + 功能说明: + • 上半部分:带颜色的形状示例 + • 下半部分:无颜色的练习区域 + • 图片大小:A4 纸张,适合打印 + + + + + - - + + - - - 背景颜色: - - - {{item.name}} - - + + + 生成的练习页 + + 点击图片可放大预览 • A4尺寸 • 适合打印 diff --git a/miniprogram/pages/shapePrint/shapes.js b/miniprogram/pages/shapePrint/shapes.js index 15e8cc1..90384ff 100644 --- a/miniprogram/pages/shapePrint/shapes.js +++ b/miniprogram/pages/shapePrint/shapes.js @@ -1,61 +1,105 @@ -// shapes.js +// shapes.js - 幼儿园到小学三年级基本几何形状定义 const shapes = [ + { + id: 'circle', + name: '圆形', + type: 'circle', + svg: '', + drawFunction: 'drawCircle', + }, { id: 'square', name: '正方形', - svg: ` - - `, + type: 'rect', + svg: '', + drawFunction: 'drawSquare', + }, + { + id: 'rectangle', + name: '长方形', + type: 'rect', + svg: '', + drawFunction: 'drawRectangle', + }, + { + id: 'triangle', + name: '三角形', + type: 'triangle', + svg: '', + drawFunction: 'drawTriangle', + }, + { + id: 'oval', + name: '椭圆形', + type: 'oval', + svg: '', + drawFunction: 'drawOval', }, { id: 'diamond', name: '菱形', - svg: ` - - `, + type: 'diamond', + svg: '', + drawFunction: 'drawDiamond', }, { - id: 'cylinder', - name: '圆柱体', - svg: ` - - - - `, + id: 'star', + name: '星形', + type: 'star', + svg: '', + drawFunction: 'drawStar', }, { - id: 'cube', - name: '立方体', - svg: ` - - - - `, + id: 'heart', + name: '心形', + type: 'heart', + svg: '', + drawFunction: 'drawHeart', }, { - id: 'circle', - name: '圆形', - svg: ` - - `, + id: 'hexagon', + name: '六边形', + type: 'hexagon', + svg: '', + drawFunction: 'drawHexagon', }, { - id: 'sphere', - name: '球体', - svg: ` - - - `, + id: 'pentagon', + name: '五边形', + type: 'pentagon', + svg: '', + drawFunction: 'drawPentagon', + }, + { + id: 'semicircle', + name: '半圆形', + type: 'semicircle', + svg: '', + drawFunction: 'drawSemicircle', + }, + { + id: 'trapezoid', + name: '梯形', + type: 'trapezoid', + svg: '', + drawFunction: 'drawTrapezoid', }, ]; // 颜色选项 const colors = [ - { name: '红色', value: '#ff5252' }, - { name: '蓝色', value: '#448aff' }, - { name: '绿色', value: '#69f0ae' }, - { name: '黄色', value: '#ffd740' }, - { name: '紫色', value: '#e040fb' }, + { name: '红色', value: '#FF4444' }, + { name: '蓝色', value: '#4A90E2' }, + { name: '绿色', value: '#7ED321' }, + { name: '黄色', value: '#F5A623' }, + { name: '紫色', value: '#9013FE' }, + { name: '橙色', value: '#FF8F00' }, + { name: '粉色', value: '#E91E63' }, + { name: '青色', value: '#00BCD4' }, + { name: '深绿', value: '#4CAF50' }, + { name: '深蓝', value: '#2196F3' }, + { name: '棕色', value: '#8D6E63' }, + { name: '灰色', value: '#9E9E9E' }, ]; export { shapes, colors }; diff --git a/project.private.config.json b/project.private.config.json index 11df584..c83aa6f 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -24,11 +24,18 @@ "miniprogram": { "list": [ { - "name": "debug页", - "pathName": "pages/debug/debug", + "name": "测试图形绘制", + "pathName": "pages/shapePrint/index", "query": "", "scene": null, "launchMode": "default" + }, + { + "name": "debug页", + "pathName": "pages/debug/debug", + "query": "", + "launchMode": "default", + "scene": null } ] }