feat:数字首页
@@ -2,10 +2,15 @@
|
||||
"pages": [
|
||||
"pages/index/index",
|
||||
"pages/shape/index",
|
||||
"pages/debug/debug",
|
||||
"pages/wordDemo/index",
|
||||
"pages/mathIndex/mathIndex",
|
||||
"pages/copyBook/copyBook",
|
||||
"pages/buttonTest/buttonTest"
|
||||
"pages/debug/debug"
|
||||
],
|
||||
"subPackages": [
|
||||
{
|
||||
"root": "mathPages",
|
||||
"pages": ["numberColor/numberColor"]
|
||||
}
|
||||
],
|
||||
"window": {},
|
||||
"style": "v2",
|
||||
@@ -37,6 +42,12 @@
|
||||
"selectedIconPath": "assets/tabBar/icon-word-active.png",
|
||||
"text": "识字"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mathIndex/mathIndex",
|
||||
"iconPath": "assets/tabBar/icon-math.png",
|
||||
"selectedIconPath": "assets/tabBar/icon-math-active.png",
|
||||
"text": "数学"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/copyBook/copyBook",
|
||||
"iconPath": "assets/tabBar/icon-edit.png",
|
||||
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
@@ -1,26 +0,0 @@
|
||||
const PAPER_SIZE = {
|
||||
A4: {
|
||||
// width: 2480,
|
||||
// height: 3508,
|
||||
width: 595,
|
||||
height: 842,
|
||||
},
|
||||
A3: {
|
||||
width: 3508,
|
||||
height: 4960,
|
||||
},
|
||||
A2: {
|
||||
width: 4960,
|
||||
height: 7016,
|
||||
},
|
||||
A1: {
|
||||
width: 7016,
|
||||
height: 10032,
|
||||
},
|
||||
A0: {
|
||||
width: 10032,
|
||||
height: 14176,
|
||||
},
|
||||
};
|
||||
|
||||
export { PAPER_SIZE };
|
||||
@@ -1,125 +0,0 @@
|
||||
import { PAPER_SIZE } from './constant';
|
||||
import { getMiniCodeImagePath } from './utils';
|
||||
|
||||
class TextDrawService {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
|
||||
constructor(canvas: Canvas, ctx: RenderingContext, options?: Record<string, any>) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦呀',
|
||||
appHint: '涂色|识字|画画|认知',
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '把相同的文字涂上相同的颜色',
|
||||
radius: 80,
|
||||
colors: ['#2196F3', '#FFEB3B', '#FF9800', '#F44336'],
|
||||
characters: ['鱼', '鸟', '猫', '狗'],
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.setPaper();
|
||||
this.drawHeader();
|
||||
this.drawLegend();
|
||||
}
|
||||
|
||||
drawHeader() {
|
||||
const { canvas, ctx } = this;
|
||||
const { title } = this.options;
|
||||
|
||||
ctx.font = 'bold 80px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.textAlign = 'center';
|
||||
const titleX = canvas.width / 2;
|
||||
const titleY = 120;
|
||||
ctx.fillText(title, titleX, titleY);
|
||||
|
||||
// 记录当前X、Y坐标
|
||||
this.currentX = titleX;
|
||||
this.currentY = titleY + 50;
|
||||
// 绘制分割线
|
||||
this.drawLine(this.currentY);
|
||||
}
|
||||
|
||||
// 绘制示例
|
||||
drawLegend() {
|
||||
const { ctx, options } = this;
|
||||
const { colors, characters, radius } = options;
|
||||
const startX = (this.canvas.width - colors.length * 400) / 2; // 计算起始X坐标以居中
|
||||
this.currentY = this.currentY + radius + 50; // radius 为圆的半径,再加上离线的边距为50
|
||||
const startY = this.currentY; // 设置起始Y坐标
|
||||
|
||||
ctx.moveTo(startX, startY);
|
||||
colors.forEach((color: string, index: number) => {
|
||||
const x = startX + index * 500; // 计算每个圆的X坐标
|
||||
const y = startY; // 计算圆的Y坐标
|
||||
|
||||
// 绘制圆
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = color;
|
||||
ctx.fill();
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// 绘制长方形
|
||||
ctx.fillStyle = '#fff'; // 长方形背景色
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 4;
|
||||
const rectangleX = x - 120; // 计算字符X坐标
|
||||
const rectangleY = y + radius + 40; // 计算字符Y坐标
|
||||
ctx.strokeRect(rectangleX, rectangleY, 240, 100); // 绘制长方形
|
||||
// ctx.fillRect(x - 75, y + 10, 240, 100); // 填充长方形
|
||||
|
||||
// 绘制字符
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = 'bold 60px "Microsoft Yahei"';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(characters[index], rectangleX + 120, rectangleY + 70);
|
||||
this.currentY = rectangleY + 100; // 100会矩形宽度
|
||||
});
|
||||
|
||||
this.currentY = this.currentY + 50; // 计算分割线的Y坐标,距离示例图20px
|
||||
this.drawLine(this.currentY);
|
||||
}
|
||||
|
||||
drawLine(linY: number) {
|
||||
const { canvas, ctx } = this;
|
||||
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(20, linY);
|
||||
ctx.lineTo(canvas.width - 20, linY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
setPaper() {
|
||||
const ctx = this.ctx;
|
||||
const { width, height } = PAPER_SIZE[this.paperSize];
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const canvas = this.canvas;
|
||||
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextDrawService;
|
||||
@@ -1,237 +0,0 @@
|
||||
// import { PAPER_SIZE } from './constant';
|
||||
import { getMiniCodeImage } from './utils';
|
||||
|
||||
class TextDrawService {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
|
||||
constructor(canvas: Canvas, ctx: RenderingContext, options?: Record<string, any>) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦呀',
|
||||
appHint: '涂色|识字|画画|认知',
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '把相同的文字涂上相同的颜色',
|
||||
radius: 36,
|
||||
colors: ['#2196F3', '#FFEB3B', '#FF9800', '#F44336', '#F44336'],
|
||||
characters: ['鱼', '鸟', '猫', '狗', '牛'],
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.setPaper();
|
||||
// this.setPaper3();
|
||||
this.drawHeader();
|
||||
// this.drawLegend();
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
const { canvas, ctx } = this;
|
||||
const { appName, appHint, title, subTitle } = this.options;
|
||||
|
||||
this.currentX = 60;
|
||||
this.currentY = 60;
|
||||
const titleX = this.currentX + 140 + 14;
|
||||
const titleY = 87;
|
||||
const image = await getMiniCodeImage(canvas);
|
||||
ctx.drawImage(image, 60, 60, 140, 140);
|
||||
|
||||
ctx.font = 'bold 36px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
ctx.fillText(appName, titleX, titleY);
|
||||
|
||||
ctx.font = '24px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(appHint, titleX, 163);
|
||||
|
||||
// 绘制分割线
|
||||
ctx.strokeStyle = '#888';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(483, 70);
|
||||
ctx.lineTo(483, 70 + 140);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.font = 'bold 36px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(title, 538, titleY);
|
||||
|
||||
ctx.font = '24px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(subTitle, 538, 163);
|
||||
|
||||
this.drawLegend();
|
||||
}
|
||||
|
||||
/** 绘制示例
|
||||
* 矩形: x、y为左上角
|
||||
* 圆形:x、y为圆心
|
||||
* 文字:x textAlign 为 start,文本左边缘对齐
|
||||
* y textBaseline 为 alphabetic,y 对应字母基线(类似左下角)
|
||||
*
|
||||
* */
|
||||
drawLegend() {
|
||||
const { canvas, ctx, options } = this;
|
||||
const { colors, characters, radius } = options;
|
||||
const rectWidth = 100;
|
||||
const rectHeight = 48;
|
||||
const startX = 978;
|
||||
const startY = 60 + radius; // 设置起始Y坐标
|
||||
const len = characters.length || 4;
|
||||
const canvasWidth = canvas.width;
|
||||
/** 计算每个圆之间的间距 俩个60,一个是页面右侧边距,另一个是圆离右边距地边距*/
|
||||
const spaceWidth = (canvasWidth - startX - 60 - 60 - radius * (len - 1)) / (len - 1);
|
||||
|
||||
ctx.moveTo(startX, startY);
|
||||
colors.forEach((color: string, index: number) => {
|
||||
const x = startX + index * (spaceWidth + radius);
|
||||
const y = startY; // 计算圆的Y坐标
|
||||
|
||||
// 绘制圆
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// 绘制长方形
|
||||
ctx.fillStyle = '#fff'; // 长方形背景色
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 2;
|
||||
const rectangleX = x - rectWidth / 2; // 从圆形移动一半的长方形的长
|
||||
const rectangleY = y + radius + 18; // 从圆形下移一个半径,再加上18的间距
|
||||
ctx.strokeRect(rectangleX, rectangleY, rectWidth, rectHeight); // 绘制长方形
|
||||
|
||||
// 绘制字符
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = 'bold 24px "Microsoft Yahei"';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillText(characters[index], rectangleX + 38, 163);
|
||||
});
|
||||
|
||||
this.currentY = 220; // 计算分割线的Y坐标,距离示例图20px
|
||||
this.drawLine(this.currentY);
|
||||
}
|
||||
|
||||
drawLine(linY: number) {
|
||||
const { canvas, ctx } = this;
|
||||
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(20, linY);
|
||||
ctx.lineTo(canvas.width - 20, linY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
setPaper() {
|
||||
const { ctx, canvas } = this;
|
||||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||||
const { width, height } = { width: 595 * dpr, height: 842 * dpr };
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
setPaper3() {
|
||||
const { ctx, canvas } = this;
|
||||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||||
// @ts-ignore
|
||||
const winWidth = canvas._width;
|
||||
// @ts-ignore
|
||||
const winHeight = canvas._height; // 容器逻辑尺寸
|
||||
|
||||
// 计算适配后的物理尺寸
|
||||
const targetWidth = winWidth * dpr; // 宽度100%适配容器
|
||||
const aspectRatio = 842 / 595; // 原图宽高比(示例为A4纸比例)
|
||||
let targetHeight = targetWidth * aspectRatio; // 按比例计算高度
|
||||
|
||||
// 判断是否需要裁剪高度
|
||||
if (targetHeight > winHeight * dpr) {
|
||||
targetHeight = winHeight * dpr; // 若超出容器高度则强制裁剪
|
||||
}
|
||||
|
||||
// 设置 Canvas 物理像素
|
||||
canvas.width = targetWidth;
|
||||
canvas.height = targetHeight;
|
||||
|
||||
console.log('targetWidth---:', targetWidth);
|
||||
console.log('targetHeight---:', targetHeight);
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
ctx.fillRect(0, 0, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
setPaper2() {
|
||||
const { canvas, ctx } = this;
|
||||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||||
const { width, height } = { width: 595 * dpr, height: 842 * dpr };
|
||||
// @ts-ignore
|
||||
const winWidth = canvas._width;
|
||||
// @ts-ignore
|
||||
const winHeight = canvas._height;
|
||||
|
||||
const canvasAspect = width / height; // Canvas 内容的宽高比
|
||||
const containerAspect = winHeight / winWidth; // 容器的宽高比
|
||||
|
||||
// 选择缩放比例较小的方向(确保内容完全显示)
|
||||
let scaleRatio =
|
||||
containerAspect > canvasAspect
|
||||
? winWidth / width // 以宽度为基准
|
||||
: winHeight / height; // 以高度为基准
|
||||
|
||||
console.log('scaleRatio-----:', scaleRatio);
|
||||
|
||||
const scaledWidth = width * scaleRatio;
|
||||
const scaledHeight = height * scaleRatio;
|
||||
|
||||
// 避免超过小程序画布最大限制(4096)
|
||||
if (scaledWidth > 4096 || scaledHeight > 4096) {
|
||||
scaleRatio *= Math.min(4096 / scaledWidth, 4096 / scaledHeight);
|
||||
}
|
||||
|
||||
console.log('scaledWidth-----:', scaledWidth);
|
||||
console.log('scaledHeight-----:', scaledHeight);
|
||||
|
||||
canvas.width = scaledWidth;
|
||||
canvas.height = scaledHeight;
|
||||
|
||||
ctx.translate((winWidth * dpr) / 2, (winHeight * dpr) / 2);
|
||||
ctx.scale(scaleRatio * dpr, scaleRatio * dpr);
|
||||
ctx.translate(-width / 2, -height / 2); // 将内容原点移至中心
|
||||
|
||||
// this.canvas.width = width;
|
||||
// this.canvas.height = height;
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const canvas = this.canvas;
|
||||
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextDrawService;
|
||||
@@ -1,224 +0,0 @@
|
||||
// import { PAPER_SIZE } from './constant';
|
||||
import { PAPER_SIZE } from './constant';
|
||||
import { getMiniCodeImage } from './utils';
|
||||
|
||||
class TextDrawService {
|
||||
canvas: WechatMiniprogram.Canvas;
|
||||
ctx: RenderingContext;
|
||||
options: Record<string, any>;
|
||||
paperSize: PaperSize;
|
||||
currentX: number;
|
||||
currentY: number;
|
||||
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, any>,
|
||||
) {
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.paperSize = 'A4';
|
||||
this.options = {
|
||||
appName: '涂鸦呀',
|
||||
appHint: '涂色|识字|画画|认知',
|
||||
title: '找一找 涂 色',
|
||||
subTitle: '把相同的文字涂上相同的颜色',
|
||||
colors: ['#FF0000', '#FDAF1A', '#0057FC', '#09FF00', '#8D00FF'],
|
||||
characters: ['文', '升', '国', '舟', '丹'],
|
||||
...options,
|
||||
};
|
||||
this.currentX = 0;
|
||||
this.currentY = 0;
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.setPaper();
|
||||
this.drawHeader();
|
||||
this.drawLegend();
|
||||
this.drawContent();
|
||||
}
|
||||
|
||||
async drawHeader() {
|
||||
const { canvas, ctx } = this;
|
||||
const { appName, appHint, title, subTitle } = this.options;
|
||||
|
||||
this.currentX = 80;
|
||||
this.currentY = 80;
|
||||
const titleX = this.currentX + 200 + 48;
|
||||
const titleY = 80;
|
||||
const image = await getMiniCodeImage(canvas);
|
||||
ctx.drawImage(image, 80, 80, 200, 200);
|
||||
|
||||
ctx.font = 'bold 64px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'top';
|
||||
|
||||
ctx.fillText(appName, titleX, titleY);
|
||||
|
||||
ctx.font = '48px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(appHint, titleX, 186);
|
||||
|
||||
// 绘制分割线
|
||||
// ctx.strokeStyle = '#888';
|
||||
// ctx.lineWidth = 2;
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(483, 70);
|
||||
// ctx.lineTo(483, 70 + 140);
|
||||
// ctx.stroke();
|
||||
|
||||
ctx.font = 'bold 64px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillText(title, 1015, titleY);
|
||||
|
||||
ctx.font = '48px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#666';
|
||||
ctx.fillText(subTitle, 1015, 186);
|
||||
|
||||
this.currentY = 304;
|
||||
this.drawLine(this.currentY);
|
||||
}
|
||||
|
||||
/** 绘制示例
|
||||
* 矩形: x、y为左上角
|
||||
* 圆形:x、y为圆心
|
||||
* 文字:x textAlign 为 start,文本左边缘对齐
|
||||
* y textBaseline 为 alphabetic,y 对应字母基线(类似左下角)
|
||||
*
|
||||
* */
|
||||
drawLegend() {
|
||||
const { canvas, ctx, options } = this;
|
||||
const { colors, characters } = options;
|
||||
const radius = 65;
|
||||
const rectWidth = 180;
|
||||
const rectHeight = 80;
|
||||
const startX = 260 + radius; // 圆形的X坐标是圆心
|
||||
const startY = 304 + 26 + radius; // 圆形的Y坐标是圆心
|
||||
const len = characters.length || 4;
|
||||
const canvasWidth = canvas.width;
|
||||
/** 计算每个圆之间的间距 俩个60,一个是页面右侧边距,另一个是圆离右边距地边距*/
|
||||
const spaceWidth = (canvasWidth - startX * 2) / (len - 1);
|
||||
|
||||
ctx.moveTo(startX, startY);
|
||||
colors.forEach((color: string, index: number) => {
|
||||
const x = startX + index * spaceWidth;
|
||||
const y = startY; // 计算圆的Y坐标
|
||||
|
||||
// 绘制圆
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.fillStyle = color;
|
||||
ctx.lineWidth = 4;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
// 绘制长方形
|
||||
ctx.fillStyle = '#fff'; // 长方形背景色
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 4;
|
||||
const rectangleX = x - rectWidth / 2; // 从圆形移动一半的长方形的长
|
||||
const rectangleY = y + radius + 43; // 从圆形下移一个半径,再加上43的间距
|
||||
ctx.strokeRect(rectangleX, rectangleY, rectWidth, rectHeight); // 绘制长方形
|
||||
|
||||
// 绘制字符
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = 'bold 48px "Microsoft Yahei"';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(
|
||||
characters[index],
|
||||
rectangleX + rectWidth / 2,
|
||||
rectangleY + rectHeight / 2,
|
||||
rectWidth,
|
||||
);
|
||||
});
|
||||
|
||||
this.currentY = 612; // 计算分割线的Y坐标,距离示例图20px
|
||||
this.drawLine(this.currentY);
|
||||
}
|
||||
|
||||
drawContent() {
|
||||
const { canvas, ctx, options } = this;
|
||||
const { characters } = options;
|
||||
|
||||
const len = characters.length;
|
||||
const rows = 8;
|
||||
const radius = 80;
|
||||
const fontSize = 72;
|
||||
|
||||
const startY = this.currentY + 62 + radius;
|
||||
const startX1 = 310 + radius;
|
||||
const startX2 = 200 + radius;
|
||||
const canvasWidth = canvas.width;
|
||||
const spaceWidthFrist = (canvasWidth - startX1 * 2) / (5 - 1);
|
||||
const spaceWidthSecond = (canvasWidth - startX2 * 2) / (6 - 1);
|
||||
const spaceHeight = 54;
|
||||
|
||||
ctx.moveTo(startX1, startY);
|
||||
for (let i = 0; i < rows; i++) {
|
||||
const cols = i % 2 === 0 ? 5 : 6;
|
||||
|
||||
for (let j = 0; j < cols; j++) {
|
||||
const y = startY + i * (spaceHeight + radius * 2);
|
||||
const x =
|
||||
i % 2 === 0
|
||||
? startX1 + j * spaceWidthFrist
|
||||
: startX2 + j * spaceWidthSecond;
|
||||
|
||||
const character = characters[Math.floor(Math.random() * len)];
|
||||
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
ctx.closePath();
|
||||
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.font = `${fontSize}px "Microsoft Yahei"`;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(character, x, y, radius * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawLine(linY: number) {
|
||||
const { canvas, ctx } = this;
|
||||
|
||||
ctx.strokeStyle = '#000';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(80, linY);
|
||||
ctx.lineTo(canvas.width - 80, linY);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
setPaper() {
|
||||
const { ctx, canvas } = this;
|
||||
const { pixelRatio: dpr } = wx.getWindowInfo();
|
||||
// const { width, height } = { width: 595 * dpr, height: 842 * dpr };
|
||||
let { width, height } = PAPER_SIZE[this.paperSize];
|
||||
width = width * dpr;
|
||||
height = height * dpr;
|
||||
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
this.clear();
|
||||
ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const canvas = this.canvas;
|
||||
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
}
|
||||
|
||||
export default TextDrawService;
|
||||
@@ -1,34 +0,0 @@
|
||||
import { PAPER_SIZE } from './constant';
|
||||
|
||||
export function setCanvasSize(canvas: Canvas, paperSize: PaperSize) {
|
||||
const sizeObj = PAPER_SIZE[paperSize];
|
||||
// const { pixelRatio: dpr, windowWidth, windowHeight } = wx.getWindowInfo();
|
||||
// const printWidth = A4.width;
|
||||
// const printHeight = A4.height;
|
||||
// @ts-ignore
|
||||
// const originWidth = canvas._width || windowWidth;
|
||||
// // @ts-ignore
|
||||
// const originHeight = canvas._height || windowHeight;
|
||||
|
||||
canvas.width = sizeObj.width;
|
||||
canvas.height = sizeObj.height;
|
||||
console.log('canvas----:', canvas);
|
||||
return canvas;
|
||||
}
|
||||
|
||||
export async function getMiniCodeImage(canvas: Canvas) {
|
||||
return getImage(canvas, '/assets/imgs/doodle-mini-code.jpg');
|
||||
}
|
||||
|
||||
export async function getImage(canvas: Canvas, path: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const image = canvas.createImage();
|
||||
image.onload = () => {
|
||||
resolve(image);
|
||||
};
|
||||
image.onerror = () => {
|
||||
reject('图片加载异常');
|
||||
};
|
||||
image.src = path;
|
||||
});
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "形状涂色练习生成器",
|
||||
"navigationBarBackgroundColor": "#667eea",
|
||||
"homeButton": true,
|
||||
"backgroundColor": "#f5f5f5",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {
|
||||
"van-popup": "@vant/weapp/popup/index",
|
||||
"van-icon": "@vant/weapp/icon/index",
|
||||
"color-picker": "/components/color-picker/color-picker"
|
||||
}
|
||||
}
|
||||
@@ -1,537 +0,0 @@
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30rpx;
|
||||
padding: 30rpx;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 25rpx;
|
||||
box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15);
|
||||
|
||||
.title {
|
||||
font-size: 40rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
display: block;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.section {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border-radius: 25rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 25rpx;
|
||||
box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15);
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 25rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
padding-bottom: 15rpx;
|
||||
}
|
||||
|
||||
/* 选择形状按钮 */
|
||||
.select-shapes-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
border: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.4);
|
||||
|
||||
.btn-text {
|
||||
margin-left: 10rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
/* 形状卡片容器 */
|
||||
.shape-cards-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
/* 形状卡片 */
|
||||
.shape-card {
|
||||
position: relative;
|
||||
padding: 20rpx;
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
right: 10rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.shape-preview-container {
|
||||
position: relative;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
margin-bottom: 15rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.shape-preview-canvas {
|
||||
position: absolute;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
opacity: 0;
|
||||
/* 隐藏,只用于未来可能的Canvas预览 */
|
||||
}
|
||||
|
||||
.shape-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.color-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
|
||||
.color-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #ddd;
|
||||
}
|
||||
|
||||
.color-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 形状选择弹窗样式 */
|
||||
.shape-selector-popup {
|
||||
.van-popup {
|
||||
max-height: 80vh;
|
||||
}
|
||||
}
|
||||
|
||||
.shape-selector-container {
|
||||
padding: 30rpx;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.popup-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 2rpx solid #f0f0f0;
|
||||
|
||||
.popup-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.selected-count {
|
||||
font-size: 24rpx;
|
||||
color: #667eea;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
padding: 8rpx 15rpx;
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.shape-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.popup-actions {
|
||||
padding-top: 20rpx;
|
||||
border-top: 2rpx solid #f0f0f0;
|
||||
|
||||
.confirm-btn {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
font-size: 30rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 弹窗中的形状选项 */
|
||||
.shape-option {
|
||||
position: relative;
|
||||
height: 140rpx;
|
||||
border: 3rpx solid #e0e0e0;
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(145deg, #ffffff, #f0f0f0);
|
||||
transition: all 0.3s ease;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
&.selected {
|
||||
border-color: #667eea;
|
||||
background: linear-gradient(145deg, #e3f2fd, #bbdefb);
|
||||
transform: translateY(-5rpx);
|
||||
box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.3);
|
||||
|
||||
.shape-name {
|
||||
color: #667eea;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
background: #f5f5f5;
|
||||
border-color: #ddd;
|
||||
|
||||
.shape-name {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-mark {
|
||||
position: absolute;
|
||||
top: 8rpx;
|
||||
right: 8rpx;
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
background: #667eea;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
}
|
||||
}
|
||||
|
||||
.shape-preview-box {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
margin-bottom: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* SVG 样式 */
|
||||
.shape-svg {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.shape-svg-small {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 保留基础样式,移除所有形状相关的CSS */
|
||||
|
||||
.shape-name {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 颜色选择器 */
|
||||
.color-selector {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120rpx, 1fr));
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.color-option {
|
||||
height: 100rpx;
|
||||
border-radius: 20rpx;
|
||||
border: 3rpx solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
&.active {
|
||||
border-color: #667eea;
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 0 0 6rpx rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
|
||||
.color-name {
|
||||
color: white;
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
text-shadow: 1rpx 1rpx 2rpx rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加按钮 */
|
||||
.add-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(45deg, #667eea, #764ba2);
|
||||
border: none;
|
||||
color: white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 8rpx 25rpx rgba(102, 126, 234, 0.4);
|
||||
|
||||
.add-icon {
|
||||
font-size: 40rpx;
|
||||
margin-right: 10rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
/* 已选择的形状 */
|
||||
.selected-shapes {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.selected-shape {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
background: linear-gradient(145deg, #f8f9fa, #e9ecef);
|
||||
border-radius: 20rpx;
|
||||
border: 2rpx solid #e0e0e0;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4rpx 15rpx rgba(0, 0, 0, 0.1);
|
||||
|
||||
&:active {
|
||||
background: linear-gradient(145deg, #ffebee, #ffcdd2);
|
||||
border-color: #ff5722;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.shape-preview {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
margin-right: 20rpx;
|
||||
border: 2rpx solid #333;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.shape-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5rpx;
|
||||
}
|
||||
|
||||
.shape-info {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.color-info {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.remove-hint {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* 设置项 */
|
||||
.setting-item {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-label {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
margin-bottom: 15rpx;
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.bg-radio-group {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 25rpx;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.bg-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
padding: 10rpx 15rpx;
|
||||
border-radius: 15rpx;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
|
||||
text {
|
||||
font-size: 26rpx;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.feature-intro {
|
||||
margin-top: 30rpx;
|
||||
padding: 25rpx;
|
||||
background: linear-gradient(145deg, #f0f8ff, #e6f3ff);
|
||||
border-radius: 15rpx;
|
||||
border-left: 5rpx solid #667eea;
|
||||
}
|
||||
|
||||
.intro-title {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
display: block;
|
||||
margin-bottom: 8rpx;
|
||||
padding-left: 15rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 操作按钮 */
|
||||
.generate-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: linear-gradient(45deg, #4CAF50, #45a049);
|
||||
border: none;
|
||||
color: white;
|
||||
box-shadow: 0 8rpx 25rpx rgba(76, 175, 80, 0.4);
|
||||
|
||||
&:disabled {
|
||||
background: #ccc;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.save-btn {
|
||||
width: 100%;
|
||||
height: 90rpx;
|
||||
font-size: 32rpx;
|
||||
border-radius: 20rpx;
|
||||
background: linear-gradient(45deg, #ff9800, #f57c00);
|
||||
border: none;
|
||||
color: white;
|
||||
box-shadow: 0 8rpx 25rpx rgba(255, 152, 0, 0.4);
|
||||
}
|
||||
|
||||
/* 隐藏的画布 */
|
||||
.hidden-canvas {
|
||||
position: fixed;
|
||||
top: -9999rpx;
|
||||
left: -9999rpx;
|
||||
width: 595px;
|
||||
height: 842px;
|
||||
}
|
||||
|
||||
/* 预览图片 */
|
||||
.preview-image {
|
||||
width: 100%;
|
||||
max-height: 600rpx;
|
||||
border-radius: 20rpx;
|
||||
border: 3rpx solid #e0e0e0;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 8rpx 25rpx rgba(0, 0, 0, 0.15);
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
}
|
||||
|
||||
.preview-hint {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-top: 20rpx;
|
||||
padding: 15rpx;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-radius: 15rpx;
|
||||
}
|
||||
@@ -1,171 +0,0 @@
|
||||
<view class="container">
|
||||
<!-- 标题 -->
|
||||
<view class="header">
|
||||
<text class="title">形状涂色练习生成器</text>
|
||||
<text class="subtitle">为小朋友制作涂色练习页</text>
|
||||
</view>
|
||||
|
||||
<!-- 选择形状按钮 -->
|
||||
<view class="section">
|
||||
<button
|
||||
class="select-shapes-btn"
|
||||
bindtap="openShapeSelector"
|
||||
type="primary">
|
||||
<van-icon name="add-o" color="white" size="20" />
|
||||
<text class="btn-text">选择形状 ({{selectedShapes.length}}/6)</text>
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 已选择的形状卡片 -->
|
||||
<view class="section" wx:if="{{selectedShapes.length > 0}}">
|
||||
<view class="section-title"
|
||||
>已选择的形状 ({{selectedShapes.length}})</view
|
||||
>
|
||||
<view class="shape-cards-container">
|
||||
<view
|
||||
wx:for="{{selectedShapes}}"
|
||||
wx:key="id"
|
||||
class="shape-card"
|
||||
bindtap="openColorPicker"
|
||||
data-index="{{index}}">
|
||||
<!-- 删除按钮 -->
|
||||
<view
|
||||
class="delete-btn"
|
||||
catchtap="deleteShape"
|
||||
data-index="{{index}}">
|
||||
<van-icon name="close" color="#ff4757" size="16" />
|
||||
</view>
|
||||
<!-- 形状预览 -->
|
||||
<view class="shape-preview-container">
|
||||
<image
|
||||
src="{{item.svgContent}}"
|
||||
class="shape-svg"
|
||||
mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
<!-- 形状名称 -->
|
||||
<text class="shape-name">{{item.name}}</text>
|
||||
<!-- 颜色信息 -->
|
||||
<view class="color-info">
|
||||
<view
|
||||
class="color-dot"
|
||||
style="background-color: {{item.fillColor}};"></view>
|
||||
<text class="color-text">{{item.colorName}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 设置区域 -->
|
||||
<view class="section" wx:if="{{selectedShapes.length > 0}}">
|
||||
<view class="section-title">打印设置</view>
|
||||
<view class="setting-item">
|
||||
<text class="setting-label">背景颜色:</text>
|
||||
<radio-group bindchange="setBackground" class="bg-radio-group">
|
||||
<label
|
||||
wx:for="{{backgrounds}}"
|
||||
wx:key="value"
|
||||
class="bg-option">
|
||||
<radio
|
||||
value="{{item.value}}"
|
||||
checked="{{bgColor === item.value}}"
|
||||
color="#1976d2" />
|
||||
<text>{{item.name}}</text>
|
||||
</label>
|
||||
</radio-group>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 生成按钮 -->
|
||||
<view class="section" wx:if="{{selectedShapes.length > 0}}">
|
||||
<button
|
||||
class="generate-btn"
|
||||
bindtap="generateImage"
|
||||
type="primary"
|
||||
loading="{{isGenerating}}"
|
||||
disabled="{{selectedShapes.length === 0}}">
|
||||
{{isGenerating ? '生成中...' : '生成练习页'}}
|
||||
</button>
|
||||
|
||||
<button
|
||||
wx:if="{{generatedImage}}"
|
||||
class="save-btn"
|
||||
bindtap="saveToAlbum"
|
||||
type="warn">
|
||||
保存到相册
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 预览生成的图片 -->
|
||||
<view class="section" wx:if="{{generatedImage}}">
|
||||
<view class="section-title">生成的练习页</view>
|
||||
<image
|
||||
src="{{generatedImage}}"
|
||||
class="preview-image"
|
||||
mode="aspectFit"
|
||||
bindtap="previewImage" />
|
||||
<text class="preview-hint">点击图片可放大预览 • A4尺寸 • 适合打印</text>
|
||||
</view>
|
||||
|
||||
<!-- 形状选择弹窗 -->
|
||||
<van-popup
|
||||
show="{{showShapeSelector}}"
|
||||
bind:close="closeShapeSelector"
|
||||
custom-class="shape-selector-popup"
|
||||
position="bottom"
|
||||
round>
|
||||
<view class="shape-selector-container">
|
||||
<view class="popup-header">
|
||||
<text class="popup-title">选择形状 (最多6个)</text>
|
||||
<view class="selected-count"
|
||||
>已选: {{selectedShapes.length}}/6</view
|
||||
>
|
||||
</view>
|
||||
|
||||
<view class="shape-grid">
|
||||
<view
|
||||
wx:for="{{availableShapes}}"
|
||||
wx:key="id"
|
||||
class="shape-option {{tempSelectedShapeIds.indexOf(item.id) !== -1 ? 'selected' : ''}} {{selectedShapes.length >= 6 && tempSelectedShapeIds.indexOf(item.id) === -1 ? 'disabled' : ''}}"
|
||||
bindtap="toggleShape"
|
||||
data-shape-id="{{item.id}}">
|
||||
<!-- 选中标识 -->
|
||||
<view
|
||||
class="selected-mark"
|
||||
wx:if="{{tempSelectedShapeIds.indexOf(item.id) !== -1}}">
|
||||
<van-icon name="success" color="white" size="12" />
|
||||
</view>
|
||||
<!-- 形状预览 -->
|
||||
<view class="shape-preview-box">
|
||||
<image
|
||||
src="{{item.svgContent}}"
|
||||
class="shape-svg-small"
|
||||
mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
<text class="shape-name">{{item.name}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="popup-actions">
|
||||
<button
|
||||
class="confirm-btn"
|
||||
bindtap="confirmShapeSelection"
|
||||
type="primary">
|
||||
确认选择
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</van-popup>
|
||||
|
||||
<!-- 颜色选择器 -->
|
||||
<color-picker
|
||||
show="{{showColorPicker}}"
|
||||
currentColor="{{currentEditingColor}}"
|
||||
bind:onClose="closeColorPicker"
|
||||
bind:onChange="onColorChange">
|
||||
</color-picker>
|
||||
|
||||
<!-- Canvas 画布 (隐藏) -->
|
||||
<canvas type="2d" id="mainCanvas" class="hidden-canvas"></canvas>
|
||||
</view>
|
||||
@@ -1,134 +0,0 @@
|
||||
// shapes.js - 幼儿园到小学三年级基本几何形状定义
|
||||
const shapes = [
|
||||
{
|
||||
id: 'circle',
|
||||
name: '圆形',
|
||||
type: 'circle',
|
||||
svg: '<circle cx="50" cy="50" r="40" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawCircle',
|
||||
},
|
||||
{
|
||||
id: 'square',
|
||||
name: '正方形',
|
||||
type: 'rect',
|
||||
svg: '<rect x="10" y="10" width="80" height="80" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawSquare',
|
||||
},
|
||||
{
|
||||
id: 'rectangle',
|
||||
name: '长方形',
|
||||
type: 'rect',
|
||||
svg: '<rect x="5" y="25" width="90" height="50" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawRectangle',
|
||||
},
|
||||
{
|
||||
id: 'triangle',
|
||||
name: '三角形',
|
||||
type: 'triangle',
|
||||
svg: '<polygon points="50,10 20,80 80,80" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawTriangle',
|
||||
},
|
||||
{
|
||||
id: 'oval',
|
||||
name: '椭圆形',
|
||||
type: 'oval',
|
||||
svg: '<ellipse cx="50" cy="50" rx="40" ry="25" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawOval',
|
||||
},
|
||||
{
|
||||
id: 'diamond',
|
||||
name: '菱形',
|
||||
type: 'diamond',
|
||||
svg: '<polygon points="50,10 80,50 50,90 20,50" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawDiamond',
|
||||
},
|
||||
{
|
||||
id: 'star',
|
||||
name: '星形',
|
||||
type: 'star',
|
||||
svg: '<polygon points="50,5 60,35 90,35 70,55 80,85 50,70 20,85 30,55 10,35 40,35" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawStar',
|
||||
},
|
||||
{
|
||||
id: 'heart',
|
||||
name: '心形',
|
||||
type: 'heart',
|
||||
svg: '<path d="M50,85 C30,65 10,45 10,25 C10,15 20,5 30,5 C40,5 50,15 50,25 C50,15 60,5 70,5 C80,5 90,15 90,25 C90,45 70,65 50,85 Z" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawHeart',
|
||||
},
|
||||
{
|
||||
id: 'hexagon',
|
||||
name: '六边形',
|
||||
type: 'hexagon',
|
||||
svg: '<polygon points="25,15 75,15 90,50 75,85 25,85 10,50" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawHexagon',
|
||||
},
|
||||
{
|
||||
id: 'pentagon',
|
||||
name: '五边形',
|
||||
type: 'pentagon',
|
||||
svg: '<polygon points="50,10 80,35 65,75 35,75 20,35" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawPentagon',
|
||||
},
|
||||
{
|
||||
id: 'semicircle',
|
||||
name: '半圆形',
|
||||
type: 'semicircle',
|
||||
svg: '<path d="M 15 50 A 35 35 0 0 1 85 50 Z" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawSemicircle',
|
||||
},
|
||||
{
|
||||
id: 'trapezoid',
|
||||
name: '梯形',
|
||||
type: 'trapezoid',
|
||||
svg: '<polygon points="30,20 70,20 85,80 15,80" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawTrapezoid',
|
||||
},
|
||||
// 新增3D形状
|
||||
{
|
||||
id: 'cylinder',
|
||||
name: '圆柱体',
|
||||
type: '3d-cylinder',
|
||||
svg: '<ellipse cx="50" cy="20" rx="30" ry="8" stroke="#333" stroke-width="2" fill="none"/><rect x="20" y="20" width="60" height="50" stroke="none" fill="none"/><line x1="20" y1="20" x2="20" y2="70" stroke="#333" stroke-width="2"/><line x1="80" y1="20" x2="80" y2="70" stroke="#333" stroke-width="2"/><ellipse cx="50" cy="70" rx="30" ry="8" stroke="#333" stroke-width="2" fill="none"/>',
|
||||
drawFunction: 'drawCylinder',
|
||||
},
|
||||
{
|
||||
id: 'cube',
|
||||
name: '正方体',
|
||||
type: '3d-cube',
|
||||
svg: '<polygon points="20,30 60,30 60,70 20,70" stroke="#333" stroke-width="2" fill="none"/><polygon points="30,20 70,20 70,60 30,60" stroke="#333" stroke-width="2" fill="none"/><line x1="20" y1="30" x2="30" y2="20" stroke="#333" stroke-width="2"/><line x1="60" y1="30" x2="70" y2="20" stroke="#333" stroke-width="2"/><line x1="60" y1="70" x2="70" y2="60" stroke="#333" stroke-width="2"/>',
|
||||
drawFunction: 'drawCube',
|
||||
},
|
||||
{
|
||||
id: 'sphere',
|
||||
name: '球体',
|
||||
type: '3d-sphere',
|
||||
svg: '<circle cx="50" cy="50" r="35" stroke="#333" stroke-width="2" fill="none"/><ellipse cx="50" cy="50" rx="35" ry="15" stroke="#333" stroke-width="1" fill="none" opacity="0.5"/><ellipse cx="50" cy="50" rx="15" ry="35" stroke="#333" stroke-width="1" fill="none" opacity="0.5"/>',
|
||||
drawFunction: 'drawSphere',
|
||||
},
|
||||
{
|
||||
id: 'cone',
|
||||
name: '圆锥体',
|
||||
type: '3d-cone',
|
||||
svg: '<ellipse cx="50" cy="80" rx="30" ry="8" stroke="#333" stroke-width="2" fill="none"/><line x1="20" y1="80" x2="50" y2="20" stroke="#333" stroke-width="2"/><line x1="80" y1="80" x2="50" y2="20" stroke="#333" stroke-width="2"/>',
|
||||
drawFunction: 'drawCone',
|
||||
},
|
||||
];
|
||||
|
||||
// 颜色选项
|
||||
const colors = [
|
||||
{ name: '红色', value: '#FF4444' },
|
||||
{ name: '蓝色', value: '#4A90E2' },
|
||||
{ name: '绿色', value: '#7ED321' },
|
||||
{ name: '黄色', value: '#F5A623' },
|
||||
{ name: '紫色', value: '#9013FE' },
|
||||
{ name: '橙色', value: '#FF8F00' },
|
||||
{ name: '粉色', value: '#E91E63' },
|
||||
{ name: '青色', value: '#00BCD4' },
|
||||
{ name: '深绿', value: '#4CAF50' },
|
||||
{ name: '深蓝', value: '#2196F3' },
|
||||
{ name: '棕色', value: '#8D6E63' },
|
||||
{ name: '灰色', value: '#9E9E9E' },
|
||||
];
|
||||
|
||||
export { shapes, colors };
|
||||
@@ -1,54 +0,0 @@
|
||||
Page({
|
||||
data: {
|
||||
showModal: false,
|
||||
texts: ['', '', '', ''],
|
||||
},
|
||||
|
||||
onReady: function () {
|
||||
this.drawCircles();
|
||||
},
|
||||
|
||||
drawCircles: function () {
|
||||
const ctx = wx.createCanvasContext('myCanvas');
|
||||
const { texts } = this.data;
|
||||
|
||||
// 绘制4个圆圈
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const x = 50 + i * 100;
|
||||
const y = 50;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 40, 0, 2 * Math.PI);
|
||||
ctx.setFillStyle('white');
|
||||
ctx.fill();
|
||||
ctx.setStrokeStyle('black');
|
||||
ctx.stroke();
|
||||
|
||||
if (texts[i]) {
|
||||
ctx.setFillStyle('black');
|
||||
ctx.setFontSize(20);
|
||||
ctx.setTextAlign('center');
|
||||
ctx.setTextBaseline('middle');
|
||||
ctx.fillText(texts[i], x, y);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.draw();
|
||||
},
|
||||
|
||||
showInput: function () {
|
||||
this.setData({ showModal: true });
|
||||
},
|
||||
|
||||
updateText: function (e) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const value = e.detail.value;
|
||||
const { texts } = this.data;
|
||||
texts[index] = value;
|
||||
this.setData({ texts });
|
||||
},
|
||||
|
||||
confirmInput: function () {
|
||||
this.setData({ showModal: false });
|
||||
this.drawCircles();
|
||||
},
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "文本绘制 示例",
|
||||
"navigationBarBackgroundColor": "#d2e7d8",
|
||||
"homeButton": true,
|
||||
"backgroundColor": "#e8eddb",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
.page-container {
|
||||
height: 100vh;
|
||||
background-color: transparent;
|
||||
overflow-y: hidden;
|
||||
|
||||
.text-canvas {
|
||||
margin: 20rpx 20rpx 60rpx;
|
||||
width: calc(100% - 40rpx);
|
||||
height: calc(100% - 80rpx);
|
||||
border: 1rpx solid #333;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
Page({
|
||||
canvas: null as WechatMiniprogram.Canvas | null,
|
||||
ctx: null as CanvasRenderingContext2D | null,
|
||||
|
||||
data: {
|
||||
showModal: false,
|
||||
texts: ['', '', '', ''],
|
||||
},
|
||||
|
||||
onReady: function () {
|
||||
this.initCanvas();
|
||||
},
|
||||
|
||||
initCanvas() {
|
||||
const query = wx.createSelectorQuery();
|
||||
query
|
||||
.select('#textCanvas')
|
||||
.fields({ node: true, size: true })
|
||||
.exec((res) => {
|
||||
const canvas = res[0].node;
|
||||
const ctx = canvas.getContext('2d');
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
const dpi = 300; // 明确声明 DPI
|
||||
const mmToInch = 25.4;
|
||||
const width = (210 / mmToInch) * dpi; // 2480px
|
||||
const height = (297 / mmToInch) * dpi; // 3508px
|
||||
const pixelRatio = wx.getWindowInfo().pixelRatio;
|
||||
|
||||
// 设置画布实际像素为 A4 尺寸
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
// 适配屏幕显示(非必须,仅用于预览)
|
||||
canvas.style = canvas.style || {};
|
||||
canvas.style.width = `${width / pixelRatio}px`;
|
||||
canvas.style.height = `${height / pixelRatio}px`;
|
||||
|
||||
// 缩放上下文以适配高清屏
|
||||
ctx.scale(pixelRatio, pixelRatio);
|
||||
this.drawCircles();
|
||||
});
|
||||
},
|
||||
|
||||
setCanvasSize() { },
|
||||
|
||||
drawCircles: function () {
|
||||
if (!this.canvas) {
|
||||
return;
|
||||
}
|
||||
const ctx = this.ctx as CanvasRenderingContext2D;
|
||||
const { texts } = this.data;
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
|
||||
// 绘制4个圆圈
|
||||
for (let i = 0; i < 4; i++) {
|
||||
const x = 50 + i * 100;
|
||||
const y = 50;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 40, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.stroke();
|
||||
|
||||
if (texts[i]) {
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.font = '20px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(texts[i], x, y);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
showInput: function () {
|
||||
this.setData({ showModal: true });
|
||||
},
|
||||
|
||||
updateText: function (e: WechatMiniprogram.CustomEvent) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const value = e.detail.value;
|
||||
const { texts } = this.data;
|
||||
texts[index] = value;
|
||||
this.setData({ texts });
|
||||
},
|
||||
|
||||
confirmInput: function () {
|
||||
this.setData({ showModal: false });
|
||||
this.drawCircles();
|
||||
},
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<view class="container">
|
||||
<canvas canvas-id="myCanvas" style="width: 100%; height: 100vh;"></canvas>
|
||||
<button class="input-button" bindtap="showInput">输入文本</button>
|
||||
<view wx:if="{{showModal}}" class="modal">
|
||||
<view class="modal-content">
|
||||
<input placeholder="输入第一个文本" bindinput="updateText" data-index="0" />
|
||||
<input placeholder="输入第二个文本" bindinput="updateText" data-index="1" />
|
||||
<input placeholder="输入第三个文本" bindinput="updateText" data-index="2" />
|
||||
<input placeholder="输入第四个文本" bindinput="updateText" data-index="3" />
|
||||
<button bindtap="confirmInput">确认</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,34 +0,0 @@
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
canvas {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.input-button {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
width: 80%;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "文本绘制 示例",
|
||||
"navigationBarBackgroundColor": "#798154",
|
||||
"homeButton": false,
|
||||
"backgroundColorContent": "#ffffff00",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
// .color-picker {
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// padding: 20rpx;
|
||||
// }
|
||||
|
||||
// .color-circle {
|
||||
// width: 80rpx;
|
||||
// height: 80rpx;
|
||||
// border-radius: 50%;
|
||||
// margin: 0 40rpx;
|
||||
// border: 4rpx solid #333;
|
||||
// }
|
||||
|
||||
// .action-btn {
|
||||
// width: 60%;
|
||||
// background: #4CAF50;
|
||||
// color: white;
|
||||
// margin: 40rpx auto;
|
||||
// }
|
||||
|
||||
.container {
|
||||
height: 100vh;
|
||||
background-color: transparent;
|
||||
background-color: transparent;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.canvas-container {
|
||||
margin-top: 20rpx;
|
||||
// margin: 20rpx;
|
||||
// width: calc(100% - 40rpx);
|
||||
// // width: 100%;
|
||||
// height: 580px;
|
||||
// height: calc(100% - 80rpx);
|
||||
border: 2rpx solid #333;
|
||||
background-color: #fff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
// import TextDrawService from '../../canvasService/textDrawService';
|
||||
// import TextDrawServiceV2 from '../../canvasService/textDrawServiceV2';
|
||||
import { PAPER_SIZE } from '../../canvasService/constant';
|
||||
import TextDrawServiceV3 from '../../canvasService/textDrawServiceV3';
|
||||
|
||||
Page({
|
||||
canvas: null as Canvas | null,
|
||||
ctx: null as RenderingContext | null,
|
||||
data: {
|
||||
characters: ['鱼', '鸟', '牛', '羊'] as const, // 固定汉字类型
|
||||
colorMap: {
|
||||
鱼: '#2196F3', // 蓝色
|
||||
鸟: '#FFEB3B', // 黄色
|
||||
牛: '#FF9800', // 橙色
|
||||
羊: '#9C27B0', // 紫色
|
||||
} as Record<string, string>,
|
||||
matrix: [] as string[], // 随机汉字矩阵
|
||||
selectedColor: '', // 当前选中颜色
|
||||
selectedChar: '', // 当前选中汉字
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
console.log('textPaint2 onLoad');
|
||||
// this.setCanvasBoxSize();
|
||||
// this.generateMatrix();
|
||||
},
|
||||
|
||||
onReady() {
|
||||
wx.nextTick(() => {
|
||||
this.setCanvasBoxSize();
|
||||
});
|
||||
},
|
||||
|
||||
// setCanvasBoxSize() {
|
||||
// const { width: paperWidth, height: paperHeight } = PAPER_SIZE['A4'];
|
||||
|
||||
// // 逻辑尺寸(容器宽高)
|
||||
// const boxWidth = windowWidth - 40;
|
||||
// const boxHeight = boxWidth / (paperWidth / paperHeight);
|
||||
// console.log('boxWidth---:', boxWidth);
|
||||
// console.log('boxHeight---:', boxHeight);
|
||||
// this.setData({ boxWidth, boxHeight });
|
||||
// // // 实际像素尺寸(需乘以设备像素比)
|
||||
// // const canvasWidth = boxWidth * pixelRatio;
|
||||
// // const canvasHeight = boxHeight * pixelRatio;
|
||||
|
||||
// // // 更新 Canvas 节点属性
|
||||
// // const query = wx.createSelectorQuery().select('#textCanvas');
|
||||
// // query.fields({ node: true, size: true }).exec((res) => {
|
||||
// // const canvas = res[0].node;
|
||||
// // canvas.width = canvasWidth;
|
||||
// // canvas.height = canvasHeight;
|
||||
// // // 缩放上下文以适配逻辑尺寸
|
||||
// // const ctx = canvas.getContext('2d');
|
||||
// // ctx.scale(pixelRatio, pixelRatio);
|
||||
|
||||
// // this.canvas = canvas;
|
||||
// // this.ctx = ctx;
|
||||
|
||||
// // const textDrawService = new TextDrawServiceV3(canvas, ctx);
|
||||
// // textDrawService.draw();
|
||||
// // ctx.fillStyle = '#fff'; // 设置背景色为白色
|
||||
// // ctx.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
// // });
|
||||
// },
|
||||
|
||||
// 生成6列随机矩阵
|
||||
generateMatrix() {
|
||||
const shuffled = [...this.data.characters]
|
||||
.sort(() => Math.random() - 0.5)
|
||||
.concat(
|
||||
...Array(42)
|
||||
.fill(null)
|
||||
.map(() => this.data.characters[Math.floor(Math.random() * 4)]),
|
||||
);
|
||||
this.setData({ matrix: shuffled.slice(0, 42) }); // 6列x7行
|
||||
},
|
||||
|
||||
setCanvasBoxSize() {
|
||||
const { width, height } = PAPER_SIZE['A4'];
|
||||
const { windowWidth } = wx.getWindowInfo();
|
||||
const boxWidth = windowWidth - 20;
|
||||
const boxHeight = boxWidth / (width / height);
|
||||
console.log('boxWidth----:', boxWidth);
|
||||
console.log('boxHeight----:', boxHeight);
|
||||
this.setData({ boxHeight, boxWidth }, () => {
|
||||
this.initCanvas();
|
||||
});
|
||||
},
|
||||
|
||||
// 初始化A4画布(300DPI)
|
||||
initCanvas() {
|
||||
wx.createSelectorQuery()
|
||||
.select('#textCanvas')
|
||||
.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];
|
||||
console.log('rect-----:', rect);
|
||||
// const width = rect.width * dpr;
|
||||
// const height = rect.height * dpr;
|
||||
|
||||
// 设置canvas的实际尺寸
|
||||
// canvas.width = width;
|
||||
// canvas.height = height;
|
||||
// ctx.scale(dpr, dpr);
|
||||
|
||||
// 使用TextDrawServiceV3绘制
|
||||
const textDrawService = new TextDrawServiceV3(canvas, ctx);
|
||||
textDrawService.draw();
|
||||
}
|
||||
}
|
||||
|
||||
// const canvas = res[0].node;
|
||||
// const ctx = canvas.getContext('2d');
|
||||
// this.canvas = canvas;
|
||||
// this.ctx = ctx;
|
||||
|
||||
// const textDrawService = new TextDrawServiceV3(canvas, ctx);
|
||||
// textDrawService.draw();
|
||||
|
||||
// this.setCanvasSize(canvas, ctx);
|
||||
// ctx.fillRect(0, 0, 2480, 3508);
|
||||
// this.drawGameBoard(ctx);
|
||||
});
|
||||
},
|
||||
|
||||
setCanvasSize(canvas: Canvas, ctx: RenderingContext) {
|
||||
const A4 = {
|
||||
width: 2480,
|
||||
height: 3508,
|
||||
};
|
||||
const { pixelRatio: dpr, windowWidth, windowHeight } = wx.getWindowInfo();
|
||||
// const printWidth = A4.width;
|
||||
// const printHeight = A4.height;
|
||||
// @ts-ignore
|
||||
const originWidth = canvas._width || windowWidth;
|
||||
// @ts-ignore
|
||||
const originHeight = canvas._height || windowHeight;
|
||||
|
||||
console.log('originWidth---:', originWidth);
|
||||
console.log('originHeight---:', originHeight);
|
||||
|
||||
canvas.width = A4.width;
|
||||
canvas.height = A4.height;
|
||||
console.log('canvas----:', canvas);
|
||||
|
||||
// 计算显示比例
|
||||
// const scaleRatio = Math.min(originWidth / printWidth, originHeight / printHeight);
|
||||
// console.log('scaleRatio---:', scaleRatio);
|
||||
// console.log('dpr---:', dpr);
|
||||
// 设置坐标系
|
||||
// ctx.scale(scaleRatio * dpr, scaleRatio * dpr);
|
||||
},
|
||||
|
||||
// 绘制完整游戏界面
|
||||
drawGameBoard(ctx?: RenderingContext) {
|
||||
const canvas = this.canvas as Canvas;
|
||||
ctx = ctx || (this.ctx as RenderingContext);
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = '#fff'; // 设置填充色为白色
|
||||
ctx.fillRect(0, 0, 2480, 3508); // 覆盖整个A4画布
|
||||
|
||||
// 绘制标题
|
||||
// 绘制标题并居中显示
|
||||
ctx.font = 'bold 80px "Microsoft Yahei"';
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.textAlign = 'center';
|
||||
const titleX = canvas.width / 2;
|
||||
const titleY = 150;
|
||||
ctx.fillText('找一找 涂 色', titleX, titleY);
|
||||
// 绘制颜色示例(顶部)
|
||||
this.drawColorLegend(ctx, 250);
|
||||
|
||||
// 绘制汉字矩阵(主体)
|
||||
this.drawCharacterMatrix(ctx, 600);
|
||||
},
|
||||
|
||||
// 绘制颜色示例
|
||||
drawColorLegend(ctx: RenderingContext, y: number) {
|
||||
console.log('this.data:', this.data);
|
||||
let x = 500;
|
||||
this.data.characters.forEach((char) => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 50, 0, Math.PI * 2);
|
||||
ctx.fillStyle = this.data.colorMap[char] as string;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.stroke();
|
||||
x += 300;
|
||||
});
|
||||
},
|
||||
|
||||
// 绘制汉字矩阵(6列x7行)
|
||||
drawCharacterMatrix(ctx: RenderingContext, startY: number) {
|
||||
const cols = 6;
|
||||
const cellSize = 350; // 每个单元格大小
|
||||
const colorMap = this.data.colorMap;
|
||||
|
||||
this.data.matrix.forEach((char: string, index) => {
|
||||
char = char || '';
|
||||
const row = Math.floor(index / cols);
|
||||
const col = index % cols;
|
||||
|
||||
// 计算坐标
|
||||
const x = 300 + col * cellSize;
|
||||
const y = startY + row * cellSize;
|
||||
|
||||
// 绘制圆形
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, 120, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = '#333';
|
||||
ctx.lineWidth = 4;
|
||||
ctx.stroke();
|
||||
|
||||
// 绘制汉字
|
||||
ctx.font = 'bold 100px "Microsoft Yahei"';
|
||||
ctx.fillStyle = (colorMap[char] as string) || '';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(char, x, y);
|
||||
});
|
||||
},
|
||||
|
||||
// 点击颜色选择
|
||||
handleColorSelect(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
const selectedChar = this.data.characters[index];
|
||||
this.setData({
|
||||
selectedColor: this.data.colorMap[selectedChar],
|
||||
selectedChar,
|
||||
});
|
||||
},
|
||||
|
||||
// 点击汉字涂色
|
||||
handleCharacterTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = e.currentTarget.dataset.index;
|
||||
if (!this.data.selectedColor) return;
|
||||
|
||||
const matrix = this.data.matrix.map((char, i) =>
|
||||
i === index ? this.data.selectedChar : char,
|
||||
);
|
||||
|
||||
this.setData({ matrix }, () => {
|
||||
this.drawGameBoard();
|
||||
});
|
||||
},
|
||||
|
||||
// 导出A4图片
|
||||
exportToPrint() {
|
||||
console.log('this.canvas:', this.canvas);
|
||||
if (this.canvas) {
|
||||
wx.canvasToTempFilePath({
|
||||
canvas: this.canvas,
|
||||
fileType: 'png',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
// wx.getImageInfo({
|
||||
// src: res.tempFilePath,
|
||||
// success: (res) =>
|
||||
// console.log('getImageInfo image size :', res.width, res.height), // 应输出2480x3508
|
||||
// });
|
||||
wx.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: () => wx.showToast({ title: '保存成功' }),
|
||||
});
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
<view class="container">
|
||||
<!-- 颜色选择区 -->
|
||||
<!-- <view class="color-picker">
|
||||
<block wx:for="{{characters}}" wx:key="index">
|
||||
<view
|
||||
class="color-circle"
|
||||
style="background: {{colorMap.get(item)}}"
|
||||
bindtap="handleColorSelect"
|
||||
data-index="{{index}}"></view>
|
||||
</block>
|
||||
</view> -->
|
||||
<!-- 画布容器 -->
|
||||
<canvas type="2d" id="textCanvas" class="canvas-container" style="width:{{boxWidth}}px;height:{{boxHeight}}px" />
|
||||
<view>当前宽度:{{boxWidth}}px,高度:{{boxHeight}}px</view>
|
||||
<!-- 操作按钮 -->
|
||||
<button class="action-btn" bindtap="exportToPrint">导出A4图片</button>
|
||||
</view>
|
||||
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 326 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 276 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"navigationBarTitleText": "看数字,涂一涂",
|
||||
"navigationBarBackgroundColor": "#FFD719",
|
||||
"homeButton": true,
|
||||
"backgroundColor": "#F6F6F6",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {
|
||||
"toy-button": "../../ui/button/button",
|
||||
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
page {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
background-color: #f6f6f6;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
padding-bottom: 160rpx; // 为底部按钮预留空间
|
||||
|
||||
.empty {
|
||||
height: 50rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
background-color: #ffffff;
|
||||
border-radius: 20rpx;
|
||||
padding: 36rpx 24rpx;
|
||||
box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15);
|
||||
margin: 30rpx 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.wrapper-title {
|
||||
font-size: 32rpx;
|
||||
color: #141414;
|
||||
margin-bottom: 36rpx;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.canvas-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 400rpx;
|
||||
background: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
border: 2rpx dashed #dee2e6;
|
||||
}
|
||||
|
||||
.canvas-content {
|
||||
max-width: 100%;
|
||||
border-radius: 8rpx;
|
||||
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// .canvas-wrapper {
|
||||
// width: 100%;
|
||||
// margin-bottom: 40rpx;
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
// background: #f8f8f8;
|
||||
// border-radius: 12rpx;
|
||||
// padding: 20rpx;
|
||||
// box-sizing: border-box;
|
||||
// }
|
||||
|
||||
// .canvas-content {
|
||||
// width: 100%;
|
||||
// height: 100%;
|
||||
// }
|
||||
}
|
||||
|
||||
/* 数字选择区域 */
|
||||
.number-selection-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.number-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 60rpx;
|
||||
width: 100%;
|
||||
margin-top: 40rpx;
|
||||
}
|
||||
|
||||
.number-item {
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #93d333;
|
||||
box-shadow: 0 0 0 4rpx rgba(147, 211, 51, 0.2);
|
||||
}
|
||||
|
||||
.number-text {
|
||||
font-size: 28rpx;
|
||||
color: #141414;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 随机按钮样式 */
|
||||
&.random-button {
|
||||
flex: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
|
||||
.random-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
|
||||
.toy-icon {
|
||||
font-size: 32rpx;
|
||||
color: #141414;
|
||||
}
|
||||
}
|
||||
|
||||
.number-text {
|
||||
// flex: 0;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #93d333;
|
||||
box-shadow: 0 0 0 4rpx rgba(147, 211, 51, 0.2);
|
||||
|
||||
.random-icon .toy-icon {
|
||||
color: #93d333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部按钮区域 */
|
||||
.bottom-btn-box {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 200rpx;
|
||||
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;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
box-sizing: border-box;
|
||||
border-radius: 16rpx 16rpx 0 0;
|
||||
z-index: 100;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
import { PAPER_SIZE } from '../../constants/colors';
|
||||
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,
|
||||
|
||||
data: {
|
||||
pageTitle: '看数字,涂一涂', // 默认标题
|
||||
selectedNumber: 0, // 选中的数字
|
||||
functionId: '', // 功能ID,用于判断标题
|
||||
numberList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], // 数字列表
|
||||
showShareDialog: false, // 显示分享引导弹窗
|
||||
},
|
||||
|
||||
onLoad(options: { id?: string }) {
|
||||
// 根据id参数判断页面标题
|
||||
const functionId = options.id || 'number-find-color';
|
||||
this.setData({
|
||||
functionId,
|
||||
});
|
||||
|
||||
// 根据functionId设置页面标题
|
||||
const titleMap: Record<string, string> = {
|
||||
'number-find-color': '看数字,涂一涂',
|
||||
'number-coloring': '数字涂色卡',
|
||||
'counting-matching': '数一数,连一连',
|
||||
'missing-number': '填上缺少的数字',
|
||||
compare: '数一数,比大小',
|
||||
'counting-select': '数一数,选一选',
|
||||
'counting-fill': '数一数,填一填',
|
||||
'addition-5': '5以内加法',
|
||||
'addition-10': '10以内加法',
|
||||
'addition-subtraction-10': '10以内加减法',
|
||||
};
|
||||
|
||||
const pageTitle = titleMap[functionId] || '看数字,涂一涂';
|
||||
this.setData({
|
||||
pageTitle,
|
||||
});
|
||||
|
||||
// 设置导航栏标题
|
||||
wx.setNavigationBarTitle({
|
||||
title: pageTitle,
|
||||
});
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 初始化canvas
|
||||
this.initCanvas();
|
||||
},
|
||||
|
||||
/**
|
||||
* 初始化Canvas
|
||||
*/
|
||||
initCanvas() {
|
||||
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.setData({
|
||||
boxWidth,
|
||||
boxHeight,
|
||||
});
|
||||
|
||||
// 初始化canvas上下文
|
||||
const canvas = wx
|
||||
.createSelectorQuery()
|
||||
.select('#canvasContent');
|
||||
canvas.fields({ node: true, size: true }).exec((res) => {
|
||||
if (res[0]) {
|
||||
const canvasNode = res[0].node;
|
||||
const ctx = canvasNode.getContext('2d');
|
||||
const dpr = wx.getSystemInfoSync().pixelRatio;
|
||||
|
||||
canvasNode.width = boxWidth * dpr;
|
||||
canvasNode.height = boxHeight * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
this.canvas = canvasNode;
|
||||
this.ctx = ctx;
|
||||
|
||||
// 绘制初始内容
|
||||
this.drawCanvas();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.exec();
|
||||
},
|
||||
|
||||
/**
|
||||
* 绘制Canvas内容
|
||||
*/
|
||||
drawCanvas() {
|
||||
if (!this.ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx = this.ctx;
|
||||
const width = this.boxWidth;
|
||||
const height = this.boxHeight;
|
||||
|
||||
// 清空画布
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 设置背景色
|
||||
ctx.fillStyle = '#FFFFFF';
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
|
||||
// TODO: 根据选中的数字绘制内容
|
||||
if (this.data.selectedNumber > 0) {
|
||||
// 绘制示例:显示选中的数字
|
||||
ctx.fillStyle = '#141414';
|
||||
ctx.font = 'bold 120px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(
|
||||
String(this.data.selectedNumber),
|
||||
width / 2,
|
||||
height / 2,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 选择数字
|
||||
*/
|
||||
onSelectNumber(e: WechatMiniprogram.TouchEvent) {
|
||||
const number = parseInt(e.currentTarget.dataset.number);
|
||||
this.setData({
|
||||
selectedNumber: number,
|
||||
});
|
||||
this.drawCanvas();
|
||||
},
|
||||
|
||||
/**
|
||||
* 随机选择数字
|
||||
*/
|
||||
onRandom() {
|
||||
const randomNumber = Math.floor(Math.random() * 10) + 1;
|
||||
this.setData({
|
||||
selectedNumber: randomNumber,
|
||||
});
|
||||
this.drawCanvas();
|
||||
},
|
||||
|
||||
/**
|
||||
* 导出打印
|
||||
*/
|
||||
exportToPrint() {
|
||||
if (!this.canvas || this.data.selectedNumber <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否需要显示分享引导
|
||||
if (shouldShowShareGuide()) {
|
||||
this.setData({ showShareDialog: true });
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接下载
|
||||
checkAndSaveImage(this.canvas);
|
||||
},
|
||||
|
||||
/**
|
||||
* 分享小程序
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
return {
|
||||
title: '涂鸦丫-数学学习涂鸦卡',
|
||||
path: `/mathPages/numberColor/numberColor?id=${this.data.functionId}`,
|
||||
imageUrl:
|
||||
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
||||
};
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
return {
|
||||
title: '涂鸦丫-数学学习涂鸦卡',
|
||||
query: `id=${this.data.functionId}`,
|
||||
imageUrl:
|
||||
'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
||||
};
|
||||
},
|
||||
|
||||
/** 关闭分享引导弹窗 */
|
||||
onCloseShareDialog() {
|
||||
this.setData({ showShareDialog: false });
|
||||
},
|
||||
|
||||
/** 分享成功回调 */
|
||||
onShareSuccess() {
|
||||
this.setData({ showShareDialog: false });
|
||||
// 分享成功后直接下载
|
||||
if (this.canvas) {
|
||||
checkAndSaveImage(this.canvas);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
<view class="page-container">
|
||||
<view class="wrapper">
|
||||
<text class="wrapper-title">预览打印效果</text>
|
||||
|
||||
<!-- 预览打印效果 -->
|
||||
<view id="canvasWrapper" class="canvas-wrapper">
|
||||
<canvas
|
||||
type="2d"
|
||||
id="canvasContent"
|
||||
class="canvas-content"
|
||||
style="width:{{boxWidth}}px;height:{{boxHeight}}px" />
|
||||
</view>
|
||||
|
||||
<!-- 数字选择区域 -->
|
||||
<view class="number-selection-area">
|
||||
<!-- 第一行:1-4 -->
|
||||
<view class="number-row">
|
||||
<view
|
||||
wx:for="{{numberList}}"
|
||||
wx:key="index"
|
||||
wx:for-item="number"
|
||||
wx:if="{{index < 4}}"
|
||||
class="number-item {{selectedNumber === number ? 'active' : ''}}"
|
||||
data-number="{{number}}"
|
||||
bind:tap="onSelectNumber">
|
||||
<view class="number-text">{{number}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第二行:5-8 -->
|
||||
<view class="number-row">
|
||||
<view
|
||||
wx:for="{{numberList}}"
|
||||
wx:key="index"
|
||||
wx:for-item="number"
|
||||
wx:if="{{index >= 4 && index < 8}}"
|
||||
class="number-item {{selectedNumber === number ? 'active' : ''}}"
|
||||
data-number="{{number}}"
|
||||
bind:tap="onSelectNumber">
|
||||
<view class="number-text">{{number}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 第三行:9、10和随机按钮 -->
|
||||
<view class="number-row">
|
||||
<view
|
||||
wx:for="{{numberList}}"
|
||||
wx:key="index"
|
||||
wx:for-item="number"
|
||||
wx:if="{{index >= 8}}"
|
||||
class="number-item {{selectedNumber === number ? 'active' : ''}}"
|
||||
data-number="{{number}}"
|
||||
bind:tap="onSelectNumber">
|
||||
<view class="number-text">{{number}}</view>
|
||||
</view>
|
||||
<view class="number-item random-button" bind:tap="onRandom">
|
||||
<view class="random-icon">
|
||||
<text class="toy-icon toy-icon-refresh"></text>
|
||||
</view>
|
||||
<view class="number-text">随机</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty"></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="{{selectedNumber <= 0}}"
|
||||
icon="download"
|
||||
icon-class-prefix="toy-icon">
|
||||
下载打印
|
||||
</toy-button>
|
||||
</view>
|
||||
<share-guide-popup
|
||||
show="{{showShareDialog}}"
|
||||
bind:onClose="onCloseShareDialog"
|
||||
bind:onShareSuccess="onShareSuccess" />
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "按钮组件测试",
|
||||
"navigationBarBackgroundColor": "#FFD719",
|
||||
"backgroundColor": "#F6F6F6",
|
||||
"usingComponents": {
|
||||
"toy-button": "../../ui/button/button",
|
||||
"van-icon": "@vant/weapp/icon/index",
|
||||
"word-card": "../../components/word-card/word-card"
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
page {
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
.button-test-container {
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
display: block;
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.word-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 32rpx;
|
||||
width: 100%;
|
||||
max-width: 600rpx;
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
Page({
|
||||
data: {},
|
||||
|
||||
onButtonClick(e: WechatMiniprogram.TouchEvent) {
|
||||
console.log('按钮点击:', e);
|
||||
wx.showToast({
|
||||
title: '按钮被点击了',
|
||||
icon: 'success',
|
||||
duration: 1000
|
||||
});
|
||||
},
|
||||
|
||||
onWordColorTap(e: any) {
|
||||
console.log('点击了颜色:', e);
|
||||
wx.showToast({
|
||||
title: '点击了颜色',
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
|
||||
onWordDelete(e: any) {
|
||||
console.log('删除文字:', e);
|
||||
wx.showToast({
|
||||
title: '删除文字',
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,160 +0,0 @@
|
||||
<view class="button-test-container">
|
||||
<view class="test-section">
|
||||
<text class="section-title">主要按钮 (Primary)</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="primary" bind:click="onButtonClick"
|
||||
>主要按钮</toy-button
|
||||
>
|
||||
<toy-button type="primary" disabled bind:click="onButtonClick"
|
||||
>禁用状态</toy-button
|
||||
>
|
||||
<toy-button type="primary" loading bind:click="onButtonClick"
|
||||
>加载中</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">次要按钮 (Secondary)</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="secondary" bind:click="onButtonClick"
|
||||
>次要按钮</toy-button
|
||||
>
|
||||
<toy-button type="secondary" disabled bind:click="onButtonClick"
|
||||
>禁用状态</toy-button
|
||||
>
|
||||
<toy-button type="secondary" loading bind:click="onButtonClick"
|
||||
>加载中</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">绿色按钮 (Green)</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="green" bind:click="onButtonClick"
|
||||
>绿色按钮</toy-button
|
||||
>
|
||||
<toy-button type="green" disabled bind:click="onButtonClick"
|
||||
>禁用状态</toy-button
|
||||
>
|
||||
<toy-button type="green" loading bind:click="onButtonClick"
|
||||
>加载中</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">白色按钮 (White)</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="white" bind:click="onButtonClick"
|
||||
>白色按钮</toy-button
|
||||
>
|
||||
<toy-button type="white" disabled bind:click="onButtonClick"
|
||||
>禁用状态</toy-button
|
||||
>
|
||||
<toy-button type="white" plain bind:click="onButtonClick"
|
||||
>透明模式</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">扁平化按钮 (Flat)</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="primary" flat bind:click="onButtonClick"
|
||||
>扁平主要</toy-button
|
||||
>
|
||||
<toy-button type="secondary" flat bind:click="onButtonClick"
|
||||
>扁平次要</toy-button
|
||||
>
|
||||
<toy-button type="white" flat bind:click="onButtonClick"
|
||||
>扁平白色</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">带图标按钮</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="primary" icon="add" bind:click="onButtonClick"
|
||||
>添加</toy-button
|
||||
>
|
||||
<toy-button
|
||||
type="secondary"
|
||||
icon="delete"
|
||||
bind:click="onButtonClick"
|
||||
>删除</toy-button
|
||||
>
|
||||
<toy-button type="white" icon="edit" bind:click="onButtonClick"
|
||||
>编辑</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">不同尺寸</text>
|
||||
<view class="button-group">
|
||||
<toy-button
|
||||
type="primary"
|
||||
width="200rpx"
|
||||
bind:click="onButtonClick">
|
||||
小按钮
|
||||
</toy-button>
|
||||
<toy-button
|
||||
type="secondary"
|
||||
width="300rpx"
|
||||
bind:click="onButtonClick"
|
||||
>中按钮</toy-button
|
||||
>
|
||||
<toy-button type="white" width="400rpx" bind:click="onButtonClick">
|
||||
大按钮
|
||||
</toy-button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">点击动效测试</text>
|
||||
<view class="button-group">
|
||||
<toy-button type="primary" bind:click="onButtonClick"
|
||||
>点击我测试动效</toy-button
|
||||
>
|
||||
<toy-button type="secondary" bind:click="onButtonClick"
|
||||
>抖动效果</toy-button
|
||||
>
|
||||
<toy-button type="white" bind:click="onButtonClick"
|
||||
>缩放动画</toy-button
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="test-section">
|
||||
<text class="section-title">Word Card 测试</text>
|
||||
<view class="word-cards-grid">
|
||||
<word-card
|
||||
word="王"
|
||||
color="#FFD719"
|
||||
bind:colorTap="onWordColorTap"
|
||||
bind:delete="onWordDelete">
|
||||
</word-card>
|
||||
<word-card
|
||||
word="手"
|
||||
color="#93D333"
|
||||
bind:colorTap="onWordColorTap"
|
||||
bind:delete="onWordDelete">
|
||||
</word-card>
|
||||
<word-card
|
||||
word="哭"
|
||||
color="#FF6B9D"
|
||||
bind:colorTap="onWordColorTap"
|
||||
bind:delete="onWordDelete">
|
||||
</word-card>
|
||||
<word-card
|
||||
word="少"
|
||||
color="#4ECDC4"
|
||||
bind:colorTap="onWordColorTap"
|
||||
bind:delete="onWordDelete">
|
||||
</word-card>
|
||||
</view>
|
||||
</view>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"navigationBarTitleText": "涂鸦丫-数学",
|
||||
"navigationBarBackgroundColor": "#FFD719",
|
||||
"homeButton": true,
|
||||
"backgroundColor": "#F6F6F6",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
page {
|
||||
background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
background-color: #f6f6f6;
|
||||
padding: 0 24rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.empty {
|
||||
height: 190rpx;
|
||||
}
|
||||
}
|
||||
|
||||
/* 数学页面标题和描述 */
|
||||
.math-page-header {
|
||||
margin-bottom: 40rpx;
|
||||
text-align: center;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
|
||||
.math-page-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.math-page-subtitle {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 数学功能列表样式 */
|
||||
.math-functions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
|
||||
.math-function-item {
|
||||
background: #ffffff;
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
// box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
transition: all 0.2s ease-in-out;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 24rpx;
|
||||
opacity: 0;
|
||||
background: rgba(147, 211, 51, 0.1);
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
&:active {
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
border-color: #93d333;
|
||||
box-shadow: 0 8rpx 24rpx rgba(147, 211, 51, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.function-item-image {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.function-image-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, rgba(255, 215, 25, 0.2) 0%, rgba(255, 169, 64, 0.2) 100%);
|
||||
border-radius: 20rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 56rpx;
|
||||
}
|
||||
|
||||
.function-item-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.function-item-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.function-item-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.function-item-arrow {
|
||||
font-size: 36rpx;
|
||||
color: #999999;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* 使用提示 */
|
||||
.usage-hint {
|
||||
background: linear-gradient(135deg, #fff7e6 0%, #fffbe6 100%);
|
||||
border: 2rpx solid #ffd719;
|
||||
border-radius: 24rpx;
|
||||
padding: 32rpx;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 24rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.hint-icon {
|
||||
font-size: 40rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.hint-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.hint-title {
|
||||
font-size: 28rpx;
|
||||
font-weight: 600;
|
||||
color: #141414;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.hint-desc {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
white-space: pre-line;
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
Page({
|
||||
data: {
|
||||
functionList: [
|
||||
{
|
||||
id: 'number-find-color',
|
||||
page: 'numberColor',
|
||||
title: '看数字,涂一涂',
|
||||
desc: '给一个数字,找一找下面相同的数字,涂上颜色',
|
||||
icon: '🔍',
|
||||
},
|
||||
{
|
||||
id: 'number-coloring',
|
||||
title: '数字涂色卡',
|
||||
desc: '一边写数字、一边学数数,数字和卡通图片一一对应',
|
||||
icon: '🎨',
|
||||
},
|
||||
{
|
||||
id: 'counting-matching',
|
||||
title: '数一数,连一连',
|
||||
desc: '通过连线配对数字和对应的数量图形',
|
||||
icon: '🔗',
|
||||
},
|
||||
{
|
||||
id: 'missing-number',
|
||||
title: '填上缺少的数字',
|
||||
desc: '在数字序列中找出并填写缺失的数字',
|
||||
icon: '❓',
|
||||
},
|
||||
{
|
||||
id: 'compare',
|
||||
title: '数一数,比大小',
|
||||
desc: '通过数数比较两组物品的数量大小',
|
||||
icon: '⚖️',
|
||||
},
|
||||
{
|
||||
id: 'counting-select',
|
||||
title: '数一数,选一选',
|
||||
desc: '数出物品数量,从多个选项中选择正确答案',
|
||||
icon: '✓',
|
||||
},
|
||||
{
|
||||
id: 'counting-fill',
|
||||
title: '数一数,填一填',
|
||||
desc: '数出物品数量,填写对应的数字',
|
||||
icon: '✏️',
|
||||
},
|
||||
{
|
||||
id: 'addition-5',
|
||||
title: '5以内加法',
|
||||
desc: '练习5以内的加法运算,图形化展示',
|
||||
icon: '➕',
|
||||
},
|
||||
{
|
||||
id: 'addition-10',
|
||||
title: '10以内加法',
|
||||
desc: '练习10以内的加法运算,图形化展示',
|
||||
icon: '➕',
|
||||
},
|
||||
{
|
||||
id: 'addition-subtraction-10',
|
||||
title: '10以内加减法',
|
||||
desc: '练习10以内的加法和减法混合运算',
|
||||
icon: '±',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
// 页面加载
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开数学功能页面
|
||||
*/
|
||||
openMathFunction(e: WechatMiniprogram.TouchEvent) {
|
||||
const functionId = e.currentTarget.dataset.function;
|
||||
const functionItem = this.data.functionList.find(
|
||||
(item) => item.id === functionId,
|
||||
);
|
||||
|
||||
if (!functionItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果有page字段,跳转到对应页面
|
||||
if (functionItem.page) {
|
||||
wx.navigateTo({
|
||||
url: `/mathPages/${functionItem.page}/${functionItem.page}?id=${functionId}`,
|
||||
});
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: `即将打开:${functionItem.title}`,
|
||||
icon: 'none',
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
<view class="page-container">
|
||||
<!-- 页面标题和描述 -->
|
||||
<view class="math-page-header">
|
||||
<view class="math-page-title">数学学习涂鸦卡</view>
|
||||
<view class="math-page-subtitle"
|
||||
>选择功能,生成可打印的数学学习涂鸦卡</view
|
||||
>
|
||||
</view>
|
||||
|
||||
<!-- 功能列表 -->
|
||||
<view class="math-functions-list">
|
||||
<view
|
||||
class="math-function-item"
|
||||
wx:for="{{functionList}}"
|
||||
wx:key="id"
|
||||
data-function="{{item.id}}"
|
||||
bind:tap="openMathFunction">
|
||||
<view class="function-item-image">
|
||||
<view class="function-image-placeholder">{{item.icon}}</view>
|
||||
</view>
|
||||
<view class="function-item-content">
|
||||
<view class="function-item-title">{{item.title}}</view>
|
||||
<view class="function-item-desc">{{item.desc}}</view>
|
||||
</view>
|
||||
<view class="function-item-arrow">→</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 使用提示 -->
|
||||
<view class="usage-hint">
|
||||
<view class="hint-icon">💡</view>
|
||||
<view class="hint-text">
|
||||
<view class="hint-title">使用提示</view>
|
||||
<view class="hint-desc">
|
||||
1. 点击上方功能卡片选择要生成的涂鸦卡类型\n 2.
|
||||
选择数字范围、图形类型等参数\n 3. 预览生成的涂鸦卡效果\n 4.
|
||||
下载A4格式PNG图片,打印后使用
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="empty"></view>
|
||||
</view>
|
||||
@@ -23,12 +23,26 @@
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "mathPages/numberColor/numberColor",
|
||||
"pathName": "mathPages/numberColor/numberColor",
|
||||
"query": "id=number-find-color",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "pages/mathIndex/mathIndex",
|
||||
"pathName": "pages/mathIndex/mathIndex",
|
||||
"query": "",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/copyBook/copyBook",
|
||||
"pathName": "pages/copyBook/copyBook",
|
||||
"query": "",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/buttonTest/buttonTest",
|
||||
|
||||