337 lines
10 KiB
TypeScript
337 lines
10 KiB
TypeScript
import { SHAPES, ShapeCard } from '../../constants/shapes';
|
||
import { WATER_COLORS, PAPER_SIZE } from '../../constants/colors';
|
||
import ShapeDrawService from '../../service/shapeDrawService';
|
||
import { checkAndSaveImage } from '../../utils/saveImage';
|
||
import { shouldShowShareGuide } from '../../utils/shareGuide';
|
||
|
||
Page({
|
||
canvas: null as Canvas | null,
|
||
ctx: null as RenderingContext | null,
|
||
boxHeight: 0,
|
||
boxWidth: 0,
|
||
shapeDrawService: null as ShapeDrawService | null,
|
||
timer: null as any,
|
||
|
||
data: {
|
||
shapeList: [] as ShapeCard[],
|
||
showSelectShapePopup: false,
|
||
selectedShapeIndex: -1,
|
||
showColorPopup: false,
|
||
currentIndex: 0,
|
||
currentColor: '',
|
||
isPCDevtool: false,
|
||
showShareDialog: 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);
|
||
}
|
||
},
|
||
|
||
handleSelectShape() {
|
||
this.setData({
|
||
showSelectShapePopup: true,
|
||
selectedShapeIndex: -1,
|
||
});
|
||
},
|
||
|
||
openSelectShapePopup(e: any) {
|
||
const { index } = e.detail;
|
||
this.setData({
|
||
showSelectShapePopup: true,
|
||
selectedShapeIndex: index,
|
||
});
|
||
},
|
||
|
||
closeSelectShapePopup() {
|
||
this.setData({
|
||
showSelectShapePopup: false,
|
||
});
|
||
},
|
||
|
||
onChangeShape(e: any) {
|
||
const { shapes, singleMode, selectedShapeIndex } = e.detail;
|
||
const prevShapeList: ShapeCard[] = this.data.shapeList || [];
|
||
|
||
let newShapeList: ShapeCard[] = [];
|
||
|
||
if (singleMode) {
|
||
// 单选模式,只替换 selectedShapeIndex 对应的 shape,其他 shape 保持不变和原有颜色
|
||
newShapeList = prevShapeList.map((item, idx) => {
|
||
if (idx === selectedShapeIndex) {
|
||
// 替换为新选中的 shape,保留原有 fillColor
|
||
return {
|
||
...shapes[0],
|
||
fillColor: item.fillColor,
|
||
};
|
||
} else {
|
||
// 其他 shape 不变
|
||
return item;
|
||
}
|
||
});
|
||
} else {
|
||
// 多选模式,原有逻辑
|
||
// 获取所有可用颜色
|
||
const colorList = WATER_COLORS.basic12.map((item) => item.hex);
|
||
|
||
// 记录已被使用的颜色(只考虑当前 shapeList 中的 fillColor)
|
||
const usedColors = prevShapeList
|
||
.map((item) => item.fillColor)
|
||
.filter(Boolean);
|
||
|
||
// 记录已分配的颜色,避免重复
|
||
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,
|
||
shapeList: newShapeList,
|
||
},
|
||
() => {
|
||
this.drawCanvas();
|
||
},
|
||
);
|
||
},
|
||
/**
|
||
* 随机生成6个图形
|
||
*/
|
||
refreshShapeCard() {
|
||
// 随机取6个图形
|
||
const shapesCopy = [...SHAPES];
|
||
for (let i = shapesCopy.length - 1; i > 0; i--) {
|
||
const j = Math.floor(Math.random() * (i + 1));
|
||
[shapesCopy[i], shapesCopy[j]] = [shapesCopy[j], shapesCopy[i]];
|
||
}
|
||
const selectedShapes = shapesCopy.slice(0, 6);
|
||
|
||
// 随机取6个颜色
|
||
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, 6);
|
||
|
||
// 分配颜色到图形
|
||
const shapeList = selectedShapes.map((shape, idx) => ({
|
||
...shape,
|
||
fillColor: selectedColors[idx],
|
||
}));
|
||
|
||
// 更新数据
|
||
this.setData(
|
||
{
|
||
shapeList,
|
||
},
|
||
() => {
|
||
// 绘制canvas
|
||
this.drawCanvas();
|
||
},
|
||
);
|
||
},
|
||
onDelete(e: any) {
|
||
const { index } = e.detail;
|
||
const newShapeList = this.data.shapeList.filter((_, i) => i !== index);
|
||
this.setData(
|
||
{
|
||
shapeList: newShapeList,
|
||
},
|
||
() => {
|
||
// 绘制canvas
|
||
this.drawCanvas();
|
||
},
|
||
);
|
||
},
|
||
onColorTap(e: any) {
|
||
const { index, fillColor } = e.detail;
|
||
this.setData({
|
||
showColorPopup: true,
|
||
currentColor: fillColor,
|
||
selectedShapeIndex: index,
|
||
});
|
||
},
|
||
onCloseColorPopup() {
|
||
this.setData({
|
||
showColorPopup: false,
|
||
currentColor: '',
|
||
selectedShapeIndex: 0,
|
||
});
|
||
},
|
||
onChangeColor(e: any) {
|
||
const { color } = e.detail;
|
||
const { selectedShapeIndex } = this.data;
|
||
|
||
this.setData(
|
||
{
|
||
showColorPopup: false,
|
||
[`shapeList[${selectedShapeIndex}].fillColor`]: color,
|
||
},
|
||
() => {
|
||
// 绘制canvas
|
||
this.drawCanvas();
|
||
},
|
||
);
|
||
},
|
||
|
||
/** 下载打印 */
|
||
exportToPrint() {
|
||
if (!this.canvas || this.data.shapeList.length <= 0) {
|
||
return;
|
||
}
|
||
|
||
// 检查是否需要显示分享引导
|
||
if (shouldShowShareGuide()) {
|
||
this.setData({ showShareDialog: true });
|
||
return;
|
||
}
|
||
|
||
// 直接下载
|
||
checkAndSaveImage(this.canvas);
|
||
},
|
||
|
||
/** 关闭分享引导弹窗 */
|
||
onCloseShareDialog() {
|
||
this.setData({ showShareDialog: false });
|
||
},
|
||
|
||
onShareAppMessage() {
|
||
return {
|
||
title: '涂鸦丫-涂色|识字|图形|打印',
|
||
path: '/pages/shape/index',
|
||
imageUrl:
|
||
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
||
};
|
||
},
|
||
onShareTimeline() {
|
||
return {
|
||
title: '涂鸦丫-涂色|识字|图形|打印',
|
||
query: '/pages/shape/index',
|
||
imageUrl:
|
||
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
||
};
|
||
},
|
||
|
||
/** 分享成功回调(由组件触发) */
|
||
onShareSuccess() {
|
||
// 只关闭弹窗,不触发下载
|
||
this.setData({ showShareDialog: false });
|
||
},
|
||
|
||
onUnload() {
|
||
if (this.timer) {
|
||
clearTimeout(this.timer);
|
||
}
|
||
},
|
||
});
|