feat: 图形测试

This commit is contained in:
2025-07-23 12:49:16 +08:00
parent 9ade9d8357
commit 5412f957c1
3 changed files with 126 additions and 551 deletions
+105 -15
View File
@@ -35,14 +35,68 @@ Page({
onLoad() { onLoad() {
console.log('形状绘制页面加载完成'); console.log('形状绘制页面加载完成');
// 为可选形状生成SVG内容
const shapesWithSVG = this.data.availableShapes.map((shape) => ({
...shape,
svgContent: this.createSVGElement(shape, 'transparent', '#333', 50),
}));
// 初始化临时选择列表 // 初始化临时选择列表
this.setData({ this.setData({
availableShapes: shapesWithSVG,
tempSelectedShapeIds: this.data.selectedShapes.map( tempSelectedShapeIds: this.data.selectedShapes.map(
(shape) => shape.id, (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 = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="${size}" height="${size}">${coloredSVG}</svg>`;
// 转换为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() { openShapeSelector() {
this.setData({ this.setData({
@@ -107,8 +161,17 @@ Page({
(s) => s.shapeId === shapeId, (s) => s.shapeId === shapeId,
); );
if (existingShape) { if (existingShape) {
// 保持原有颜色 // 保持原有颜色,更新SVG内容
newSelectedShapes.push(existingShape); const updatedShape = {
...existingShape,
svgContent: this.createSVGElement(
shape,
existingShape.fillColor,
'#333',
60,
),
};
newSelectedShapes.push(updatedShape);
const colorIndex = this.data.basic12Colors.findIndex( const colorIndex = this.data.basic12Colors.findIndex(
(c) => c.hex === existingShape.fillColor, (c) => c.hex === existingShape.fillColor,
); );
@@ -119,14 +182,21 @@ Page({
// 新形状,分配随机颜色 // 新形状,分配随机颜色
const randomColor = const randomColor =
this.getRandomUnusedColor(usedColorIndexes); this.getRandomUnusedColor(usedColorIndexes);
newSelectedShapes.push({ const newShape = {
id: Date.now() + index, id: Date.now() + index,
shapeId: shape.id, shapeId: shape.id,
name: shape.name, name: shape.name,
fillColor: randomColor.hex, fillColor: randomColor.hex,
colorName: randomColor.name, colorName: randomColor.name,
drawFunction: shape.drawFunction, drawFunction: shape.drawFunction,
}); svgContent: this.createSVGElement(
shape,
randomColor.hex,
'#333',
60,
),
};
newSelectedShapes.push(newShape);
} }
} }
}); });
@@ -250,6 +320,10 @@ Page({
if (currentEditingIndex >= 0) { if (currentEditingIndex >= 0) {
const newSelectedShapes = [...selectedShapes]; 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) || const colorInfo = basic12Colors.find((c) => c.hex === color) ||
WATER_COLORS.extended24.find((c) => c.hex === color) || { WATER_COLORS.extended24.find((c) => c.hex === color) || {
name: '自定义色', name: '自定义色',
@@ -257,9 +331,15 @@ Page({
}; };
newSelectedShapes[currentEditingIndex] = { newSelectedShapes[currentEditingIndex] = {
...newSelectedShapes[currentEditingIndex], ...currentShape,
fillColor: color, fillColor: color,
colorName: colorInfo.name, colorName: colorInfo.name,
svgContent: this.createSVGElement(
shapeDefinition,
color,
'#333',
60,
),
}; };
this.setData( this.setData(
@@ -338,19 +418,29 @@ Page({
this.drawPracticeArea(ctx, width, height); this.drawPracticeArea(ctx, width, height);
// 导出图片 // 导出图片
const tempFilePath = canvas.toTempFilePathSync({ wx.canvasToTempFilePath({
canvas: canvas,
fileType: 'png', fileType: 'png',
quality: 1, quality: 1,
}); success: (res) => {
this.setData({
generatedImage: res.tempFilePath,
isGenerating: false,
});
this.setData({ wx.showToast({
generatedImage: tempFilePath, title: '生成完成',
isGenerating: false, icon: 'success',
}); });
},
wx.showToast({ fail: (error) => {
title: '生成完成', console.error('导出图片失败:', error);
icon: 'success', this.setData({ isGenerating: false });
wx.showToast({
title: '生成失败,请重试',
icon: 'none',
});
},
}); });
} catch (error) { } catch (error) {
console.error('生成图片失败:', error); console.error('生成图片失败:', error);
+11 -492
View File
@@ -271,502 +271,21 @@
justify-content: center; justify-content: center;
} }
.shape-icon { /* SVG 样式 */
.shape-svg {
width: 60rpx;
height: 60rpx;
display: block;
}
.shape-svg-small {
width: 50rpx; width: 50rpx;
height: 50rpx; height: 50rpx;
border: 2rpx solid #666; display: block;
&.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;
}
}
} }
/* 保留基础样式,移除所有形状相关的CSS */
.shape-name { .shape-name {
font-size: 24rpx; font-size: 24rpx;
color: #666; color: #666;
+10 -44
View File
@@ -37,32 +37,11 @@
</view> </view>
<!-- 形状预览 --> <!-- 形状预览 -->
<view class="shape-preview-container"> <view class="shape-preview-container">
<canvas <image
type="2d" src="{{item.svgContent}}"
id="preview-canvas-{{index}}" class="shape-svg"
class="shape-preview-canvas" mode="aspectFit">
style="background-color: {{item.fillColor}};"> </image>
</canvas>
<view
class="shape-icon shape-icon-{{item.id}}"
style="--fill-color: {{item.fillColor}}; --border-color: #333333;">
<!-- 圆柱体底部 -->
<view
wx:if="{{item.id === 'cylinder'}}"
class="cylinder-bottom"></view>
<!-- 正方体右面 -->
<view
wx:if="{{item.id === 'cube'}}"
class="cube-right"></view>
<!-- 圆锥底部 -->
<view
wx:if="{{item.id === 'cone'}}"
class="cone-bottom"></view>
<!-- 爱心右半圆 -->
<view
wx:if="{{item.id === 'heart'}}"
class="heart-right"></view>
</view>
</view> </view>
<!-- 形状名称 --> <!-- 形状名称 -->
<text class="shape-name">{{item.name}}</text> <text class="shape-name">{{item.name}}</text>
@@ -158,24 +137,11 @@
</view> </view>
<!-- 形状预览 --> <!-- 形状预览 -->
<view class="shape-preview-box"> <view class="shape-preview-box">
<view class="shape-icon shape-icon-{{item.id}}"> <image
<!-- 圆柱体底部 --> src="{{item.svgContent}}"
<view class="shape-svg-small"
wx:if="{{item.id === 'cylinder'}}" mode="aspectFit">
class="cylinder-bottom"></view> </image>
<!-- 正方体右面 -->
<view
wx:if="{{item.id === 'cube'}}"
class="cube-right"></view>
<!-- 圆锥底部 -->
<view
wx:if="{{item.id === 'cone'}}"
class="cone-bottom"></view>
<!-- 爱心右半圆 -->
<view
wx:if="{{item.id === 'heart'}}"
class="heart-right"></view>
</view>
</view> </view>
<text class="shape-name">{{item.name}}</text> <text class="shape-name">{{item.name}}</text>
</view> </view>