feat:完成生成打印纸的步骤
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"tabWidth": 4,
|
"tabWidth": 4,
|
||||||
"printWidth": 100,
|
"printWidth": 80,
|
||||||
"formatOnSave": true,
|
"formatOnSave": true,
|
||||||
"bracketSameLine": true,
|
"bracketSameLine": true,
|
||||||
"overrides": [
|
"overrides": [
|
||||||
|
|||||||
@@ -8,3 +8,20 @@
|
|||||||
// padding: 200rpx 0;
|
// padding: 200rpx 0;
|
||||||
// box-sizing: border-box;
|
// box-sizing: border-box;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
.border-1px {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.border-1px::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
border: 1px solid #141414;
|
||||||
|
transform: scale(0.5);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ class TextDrawService {
|
|||||||
currentX: number;
|
currentX: number;
|
||||||
currentY: number;
|
currentY: number;
|
||||||
|
|
||||||
constructor(canvas: Canvas, ctx: RenderingContext, options?: Record<string, any>) {
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.ctx = ctx;
|
this.ctx = ctx;
|
||||||
this.paperSize = 'A4';
|
this.paperSize = 'A4';
|
||||||
@@ -160,7 +164,9 @@ class TextDrawService {
|
|||||||
for (let j = 0; j < cols; j++) {
|
for (let j = 0; j < cols; j++) {
|
||||||
const y = startY + i * (spaceHeight + radius * 2);
|
const y = startY + i * (spaceHeight + radius * 2);
|
||||||
const x =
|
const x =
|
||||||
i % 2 === 0 ? startX1 + j * spaceWidthFrist : startX2 + j * spaceWidthSecond;
|
i % 2 === 0
|
||||||
|
? startX1 + j * spaceWidthFrist
|
||||||
|
: startX2 + j * spaceWidthSecond;
|
||||||
|
|
||||||
const character = characters[Math.floor(Math.random() * len)];
|
const character = characters[Math.floor(Math.random() * len)];
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
// width: 324rpx;
|
// width: 324rpx;
|
||||||
height: 100rpx;
|
height: 100rpx;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
background-color: #EEFCFC;
|
background-color: #eefcfc;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-shadow: 0 2rpx 2rpx rgba(0, 0, 0, 0.35);
|
box-shadow: 0 2rpx 2rpx rgba(0, 0, 0, 0.35);
|
||||||
|
|
||||||
@@ -15,6 +15,21 @@
|
|||||||
background-color: #ff0000;
|
background-color: #ff0000;
|
||||||
margin-left: 20rpx;
|
margin-left: 20rpx;
|
||||||
border: 1rpx solid #141414;
|
border: 1rpx solid #141414;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 200%;
|
||||||
|
height: 200%;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #141414;
|
||||||
|
transform: scale(0.5);
|
||||||
|
transform-origin: 0 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.word-box {
|
.word-box {
|
||||||
@@ -31,7 +46,6 @@
|
|||||||
font-size: 28rpx;
|
font-size: 28rpx;
|
||||||
color: #141414;
|
color: #141414;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,15 @@ Component({
|
|||||||
* 组件的属性列表
|
* 组件的属性列表
|
||||||
*/
|
*/
|
||||||
properties: {
|
properties: {
|
||||||
extClass: {
|
key: {
|
||||||
|
type: Number,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
word: {
|
||||||
|
type: String,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
type: String,
|
type: String,
|
||||||
value: '',
|
value: '',
|
||||||
},
|
},
|
||||||
@@ -15,7 +23,8 @@ Component({
|
|||||||
* 组件的初始数据
|
* 组件的初始数据
|
||||||
*/
|
*/
|
||||||
data: {
|
data: {
|
||||||
displayStyle: '',
|
word: '',
|
||||||
|
color: '',
|
||||||
},
|
},
|
||||||
lifetimes: {
|
lifetimes: {
|
||||||
attached() {},
|
attached() {},
|
||||||
@@ -23,5 +32,10 @@ Component({
|
|||||||
/**
|
/**
|
||||||
* 组件的方法列表
|
* 组件的方法列表
|
||||||
*/
|
*/
|
||||||
methods: {},
|
methods: {
|
||||||
|
onDelete() {
|
||||||
|
const { key, word } = this.data;
|
||||||
|
this.triggerEvent('onDelete', { key, word });
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<view class="word-card">
|
<view class="word-card">
|
||||||
<view class="color-box"></view>
|
<view class="color-box" style="background-color: {{color}};"></view>
|
||||||
<view class="word-box">
|
<view class="word-box border-1px">
|
||||||
<text class="word-content">文</text>
|
<text class="word-content">{{word}}</text>
|
||||||
</view>
|
</view>
|
||||||
<view class="delete-icon">
|
<view class="delete-icon" bindtap="onDelete">
|
||||||
<van-icon name="delete" custom-class="delete-icon-inner" size="44rpx" />
|
<van-icon name="delete" custom-class="delete-icon-inner" size="44rpx" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -5,23 +5,31 @@ Component({
|
|||||||
/**
|
/**
|
||||||
* 组件的属性列表
|
* 组件的属性列表
|
||||||
*/
|
*/
|
||||||
properties: {
|
properties: {},
|
||||||
extClass: {
|
|
||||||
type: String,
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
* 组件的初始数据
|
* 组件的初始数据
|
||||||
*/
|
*/
|
||||||
data: {
|
data: {},
|
||||||
displayStyle: '',
|
|
||||||
},
|
|
||||||
lifetimes: {
|
lifetimes: {
|
||||||
attached() {},
|
attached() {
|
||||||
|
(this as any).words = '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* 组件的方法列表
|
* 组件的方法列表
|
||||||
*/
|
*/
|
||||||
methods: {},
|
methods: {
|
||||||
|
onConfirm(e: WechatMiniprogram.Input) {
|
||||||
|
let { value } = e.detail;
|
||||||
|
// (this as any).words = value;
|
||||||
|
value = value.replace(' ', '');
|
||||||
|
if (value.length > 0) {
|
||||||
|
this.triggerEvent('onConfirm', { value });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// onTap() {
|
||||||
|
// this.triggerEvent('onTap', { words: (this as any).words });
|
||||||
|
// },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
// export const COLOR_LIST = ['#FF6633', '#FFB399', '#FF33FF', '#FFFF99', '#00B3E6'];
|
||||||
|
export const WATER_COLORS = {
|
||||||
|
// 基础 12色
|
||||||
|
basic12: [
|
||||||
|
{ name: '红色', hex: '#FF0000' },
|
||||||
|
{ name: '橙色', hex: '#FF7F00' },
|
||||||
|
{ name: '黄色', hex: '#FFFF00' },
|
||||||
|
{ name: '黄绿色', hex: '#ADFF2F' },
|
||||||
|
{ name: '绿色', hex: '#00FF00' },
|
||||||
|
{ name: '青色', hex: '#00FFFF' },
|
||||||
|
{ name: '蓝色', hex: '#0000FF' },
|
||||||
|
{ name: '紫色', hex: '#8A2BE2' },
|
||||||
|
{ name: '粉红色', hex: '#FF69B4' },
|
||||||
|
{ name: '棕色', hex: '#A52A2A' },
|
||||||
|
{ name: '橙红色', hex: '#FF4500' },
|
||||||
|
{ name: '黑色', hex: '#000000' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 扩展 24色
|
||||||
|
extended24: [
|
||||||
|
{ name: '红色', hex: '#FF0000' },
|
||||||
|
{ name: '橙红色', hex: '#FF4500' },
|
||||||
|
{ name: '橙色', hex: '#FF7F00' },
|
||||||
|
{ name: '橙黄色', hex: '#FFA500' },
|
||||||
|
{ name: '黄色', hex: '#FFFF00' },
|
||||||
|
{ name: '黄绿色', hex: '#ADFF2F' },
|
||||||
|
{ name: '草绿色', hex: '#7CFC00' },
|
||||||
|
{ name: '绿色', hex: '#00FF00' },
|
||||||
|
{ name: '深绿色', hex: '#006400' },
|
||||||
|
{ name: '青色', hex: '#00FFFF' },
|
||||||
|
{ name: '天蓝色', hex: '#87CEEB' },
|
||||||
|
{ name: '蓝色', hex: '#0000FF' },
|
||||||
|
{ name: '深蓝色', hex: '#00008B' },
|
||||||
|
{ name: '蓝紫色', hex: '#8A2BE2' },
|
||||||
|
{ name: '紫色', hex: '#800080' },
|
||||||
|
{ name: '紫红色', hex: '#FF00FF' },
|
||||||
|
{ name: '粉红色', hex: '#FF69B4' },
|
||||||
|
{ name: '深粉色', hex: '#FF1493' },
|
||||||
|
{ name: '棕色', hex: '#A52A2A' },
|
||||||
|
{ name: '深棕色', hex: '#8B4513' },
|
||||||
|
{ name: '灰色', hex: '#808080' },
|
||||||
|
{ name: '白色', hex: '#FFFFFF' },
|
||||||
|
{ name: '米色', hex: '#F5F5DC' },
|
||||||
|
{ name: '黑色', hex: '#000000' },
|
||||||
|
],
|
||||||
|
|
||||||
|
// 特殊 36色
|
||||||
|
special36: [
|
||||||
|
{ name: '红色', hex: '#FF0000' },
|
||||||
|
{ name: '绯红', hex: '#DC143C' },
|
||||||
|
{ name: '橙红色', hex: '#FF4500' },
|
||||||
|
{ name: '橙色', hex: '#FF7F00' },
|
||||||
|
{ name: '橙黄色', hex: '#FFA500' },
|
||||||
|
{ name: '金色', hex: '#FFD700' },
|
||||||
|
{ name: '黄色', hex: '#FFFF00' },
|
||||||
|
{ name: '柠檬黄', hex: '#FFFACD' },
|
||||||
|
{ name: '黄绿色', hex: '#ADFF2F' },
|
||||||
|
{ name: '草绿色', hex: '#7CFC00' },
|
||||||
|
{ name: '绿色', hex: '#00FF00' },
|
||||||
|
{ name: '深绿色', hex: '#006400' },
|
||||||
|
{ name: '橄榄绿', hex: '#808000' },
|
||||||
|
{ name: '青色', hex: '#00FFFF' },
|
||||||
|
{ name: '蓝绿色', hex: '#20B2AA' },
|
||||||
|
{ name: '天蓝色', hex: '#87CEEB' },
|
||||||
|
{ name: '蓝色', hex: '#0000FF' },
|
||||||
|
{ name: '深蓝色', hex: '#00008B' },
|
||||||
|
{ name: '海军蓝', hex: '#000080' },
|
||||||
|
{ name: '蓝紫色', hex: '#8A2BE2' },
|
||||||
|
{ name: '靛蓝色', hex: '#4B0082' },
|
||||||
|
{ name: '紫色', hex: '#800080' },
|
||||||
|
{ name: '紫罗兰', hex: '#9400D3' },
|
||||||
|
{ name: '紫红色', hex: '#FF00FF' },
|
||||||
|
{ name: '粉红色', hex: '#FF69B4' },
|
||||||
|
{ name: '深粉色', hex: '#FF1493' },
|
||||||
|
{ name: '玫瑰红', hex: '#FF007F' },
|
||||||
|
{ name: '棕色', hex: '#A52A2A' },
|
||||||
|
{ name: '深棕色', hex: '#8B4513' },
|
||||||
|
{ name: '咖啡色', hex: '#6F4E37' },
|
||||||
|
{ name: '灰色', hex: '#808080' },
|
||||||
|
{ name: '银灰色', hex: '#C0C0C0' },
|
||||||
|
{ name: '白色', hex: '#FFFFFF' },
|
||||||
|
{ name: '米色', hex: '#F5F5DC' },
|
||||||
|
{ name: '象牙白', hex: '#FFFFF0' },
|
||||||
|
{ name: '黑色', hex: '#000000' },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
page {
|
page {
|
||||||
background-color: #F6F6F6;
|
background-color: #f6f6f6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-container {
|
.page-container {
|
||||||
background-color: #F6F6F6;
|
background-color: #f6f6f6;
|
||||||
padding: 24rpx;
|
padding: 0 24rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
@@ -23,7 +23,15 @@ page {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #previewWrapper {
|
||||||
|
// margin-bottom: 200rpx;
|
||||||
|
// }
|
||||||
|
|
||||||
.word-card-box {
|
.word-card-box {
|
||||||
margin-top: 30rpx;
|
margin-top: 30rpx;
|
||||||
@@ -35,4 +43,29 @@ page {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 24rpx;
|
margin-bottom: 24rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
height: 190rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.canvas-wrapper {
|
||||||
|
margin-top: 20rpx;
|
||||||
|
width: 100%;
|
||||||
|
border: 1rpx solid #141414;
|
||||||
|
background-color: #fff;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-area {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 190rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0px -2px 4px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,125 @@
|
|||||||
|
import { WATER_COLORS, PAPER_SIZE } from '../../constants/index';
|
||||||
|
import TextDrawService from '../../service/textDrawService';
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
data: {},
|
canvas: null as Canvas | null,
|
||||||
|
ctx: null as RenderingContext | null,
|
||||||
|
boxHeight: 0,
|
||||||
|
boxWidth: 0,
|
||||||
|
textDrawService: null as TextDrawService | null,
|
||||||
|
|
||||||
|
data: {
|
||||||
|
cardList: [] as Array<{ color: string; word: string }>,
|
||||||
|
},
|
||||||
|
|
||||||
|
onReady() {
|
||||||
|
const query = wx.createSelectorQuery();
|
||||||
|
query
|
||||||
|
.select('#canvasWrapper')
|
||||||
|
.boundingClientRect((rect) => {
|
||||||
|
if (rect) {
|
||||||
|
const { width, height } = PAPER_SIZE['A4'];
|
||||||
|
const boxWidth = rect.width;
|
||||||
|
const boxHeight = boxWidth / (width / height);
|
||||||
|
|
||||||
|
this.boxHeight = boxHeight;
|
||||||
|
this.boxWidth = boxWidth;
|
||||||
|
this.initCanvas(boxWidth, boxHeight);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.exec();
|
||||||
|
},
|
||||||
|
|
||||||
|
// renderCanvas() {
|
||||||
|
// // wx.nextTick(() => {
|
||||||
|
// // this.setCanvasBoxSize();
|
||||||
|
// // });
|
||||||
|
// console.log('this.boxWidth:', this.boxWidth);
|
||||||
|
// console.log('this.boxHeight:', this.boxHeight);
|
||||||
|
// if (this.boxWidth && this.boxHeight) {
|
||||||
|
// this.initCanvas(this.boxWidth, this.boxHeight);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
|
||||||
|
// setCanvasBoxSize() {
|
||||||
|
// const { windowWidth } = wx.getWindowInfo();
|
||||||
|
// console.log('windowWidth---:', windowWidth);
|
||||||
|
// const query = wx.createSelectorQuery();
|
||||||
|
// query
|
||||||
|
// .select('#canvasWrapper')
|
||||||
|
// .boundingClientRect((rect) => {
|
||||||
|
// if (rect) {
|
||||||
|
// const boxWidth = rect.width;
|
||||||
|
// const boxHeight = boxWidth / (width / height);
|
||||||
|
|
||||||
|
// console.log('boxWidth----:', boxWidth);
|
||||||
|
// console.log('boxHeight----:', boxHeight);
|
||||||
|
|
||||||
|
// this.initCanvas(boxHeight, boxWidth);
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .exec();
|
||||||
|
// },
|
||||||
|
|
||||||
|
initCanvas(boxWidth: number, boxHeight: number) {
|
||||||
|
wx.createSelectorQuery()
|
||||||
|
.select('#canvasContent')
|
||||||
|
.fields({
|
||||||
|
node: true,
|
||||||
|
size: true,
|
||||||
|
})
|
||||||
|
.exec((res) => {
|
||||||
|
if (res[0] && res[0].node) {
|
||||||
|
const canvas = res[0].node;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
if (ctx) {
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = ctx;
|
||||||
|
const rect = res[0];
|
||||||
|
console.log('rect-----:', rect);
|
||||||
|
this.textDrawService = new TextDrawService(canvas, ctx);
|
||||||
|
// textDrawService.draw();
|
||||||
|
this.setData({ boxWidth, boxHeight });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
tapCard(e: WechatMiniprogram.TouchEvent) {
|
tapCard(e: WechatMiniprogram.TouchEvent) {
|
||||||
const { url } = e.currentTarget.dataset;
|
const { url } = e.currentTarget.dataset;
|
||||||
console.log('navigateTo url:', url);
|
console.log('navigateTo url:', url);
|
||||||
wx.navigateTo({ url });
|
wx.navigateTo({ url });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onConfirmInput(e: WechatMiniprogram.Input) {
|
||||||
|
const { value } = e.detail;
|
||||||
|
console.log('indexPage onConfirmInput value:', value);
|
||||||
|
const characters = value.split('');
|
||||||
|
const shuffledColors = [...WATER_COLORS.basic12].sort(
|
||||||
|
() => Math.random() - 0.5,
|
||||||
|
);
|
||||||
|
const updatedCardList = characters.map((char, index) => ({
|
||||||
|
color: shuffledColors[index % shuffledColors.length].hex,
|
||||||
|
word: char,
|
||||||
|
}));
|
||||||
|
console.log('updatedCardList', updatedCardList);
|
||||||
|
this.setData({ cardList: updatedCardList });
|
||||||
|
// this.renderCanvas();
|
||||||
|
this.textDrawService?.draw(updatedCardList);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteWordCard(e: WechatMiniprogram.CustomEvent) {
|
||||||
|
const { key, word } = e.detail;
|
||||||
|
console.log('deleteWordCard key:', key, 'word:', word);
|
||||||
|
const { cardList } = this.data;
|
||||||
|
const updatedCardList = cardList.filter((_, index) => index !== key);
|
||||||
|
this.setData({ cardList: updatedCardList });
|
||||||
|
wx.showToast({
|
||||||
|
title: `已删除 "${word}"`,
|
||||||
|
icon: 'none',
|
||||||
|
duration: 2000,
|
||||||
|
});
|
||||||
|
// this.renderCanvas();
|
||||||
|
this.textDrawService?.draw(updatedCardList);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,19 +1,47 @@
|
|||||||
<scroll-view class="page-container" scroll-y type="list">
|
<view class="page-container">
|
||||||
<view class="wrapper">
|
<view class="wrapper">
|
||||||
<text class="wrapper-title">设置文字和颜色</text>
|
<text class="wrapper-title">设置文字和颜色12</text>
|
||||||
<word-input />
|
<word-input bind:onConfirm="onConfirmInput" />
|
||||||
<view class="word-card-box">
|
<view class="word-card-box">
|
||||||
<view class="word-card-list">
|
<block
|
||||||
<word-card />
|
wx:for="{{cardList}}"
|
||||||
<word-card />
|
wx:for-item="item"
|
||||||
|
wx:for-index="index">
|
||||||
|
<view class="word-card-list" wx:if="{{index % 2 === 0}}">
|
||||||
|
<word-card
|
||||||
|
key="{{index}}"
|
||||||
|
word="{{item.word}}"
|
||||||
|
color="{{item.color}}"
|
||||||
|
bind:onDelete="deleteWordCard" />
|
||||||
|
<word-card
|
||||||
|
wx:if="{{cardList[index + 1]}}"
|
||||||
|
key="{{index + 1}}"
|
||||||
|
word="{{cardList[index + 1].word}}"
|
||||||
|
color="{{cardList[index + 1].color}}"
|
||||||
|
bind:onDelete="deleteWordCard" />
|
||||||
</view>
|
</view>
|
||||||
<view class="word-card-list">
|
</block>
|
||||||
<word-card />
|
|
||||||
<word-card />
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
<view
|
||||||
<view class="wrapper">
|
id="previewWrapper"
|
||||||
|
class="wrapper {{cardList.length > 0 ? '' : 'hidden'}}">
|
||||||
<text class="wrapper-title">预览打印效果</text>
|
<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>
|
||||||
</scroll-view>
|
</view>
|
||||||
|
<view class="empty"></view>
|
||||||
|
</view>
|
||||||
|
<view class="btn-area">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
bindtap="onPrint"
|
||||||
|
wx:if="{{cardList.length > 0}}">
|
||||||
|
打印
|
||||||
|
</button>
|
||||||
|
</view>
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
// import { PAPER_SIZE } from './constant';
|
||||||
|
import { PAPER_SIZE } from '../constants/index';
|
||||||
|
import { getMiniCodeImage } from '../utils/index';
|
||||||
|
|
||||||
|
function calculateCircleCenters(
|
||||||
|
canvasWidth: number,
|
||||||
|
circleCount: number,
|
||||||
|
padding: number,
|
||||||
|
radius: number,
|
||||||
|
) {
|
||||||
|
const availableWidth = canvasWidth - 2 * padding;
|
||||||
|
const maxSpacing = 5.5 * radius;
|
||||||
|
const centers = [];
|
||||||
|
|
||||||
|
if (circleCount === 1) {
|
||||||
|
// 单个圆圈直接居中
|
||||||
|
centers.push(canvasWidth / 2);
|
||||||
|
} else {
|
||||||
|
const requiredSpace = 2 * radius * circleCount;
|
||||||
|
const spaceBetween =
|
||||||
|
(availableWidth - requiredSpace) / (circleCount - 1);
|
||||||
|
console.log('spaceBetween---:', spaceBetween);
|
||||||
|
|
||||||
|
if (spaceBetween > maxSpacing) {
|
||||||
|
// 超过最大间距时,固定间距并居中对齐
|
||||||
|
const totalWidth = 2 * radius + (circleCount - 1) * maxSpacing;
|
||||||
|
const startX = (canvasWidth - totalWidth) / 2 + radius;
|
||||||
|
|
||||||
|
for (let i = 0; i < circleCount; i++) {
|
||||||
|
centers.push(startX + i * maxSpacing);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 正常均匀分布
|
||||||
|
const startX = padding + radius;
|
||||||
|
|
||||||
|
for (let i = 0; i < circleCount; i++) {
|
||||||
|
centers.push(startX + i * (spaceBetween + 2 * radius));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return centers;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TextDrawService {
|
||||||
|
canvas: WechatMiniprogram.Canvas;
|
||||||
|
ctx: RenderingContext;
|
||||||
|
options: Record<string, any>;
|
||||||
|
paperSize: PaperSize;
|
||||||
|
currentX: number;
|
||||||
|
currentY: number;
|
||||||
|
colors: string[];
|
||||||
|
characters: string[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
canvas: Canvas,
|
||||||
|
ctx: RenderingContext,
|
||||||
|
options?: Record<string, any>,
|
||||||
|
) {
|
||||||
|
options = options || {};
|
||||||
|
this.canvas = canvas;
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.paperSize = 'A4';
|
||||||
|
this.options = {
|
||||||
|
appName: '涂鸦呀',
|
||||||
|
appHint: '涂色|识字|画画|认知',
|
||||||
|
title: '找一找 涂 色',
|
||||||
|
subTitle: '把相同的文字涂上相同的颜色',
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
this.currentX = 0;
|
||||||
|
this.currentY = 0;
|
||||||
|
this.colors = ['#000'];
|
||||||
|
this.characters = ['王'];
|
||||||
|
}
|
||||||
|
|
||||||
|
draw(list: Array<{ color: string; word: string }>) {
|
||||||
|
this.colors = list.map((item) => item.color || '#000');
|
||||||
|
this.characters = list.map((item) => item.word || '日');
|
||||||
|
this.clear();
|
||||||
|
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, colors, characters } = 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;
|
||||||
|
const centers = calculateCircleCenters(canvasWidth, len, 220, 65);
|
||||||
|
/** 计算每个圆之间的间距 俩个60,一个是页面右侧边距,另一个是圆离右边距地边距*/
|
||||||
|
const spaceWidth = len > 3 ? (canvasWidth - startX * 2) / (len - 1) : 0;
|
||||||
|
|
||||||
|
ctx.moveTo(startX, startY);
|
||||||
|
colors.forEach((color: string, index: number) => {
|
||||||
|
const x = centers[index] || startX + index * spaceWidth; // 计算圆的X坐标
|
||||||
|
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, characters } = 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;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"component": true,
|
||||||
|
"styleIsolation": "apply-shared",
|
||||||
|
"usingComponents": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
Component({
|
||||||
|
options: {
|
||||||
|
// multipleSlots: true, // 在组件定义时的选项中启用多slot支持
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的属性列表
|
||||||
|
*/
|
||||||
|
properties: {
|
||||||
|
key: {
|
||||||
|
type: Number,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
word: {
|
||||||
|
type: String,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的初始数据
|
||||||
|
*/
|
||||||
|
data: {
|
||||||
|
word: '',
|
||||||
|
color: '',
|
||||||
|
},
|
||||||
|
lifetimes: {
|
||||||
|
attached() {},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 组件的方法列表
|
||||||
|
*/
|
||||||
|
methods: {
|
||||||
|
onDelete() {
|
||||||
|
const { key, word } = this.data;
|
||||||
|
this.triggerEvent('onDelete', { key, word });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,3 +1,20 @@
|
|||||||
|
export async function getMiniCodeImage(canvas: Canvas) {
|
||||||
|
return getImage(canvas, '/assets/imgs/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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export const formatTime = (date: Date) => {
|
export const formatTime = (date: Date) => {
|
||||||
const year = date.getFullYear();
|
const year = date.getFullYear();
|
||||||
const month = date.getMonth() + 1;
|
const month = date.getMonth() + 1;
|
||||||
+2
-2
@@ -18,7 +18,7 @@
|
|||||||
"postcss": false,
|
"postcss": false,
|
||||||
"minified": true,
|
"minified": true,
|
||||||
"minifyWXSS": true,
|
"minifyWXSS": true,
|
||||||
"enhance": false,
|
"enhance": true,
|
||||||
"showShadowRootInWxmlPanel": false,
|
"showShadowRootInWxmlPanel": false,
|
||||||
"packNpmRelationList": [
|
"packNpmRelationList": [
|
||||||
{
|
{
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
"ignoreUploadUnusedFiles": true,
|
"ignoreUploadUnusedFiles": true,
|
||||||
"compileHotReLoad": false,
|
"compileHotReLoad": false,
|
||||||
"skylineRenderEnable": true,
|
"skylineRenderEnable": true,
|
||||||
"es6": false,
|
"es6": true,
|
||||||
"packNpmManually": true
|
"packNpmManually": true
|
||||||
},
|
},
|
||||||
"simulatorType": "wechat",
|
"simulatorType": "wechat",
|
||||||
|
|||||||
Reference in New Issue
Block a user