From 9df15e1ab4f11edcbe32f15cf5115b9bac69ee87 Mon Sep 17 00:00:00 2001 From: R524809 Date: Tue, 30 Sep 2025 17:48:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=BC=80=E5=8F=91=E7=BB=83=E5=AD=97?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/app.json | 3 +- miniprogram/pages/copyBook/index.json | 10 ++ miniprogram/pages/copyBook/index.less | 147 ++++++++++++++++++++++++++ miniprogram/pages/copyBook/index.ts | 91 ++++++++++++++++ miniprogram/pages/copyBook/index.wxml | 67 ++++++++++++ miniprogram/pages/wordDemo/index.less | 78 ++++++++++++++ miniprogram/pages/wordDemo/index.ts | 31 ++++++ miniprogram/pages/wordDemo/index.wxml | 25 +++++ 8 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 miniprogram/pages/copyBook/index.json create mode 100644 miniprogram/pages/copyBook/index.less create mode 100644 miniprogram/pages/copyBook/index.ts create mode 100644 miniprogram/pages/copyBook/index.wxml diff --git a/miniprogram/app.json b/miniprogram/app.json index 711aeb2..2fc6fbf 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -4,6 +4,7 @@ "pages/shape/index", "pages/debug/debug", "pages/wordDemo/index", + "pages/copyBook/index", "pages/buttonTest/buttonTest" ], "window": {}, @@ -43,7 +44,7 @@ "text": "图形" }, { - "pagePath": "pages/wordDemo/index", + "pagePath": "pages/copyBook/index", "iconPath": "assets/tabBar/icon-word.png", "selectedIconPath": "assets/tabBar/icon-word-active.png", "text": "练字" diff --git a/miniprogram/pages/copyBook/index.json b/miniprogram/pages/copyBook/index.json new file mode 100644 index 0000000..b5d9f55 --- /dev/null +++ b/miniprogram/pages/copyBook/index.json @@ -0,0 +1,10 @@ +{ + "navigationBarTitleText": "练字", + "usingComponents": { + "toy-button": "/ui/button/button", + "van-icon": "@vant/weapp/icon/index", + "word-input": "/components/word-input/word-input", + "word-card": "/components/word-card/word-card", + "word-picker": "/components/word-picker/word-picker" + } +} diff --git a/miniprogram/pages/copyBook/index.less b/miniprogram/pages/copyBook/index.less new file mode 100644 index 0000000..45e8d68 --- /dev/null +++ b/miniprogram/pages/copyBook/index.less @@ -0,0 +1,147 @@ +page { + background-color: #f6f6f6; +} + +.page-container { + background-color: #f6f6f6; + padding: 0 24rpx; + box-sizing: border-box; +} + +.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; + } + + .word-card-box { + padding: 34rpx 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; + } + } + + .word-cards-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 40rpx; + } + } + + .word-input-box { + display: flex; + flex-direction: row; + justify-content: space-between; + } +} + +.tianzige { + display: grid; + grid-auto-rows: min-content; + row-gap: 12rpx; +} + +.tzg-row { + display: grid; + grid-template-columns: repeat(12, 1fr); + column-gap: 12rpx; +} + +.tzg-cell { + position: relative; + background: #fff; + border: 4rpx solid #333; + height: 120rpx; + border-radius: 6rpx; + overflow: hidden; +} + +.tzg-word { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 72rpx; + color: #222; + font-family: 'Kaiti', 'KaiTi', 'STKaiti', 'FZKai-Z03S', serif; +} + +.mid-line { + position: absolute; + background: repeating-linear-gradient(to right, + rgba(0, 0, 0, 0.25), + rgba(0, 0, 0, 0.25) 2rpx, + transparent 2rpx, + transparent 6rpx); +} + +.mid-line.h { + left: 0; + right: 0; + top: 50%; + height: 2rpx; + transform: translateY(-50%); +} + +.mid-line.v { + top: 0; + bottom: 0; + left: 50%; + width: 2rpx; + transform: translateX(-50%); +} + +.empty-tip { + text-align: center; + color: #999; + font-size: 28rpx; +} \ No newline at end of file diff --git a/miniprogram/pages/copyBook/index.ts b/miniprogram/pages/copyBook/index.ts new file mode 100644 index 0000000..11e9e4c --- /dev/null +++ b/miniprogram/pages/copyBook/index.ts @@ -0,0 +1,91 @@ +Page({ + data: { + // 选中字卡列表,复用 index 页的结构 [{ word, color }] + cardList: [] as { word: string; color: string }[], + showSelectWordPopup: false, + + // 田字格配置 + rows: 10, + cols: 12, + rowsArray: [] as number[], + colsArray: [] as number[], + rowHeaderWords: [] as string[], + }, + + onLoad() { + this.initGrid(); + }, + + // 输入组件回调 + onConfirmInput(e: any) { + const value: string = (e && e.detail && e.detail.value) || ''; + const text = (value || '').trim(); + if (!text) return; + + if (!this.isChineseText(text)) { + wx.showToast({ title: '请输入汉字', icon: 'none' }); + return; + } + + const chars = this.splitToSingleChars(text); + const { cardList } = this.data as any; + const exists = new Set(cardList.map((c: any) => c.word)); + const newList = [...cardList]; + chars.forEach((ch) => { + if (!exists.has(ch)) { + newList.push({ word: ch, color: '#141414' }); + } + }); + this.setData({ cardList: newList }, () => this.updateRowHeaderWords()); + }, + + // 选择面板 + openSelectWordPopup() { + this.setData({ showSelectWordPopup: true }); + }, + closeSelectWordPopup() { + this.setData({ showSelectWordPopup: false }); + }, + onChangeWord(e: any) { + const { cardList } = e.detail || {}; + this.setData({ cardList }, () => this.updateRowHeaderWords()); + }, + + // 删除与颜色 + deleteWordCard(e: any) { + const index = e.detail && e.detail.index; + const { cardList } = this.data as any; + const next = cardList.filter((_: any, i: number) => i !== index); + this.setData({ cardList: next }, () => this.updateRowHeaderWords()); + }, + onColorTap() { }, + + // 校验 & 分割 + isChineseText(text: string): boolean { + const chineseRegex = /^[\u4e00-\u9fff]+$/; + return chineseRegex.test(text); + }, + splitToSingleChars(text: string): string[] { + const chineseRegex = /[\u4e00-\u9fff]/g; + const matches = text.match(chineseRegex); + return matches || []; + }, + + // 田字格 + initGrid() { + const { rows, cols } = this.data as any; + const rowsArray = Array.from({ length: rows }, (_, i) => i); + const colsArray = Array.from({ length: cols }, (_, i) => i); + this.setData({ rowsArray, colsArray }); + }, + updateRowHeaderWords() { + const { rows, cardList } = this.data as any; + const rowHeaderWords: string[] = []; + for (let i = 0; i < rows; i++) { + rowHeaderWords.push((cardList[i] && cardList[i].word) || ''); + } + this.setData({ rowHeaderWords }); + }, +}); + + diff --git a/miniprogram/pages/copyBook/index.wxml b/miniprogram/pages/copyBook/index.wxml new file mode 100644 index 0000000..0b372b6 --- /dev/null +++ b/miniprogram/pages/copyBook/index.wxml @@ -0,0 +1,67 @@ + + + 自定义练字 + + + + 选择文字 + + + + + + + + + + + + 还没有添加文字 + 请在上方输入文字或点击"选择文字"按钮
开始创建你的练字田字格吧!
+
+
+
+ + + 预览练字田字格 + + + + {{rowHeaderWords[rowIndex]}} + + + + + + 请选择或输入汉字后生成田字格 + + + +
diff --git a/miniprogram/pages/wordDemo/index.less b/miniprogram/pages/wordDemo/index.less index d35ce8e..1e30e29 100644 --- a/miniprogram/pages/wordDemo/index.less +++ b/miniprogram/pages/wordDemo/index.less @@ -10,6 +10,84 @@ page { overflow-y: auto; } +.preview-section { + margin-top: 24rpx; + background: #fff; + border-radius: 20rpx; + padding: 24rpx; + box-shadow: 0 6rpx 8rpx rgba(0, 0, 0, 0.15); +} + +.section-title { + display: block; + text-align: center; + font-size: 32rpx; + font-weight: bold; + margin-bottom: 24rpx; +} + +.tianzige { + display: grid; + grid-auto-rows: min-content; + row-gap: 12rpx; +} + +.tzg-row { + display: grid; + grid-template-columns: repeat(12, 1fr); + column-gap: 12rpx; +} + +.tzg-cell { + position: relative; + background: #fff; + border: 4rpx solid #333; + height: 120rpx; + border-radius: 6rpx; + overflow: hidden; +} + +.tzg-word { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + font-size: 72rpx; + color: #222; + font-family: 'Kaiti', 'KaiTi', 'STKaiti', 'FZKai-Z03S', serif; +} + +.mid-line { + position: absolute; + background: repeating-linear-gradient(to right, + rgba(0, 0, 0, 0.25), + rgba(0, 0, 0, 0.25) 2rpx, + transparent 2rpx, + transparent 6rpx); +} + +.mid-line.h { + left: 0; + right: 0; + top: 50%; + height: 2rpx; + transform: translateY(-50%); +} + +.mid-line.v { + top: 0; + bottom: 0; + left: 50%; + width: 2rpx; + transform: translateX(-50%); +} + +.empty-tip { + text-align: center; + color: #999; + font-size: 28rpx; +} + /* 输入区域样式 */ .input-section { background-color: #fff; diff --git a/miniprogram/pages/wordDemo/index.ts b/miniprogram/pages/wordDemo/index.ts index 55ac4dd..c6f53b7 100644 --- a/miniprogram/pages/wordDemo/index.ts +++ b/miniprogram/pages/wordDemo/index.ts @@ -3,10 +3,17 @@ Page({ inputWord: '', // 输入框中的汉字 wordList: [] as string[], // 选中的汉字列表 availableWords: [] as string[], // 从grade3.json中提取的前30个汉字 + // 田字格配置 + rows: 10, + cols: 12, + rowsArray: [] as number[], + colsArray: [] as number[], + rowHeaderWords: [] as string[], }, onLoad() { this.loadGrade3Words(); + this.initGrid(); }, // 加载grade3.json中的前30个汉字 @@ -70,6 +77,8 @@ Page({ this.setData({ wordList: newWordList, inputWord: '' // 清空输入框 + }, () => { + this.updateRowHeaderWords(); }); wx.showToast({ @@ -128,6 +137,8 @@ Page({ const newWordList = wordList.filter((_, i) => i !== index); this.setData({ wordList: newWordList + }, () => { + this.updateRowHeaderWords(); }); wx.showToast({ title: '已移除', @@ -139,10 +150,30 @@ Page({ onClearAll() { this.setData({ wordList: [] + }, () => { + this.updateRowHeaderWords(); }); wx.showToast({ title: '已清空', icon: 'success' }); }, + + // 初始化田字格行列数组 + initGrid() { + const { rows, cols } = this.data as any; + const rowsArray = Array.from({ length: rows }, (_, i) => i); + const colsArray = Array.from({ length: cols }, (_, i) => i); + this.setData({ rowsArray, colsArray }); + }, + + // 根据选中的汉字生成每行首字数组 + updateRowHeaderWords() { + const { rows, wordList } = this.data as any; + const rowHeaderWords: string[] = []; + for (let i = 0; i < rows; i++) { + rowHeaderWords.push(wordList[i] || ''); + } + this.setData({ rowHeaderWords }); + }, }); diff --git a/miniprogram/pages/wordDemo/index.wxml b/miniprogram/pages/wordDemo/index.wxml index 4132e2d..2bf6a65 100644 --- a/miniprogram/pages/wordDemo/index.wxml +++ b/miniprogram/pages/wordDemo/index.wxml @@ -61,3 +61,28 @@ + + + + 预览练字田字格 + + + + {{rowHeaderWords[rowIndex]}} + + + + + + 请选择或输入汉字后生成田字格 +