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 = `
+
+ `;
+
+ 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
}
]
}