From d1b451418e33d8f483db00743bb818708867a60e Mon Sep 17 00:00:00 2001 From: joey Date: Tue, 8 Jul 2025 17:07:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B5=8B=E8=AF=95=E5=9B=BE=E5=BD=A2?= =?UTF-8?q?=E7=BB=98=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/app.json | 18 +- miniprogram/pages/demoIndex/index.ts | 4 +- miniprogram/pages/shapePrint/index.js | 208 ++++++++++++++++++++++++ miniprogram/pages/shapePrint/index.json | 7 + miniprogram/pages/shapePrint/index.less | 84 ++++++++++ miniprogram/pages/shapePrint/index.wxml | 62 +++++++ miniprogram/pages/shapePrint/shapes.js | 61 +++++++ 7 files changed, 441 insertions(+), 3 deletions(-) create mode 100644 miniprogram/pages/shapePrint/index.js create mode 100644 miniprogram/pages/shapePrint/index.json create mode 100644 miniprogram/pages/shapePrint/index.less create mode 100644 miniprogram/pages/shapePrint/index.wxml create mode 100644 miniprogram/pages/shapePrint/shapes.js diff --git a/miniprogram/app.json b/miniprogram/app.json index fe42e9c..57bacf7 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -1,5 +1,9 @@ { - "pages": ["pages/index/index", "pages/debug/debug"], + "pages": [ + "pages/index/index", + "pages/debug/debug", + "pages/shapePrint/index" + ], "window": {}, "style": "v2", "rendererOptions": { @@ -18,5 +22,17 @@ "scope.writePhotosAlbum": { "desc": "需要保存涂色卡到相册" } + }, + "tabBar": { + "list": [ + { + "pagePath": "pages/index/index", + "text": "识字" + }, + { + "pagePath": "pages/shapePrint/index", + "text": "图形" + } + ] } } diff --git a/miniprogram/pages/demoIndex/index.ts b/miniprogram/pages/demoIndex/index.ts index 87dfc38..96abb70 100644 --- a/miniprogram/pages/demoIndex/index.ts +++ b/miniprogram/pages/demoIndex/index.ts @@ -8,8 +8,8 @@ Page({ }, { key: 1, - title: '文本绘制2', - url: '/demoPages/textPaint2/textPaint', + title: '图形绘制', + url: '/demoPages/shapePrint/index', }, { key: 2, diff --git a/miniprogram/pages/shapePrint/index.js b/miniprogram/pages/shapePrint/index.js new file mode 100644 index 0000000..d90d2e9 --- /dev/null +++ b/miniprogram/pages/shapePrint/index.js @@ -0,0 +1,208 @@ +// 引入形状数据 +import { shapes, colors } from './shapes'; + +Page({ + data: { + shapeCards: [], + columns: 3, + bgColor: '#FFFFFF', + 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' }, + ], + }, + + 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 }); + }, + + 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) { + this.setData({ + columns: e.detail.value, + }); + }, + + // 设置背景颜色 + setBackground(e) { + this.setData({ + bgColor: e.detail.value, + }); + }, + + // 生成拼接图片 + async generateImage() { + 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; + + // 创建离屏Canvas + const offscreenCanvas = wx.createOffscreenCanvas({ + type: '2d', + width: canvasWidth, + height: canvasHeight, + }); + + const ctx = offscreenCanvas.getContext('2d'); + + // 填充背景 + if (this.data.bgColor !== 'transparent') { + ctx.fillStyle = this.data.bgColor; + ctx.fillRect(0, 0, canvasWidth, canvasHeight); + } + + // 批量加载图片的Promise数组 + const loadPromises = this.data.shapeCards.map((card) => { + return new Promise((resolve) => { + wx.getImageInfo({ + src: card.svgData, + success: (res) => { + resolve({ + path: res.path, + card, + }); + }, + fail: () => { + resolve(null); + }, + }); + }); + }); + + // 等待所有图片加载完成 + 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) { + this.setData({ isGenerating: false }); + console.error(e); + wx.showToast({ title: '生成出错,请重试', icon: 'none' }); + } + }, + + // 保存到相册 + saveToAlbum() { + if (!this.data.generatedImage) return; + + wx.saveImageToPhotosAlbum({ + filePath: this.data.generatedImage, + success: () => { + wx.showToast({ title: '保存成功', icon: 'success' }); + }, + fail: (err) => { + if (err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') { + this.showAuthGuide(); + } else { + wx.showToast({ title: '保存失败', icon: 'none' }); + } + }, + }); + }, + + // 显示授权引导 + showAuthGuide() { + wx.showModal({ + title: '权限申请', + content: '需要相册访问权限才能保存图片,请开启权限', + success: (res) => { + if (res.confirm) { + wx.openSetting(); + } + }, + }); + }, + + // 预览大图 + previewFullImage() { + if (!this.data.generatedImage) return; + + wx.previewImage({ + current: this.data.generatedImage, + urls: [this.data.generatedImage], + }); + }, +}); diff --git a/miniprogram/pages/shapePrint/index.json b/miniprogram/pages/shapePrint/index.json new file mode 100644 index 0000000..b686e95 --- /dev/null +++ b/miniprogram/pages/shapePrint/index.json @@ -0,0 +1,7 @@ +{ + "navigationBarTitleText": "图形绘制 示例", + "navigationBarBackgroundColor": "#d2e7d8", + "homeButton": true, + "backgroundColor": "#e8eddb", + "enablePullDownRefresh": false +} diff --git a/miniprogram/pages/shapePrint/index.less b/miniprogram/pages/shapePrint/index.less new file mode 100644 index 0000000..3b5fa5d --- /dev/null +++ b/miniprogram/pages/shapePrint/index.less @@ -0,0 +1,84 @@ +.container { + padding: 20rpx; + background-color: #f5f7fa; + min-height: 100vh; + display: flex; + flex-direction: column; +} + +.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; + 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; +} + +.color-box { + position: absolute; + bottom: 10rpx; + right: 10rpx; + width: 30rpx; + height: 30rpx; + border-radius: 6rpx; + border: 1rpx solid #eee; +} + +.control-panel { + 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); +} + +.bg-picker { + display: flex; + flex-wrap: wrap; + 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); +} + +.label { + font-weight: bold; + color: #333; +} + +.preview-image { + width: 100%; + height: 600rpx; + background-color: white; + border-radius: 20rpx; + margin-top: 20rpx; + box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.1); + border: 1rpx solid #eee; +} \ No newline at end of file diff --git a/miniprogram/pages/shapePrint/index.wxml b/miniprogram/pages/shapePrint/index.wxml new file mode 100644 index 0000000..2bd8678 --- /dev/null +++ b/miniprogram/pages/shapePrint/index.wxml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + 布局列数: {{columns}} + + + + + + + + + + + + 背景颜色: + + + {{item.name}} + + + + diff --git a/miniprogram/pages/shapePrint/shapes.js b/miniprogram/pages/shapePrint/shapes.js new file mode 100644 index 0000000..15e8cc1 --- /dev/null +++ b/miniprogram/pages/shapePrint/shapes.js @@ -0,0 +1,61 @@ +// shapes.js +const shapes = [ + { + id: 'square', + name: '正方形', + svg: ` + + `, + }, + { + id: 'diamond', + name: '菱形', + svg: ` + + `, + }, + { + id: 'cylinder', + name: '圆柱体', + svg: ` + + + + `, + }, + { + id: 'cube', + name: '立方体', + svg: ` + + + + `, + }, + { + id: 'circle', + name: '圆形', + svg: ` + + `, + }, + { + id: 'sphere', + name: '球体', + svg: ` + + + `, + }, +]; + +// 颜色选项 +const colors = [ + { name: '红色', value: '#ff5252' }, + { name: '蓝色', value: '#448aff' }, + { name: '绿色', value: '#69f0ae' }, + { name: '黄色', value: '#ffd740' }, + { name: '紫色', value: '#e040fb' }, +]; + +export { shapes, colors };