feat: 生产 shape

This commit is contained in:
2025-08-08 17:32:39 +08:00
parent 738358ab76
commit 64deb9f3be
16 changed files with 1219 additions and 160 deletions
+2 -1
View File
@@ -10,6 +10,7 @@
"van-popup": "@vant/weapp/popup/index",
"toy-button": "../../ui/button/button",
"shape-card": "../../components/shape-card/shape-card",
"shape-picker": "../../components/shape-picker/shape-picker"
"shape-picker": "../../components/shape-picker/shape-picker",
"color-picker": "../../components/color-picker/color-picker"
}
}
+16
View File
@@ -42,4 +42,20 @@ page {
row-gap: 24rpx;
justify-content: flex-start;
}
}
.bottom-btn-box {
position: fixed;
left: 0;
bottom: 0;
width: 100%;
height: 120rpx;
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
box-shadow: 0px -3.9rpx 5.2rpx rgba(0, 0, 0, 0.15);
padding: 0 24rpx;
box-sizing: border-box;
border-radius: 16rpx 16rpx 0 0;
}
+231 -37
View File
@@ -1,68 +1,173 @@
import { SHAPES, ShapeCard } from '../../constants/shapes';
import { WATER_COLORS, PAPER_SIZE } from '../../constants/colors';
import ShapeDrawService from '../../service/shapeDrawService';
import { checkAndSaveImage } from '../../utils/saveImage';
Page({
canvas: null as Canvas | null,
ctx: null as RenderingContext | null,
boxHeight: 0,
boxWidth: 0,
shapeDrawService: null as ShapeDrawService | null,
data: {
shapeList: [] as ShapeCard[],
showSelectShapePopup: false,
currentShapeKey: '',
currentShapeId: '',
showColorPopup: false,
currentIndex: 0,
currentColor: '',
isPCDevtool: false,
},
onLoad() {
// 判断是否在PC端开发工具上运行
// const systemInfo = wx.getDeviceInfo();
// if (systemInfo.platform === 'devtools') {
// this.setData({
// isPCDevtool: true,
// });
// }
this.refreshShapeCard();
},
onReady() {
const query = wx.createSelectorQuery();
query
.select('#canvasWrapper')
.boundingClientRect((rect) => {
if (rect) {
const { width, height } = PAPER_SIZE['A4'];
const boxWidth = rect.width;
const boxHeight = boxWidth / (width / height);
this.boxHeight = boxHeight;
this.boxWidth = boxWidth;
this.initCanvas(boxWidth, boxHeight);
}
})
.exec();
},
initCanvas(boxWidth: number, boxHeight: number) {
wx.createSelectorQuery()
.select('#canvasContent')
.fields({
node: true,
size: true,
})
.exec((res) => {
if (res[0] && res[0].node) {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
if (ctx) {
this.canvas = canvas;
this.ctx = ctx;
// const rect = res[0];
this.shapeDrawService = new ShapeDrawService(canvas, ctx);
// shapeDrawService.draw();
this.setData({ boxWidth, boxHeight });
this.drawCanvas();
}
}
});
},
/** 变更shapeList 并更新canvas */
drawCanvas(
updatedShapeList?: ShapeCard[],
callback: () => void = () => { },
) {
if (updatedShapeList) {
this.setData({ shapeList: updatedShapeList }, () => {
this.shapeDrawService?.draw(updatedShapeList!);
callback();
});
} else {
const shapeList = this.data.shapeList;
this.shapeDrawService?.draw(shapeList);
}
},
openSelectShapePopup(e: any) {
const { key } = e.detail;
this.setData({
showSelectShapePopup: true,
currentShapeKey: key
currentShapeId: key
});
},
closeSelectShapePopup() {
this.setData({
showSelectShapePopup: false,
currentShapeKey: ''
currentShapeId: ''
});
},
onChangeShape(e: any) {
const { shape, currentShapeKey, shapes } = e.detail;
console.log('shape', shape);
console.log('shapes', shapes);
if (currentShapeKey) {
this.setData({
showSelectShapePopup: false,
currentShapeKey: '',
shapeList: this.data.shapeList.map(item => {
if (item.id === currentShapeKey) {
return { ...item, fillColor: item.fillColor };
}
return item;
})
});
} else {
// 随机从 WATER_COLORS.basic12 中获取不重复的颜色
const colorList = WATER_COLORS.basic12.map(item => item.hex);
const colorsCopy = [...colorList];
for (let i = colorsCopy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[colorsCopy[i], colorsCopy[j]] = [colorsCopy[j], colorsCopy[i]];
}
const selectedColors = colorsCopy.slice(0, shapes.length);
const { shapes } = e.detail;
const prevShapeList: ShapeCard[] = this.data.shapeList || [];
// 1. 保留已选图形的 fillColor,不变
// 2. 新增的图形分配未被占用的颜色,且不重复
this.setData({
showSelectShapePopup: false,
currentShapeKey: '',
shapeList: shapes.map((item: ShapeCard, idx: number) => ({
...item,
fillColor: selectedColors[idx] || item.fillColor
}))
});
// 获取所有可用颜色
const colorList = WATER_COLORS.basic12.map(item => item.hex);
// 记录已被使用的颜色(只考虑当前 shapeList 中的 fillColor
const usedColors = prevShapeList.map(item => item.fillColor).filter(Boolean);
// 新的 shapeList
const newShapeList: ShapeCard[] = [];
// 记录已分配的颜色,避免重复
const assignedColors = [...usedColors];
// 先将 shapes 转为 Map,方便查找
const prevShapeMap = new Map(prevShapeList.map(item => [item.id, item]));
// 随机打乱剩余可用颜色
function shuffle(arr: string[]) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr;
}
// 计算未被占用的颜色
let availableColors = colorList.filter(color => !assignedColors.includes(color));
availableColors = shuffle(availableColors);
shapes.forEach((item: ShapeCard) => {
if (prevShapeMap.has(item.id)) {
// 已存在,保留原有 fillColor
newShapeList.push({
...item,
fillColor: prevShapeMap.get(item.id)!.fillColor
});
} else {
// 新增,分配未被占用的颜色
const color = availableColors.shift() || colorList[0];
assignedColors.push(color);
newShapeList.push({
...item,
fillColor: color
});
}
});
this.setData({
showSelectShapePopup: false,
currentShapeId: '',
shapeList: newShapeList
}, () => {
// 绘制canvas
this.drawCanvas();
});
},
/**
* 随机生成6个图形
@@ -94,6 +199,95 @@ Page({
// 更新数据
this.setData({
shapeList
}, () => {
// 绘制canvas
this.drawCanvas();
});
}
});
},
onDelete(e: any) {
const { index } = e.detail;
const newShapeList = this.data.shapeList.filter((item, i) => i !== index);
this.setData({
shapeList: newShapeList
}, () => {
// 绘制canvas
this.drawCanvas();
});
},
onColorTap(e: any) {
const { index, fillColor } = e.detail;
console.log('shape Page onColorTap', index, fillColor);
this.setData({
showColorPopup: true,
currentColor: fillColor,
currentIndex: index
});
},
onCloseColorPopup() {
this.setData({
showColorPopup: false,
currentColor: '',
currentIndex: 0
});
},
onChangeColor(e: any) {
const { color } = e.detail;
const { currentIndex } = this.data;
this.setData({
showColorPopup: false,
[`shapeList[${currentIndex}].fillColor`]: color
}, () => {
// 绘制canvas
this.drawCanvas();
})
},
// 测试ShapeDrawService
// testShapeDrawService() {
// if (!this.shapeDrawService) {
// wx.showToast({
// title: 'Canvas未初始化',
// icon: 'none'
// });
// return;
// }
// // 使用当前选中的图形进行测试
// const testShapes = this.data.shapeList.slice(0, 3); // 只测试前3个图形
// if (testShapes.length === 0) {
// wx.showToast({
// title: '请先选择图形',
// icon: 'none'
// });
// return;
// }
// this.shapeDrawService.draw(testShapes);
// wx.showToast({
// title: '绘制完成',
// icon: 'success'
// });
// },
onShareAppMessage() {
return {
title: '涂鸦丫-涂色|识字|画画|打印',
path: '/pages/index/index',
};
},
onShareTimeline() {
return {
title: '涂鸦丫-涂色|识字|画画|打印',
query: '/pages/index/index',
};
},
/** 下载打印 */
exportToPrint() {
if (this.canvas && this.data.shapeList.length > 0) {
checkAndSaveImage(this.canvas);
return;
}
},
});
+44 -2
View File
@@ -24,7 +24,7 @@
wx:for-item="item"
wx:for-index="index"
wx:key="{{index}}"
key="{{index}}"
index="{{index}}"
svg="{{ item.svg }}"
fillColor="{{ item.fillColor }}"
bind:onShapeTap="openSelectShapePopup"
@@ -34,7 +34,49 @@
</view>
<shape-picker
show="{{showSelectShapePopup}}"
currentShapeKey="{{currentShapeKey}}"
bind:onClose="closeSelectShapePopup"
bind:onChange="onChangeShape" />
<color-picker
show="{{showColorPopup}}"
currentColor="{{currentColor}}"
bind:onClose="onCloseColorPopup"
bind:onChange="onChangeColor" />
<view id="previewWrapper" class="wrapper">
<text class="wrapper-title">预览打印效果</text>
<view
wx:if="{{!isPCDevtool}}"
id="canvasWrapper"
class="canvas-wrapper">
<canvas
type="2d"
id="canvasContent"
class="canvas-content"
style="width:{{boxWidth}}px;height:{{boxHeight}}px" />
</view>
</view>
</view>
<view class="bottom-btn-box">
<toy-button
openType="share"
type="green"
flat="{{true}}"
bind:click="onShareAppMessage"
width="220rpx"
height="80rpx"
icon="wechat"
icon-class-prefix="toy-icon">
分享
</toy-button>
<toy-button
type="primary"
flat="{{true}}"
bind:click="exportToPrint"
width="420rpx"
height="80rpx"
disabled="{{shapeList.length <= 0}}"
icon="download"
icon-class-prefix="toy-icon">
下载打印
</toy-button>
</view>