feat: 测试图形绘制
This commit is contained in:
+17
-1
@@ -1,5 +1,9 @@
|
|||||||
{
|
{
|
||||||
"pages": ["pages/index/index", "pages/debug/debug"],
|
"pages": [
|
||||||
|
"pages/index/index",
|
||||||
|
"pages/debug/debug",
|
||||||
|
"pages/shapePrint/index"
|
||||||
|
],
|
||||||
"window": {},
|
"window": {},
|
||||||
"style": "v2",
|
"style": "v2",
|
||||||
"rendererOptions": {
|
"rendererOptions": {
|
||||||
@@ -18,5 +22,17 @@
|
|||||||
"scope.writePhotosAlbum": {
|
"scope.writePhotosAlbum": {
|
||||||
"desc": "需要保存涂色卡到相册"
|
"desc": "需要保存涂色卡到相册"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tabBar": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"pagePath": "pages/index/index",
|
||||||
|
"text": "识字"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pagePath": "pages/shapePrint/index",
|
||||||
|
"text": "图形"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ Page({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 1,
|
key: 1,
|
||||||
title: '文本绘制2',
|
title: '图形绘制',
|
||||||
url: '/demoPages/textPaint2/textPaint',
|
url: '/demoPages/shapePrint/index',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 2,
|
key: 2,
|
||||||
|
|||||||
@@ -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],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"navigationBarTitleText": "图形绘制 示例",
|
||||||
|
"navigationBarBackgroundColor": "#d2e7d8",
|
||||||
|
"homeButton": true,
|
||||||
|
"backgroundColor": "#e8eddb",
|
||||||
|
"enablePullDownRefresh": false
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<view class="container">
|
||||||
|
<!-- 形状卡片预览区 -->
|
||||||
|
<view class="card-container">
|
||||||
|
<view wx:for="{{shapeCards}}" wx:key="index" class="shape-card">
|
||||||
|
<image
|
||||||
|
src="{{item.svgData}}"
|
||||||
|
mode="aspectFit"
|
||||||
|
class="shape-image" />
|
||||||
|
<view
|
||||||
|
class="color-box"
|
||||||
|
style="background-color:{{item.fillColor}}"></view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 控制面板 -->
|
||||||
|
<view class="control-panel">
|
||||||
|
<slider
|
||||||
|
min="1"
|
||||||
|
max="5"
|
||||||
|
value="{{columns}}"
|
||||||
|
bindchange="setColumns"
|
||||||
|
show-value />
|
||||||
|
<text>布局列数: {{columns}}</text>
|
||||||
|
|
||||||
|
<button
|
||||||
|
bindtap="generateImage"
|
||||||
|
type="primary"
|
||||||
|
loading="{{isGenerating}}">
|
||||||
|
生成图片
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
bindtap="saveToAlbum"
|
||||||
|
wx:if="{{generatedImage}}"
|
||||||
|
type="warn"
|
||||||
|
plain>
|
||||||
|
保存到相册
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<!-- 预览区域 -->
|
||||||
|
<image
|
||||||
|
wx:if="{{generatedImage}}"
|
||||||
|
src="{{generatedImage}}"
|
||||||
|
class="preview-image"
|
||||||
|
bindtap="previewFullImage" />
|
||||||
|
|
||||||
|
<!-- 背景选择器 -->
|
||||||
|
<view class="bg-picker">
|
||||||
|
<text class="label">背景颜色:</text>
|
||||||
|
<radio-group bindchange="setBackground">
|
||||||
|
<radio
|
||||||
|
wx:for="{{backgrounds}}"
|
||||||
|
wx:key="value"
|
||||||
|
value="{{item.value}}"
|
||||||
|
color="{{item.color}}"
|
||||||
|
checked="{{bgColor === item.value}}">
|
||||||
|
{{item.name}}
|
||||||
|
</radio>
|
||||||
|
</radio-group>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
// shapes.js
|
||||||
|
const shapes = [
|
||||||
|
{
|
||||||
|
id: 'square',
|
||||||
|
name: '正方形',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<rect x="5" y="5" width="90" height="90" fill="var(--fill-color)" stroke="black" stroke-width="2"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'diamond',
|
||||||
|
name: '菱形',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<polygon points="50,5 95,50 50,95 5,50" fill="var(--fill-color)" stroke="black" stroke-width="2"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cylinder',
|
||||||
|
name: '圆柱体',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<rect x="20" y="40" width="60" height="50" rx="5" ry="5" fill="var(--fill-color)" stroke="black" stroke-width="2"/>
|
||||||
|
<ellipse cx="50" cy="40" rx="30" ry="10" fill="var(--fill-color)" stroke="black" stroke-width="2"/>
|
||||||
|
<ellipse cx="50" cy="90" rx="30" ry="10" fill="var(--fill-color)" stroke="black" stroke-width="2" style="opacity:0.7"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cube',
|
||||||
|
name: '立方体',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<polygon points="20,30 50,10 80,30 50,50" fill="var(--fill-color)" stroke="black" stroke-width="2" opacity="0.9"/>
|
||||||
|
<polygon points="50,50 80,30 80,70 50,90" fill="var(--fill-color)" stroke="black" stroke-width="2" opacity="0.7"/>
|
||||||
|
<polygon points="20,30 20,70 50,90 50,50" fill="var(--fill-color)" stroke="black" stroke-width="2" opacity="0.8"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'circle',
|
||||||
|
name: '圆形',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="40" fill="var(--fill-color)" stroke="black" stroke-width="2"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'sphere',
|
||||||
|
name: '球体',
|
||||||
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<circle cx="50" cy="50" r="40" fill="var(--fill-color)" stroke="black" stroke-width="2" style="opacity:0.9"/>
|
||||||
|
<ellipse cx="50" cy="50" rx="30" ry="10" fill="var(--fill-color)" stroke="black" stroke-width="2" style="opacity:0.7"/>
|
||||||
|
</svg>`,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// 颜色选项
|
||||||
|
const colors = [
|
||||||
|
{ name: '红色', value: '#ff5252' },
|
||||||
|
{ name: '蓝色', value: '#448aff' },
|
||||||
|
{ name: '绿色', value: '#69f0ae' },
|
||||||
|
{ name: '黄色', value: '#ffd740' },
|
||||||
|
{ name: '紫色', value: '#e040fb' },
|
||||||
|
];
|
||||||
|
|
||||||
|
export { shapes, colors };
|
||||||
Reference in New Issue
Block a user