diff --git a/miniprogram/components/empty-state/empty-state.json b/miniprogram/components/empty-state/empty-state.json
new file mode 100644
index 0000000..59a1db6
--- /dev/null
+++ b/miniprogram/components/empty-state/empty-state.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/miniprogram/components/empty-state/empty-state.less b/miniprogram/components/empty-state/empty-state.less
new file mode 100644
index 0000000..4baaec8
--- /dev/null
+++ b/miniprogram/components/empty-state/empty-state.less
@@ -0,0 +1,67 @@
+.word-list-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 50rpx 40rpx;
+ text-align: center;
+
+ .empty-icon {
+ width: 120rpx;
+ height: 120rpx;
+ background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 32rpx;
+ box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.15);
+
+ .empty-logo {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ object-fit: cover;
+ }
+ }
+
+ .empty-title {
+ font-size: 32rpx;
+ font-weight: 600;
+ color: #374151;
+ margin-bottom: 16rpx;
+ line-height: 1.4;
+ }
+
+ .empty-desc {
+ font-size: 26rpx;
+ color: #6b7280;
+ line-height: 1.6;
+ margin-bottom: 32rpx;
+ max-width: 480rpx;
+ }
+}
+
+.empty-hint {
+ display: flex;
+ align-items: center;
+ background: #f8fafc;
+ border: 1rpx solid #e2e8f0;
+ border-radius: 12rpx;
+ padding: 20rpx 24rpx;
+ max-width: 500rpx;
+
+ .hint-icon {
+ font-size: 24rpx;
+ margin-right: 12rpx;
+ margin-top: -2rpx;
+ flex-shrink: 0;
+ }
+
+ .hint-text {
+ font-size: 22rpx;
+ color: #64748b;
+ line-height: 1.5;
+ text-align: left;
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/components/empty-state/empty-state.ts b/miniprogram/components/empty-state/empty-state.ts
new file mode 100644
index 0000000..7503814
--- /dev/null
+++ b/miniprogram/components/empty-state/empty-state.ts
@@ -0,0 +1,10 @@
+Component({
+ properties: {
+ title: { type: String, value: '' },
+ desc: { type: String, value: '' },
+ hint: { type: String, value: '' },
+ },
+ data: {},
+ methods: {},
+});
+
diff --git a/miniprogram/components/empty-state/empty-state.wxml b/miniprogram/components/empty-state/empty-state.wxml
new file mode 100644
index 0000000..8fc3a18
--- /dev/null
+++ b/miniprogram/components/empty-state/empty-state.wxml
@@ -0,0 +1,14 @@
+
+
+
+
+ {{title}}
+ {{desc}}
+
+ 💡
+ {{hint}}
+
+
diff --git a/miniprogram/components/word-card/word-card.ts b/miniprogram/components/word-card/word-card.ts
index e85efa8..a44ed7b 100644
--- a/miniprogram/components/word-card/word-card.ts
+++ b/miniprogram/components/word-card/word-card.ts
@@ -31,7 +31,9 @@ Component({
color: '',
},
lifetimes: {
- attached() { },
+ // attached() {
+ // console.log('this.data:', this.data);
+ // },
},
/**
* 组件的方法列表
diff --git a/miniprogram/components/word-picker/word-picker.ts b/miniprogram/components/word-picker/word-picker.ts
index 3af2e68..55e481b 100644
--- a/miniprogram/components/word-picker/word-picker.ts
+++ b/miniprogram/components/word-picker/word-picker.ts
@@ -10,6 +10,10 @@ Component({
type: Boolean,
value: false,
},
+ cardList: {
+ type: Array,
+ value: [],
+ },
currentTab: {
type: Number,
value: 0,
@@ -31,14 +35,20 @@ Component({
},
lifetimes: {
ready() {
+ // 初始化同步一次 selectedWords,避免首次传入时未触发 observers 的情况
+ const list = (this.data as any).cardList || [];
+ if (Array.isArray(list) && list.length > 0) {
+ const selectedWords = list.map((item: any) => item.word);
+ this.setData({ selectedWords });
+ }
},
},
- /* observers: {
+ observers: {
cardList(newVal: CardList) {
const selectedWords = newVal.map((item) => item.word);
this.setData({ selectedWords });
},
- }, */
+ },
/**
* 组件的方法列表
*/
diff --git a/miniprogram/components/word-picker/word-picker.wxml b/miniprogram/components/word-picker/word-picker.wxml
index 6a7b5dc..52eecc8 100644
--- a/miniprogram/components/word-picker/word-picker.wxml
+++ b/miniprogram/components/word-picker/word-picker.wxml
@@ -30,7 +30,7 @@
wx:for="{{wordList}}"
wx:for-index="categoryIndex"
wx:key="categoryIndex"
- title="{{wordList[categoryIndex].categoryName}}">
+ title="{{wordList[categoryIndex].icon}} {{wordList[categoryIndex].categoryName}}">
,
+ maxRow: 0 as number,
+ maxCol: 0 as number,
data: {
// 选中的汉字列表
@@ -21,18 +24,17 @@ Page({
boxHeight: 0,
},
- onLoad() {
- this.loadSvgWords();
- const hasShowIntroduction =
- wx.getStorageSync('hasShowIntroduction') || false;
- if (!hasShowIntroduction) {
+ async onLoad() {
+ await this.loadSvgWords();
+ const hasShowPracticeDemo =
+ wx.getStorageSync('hasShowPracticeDemo') || false;
+ if (!hasShowPracticeDemo) {
this.setData({
words: ['好', '好', '学', '习', '天', '天', '向', '上']
+ }, () => {
+ wx.setStorageSync('hasShowPracticeDemo', true);
})
}
- },
-
- async onReady() {
await this.setCanvasBoxSize();
await this.initCanvas()
},
@@ -40,12 +42,9 @@ Page({
async loadSvgWords() {
try {
wx.showToast({ title: '加载字体中...', icon: 'loading' });
- console.log("loadSvgWords");
// 使用带缓存的SVG汉字数据获取函数
const svgWordsData = await getWordsSvgData();
- console.log("svgWordsData", svgWordsData);
-
this.svgWords = svgWordsData;
wx.hideToast();
@@ -147,6 +146,30 @@ Page({
this.setData({ words: next }, () => this.renderPracticeContent().catch(console.error));
},
+ // 清空
+ clearWords() {
+ this.setData({ words: [] }, () => this.renderPracticeContent().catch(console.error));
+ },
+
+ // 随机生成(取基础分类,过滤为可用SVG字,最多11个)
+ refreshWords() {
+ // 选取较简单的几个分类(如 0:基础独体字,1:生活高频字,2:自然与动作)
+ const candidate = WORDS.slice(0, 3).reduce((acc: string[], c: any) => acc.concat(c.words), [] as string[]);
+ // 去重
+ const unique = Array.from(new Set(candidate));
+ // 打乱
+ const shuffled = unique.sort(() => Math.random() - 0.5);
+ // 先取前 11 个
+ const picked = shuffled.slice(0, 10);
+ // 过滤为支持的SVG字
+ const supported = this.getSupportedWords(picked);
+ if (supported.length === 0) {
+ wx.showToast({ title: '随机到的字暂不支持,重试一下', icon: 'none' });
+ return;
+ }
+ this.setData({ words: supported }, () => this.renderPracticeContent().catch(console.error));
+ },
+
// 校验 & 分割
isChineseText(text: string): boolean {
const chineseRegex = /^[\u4e00-\u9fff]+$/;
@@ -189,16 +212,16 @@ Page({
if (ctx) {
this.canvas = canvas;
this.ctx = ctx;
- this.wordDrawService = new WordDrawService(canvas, ctx, {
- appName: '涂鸦丫小程序',
- appHint: '练字|识字|涂色|打印',
- title: '田字格 练 字 贴',
- subTitle: '按笔画临摹练习'
- });
+ this.wordDrawService = new WordDrawService(canvas, ctx);
// 初始化时只绘制页眉和布局
await this.wordDrawService.drawLayout();
+ // 绘制完成后计算并缓存最大行列数(与实际绘制一致)
+ const { maxRow, maxCol } = this.wordDrawService.getMaxGridLayout();
+ this.maxRow = maxRow;
+ this.maxCol = maxCol;
+
// 如果有汉字数据,绘制练字内容
const { words } = this.data as any;
if (words && words.length > 0) {
@@ -220,7 +243,6 @@ Page({
}
const { words } = this.data as any;
- console.log("renderPracticeContent words", words);
if (!words || words.length === 0) {
this.wordDrawService.drawContentEmpty();
@@ -235,8 +257,19 @@ Page({
return;
}
- // 处理布局和生成练字数据
- const characterData = this.processCharacterLayout(supportedWords);
+ // 使用初始化阶段缓存的最大行列数;若未初始化则计算一次并缓存
+ let maxRow = this.maxRow;
+ let maxCol = this.maxCol;
+ if (!maxRow || !maxCol) {
+ const layout = this.wordDrawService!.getMaxGridLayout();
+ this.maxRow = layout.maxRow;
+ this.maxCol = layout.maxCol;
+ maxRow = layout.maxRow;
+ maxCol = layout.maxCol;
+ }
+
+ // 处理布局和生成练字数据(基于实际最大行列数)
+ const characterData = this.processCharacterLayout(supportedWords, maxRow, maxCol);
this.wordDrawService.drawPracticeContent(characterData);
},
@@ -247,8 +280,7 @@ Page({
},
/** 处理汉字布局,生成练字数据 */
- processCharacterLayout(words: string[]): CharacterItem[] {
- const maxRow = 11, maxCol = 9;
+ processCharacterLayout(words: string[], maxRow: number, maxCol: number): CharacterItem[] {
let rowIndex = 0;
const characterData: CharacterItem[] = [];
const boundaryWords: string[] = [];
@@ -257,10 +289,12 @@ Page({
words.forEach((word: string) => {
const strokes = this.svgWords[word];
const strokeCount = strokes.length;
- const totalCells = 1 + strokeCount + 2; // 预览格 + 练习格 + 空白格
+ const totalCells = 1 + strokeCount + 0; // 预览格 + 练习格 + 空白格
const totalRows = Math.ceil(totalCells / maxCol);
- rowIndex += totalRows;
+ // 这里的意思是:把这一个汉字所占的行数(totalRows)累加到 rowIndex 上。
+ // 这样 rowIndex 就记录了当前已经排布了多少行,用于判断是否超出了最大允许行数(maxRow)。
+ rowIndex = rowIndex + totalRows;
if (rowIndex <= maxRow) {
characterData.push({ character: word, strokes });
@@ -268,22 +302,20 @@ Page({
boundaryWords.push(word);
}
});
-
- // 提示超出页面的汉字
- if (boundaryWords.length > 0) {
- wx.showToast({
- title: `练字贴已超过一页,部分汉字未包含在本次生成结果中`,
- icon: 'none'
- });
- }
+ // // 提示超出页面的汉字
+ // if (showToast && boundaryWords.length > 0) {
+ // wx.showToast({
+ // title: `练字贴已超过一页,部分汉字未包含在本次生成结果中`,
+ // icon: 'none'
+ // });
+ // }
return characterData;
},
// 下载打印练字贴
exportToPrint() {
- const { words } = this.data as any;
- if (!this.canvas || words.length <= 0) {
+ if (!this.canvas) {
wx.showToast({ title: '请先生成练字贴', icon: 'none' });
return;
}
diff --git a/miniprogram/pages/copyBook/copyBook.wxml b/miniprogram/pages/copyBook/copyBook.wxml
index 8ea402c..1e60a90 100644
--- a/miniprogram/pages/copyBook/copyBook.wxml
+++ b/miniprogram/pages/copyBook/copyBook.wxml
@@ -1,6 +1,6 @@
- 自定义练字
+ 自定义练字贴
-
-
-
-
- 还没有添加文字
- 请在上方输入文字或点击"选择文字"按钮
开始创建你的练字田字格吧!
-
+
+
+
+
+ 随机生成
+
+
+ 清空
+
@@ -65,7 +80,6 @@
bind:click="exportToPrint"
width="420rpx"
height="80rpx"
- disabled="{{words.length <= 0}}"
icon="download"
icon-class-prefix="toy-icon">
下载打印
diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json
index d511005..08eaf8d 100644
--- a/miniprogram/pages/index/index.json
+++ b/miniprogram/pages/index/index.json
@@ -13,6 +13,7 @@
"color-picker": "../../components/color-picker/color-picker",
"introduction-popup": "../../components/introduction-popup/introduction-popup",
"toy-button": "../../ui/button/button",
- "word-picker": "../../components/word-picker/word-picker"
+ "word-picker": "../../components/word-picker/word-picker",
+ "empty-state": "../../components/empty-state/empty-state"
}
}
diff --git a/miniprogram/pages/index/index.less b/miniprogram/pages/index/index.less
index 6a97bb2..0a7a67a 100644
--- a/miniprogram/pages/index/index.less
+++ b/miniprogram/pages/index/index.less
@@ -34,73 +34,7 @@ page {
}
.word-card-box {
- padding: 38rpx 0;
-
- .word-list-empty {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 60rpx 40rpx;
- text-align: center;
-
- .empty-icon {
- width: 120rpx;
- height: 120rpx;
- background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-bottom: 32rpx;
- box-shadow: 0 4rpx 12rpx rgba(59, 130, 246, 0.15);
-
- .toy-icon {
- font-size: 48rpx;
- color: #3b82f6;
- }
- }
-
- .empty-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #374151;
- margin-bottom: 16rpx;
- line-height: 1.4;
- }
-
- .empty-desc {
- font-size: 26rpx;
- color: #6b7280;
- line-height: 1.6;
- margin-bottom: 32rpx;
- max-width: 400rpx;
- }
-
- .empty-hint {
- display: flex;
- align-items: center;
- background: #f8fafc;
- border: 1rpx solid #e2e8f0;
- border-radius: 12rpx;
- padding: 20rpx 24rpx;
- max-width: 500rpx;
-
- .hint-icon {
- font-size: 24rpx;
- margin-right: 12rpx;
- margin-top: -2rpx;
- flex-shrink: 0;
- }
-
- .hint-text {
- font-size: 22rpx;
- color: #64748b;
- line-height: 1.5;
- text-align: left;
- }
- }
- }
+ padding: 34rpx 0;
.word-cards-grid {
display: grid;
@@ -122,7 +56,7 @@ page {
}
// 模板选择区域
- .template-section {
+ /* .template-section {
margin-top: 40rpx;
.template-title {
@@ -292,14 +226,7 @@ page {
}
}
}
- }
-}
-
-
-.operation-box {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
+ } */
}
diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts
index 7f7488a..c1585d5 100644
--- a/miniprogram/pages/index/index.ts
+++ b/miniprogram/pages/index/index.ts
@@ -48,6 +48,8 @@ Page({
selectedWords: ['涂', '鸦', '丫', '欢', '迎', '您'],
showIntroductionPopup: true,
env: getApp().globalData.env || 'release',
+ }, () => {
+ this.drawCanvas();
});
} else {
this.setData({
diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml
index 91ff9ae..f7ad805 100644
--- a/miniprogram/pages/index/index.wxml
+++ b/miniprogram/pages/index/index.wxml
@@ -24,21 +24,11 @@
bind:onDelete="deleteWordCard"
bind:onColorTap="onColorTap" />
-
-
-
-
- 还没有添加文字
- 请在上方输入文字或点击"选择文字"按钮
开始创建你的涂鸦卡吧!
-
- 💡
- 提示:最多可以同时添加六个文字
-
-
+
-
+
预览打印效果
@@ -179,7 +169,7 @@
bind:onChange="onChangeColor" />
diff --git a/miniprogram/service/wordDrawService.ts b/miniprogram/service/wordDrawService.ts
index 8705ec9..ced36f9 100644
--- a/miniprogram/service/wordDrawService.ts
+++ b/miniprogram/service/wordDrawService.ts
@@ -231,7 +231,7 @@ class WordDrawService {
this.options = {
appName: '涂鸦丫小程序',
appHint: '识字|识图|练字|打印',
- title: '田字格 练 字 贴',
+ title: '田字格 练 字 贴',
subTitle: '按笔画临摹练习',
...options,
};
@@ -260,6 +260,7 @@ class WordDrawService {
} else {
await this.drawMiniHeader();
}
+ this.drawContentEmpty();
}
async drawContentEmpty() {
@@ -345,10 +346,10 @@ class WordDrawService {
ctx.fillText(subTitle, 1015, 186);
// 计算页眉的实际高度,包括分割线
- const headerHeight = Math.max(logoY + logoHeight, titleY + 64 + 48 + 48) + 20; // 64px字体 + 48px间距 + 48px字体 + 20px边距
+ const headerHeight = Math.max(titleY + 64 + 48 + 48) + 0; // 64px字体 + 48px间距 + 48px字体 + 20px边距
// this.currentY = headerHeight;
this.currentY += headerHeight;
- this.drawLine(this.currentY);
+ this.drawDivider(this.currentY);
}
drawMiniHeader() {
@@ -364,7 +365,7 @@ class WordDrawService {
// 计算迷你页眉的实际高度
const miniHeaderHeight = titleY + 64 + 20; // 64px字体 + 20px边距
this.currentY = miniHeaderHeight;
- this.drawLine(this.currentY);
+ this.drawDivider(this.currentY);
}
/**
@@ -392,23 +393,14 @@ class WordDrawService {
const contentHeight = canvas.height - contentTop - bottomMargin;
const cellSize = 140;
- const minGap = 24; // 最小格子列间距,用于保证横向不拥挤,后续可动态调整为实际间距
- const rowGap = 36; // 最小格子行间距,用于保证纵向不拥挤,后续可动态调整为实际间距
-
- // 计算每行可容纳的田字格数量
- const columnNumber = Math.max(1, Math.floor((contentWidth + minGap) / (cellSize + minGap)));
-
- // 计算每列可容纳的田字格数量
- const rowNumber = Math.max(1, Math.floor((contentHeight + rowGap) / (cellSize + rowGap)));
-
- // 计算总需要的田字格数量
- const totalCellsNeeded = rowNumber * columnNumber;
+ // 统一通过 getMaxGridLayout 获取最大行列数
+ const { maxRow, maxCol } = this.getMaxGridLayout();
// 动态调整间距以充分利用空间(在允许的最小间距基础上弹性扩展以适配画布)
- const actualRowGap = rowNumber > 1 ? (contentHeight - rowNumber * cellSize) / (rowNumber - 1) : 0;
- const actualColGap = columnNumber > 1 ? (contentWidth - columnNumber * cellSize) / (columnNumber - 1) : 0;
+ const actualRowGap = maxRow > 1 ? (contentHeight - maxRow * cellSize) / (maxRow - 1) : 0;
+ const actualColGap = maxCol > 1 ? (contentWidth - maxCol * cellSize) / (maxCol - 1) : 0;
- console.log(`布局信息: 总格数=${totalCellsNeeded}, 行数=${rowNumber}, 列数=${columnNumber}, 行间距=${actualRowGap.toFixed(1)}, 列间距=${actualColGap.toFixed(1)}`);
+ // console.log(`布局信息: 行数=${rowNumber}, 列数=${columnNumber}, 行间距=${actualRowGap.toFixed(1)}, 列间距=${actualColGap.toFixed(1)}`);
// 第一阶段:绘制所有空田字格
this.drawEmptyGrids({
@@ -418,8 +410,8 @@ class WordDrawService {
cellSize,
colGap: actualColGap,
rowGap: actualRowGap,
- rowNumber,
- columnNumber
+ maxRow,
+ maxCol
});
if (characterData && characterData.length > 0) {
@@ -432,26 +424,36 @@ class WordDrawService {
cellSize,
colGap: actualColGap,
rowGap: actualRowGap,
- columnNumber
+ maxCol,
+ maxRow
});
};
}
/**
- * 计算总需要的田字格数量
+ * 获取当前页面可绘制田字格的最大行数与列数(与绘制使用同一套计算规则)
*/
- // calculateTotalCellsNeeded(characters: string[], wordsMap: Record): number {
- // let totalCells = 0;
- // characters.forEach((char) => {
- // const strokes = wordsMap[char] || [];
- // const strokeCount = strokes.length;
- // const preview = 1; // 预览格
- // const practice = Math.min(strokeCount, 8); // 最多8个练习格
- // const blanks = 2; // 空白格
- // totalCells += preview + practice + blanks;
- // });
- // return totalCells;
- // }
+ getMaxGridLayout(): { maxRow: number; maxCol: number } {
+ const { canvas } = this;
+
+ // 布局参数需与 drawContent 保持一致
+ const topGap = 50;
+ const leftMargin = 120;
+ const rightMargin = 120;
+ const bottomMargin = 120;
+ const contentTop = this.currentY + topGap;
+ const contentWidth = canvas.width - leftMargin - rightMargin;
+ const contentHeight = canvas.height - contentTop - bottomMargin;
+
+ const cellSize = 140;
+ const minGap = 24;
+ const rowGap = 36;
+
+ const maxCol = Math.max(1, Math.floor((contentWidth + minGap) / (cellSize + minGap)));
+ const maxRow = Math.max(1, Math.floor((contentHeight + rowGap) / (cellSize + rowGap)));
+
+ return { maxRow, maxCol };
+ }
/**
* 绘制空田字格网格
@@ -463,8 +465,8 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
- rowNumber,
- columnNumber
+ maxRow,
+ maxCol
}: {
ctx: RenderingContext;
startX: number;
@@ -472,12 +474,11 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
- rowNumber: number; // 行数
- columnNumber: number; // 列数
+ maxRow: number; // 行数
+ maxCol: number; // 列数
}) {
- console.log('drawEmptyGrids startY----:', startY)
- for (let row = 0; row < rowNumber; row++) {
- for (let col = 0; col < columnNumber; col++) {
+ for (let row = 0; row < maxRow; row++) {
+ for (let col = 0; col < maxCol; col++) {
const x = startX + col * (cellSize + colGap) + cellSize / 2;
const y = startY + row * (cellSize + rowGap) + cellSize / 2;
drawTianZiGrid({ ctx, x, y, size: cellSize });
@@ -496,7 +497,8 @@ class WordDrawService {
cellSize,
colGap,
rowGap,
- columnNumber
+ maxCol,
+ maxRow
}: {
ctx: RenderingContext;
characterData: CharacterItem[];
@@ -505,69 +507,60 @@ class WordDrawService {
cellSize: number;
colGap: number;
rowGap: number;
- columnNumber: number;
+ maxCol: number;
+ maxRow: number;
}) {
- const reservedCells = 2;
- let rowIndex = 0, columnIndex = 0;
+ let rowIndex = 0;
- // console.log('开始绘制练字内容,汉字数量:', characters.length);
-
- characterData.forEach((item, index) => {
- const character = item.character;
+ for (let i = 0; i < characterData.length; i++) {
+ const item = characterData[i];
const strokes = item.strokes;
const strokeCount = strokes.length;
- console.log(`绘制第 ${index + 1} 个汉字 "${character}",笔画数: ${strokeCount}`);
+ const totalCells = 1 + strokeCount; // 预览 + 练习
+ const totalRows = Math.ceil(totalCells / maxCol);
- // 1. 预览格:显示完整汉字(黑色)
- this.drawPreviewCell({
- ctx,
- strokes,
- startX,
- startY,
- cellSize,
- colGap,
- rowGap,
- rowIndex,
- });
- columnIndex++;
+ if (rowIndex + totalRows > maxRow) {
+ break; // 放不下,停止绘制
+ }
- // 2. 练习格:逐笔画显示(红色)
- for (let strokeIndex = 0; strokeIndex < strokeCount; strokeIndex++) {
- // 先绘制当前笔画
- this.drawPracticeCell({
- ctx,
- strokes,
- strokeIndex,
- startX,
- startY,
- cellSize,
- colGap,
- rowGap,
- rowIndex,
- columnIndex
- });
- columnIndex++;
- if (columnIndex >= columnNumber) {
- columnIndex = 0;
- rowIndex++;
+ for (let cellIdx = 0; cellIdx < totalCells; cellIdx++) {
+ const localRowOffset = Math.floor(cellIdx / maxCol);
+ const localColIndex = cellIdx % maxCol;
+ const globalRow = rowIndex + localRowOffset;
+ if (globalRow >= maxRow) {
+ break;
+ }
+ if (cellIdx === 0) {
+ this.drawPreviewCell({
+ ctx,
+ strokes,
+ startX,
+ startY,
+ cellSize,
+ colGap,
+ rowGap,
+ rowIndex: globalRow,
+ });
+ } else {
+ const strokeIndex = cellIdx - 1;
+ this.drawPracticeCell({
+ ctx,
+ strokes,
+ strokeIndex,
+ startX,
+ startY,
+ cellSize,
+ colGap,
+ rowGap,
+ rowIndex: globalRow,
+ columnIndex: localColIndex,
+ });
}
- // console.log(`笔画 ${strokeIndex + 1} 绘制完成,当前 columnIndex: ${columnIndex}`);
- }
- if (columnIndex + reservedCells > columnNumber) {
- columnIndex = 0;
- rowIndex++;
}
- // 4. 强制下一个汉字换行到新行的第一个田字格
- if (index < characterData.length - 1) { // 不是最后一个汉字
- rowIndex = rowIndex + 1;
- columnIndex = 0;
- // console.log(`汉字 "${char}" 绘制完成,强制换行到新行,下一个汉字从 rowIndex: ${rowIndex}, columnIndex: ${columnIndex} 开始`);
- }
- });
-
- // console.log('所有汉字绘制完成');
+ rowIndex = rowIndex + totalRows;
+ }
}
/**
@@ -651,13 +644,13 @@ class WordDrawService {
offsetY: y,
size: cellSize,
uptoInclusive: strokeIndex,
- fillStyle: 'rgb(220, 20, 20)', // 深红色填充
- strokeStyle: 'rgb(220, 20, 20)', // 深红色描边
+ fillStyle: '#ccc',
+ strokeStyle: '#ccc',
lineWidth: 3, // 中等粗细
});
}
- drawLine(linY: number) {
+ drawDivider(linY: number) {
const { canvas, ctx } = this;
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
diff --git a/miniprogram/utils/getWordsSvgJson.ts b/miniprogram/utils/getWordsSvgJson.ts
index 00e3b53..fc28f96 100644
--- a/miniprogram/utils/getWordsSvgJson.ts
+++ b/miniprogram/utils/getWordsSvgJson.ts
@@ -6,8 +6,9 @@ import { getJson } from './http';
const CACHE_CONFIG = {
key: 'svgWordsData', // 缓存键名
expireDays: 7, // 缓存过期天数
- // url: 'https://cdn.joeyone.cn/words-svg-all.json' // 远程数据地址
- url: 'https://cdn.joeyone.cn/words-svg-3000.json' // 远程数据地址
+ // url: 'https://cdn.joeyone.cn/doodle/words-svg-all.json' // 远程数据地址
+ // url: 'https://cdn.joeyone.cn/doodle/words-svg-3000.json' // 远程数据地址
+ url: 'https://cdn.joeyone.cn/doodle/char_svg_3500.json' // 远程数据地址
};
/**