1014 lines
30 KiB
JavaScript
1014 lines
30 KiB
JavaScript
// 引入形状和颜色数据
|
|
import { shapes, colors } from './shapes';
|
|
import { WATER_COLORS } from '../../constants/colors';
|
|
|
|
Page({
|
|
data: {
|
|
// 可选择的形状列表
|
|
availableShapes: shapes,
|
|
// 已选择的形状列表
|
|
selectedShapes: [],
|
|
// 基础12色
|
|
basic12Colors: WATER_COLORS.basic12,
|
|
// 已使用的颜色索引(确保不重复)
|
|
usedColorIndexes: [],
|
|
// 生成的图片
|
|
generatedImage: null,
|
|
// 是否正在生成
|
|
isGenerating: false,
|
|
// 背景选项
|
|
backgrounds: [
|
|
{ name: '纯白', value: '#FFFFFF' },
|
|
{ name: '米黄', value: '#FAF5E8' },
|
|
{ name: '淡灰', value: '#F5F5F5' },
|
|
],
|
|
bgColor: '#FFFFFF',
|
|
// 弹窗控制
|
|
showShapeSelector: false,
|
|
showColorPicker: false,
|
|
// 当前编辑的形状索引
|
|
currentEditingIndex: -1,
|
|
currentEditingColor: '',
|
|
// 临时选择的形状ID列表(弹窗中的选择状态)
|
|
tempSelectedShapeIds: [],
|
|
},
|
|
|
|
onLoad() {
|
|
console.log('形状绘制页面加载完成');
|
|
// 初始化临时选择列表
|
|
this.setData({
|
|
tempSelectedShapeIds: this.data.selectedShapes.map(
|
|
(shape) => shape.id,
|
|
),
|
|
});
|
|
},
|
|
|
|
// 打开形状选择器
|
|
openShapeSelector() {
|
|
this.setData({
|
|
showShapeSelector: true,
|
|
// 将当前已选择的形状ID设置到临时列表中
|
|
tempSelectedShapeIds: this.data.selectedShapes.map(
|
|
(shape) => shape.shapeId,
|
|
),
|
|
});
|
|
},
|
|
|
|
// 关闭形状选择器
|
|
closeShapeSelector() {
|
|
this.setData({
|
|
showShapeSelector: false,
|
|
});
|
|
},
|
|
|
|
// 切换形状选择状态
|
|
toggleShape(e) {
|
|
const shapeId = e.currentTarget.dataset.shapeId;
|
|
const { tempSelectedShapeIds } = this.data;
|
|
|
|
if (tempSelectedShapeIds.includes(shapeId)) {
|
|
// 取消选择
|
|
const newTempSelected = tempSelectedShapeIds.filter(
|
|
(id) => id !== shapeId,
|
|
);
|
|
this.setData({
|
|
tempSelectedShapeIds: newTempSelected,
|
|
});
|
|
} else {
|
|
// 添加选择(最多6个)
|
|
if (tempSelectedShapeIds.length < 6) {
|
|
this.setData({
|
|
tempSelectedShapeIds: [...tempSelectedShapeIds, shapeId],
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '最多只能选择6个形状',
|
|
icon: 'none',
|
|
duration: 2000,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
// 确认形状选择
|
|
confirmShapeSelection() {
|
|
const { tempSelectedShapeIds, availableShapes, selectedShapes } =
|
|
this.data;
|
|
|
|
// 构建新的选择列表
|
|
const newSelectedShapes = [];
|
|
const usedColorIndexes = [];
|
|
|
|
tempSelectedShapeIds.forEach((shapeId, index) => {
|
|
const shape = availableShapes.find((s) => s.id === shapeId);
|
|
if (shape) {
|
|
// 查找是否已存在该形状
|
|
const existingShape = selectedShapes.find(
|
|
(s) => s.shapeId === shapeId,
|
|
);
|
|
if (existingShape) {
|
|
// 保持原有颜色
|
|
newSelectedShapes.push(existingShape);
|
|
const colorIndex = this.data.basic12Colors.findIndex(
|
|
(c) => c.hex === existingShape.fillColor,
|
|
);
|
|
if (colorIndex !== -1) {
|
|
usedColorIndexes.push(colorIndex);
|
|
}
|
|
} else {
|
|
// 新形状,分配随机颜色
|
|
const randomColor =
|
|
this.getRandomUnusedColor(usedColorIndexes);
|
|
newSelectedShapes.push({
|
|
id: Date.now() + index,
|
|
shapeId: shape.id,
|
|
name: shape.name,
|
|
fillColor: randomColor.hex,
|
|
colorName: randomColor.name,
|
|
drawFunction: shape.drawFunction,
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
this.setData(
|
|
{
|
|
selectedShapes: newSelectedShapes,
|
|
usedColorIndexes,
|
|
showShapeSelector: false,
|
|
},
|
|
() => {
|
|
// 自动生成新的打印纸
|
|
if (newSelectedShapes.length > 0) {
|
|
this.generateImage();
|
|
}
|
|
},
|
|
);
|
|
|
|
wx.showToast({
|
|
title: `已选择${newSelectedShapes.length}个形状`,
|
|
icon: 'success',
|
|
duration: 1500,
|
|
});
|
|
},
|
|
|
|
// 获取随机未使用的颜色
|
|
getRandomUnusedColor(usedIndexes) {
|
|
const { basic12Colors } = this.data;
|
|
const availableIndexes = [];
|
|
|
|
for (let i = 0; i < basic12Colors.length; i++) {
|
|
if (!usedIndexes.includes(i)) {
|
|
availableIndexes.push(i);
|
|
}
|
|
}
|
|
|
|
if (availableIndexes.length === 0) {
|
|
// 如果所有颜色都被使用,则重新开始
|
|
const randomIndex = Math.floor(
|
|
Math.random() * basic12Colors.length,
|
|
);
|
|
usedIndexes.push(randomIndex);
|
|
return basic12Colors[randomIndex];
|
|
}
|
|
|
|
const randomIndex =
|
|
availableIndexes[
|
|
Math.floor(Math.random() * availableIndexes.length)
|
|
];
|
|
usedIndexes.push(randomIndex);
|
|
return basic12Colors[randomIndex];
|
|
},
|
|
|
|
// 删除形状
|
|
deleteShape(e) {
|
|
const index = parseInt(e.currentTarget.dataset.index);
|
|
const selectedShapes = [...this.data.selectedShapes];
|
|
const deletedShape = selectedShapes[index];
|
|
|
|
// 释放颜色
|
|
const colorIndex = this.data.basic12Colors.findIndex(
|
|
(c) => c.hex === deletedShape.fillColor,
|
|
);
|
|
const usedColorIndexes = this.data.usedColorIndexes.filter(
|
|
(i) => i !== colorIndex,
|
|
);
|
|
|
|
selectedShapes.splice(index, 1);
|
|
|
|
this.setData(
|
|
{
|
|
selectedShapes,
|
|
usedColorIndexes,
|
|
},
|
|
() => {
|
|
// 如果还有形状,自动重新生成
|
|
if (selectedShapes.length > 0) {
|
|
this.generateImage();
|
|
} else {
|
|
// 清空生成的图片
|
|
this.setData({
|
|
generatedImage: null,
|
|
});
|
|
}
|
|
},
|
|
);
|
|
|
|
wx.showToast({
|
|
title: '已删除形状',
|
|
icon: 'success',
|
|
duration: 1000,
|
|
});
|
|
},
|
|
|
|
// 打开颜色选择器
|
|
openColorPicker(e) {
|
|
const index = parseInt(e.currentTarget.dataset.index);
|
|
const shape = this.data.selectedShapes[index];
|
|
|
|
this.setData({
|
|
showColorPicker: true,
|
|
currentEditingIndex: index,
|
|
currentEditingColor: shape.fillColor,
|
|
});
|
|
},
|
|
|
|
// 关闭颜色选择器
|
|
closeColorPicker() {
|
|
this.setData({
|
|
showColorPicker: false,
|
|
currentEditingIndex: -1,
|
|
currentEditingColor: '',
|
|
});
|
|
},
|
|
|
|
// 颜色改变事件
|
|
onColorChange(e) {
|
|
const { color } = e.detail;
|
|
const { currentEditingIndex, selectedShapes, basic12Colors } =
|
|
this.data;
|
|
|
|
if (currentEditingIndex >= 0) {
|
|
const newSelectedShapes = [...selectedShapes];
|
|
const colorInfo = basic12Colors.find((c) => c.hex === color) ||
|
|
WATER_COLORS.extended24.find((c) => c.hex === color) || {
|
|
name: '自定义色',
|
|
hex: color,
|
|
};
|
|
|
|
newSelectedShapes[currentEditingIndex] = {
|
|
...newSelectedShapes[currentEditingIndex],
|
|
fillColor: color,
|
|
colorName: colorInfo.name,
|
|
};
|
|
|
|
this.setData(
|
|
{
|
|
selectedShapes: newSelectedShapes,
|
|
showColorPicker: false,
|
|
currentEditingIndex: -1,
|
|
},
|
|
() => {
|
|
// 自动重新生成
|
|
this.generateImage();
|
|
},
|
|
);
|
|
|
|
wx.showToast({
|
|
title: '颜色已更新',
|
|
icon: 'success',
|
|
duration: 1000,
|
|
});
|
|
}
|
|
},
|
|
|
|
// 设置背景颜色
|
|
setBackground(e) {
|
|
this.setData(
|
|
{
|
|
bgColor: e.detail.value,
|
|
},
|
|
() => {
|
|
// 如果有形状,自动重新生成
|
|
if (this.data.selectedShapes.length > 0) {
|
|
this.generateImage();
|
|
}
|
|
},
|
|
);
|
|
},
|
|
|
|
// 生成练习页图片
|
|
async generateImage() {
|
|
if (this.data.selectedShapes.length === 0) {
|
|
wx.showToast({
|
|
title: '请先选择形状',
|
|
icon: 'none',
|
|
});
|
|
return;
|
|
}
|
|
|
|
this.setData({ isGenerating: true });
|
|
|
|
try {
|
|
const canvas = await this.getCanvas();
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
// A4纸张尺寸 (595x842 像素,72 DPI)
|
|
const width = 595;
|
|
const height = 842;
|
|
|
|
// 设置画布尺寸
|
|
canvas.width = width;
|
|
canvas.height = height;
|
|
|
|
// 设置背景
|
|
ctx.fillStyle = this.data.bgColor;
|
|
ctx.fillRect(0, 0, width, height);
|
|
|
|
// 绘制标题
|
|
this.drawTitle(ctx, width);
|
|
|
|
// 绘制有颜色的示例部分(上半部)
|
|
this.drawColoredExamples(ctx, width, height);
|
|
|
|
// 绘制分隔线
|
|
this.drawDivider(ctx, width, height);
|
|
|
|
// 绘制练习区域(下半部,无颜色)
|
|
this.drawPracticeArea(ctx, width, height);
|
|
|
|
// 导出图片
|
|
const tempFilePath = canvas.toTempFilePathSync({
|
|
fileType: 'png',
|
|
quality: 1,
|
|
});
|
|
|
|
this.setData({
|
|
generatedImage: tempFilePath,
|
|
isGenerating: false,
|
|
});
|
|
|
|
wx.showToast({
|
|
title: '生成完成',
|
|
icon: 'success',
|
|
});
|
|
} catch (error) {
|
|
console.error('生成图片失败:', error);
|
|
this.setData({ isGenerating: false });
|
|
wx.showToast({
|
|
title: '生成失败,请重试',
|
|
icon: 'none',
|
|
});
|
|
}
|
|
},
|
|
|
|
// 获取Canvas实例
|
|
getCanvas() {
|
|
return new Promise((resolve) => {
|
|
const query = wx.createSelectorQuery().in(this);
|
|
query
|
|
.select('#mainCanvas')
|
|
.fields({ node: true, size: true })
|
|
.exec((res) => {
|
|
const canvas = res[0].node;
|
|
resolve(canvas);
|
|
});
|
|
});
|
|
},
|
|
|
|
// 绘制标题
|
|
drawTitle(ctx, width) {
|
|
ctx.save();
|
|
ctx.fillStyle = '#333';
|
|
ctx.font = 'bold 24px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('形状涂色练习', width / 2, 40);
|
|
|
|
ctx.font = '16px Arial';
|
|
ctx.fillText('请为下面的形状涂上颜色', width / 2, 70);
|
|
ctx.restore();
|
|
},
|
|
|
|
// 绘制有颜色的示例
|
|
drawColoredExamples(ctx, width, height) {
|
|
const { selectedShapes } = this.data;
|
|
const startY = 100;
|
|
const shapeSize = 80;
|
|
const cols = 3;
|
|
const spacing = width / (cols + 1);
|
|
|
|
selectedShapes.forEach((shape, index) => {
|
|
const row = Math.floor(index / cols);
|
|
const col = index % cols;
|
|
const x = (col + 1) * spacing;
|
|
const y = startY + row * (shapeSize + 60);
|
|
|
|
// 绘制形状
|
|
this.drawShapeOnCanvas(ctx, shape, x, y, shapeSize, true);
|
|
|
|
// 绘制形状名称
|
|
ctx.save();
|
|
ctx.fillStyle = '#333';
|
|
ctx.font = '14px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText(shape.name, x, y + shapeSize / 2 + 25);
|
|
ctx.restore();
|
|
});
|
|
},
|
|
|
|
// 绘制分隔线
|
|
drawDivider(ctx, width, height) {
|
|
const y = height / 2;
|
|
ctx.save();
|
|
ctx.strokeStyle = '#ccc';
|
|
ctx.lineWidth = 2;
|
|
ctx.setLineDash([10, 5]);
|
|
ctx.beginPath();
|
|
ctx.moveTo(50, y);
|
|
ctx.lineTo(width - 50, y);
|
|
ctx.stroke();
|
|
|
|
// 分隔线文字
|
|
ctx.fillStyle = '#666';
|
|
ctx.font = '14px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText('练习区域(请涂色)', width / 2, y + 20);
|
|
ctx.restore();
|
|
},
|
|
|
|
// 绘制练习区域
|
|
drawPracticeArea(ctx, width, height) {
|
|
const { selectedShapes } = this.data;
|
|
const startY = height / 2 + 60;
|
|
const shapeSize = 80;
|
|
const cols = 3;
|
|
const spacing = width / (cols + 1);
|
|
|
|
selectedShapes.forEach((shape, index) => {
|
|
const row = Math.floor(index / cols);
|
|
const col = index % cols;
|
|
const x = (col + 1) * spacing;
|
|
const y = startY + row * (shapeSize + 60);
|
|
|
|
// 绘制形状(无填充色,仅轮廓)
|
|
this.drawShapeOnCanvas(ctx, shape, x, y, shapeSize, false);
|
|
|
|
// 绘制形状名称
|
|
ctx.save();
|
|
ctx.fillStyle = '#333';
|
|
ctx.font = '14px Arial';
|
|
ctx.textAlign = 'center';
|
|
ctx.fillText(shape.name, x, y + shapeSize / 2 + 25);
|
|
ctx.restore();
|
|
});
|
|
},
|
|
|
|
// 在Canvas上绘制形状
|
|
drawShapeOnCanvas(ctx, shape, x, y, size, filled) {
|
|
const color = filled ? shape.fillColor : 'transparent';
|
|
|
|
switch (shape.drawFunction) {
|
|
case 'drawCircle':
|
|
this.drawCircle(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawSquare':
|
|
this.drawSquare(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawRectangle':
|
|
this.drawRectangle(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawTriangle':
|
|
this.drawTriangle(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawOval':
|
|
this.drawOval(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawDiamond':
|
|
this.drawDiamond(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawStar':
|
|
this.drawStar(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawHeart':
|
|
this.drawHeart(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawHexagon':
|
|
this.drawHexagon(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawPentagon':
|
|
this.drawPentagon(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawSemicircle':
|
|
this.drawSemicircle(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawTrapezoid':
|
|
this.drawTrapezoid(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawCylinder':
|
|
this.drawCylinder(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawCube':
|
|
this.drawCube(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawSphere':
|
|
this.drawSphere(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
case 'drawCone':
|
|
this.drawCone(ctx, x, y, size, color, '#333', filled);
|
|
break;
|
|
}
|
|
},
|
|
|
|
// 绘制各种形状的函数(更新参数以支持边框色)
|
|
drawCircle(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, size * 0.4, 0, Math.PI * 2);
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawSquare(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const squareSize = size * 0.8;
|
|
const startX = x - squareSize / 2;
|
|
const startY = y - squareSize / 2;
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(startX, startY, squareSize, squareSize);
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeRect(startX, startY, squareSize, squareSize);
|
|
ctx.restore();
|
|
},
|
|
|
|
drawRectangle(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const rectWidth = size * 0.9;
|
|
const rectHeight = size * 0.6;
|
|
const startX = x - rectWidth / 2;
|
|
const startY = y - rectHeight / 2;
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(startX, startY, rectWidth, rectHeight);
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.strokeRect(startX, startY, rectWidth, rectHeight);
|
|
ctx.restore();
|
|
},
|
|
|
|
drawTriangle(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const triangleSize = size * 0.8;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y - triangleSize / 2);
|
|
ctx.lineTo(x - triangleSize / 2, y + triangleSize / 2);
|
|
ctx.lineTo(x + triangleSize / 2, y + triangleSize / 2);
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawOval(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y, size * 0.45, size * 0.3, 0, 0, Math.PI * 2);
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawDiamond(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const diamondSize = size * 0.4;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y - diamondSize);
|
|
ctx.lineTo(x + diamondSize, y);
|
|
ctx.lineTo(x, y + diamondSize);
|
|
ctx.lineTo(x - diamondSize, y);
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawStar(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const outerRadius = size * 0.4;
|
|
const innerRadius = size * 0.2;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 10; i++) {
|
|
const angle = (i * Math.PI) / 5;
|
|
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
|
const pointX = x + Math.cos(angle - Math.PI / 2) * radius;
|
|
const pointY = y + Math.sin(angle - Math.PI / 2) * radius;
|
|
if (i === 0) ctx.moveTo(pointX, pointY);
|
|
else ctx.lineTo(pointX, pointY);
|
|
}
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawHeart(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const heartSize = size * 0.4;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y + heartSize * 0.3);
|
|
ctx.bezierCurveTo(
|
|
x - heartSize * 0.5,
|
|
y - heartSize * 0.3,
|
|
x - heartSize,
|
|
y + heartSize * 0.1,
|
|
x,
|
|
y + heartSize * 0.8,
|
|
);
|
|
ctx.bezierCurveTo(
|
|
x + heartSize,
|
|
y + heartSize * 0.1,
|
|
x + heartSize * 0.5,
|
|
y - heartSize * 0.3,
|
|
x,
|
|
y + heartSize * 0.3,
|
|
);
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawHexagon(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.4;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 6; i++) {
|
|
const angle = (i * Math.PI) / 3;
|
|
const pointX = x + Math.cos(angle) * radius;
|
|
const pointY = y + Math.sin(angle) * radius;
|
|
if (i === 0) ctx.moveTo(pointX, pointY);
|
|
else ctx.lineTo(pointX, pointY);
|
|
}
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawPentagon(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.4;
|
|
ctx.beginPath();
|
|
for (let i = 0; i < 5; i++) {
|
|
const angle = (i * 2 * Math.PI) / 5 - Math.PI / 2;
|
|
const pointX = x + Math.cos(angle) * radius;
|
|
const pointY = y + Math.sin(angle) * radius;
|
|
if (i === 0) ctx.moveTo(pointX, pointY);
|
|
else ctx.lineTo(pointX, pointY);
|
|
}
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawSemicircle(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.4;
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, radius, 0, Math.PI);
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
drawTrapezoid(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const trapezoidSize = size * 0.4;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x - trapezoidSize * 0.6, y - trapezoidSize * 0.5);
|
|
ctx.lineTo(x + trapezoidSize * 0.6, y - trapezoidSize * 0.5);
|
|
ctx.lineTo(x + trapezoidSize, y + trapezoidSize * 0.5);
|
|
ctx.lineTo(x - trapezoidSize, y + trapezoidSize * 0.5);
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
ctx.restore();
|
|
},
|
|
|
|
// === 3D形状绘制函数 ===
|
|
|
|
// 绘制圆柱体
|
|
drawCylinder(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.3;
|
|
const height = size * 0.6;
|
|
|
|
// 绘制主体
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(x - radius, y - height / 2, radius * 2, height);
|
|
}
|
|
|
|
// 绘制主体边框
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.beginPath();
|
|
ctx.moveTo(x - radius, y - height / 2);
|
|
ctx.lineTo(x - radius, y + height / 2);
|
|
ctx.moveTo(x + radius, y - height / 2);
|
|
ctx.lineTo(x + radius, y + height / 2);
|
|
ctx.stroke();
|
|
|
|
// 绘制顶部椭圆
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y - height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2);
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
ctx.stroke();
|
|
|
|
// 绘制底部椭圆
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y + height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2);
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
},
|
|
|
|
// 绘制正方体
|
|
drawCube(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const cubeSize = size * 0.4;
|
|
const offset = cubeSize * 0.3; // 3D偏移量
|
|
|
|
// 绘制后面(右侧面)
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.globalAlpha = 0.6; // 较暗
|
|
ctx.fillRect(
|
|
x - cubeSize / 2 + offset,
|
|
y - cubeSize / 2 - offset,
|
|
cubeSize,
|
|
cubeSize,
|
|
);
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 2;
|
|
ctx.strokeRect(
|
|
x - cubeSize / 2 + offset,
|
|
y - cubeSize / 2 - offset,
|
|
cubeSize,
|
|
cubeSize,
|
|
);
|
|
|
|
// 绘制前面(正面)
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fillRect(
|
|
x - cubeSize / 2,
|
|
y - cubeSize / 2,
|
|
cubeSize,
|
|
cubeSize,
|
|
);
|
|
}
|
|
|
|
ctx.strokeRect(x - cubeSize / 2, y - cubeSize / 2, cubeSize, cubeSize);
|
|
|
|
// 绘制连接线
|
|
ctx.beginPath();
|
|
// 左上角连接线
|
|
ctx.moveTo(x - cubeSize / 2, y - cubeSize / 2);
|
|
ctx.lineTo(x - cubeSize / 2 + offset, y - cubeSize / 2 - offset);
|
|
// 右上角连接线
|
|
ctx.moveTo(x + cubeSize / 2, y - cubeSize / 2);
|
|
ctx.lineTo(x + cubeSize / 2 + offset, y - cubeSize / 2 - offset);
|
|
// 右下角连接线
|
|
ctx.moveTo(x + cubeSize / 2, y + cubeSize / 2);
|
|
ctx.lineTo(x + cubeSize / 2 + offset, y + cubeSize / 2 - offset);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
},
|
|
|
|
// 绘制球体
|
|
drawSphere(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.4;
|
|
|
|
// 绘制主体圆形
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
// 创建径向渐变模拟3D效果
|
|
const gradient = ctx.createRadialGradient(
|
|
x - radius * 0.3,
|
|
y - radius * 0.3,
|
|
0,
|
|
x,
|
|
y,
|
|
radius,
|
|
);
|
|
gradient.addColorStop(0, '#ffffff');
|
|
gradient.addColorStop(0.3, fillColor);
|
|
gradient.addColorStop(1, '#000000');
|
|
ctx.fillStyle = gradient;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
|
|
// 绘制纬线
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 1;
|
|
ctx.globalAlpha = 0.5;
|
|
|
|
// 水平纬线
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y, radius, radius * 0.3, 0, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
// 垂直经线
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y, radius * 0.3, radius, 0, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
},
|
|
|
|
// 绘制圆锥体
|
|
drawCone(ctx, x, y, size, fillColor, borderColor, filled = true) {
|
|
ctx.save();
|
|
const radius = size * 0.3;
|
|
const height = size * 0.6;
|
|
|
|
// 绘制圆锥主体
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, y - height / 2); // 顶点
|
|
ctx.lineTo(x - radius, y + height / 2); // 左下
|
|
ctx.lineTo(x + radius, y + height / 2); // 右下
|
|
ctx.closePath();
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.strokeStyle = borderColor;
|
|
ctx.lineWidth = 3;
|
|
ctx.stroke();
|
|
|
|
// 绘制底部椭圆
|
|
ctx.beginPath();
|
|
ctx.ellipse(x, y + height / 2, radius, radius * 0.3, 0, 0, Math.PI * 2);
|
|
|
|
if (filled && fillColor !== 'transparent') {
|
|
ctx.fillStyle = fillColor;
|
|
ctx.fill();
|
|
}
|
|
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
},
|
|
|
|
// 保存到相册
|
|
saveToAlbum() {
|
|
if (!this.data.generatedImage) {
|
|
wx.showToast({
|
|
title: '没有可保存的图片',
|
|
icon: 'none',
|
|
});
|
|
return;
|
|
}
|
|
|
|
wx.saveImageToPhotosAlbum({
|
|
filePath: this.data.generatedImage,
|
|
success: () => {
|
|
wx.showToast({
|
|
title: '已保存到相册',
|
|
icon: 'success',
|
|
});
|
|
},
|
|
fail: (err) => {
|
|
console.error('保存失败:', err);
|
|
if (err.errMsg.includes('auth')) {
|
|
wx.showModal({
|
|
title: '提示',
|
|
content: '需要授权访问相册才能保存图片',
|
|
confirmText: '去授权',
|
|
success: (res) => {
|
|
if (res.confirm) {
|
|
wx.openSetting();
|
|
}
|
|
},
|
|
});
|
|
} else {
|
|
wx.showToast({
|
|
title: '保存失败',
|
|
icon: 'none',
|
|
});
|
|
}
|
|
},
|
|
});
|
|
},
|
|
|
|
// 预览图片
|
|
previewImage() {
|
|
if (this.data.generatedImage) {
|
|
wx.previewImage({
|
|
urls: [this.data.generatedImage],
|
|
current: this.data.generatedImage,
|
|
});
|
|
}
|
|
},
|
|
});
|