From 5412f957c1ad773f7eece49f24ef8bccd307860b Mon Sep 17 00:00:00 2001 From: joey Date: Wed, 23 Jul 2025 12:49:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9B=BE=E5=BD=A2=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/demoPages/shapePrint/index.js | 120 ++++- miniprogram/demoPages/shapePrint/index.less | 503 +------------------- miniprogram/demoPages/shapePrint/index.wxml | 54 +-- 3 files changed, 126 insertions(+), 551 deletions(-) diff --git a/miniprogram/demoPages/shapePrint/index.js b/miniprogram/demoPages/shapePrint/index.js index 8e56018..b4b2ef3 100644 --- a/miniprogram/demoPages/shapePrint/index.js +++ b/miniprogram/demoPages/shapePrint/index.js @@ -35,14 +35,68 @@ Page({ 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({ @@ -107,8 +161,17 @@ Page({ (s) => s.shapeId === shapeId, ); if (existingShape) { - // 保持原有颜色 - newSelectedShapes.push(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, ); @@ -119,14 +182,21 @@ Page({ // 新形状,分配随机颜色 const randomColor = this.getRandomUnusedColor(usedColorIndexes); - newSelectedShapes.push({ + 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); } } }); @@ -250,6 +320,10 @@ Page({ 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: '自定义色', @@ -257,9 +331,15 @@ Page({ }; newSelectedShapes[currentEditingIndex] = { - ...newSelectedShapes[currentEditingIndex], + ...currentShape, fillColor: color, colorName: colorInfo.name, + svgContent: this.createSVGElement( + shapeDefinition, + color, + '#333', + 60, + ), }; this.setData( @@ -338,19 +418,29 @@ Page({ this.drawPracticeArea(ctx, width, height); // 导出图片 - const tempFilePath = canvas.toTempFilePathSync({ + wx.canvasToTempFilePath({ + canvas: canvas, fileType: 'png', quality: 1, - }); + success: (res) => { + this.setData({ + generatedImage: res.tempFilePath, + isGenerating: false, + }); - this.setData({ - generatedImage: tempFilePath, - isGenerating: false, - }); - - wx.showToast({ - title: '生成完成', - icon: 'success', + wx.showToast({ + title: '生成完成', + icon: 'success', + }); + }, + fail: (error) => { + console.error('导出图片失败:', error); + this.setData({ isGenerating: false }); + wx.showToast({ + title: '生成失败,请重试', + icon: 'none', + }); + }, }); } catch (error) { console.error('生成图片失败:', error); diff --git a/miniprogram/demoPages/shapePrint/index.less b/miniprogram/demoPages/shapePrint/index.less index 336d1b9..d123c37 100644 --- a/miniprogram/demoPages/shapePrint/index.less +++ b/miniprogram/demoPages/shapePrint/index.less @@ -271,502 +271,21 @@ justify-content: center; } -.shape-icon { +/* SVG 样式 */ +.shape-svg { + width: 60rpx; + height: 60rpx; + display: block; +} + +.shape-svg-small { width: 50rpx; height: 50rpx; - border: 2rpx solid #666; - - &.shape-icon-circle { - border-radius: 50%; - background: var(--fill-color, transparent); - border-color: var(--border-color, #666); - } - - &.shape-icon-square { - border-radius: 5rpx; - background: var(--fill-color, transparent); - border-color: var(--border-color, #666); - } - - &.shape-icon-rectangle { - width: 60rpx; - height: 40rpx; - border-radius: 5rpx; - background: var(--fill-color, transparent); - border-color: var(--border-color, #666); - } - - &.shape-icon-triangle { - width: 0; - height: 0; - border: none; - border-left: 25rpx solid transparent; - border-right: 25rpx solid transparent; - border-bottom: 45rpx solid var(--fill-color, red); - background: transparent; - } - - &.shape-icon-oval { - border-radius: 50%; - width: 60rpx; - height: 40rpx; - background: var(--fill-color, transparent); - border-color: var(--border-color, #666); - } - - &.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 var(--fill-color, #666); - background: transparent; - transform: rotate(45deg); - } - - /* 修正星形 */ - &.shape-icon-star { - position: relative; - width: 80rpx; - height: 80rpx; - border: none; - background: transparent; - display: flex; - align-items: center; - justify-content: center; - - --star-fill-color: var(--fill-color, red); - --star-border-color: var(--border-color, #333); - --star-border-width: 2rpx; - - /* 边框层 */ - &::before { - content: ''; - position: absolute; - left: 50%; - top: 50%; - width: 66rpx; - height: 66rpx; - transform: translate(-50%, -50%); - background: transparent; - border: var(--star-border-width, 2rpx) solid var(--star-border-color, #333); - clip-path: polygon(50% 0%, - 61% 35%, - 98% 35%, - 68% 57%, - 79% 91%, - 50% 70%, - 21% 91%, - 32% 57%, - 2% 35%, - 39% 35%); - z-index: 1; - box-sizing: border-box; - } - - &::after { - content: ''; - position: absolute; - left: 50%; - top: 50%; - width: 60rpx; - height: 60rpx; - transform: translate(-50%, -50%); - background: var(--star-fill-color, red); - clip-path: polygon(50% 0%, - 61% 35%, - 98% 35%, - 68% 57%, - 79% 91%, - 50% 70%, - 21% 91%, - 32% 57%, - 2% 35%, - 39% 35%); - z-index: 2; - pointer-events: none; - } - } - - /* 爱心形状 */ - &.shape-icon-heart { - position: relative; - width: 44rpx; - height: 40rpx; - display: flex; - align-items: center; - justify-content: center; - background: transparent; - - /* 主体部分(三角形) */ - &::after { - content: ''; - position: absolute; - left: 50%; - bottom: 0; - transform: translateX(-50%) rotate(-45deg); - width: 32rpx; - height: 32rpx; - background: var(--fill-color, #f00); - border-left: 2rpx solid var(--border-color, #333); - border-bottom: 2rpx solid var(--border-color, #333); - border-bottom-left-radius: 20rpx; - border-bottom-right-radius: 20rpx; - z-index: 1; - clip-path: polygon(0 0, 100% 0, 50% 100%); - } - - /* 左半圆 */ - &::before { - content: ''; - position: absolute; - left: 4rpx; - top: 0; - width: 24rpx; - height: 24rpx; - background: var(--fill-color, #f00); - border: 2rpx solid var(--border-color, #333); - border-bottom: none; - border-right: none; - border-radius: 50% 50% 0 0; - z-index: 2; - } - - /* 右半圆 */ - .heart-right { - position: absolute; - right: 4rpx; - top: 0; - width: 24rpx; - height: 24rpx; - background: var(--fill-color, #f00); - border: 2rpx solid var(--border-color, #333); - border-bottom: none; - border-left: none; - border-radius: 50% 50% 0 0; - z-index: 2; - } - } - - &.shape-icon-hexagon { - width: 40rpx; - height: 22rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - 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 var(--fill-color, #666); - } - - &::after { - top: 100%; - border-top: 11rpx solid var(--fill-color, #666); - } - } - - /* 修正五边形 */ - &.shape-icon-pentagon { - position: relative; - width: 40rpx; - height: 38rpx; - background: transparent; - border: none; - display: flex; - align-items: center; - justify-content: center; - - &::before { - content: ''; - position: absolute; - left: 0; - top: 0; - width: 40rpx; - height: 38rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - clip-path: polygon(50% 0%, - 100% 38%, - 81% 100%, - 19% 100%, - 0% 38%); - box-sizing: border-box; - } - } - - /* 修正半圆 */ - &.shape-icon-semicircle { - width: 50rpx; - height: 25rpx; - border: none; - background: transparent; - position: relative; - overflow: hidden; - - &::before { - content: ''; - position: absolute; - left: 0; - top: 0; - width: 50rpx; - height: 50rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - border-radius: 50%; - box-sizing: border-box; - } - } - - /* 修正梯形 */ - &.shape-icon-trapezoid { - width: 50rpx; - height: 30rpx; - border: none; - background: transparent; - position: relative; - overflow: visible; - - &::before { - content: ''; - position: absolute; - left: 0; - top: 0; - width: 50rpx; - height: 30rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - clip-path: polygon(15% 100%, - 85% 100%, - 100% 0%, - 0% 0%); - box-sizing: border-box; - } - } - - // === 3D 形状样式 === - - /* 圆柱体 */ - &.shape-icon-cylinder { - width: 60rpx; - height: 60rpx; - border: none; - background: transparent; - position: relative; - display: flex; - align-items: center; - justify-content: center; - - /* 顶部椭圆 */ - &::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 50rpx; - height: 16rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - border-radius: 50%; - z-index: 3; - } - - /* 主体圆柱 */ - &::after { - content: ''; - position: absolute; - top: 8rpx; - left: 50%; - transform: translateX(-50%); - width: 50rpx; - height: 44rpx; - background: var(--fill-color, #666); - border-left: 2rpx solid var(--border-color, #333); - border-right: 2rpx solid var(--border-color, #333); - z-index: 1; - } - - /* 底部椭圆 */ - .cylinder-bottom { - position: absolute; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 50rpx; - height: 16rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - border-radius: 50%; - z-index: 2; - } - } - - /* 正方体 */ - &.shape-icon-cube { - width: 60rpx; - height: 60rpx; - border: none; - background: transparent; - position: relative; - display: flex; - align-items: center; - justify-content: center; - - /* 正面 */ - &::before { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 40rpx; - height: 40rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - z-index: 3; - } - - /* 顶面 */ - &::after { - content: ''; - position: absolute; - top: 0; - right: 0; - width: 40rpx; - height: 40rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - transform: skewX(-30deg) scaleY(0.6); - transform-origin: bottom left; - z-index: 2; - opacity: 0.8; - } - - /* 右面 */ - .cube-right { - position: absolute; - bottom: 0; - right: 0; - width: 20rpx; - height: 40rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - border-left: none; - transform: skewY(-30deg); - transform-origin: bottom left; - z-index: 1; - opacity: 0.6; - } - } - - /* 球体 */ - &.shape-icon-sphere { - width: 50rpx; - height: 50rpx; - border: 2rpx solid var(--border-color, #333); - border-radius: 50%; - background: var(--fill-color, #666); - position: relative; - display: flex; - align-items: center; - justify-content: center; - - /* 高光 */ - &::before { - content: ''; - position: absolute; - top: 12rpx; - left: 16rpx; - width: 12rpx; - height: 8rpx; - background: rgba(255, 255, 255, 0.6); - border-radius: 50%; - } - - /* 纬线 */ - &::after { - content: ''; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 46rpx; - height: 20rpx; - border: 1rpx solid var(--border-color, #333); - border-radius: 50%; - opacity: 0.3; - } - } - - /* 圆锥体 */ - &.shape-icon-cone { - width: 60rpx; - height: 60rpx; - border: none; - background: transparent; - position: relative; - display: flex; - align-items: center; - justify-content: center; - - /* 圆锥主体 */ - &::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 25rpx solid transparent; - border-right: 25rpx solid transparent; - border-bottom: 45rpx solid var(--fill-color, #666); - z-index: 2; - } - - /* 圆锥边框 */ - &::after { - content: ''; - position: absolute; - top: -1rpx; - left: 50%; - transform: translateX(-50%); - width: 0; - height: 0; - border-left: 26rpx solid transparent; - border-right: 26rpx solid transparent; - border-bottom: 46rpx solid var(--border-color, #333); - z-index: 1; - } - - /* 底部椭圆 */ - .cone-bottom { - position: absolute; - bottom: 0; - left: 50%; - transform: translateX(-50%); - width: 50rpx; - height: 16rpx; - background: var(--fill-color, #666); - border: 2rpx solid var(--border-color, #333); - border-radius: 50%; - z-index: 3; - } - } + display: block; } +/* 保留基础样式,移除所有形状相关的CSS */ + .shape-name { font-size: 24rpx; color: #666; diff --git a/miniprogram/demoPages/shapePrint/index.wxml b/miniprogram/demoPages/shapePrint/index.wxml index 1c95cf0..f101257 100644 --- a/miniprogram/demoPages/shapePrint/index.wxml +++ b/miniprogram/demoPages/shapePrint/index.wxml @@ -37,32 +37,11 @@ - - - - - - - - - - - - + + {{item.name}} @@ -158,24 +137,11 @@ - - - - - - - - - - + + {{item.name}}