From ddab64bb20c2ac723409e71e764dd7de8366795c Mon Sep 17 00:00:00 2001 From: R524809 Date: Thu, 20 Nov 2025 17:54:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=B8=8B=E8=BD=BD?= =?UTF-8?q?=E5=88=86=E4=BA=AB=E6=A3=80=E6=9F=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- miniprogram/pages/copyBook/copyBook.json | 1 + miniprogram/pages/copyBook/copyBook.ts | 125 +++++++++++++++++++---- miniprogram/pages/copyBook/copyBook.wxml | 10 ++ miniprogram/pages/index/index.json | 1 + miniprogram/pages/index/index.ts | 58 ++++++++++- miniprogram/pages/index/index.wxml | 16 ++- miniprogram/pages/shape/index.json | 1 + miniprogram/pages/shape/index.ts | 58 ++++++++++- miniprogram/pages/shape/index.wxml | 10 ++ miniprogram/utils/saveImage.ts | 51 ++++----- miniprogram/utils/shareGuide.ts | 71 +++++++++++++ 11 files changed, 350 insertions(+), 52 deletions(-) create mode 100644 miniprogram/utils/shareGuide.ts diff --git a/miniprogram/pages/copyBook/copyBook.json b/miniprogram/pages/copyBook/copyBook.json index 5e1c6b8..9eba36f 100644 --- a/miniprogram/pages/copyBook/copyBook.json +++ b/miniprogram/pages/copyBook/copyBook.json @@ -7,6 +7,7 @@ "usingComponents": { "toy-button": "/ui/button/button", "van-icon": "@vant/weapp/icon/index", + "van-dialog": "@vant/weapp/dialog/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/copyBook.ts b/miniprogram/pages/copyBook/copyBook.ts index 2f5a432..9f7315b 100644 --- a/miniprogram/pages/copyBook/copyBook.ts +++ b/miniprogram/pages/copyBook/copyBook.ts @@ -1,5 +1,9 @@ import WordDrawService from '../../service/wordDrawService'; import { checkAndSaveImage } from '../../utils/saveImage'; +import { + shouldShowShareGuide, + recordShareSuccess, +} from '../../utils/shareGuide'; import { PAPER_SIZE } from '../../constants/colors'; import { getWordsSvgData } from '../../utils/getWordsSvgJson'; import { WORDS } from '../../constants/words'; @@ -22,6 +26,7 @@ Page({ currentColor: '', boxWidth: 0, boxHeight: 0, + showShareDialog: false, // 显示分享引导弹窗 }, async onLoad() { @@ -29,14 +34,17 @@ Page({ const hasShowPracticeDemo = wx.getStorageSync('hasShowPracticeDemo') || false; if (!hasShowPracticeDemo) { - this.setData({ - words: ['好', '好', '学', '习', '天', '天', '向', '上'] - }, () => { - wx.setStorageSync('hasShowPracticeDemo', true); - }) + this.setData( + { + words: ['好', '好', '学', '习', '天', '天', '向', '上'], + }, + () => { + wx.setStorageSync('hasShowPracticeDemo', true); + }, + ); } await this.setCanvasBoxSize(); - await this.initCanvas() + await this.initCanvas(); }, async loadSvgWords() { @@ -48,7 +56,11 @@ Page({ this.svgWords = svgWordsData; wx.hideToast(); - console.log('SVG汉字数据加载成功,共', Object.keys(this.svgWords).length, '个汉字'); + console.log( + 'SVG汉字数据加载成功,共', + Object.keys(this.svgWords).length, + '个汉字', + ); const { words } = this.data as any; if (words && words.length > 0) { @@ -70,7 +82,7 @@ Page({ // 用户点击重试,重新加载 this.loadSvgWords(); } - } + }, }); } }, @@ -143,18 +155,25 @@ Page({ const { key } = e.detail; const { words } = this.data as any; const next = words.filter((_: string, index: number) => index !== key); - this.setData({ words: next }, () => this.renderPracticeContent().catch(console.error)); + this.setData({ words: next }, () => + this.renderPracticeContent().catch(console.error), + ); }, // 清空 clearWords() { - this.setData({ words: [] }, () => this.renderPracticeContent().catch(console.error)); + 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 candidate = WORDS.slice(0, 3).reduce( + (acc: string[], c: any) => acc.concat(c.words), + [] as string[], + ); // 去重 const unique = Array.from(new Set(candidate)); // 打乱 @@ -164,10 +183,15 @@ Page({ // 过滤为支持的SVG字 const supported = this.getSupportedWords(picked); if (supported.length === 0) { - wx.showToast({ title: '随机到的字暂不支持,重试一下', icon: 'none' }); + wx.showToast({ + title: '随机到的字暂不支持,重试一下', + icon: 'none', + }); return; } - this.setData({ words: supported }, () => this.renderPracticeContent().catch(console.error)); + this.setData({ words: supported }, () => + this.renderPracticeContent().catch(console.error), + ); }, // 校验 & 分割 @@ -181,7 +205,6 @@ Page({ return matches || []; }, - setCanvasBoxSize() { const query = wx.createSelectorQuery(); query @@ -212,13 +235,17 @@ Page({ if (ctx) { this.canvas = canvas; this.ctx = ctx; - this.wordDrawService = new WordDrawService(canvas, ctx); + this.wordDrawService = new WordDrawService( + canvas, + ctx, + ); // 初始化时只绘制页眉和布局 await this.wordDrawService.drawLayout(); // 绘制完成后计算并缓存最大行列数(与实际绘制一致) - const { maxRow, maxCol } = this.wordDrawService.getMaxGridLayout(); + const { maxRow, maxCol } = + this.wordDrawService.getMaxGridLayout(); this.maxRow = maxRow; this.maxCol = maxCol; @@ -269,18 +296,25 @@ Page({ } // 处理布局和生成练字数据(基于实际最大行列数) - const characterData = this.processCharacterLayout(supportedWords, maxRow, maxCol); + const characterData = this.processCharacterLayout( + supportedWords, + maxRow, + maxCol, + ); this.wordDrawService.drawPracticeContent(characterData); }, - /** 检查汉字是否支持,返回支持的汉字列表 */ getSupportedWords(words: string[]): string[] { - return words.filter(word => this.svgWords[word]); + return words.filter((word) => this.svgWords[word]); }, /** 处理汉字布局,生成练字数据 */ - processCharacterLayout(words: string[], maxRow: number, maxCol: number): CharacterItem[] { + processCharacterLayout( + words: string[], + maxRow: number, + maxCol: number, + ): CharacterItem[] { let rowIndex = 0; const characterData: CharacterItem[] = []; const boundaryWords: string[] = []; @@ -319,11 +353,58 @@ Page({ wx.showToast({ title: '请先生成练字贴', icon: 'none' }); return; } + + // 检查是否需要显示分享引导 + if (shouldShowShareGuide()) { + this.setData({ showShareDialog: true }); + return; + } + + // 直接下载 checkAndSaveImage(this.canvas); }, + /** 关闭分享引导弹窗 */ + onCloseShareDialog() { + this.setData({ showShareDialog: false }); + }, + + /** 点击转发按钮 */ + onShareToFriend() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); + }, + + /** 点击分享到朋友圈按钮 */ + onShareToTimeline() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); + }, + // 分享功能 onShareAppMessage() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-练字|识字|打印', path: '/pages/copyBook/index', @@ -331,11 +412,11 @@ Page({ }, onShareTimeline() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-练字|识字|打印', query: '/pages/copyBook/index', }; }, }); - - diff --git a/miniprogram/pages/copyBook/copyBook.wxml b/miniprogram/pages/copyBook/copyBook.wxml index 1e60a90..ca744d3 100644 --- a/miniprogram/pages/copyBook/copyBook.wxml +++ b/miniprogram/pages/copyBook/copyBook.wxml @@ -91,6 +91,16 @@ max="11" bind:onClose="closeSelectWordPopup" bind:onChange="onChangeWord" /> + diff --git a/miniprogram/pages/index/index.json b/miniprogram/pages/index/index.json index 08eaf8d..180f50e 100644 --- a/miniprogram/pages/index/index.json +++ b/miniprogram/pages/index/index.json @@ -8,6 +8,7 @@ "van-cell": "@vant/weapp/cell/index", "van-icon": "@vant/weapp/icon/index", "van-popup": "@vant/weapp/popup/index", + "van-dialog": "@vant/weapp/dialog/index", "word-input": "../../components/word-input/word-input", "word-card": "../../components/word-card/word-card", "color-picker": "../../components/color-picker/color-picker", diff --git a/miniprogram/pages/index/index.ts b/miniprogram/pages/index/index.ts index 80be3fc..cf2054f 100644 --- a/miniprogram/pages/index/index.ts +++ b/miniprogram/pages/index/index.ts @@ -6,6 +6,10 @@ import { TemplateType, } from '../../service/drawServiceFactory'; import { checkAndSaveImage } from '../../utils/saveImage'; +import { + shouldShowShareGuide, + recordShareSuccess, +} from '../../utils/shareGuide'; Page({ canvas: null as Canvas | null, @@ -25,6 +29,7 @@ Page({ selectedTemplate: 'find', // 选中的模板类型 env: 'release', isPCDevtool: false, + showShareDialog: false, // 显示分享引导弹窗 }, onLoad() { @@ -280,10 +285,55 @@ Page({ /** 下载打印 */ exportToPrint() { - if (this.canvas && this.data.cardList.length > 0) { - checkAndSaveImage(this.canvas); + if (!this.canvas || this.data.cardList.length <= 0) { return; } + + // 检查是否需要显示分享引导 + if (shouldShowShareGuide()) { + this.setData({ showShareDialog: true }); + return; + } + + // 直接下载 + checkAndSaveImage(this.canvas); + }, + + /** 关闭分享引导弹窗 */ + onCloseShareDialog() { + this.setData({ showShareDialog: false }); + }, + + /** 点击转发按钮 */ + onShareToFriend() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas && this.data.cardList.length > 0) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); + }, + + /** 点击分享到朋友圈按钮 */ + onShareToTimeline() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas && this.data.cardList.length > 0) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); }, onColorTap(e: WechatMiniprogram.CustomEvent) { @@ -324,12 +374,16 @@ Page({ }, onShareAppMessage() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-涂色|识字|画画|打印', path: '/pages/index/index', }; }, onShareTimeline() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-涂色|识字|画画|打印', query: '/pages/index/index', diff --git a/miniprogram/pages/index/index.wxml b/miniprogram/pages/index/index.wxml index f9685f3..b244c7b 100644 --- a/miniprogram/pages/index/index.wxml +++ b/miniprogram/pages/index/index.wxml @@ -135,6 +135,16 @@ 下载打印 - + + diff --git a/miniprogram/pages/shape/index.json b/miniprogram/pages/shape/index.json index 9e3ac91..2cd7409 100644 --- a/miniprogram/pages/shape/index.json +++ b/miniprogram/pages/shape/index.json @@ -8,6 +8,7 @@ "van-divider": "@vant/weapp/divider/index", "van-icon": "@vant/weapp/icon/index", "van-popup": "@vant/weapp/popup/index", + "van-dialog": "@vant/weapp/dialog/index", "toy-button": "../../ui/button/button", "shape-card": "../../components/shape-card/shape-card", "shape-picker": "../../components/shape-picker/shape-picker", diff --git a/miniprogram/pages/shape/index.ts b/miniprogram/pages/shape/index.ts index 68d806c..e9d6232 100644 --- a/miniprogram/pages/shape/index.ts +++ b/miniprogram/pages/shape/index.ts @@ -2,6 +2,10 @@ import { SHAPES, ShapeCard } from '../../constants/shapes'; import { WATER_COLORS, PAPER_SIZE } from '../../constants/colors'; import ShapeDrawService from '../../service/shapeDrawService'; import { checkAndSaveImage } from '../../utils/saveImage'; +import { + shouldShowShareGuide, + recordShareSuccess, +} from '../../utils/shareGuide'; Page({ @@ -20,6 +24,7 @@ Page({ currentIndex: 0, currentColor: '', isPCDevtool: false, + showShareDialog: false, // 显示分享引导弹窗 }, onLoad() { @@ -263,12 +268,16 @@ Page({ }, onShareAppMessage() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-涂色|识字|画画|打印', path: '/pages/index/index', }; }, onShareTimeline() { + // 记录分享成功(用户真正分享了) + recordShareSuccess(); return { title: '涂鸦丫-涂色|识字|画画|打印', query: '/pages/index/index', @@ -277,10 +286,55 @@ Page({ /** 下载打印 */ exportToPrint() { - if (this.canvas && this.data.shapeList.length > 0) { - checkAndSaveImage(this.canvas); + if (!this.canvas || this.data.shapeList.length <= 0) { return; } + + // 检查是否需要显示分享引导 + if (shouldShowShareGuide()) { + this.setData({ showShareDialog: true }); + return; + } + + // 直接下载 + checkAndSaveImage(this.canvas); + }, + + /** 关闭分享引导弹窗 */ + onCloseShareDialog() { + this.setData({ showShareDialog: false }); + }, + + /** 点击转发按钮 */ + onShareToFriend() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas && this.data.shapeList.length > 0) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); + }, + + /** 点击分享到朋友圈按钮 */ + onShareToTimeline() { + // 记录分享成功(用户点击了分享按钮,视为已分享) + recordShareSuccess(); + this.setData({ showShareDialog: false }); + // 允许继续下载 + if (this.canvas && this.data.shapeList.length > 0) { + checkAndSaveImage(this.canvas); + } + wx.showToast({ + title: '分享后即可继续下载', + icon: 'success', + duration: 2000, + }); }, onUnload() { diff --git a/miniprogram/pages/shape/index.wxml b/miniprogram/pages/shape/index.wxml index b5a1c63..162ab85 100644 --- a/miniprogram/pages/shape/index.wxml +++ b/miniprogram/pages/shape/index.wxml @@ -67,6 +67,16 @@ + { wx.canvasToTempFilePath({ canvas: canvas, @@ -6,11 +8,15 @@ const doSaveImage = (canvas: Canvas) => { success: (res) => { wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, - success: () => wx.showToast({ - title: '保存成功,请在相册中查看', - icon: 'success', - duration: 2000, - }), + success: () => { + // 记录下载成功 + recordDownloadSuccess(); + wx.showToast({ + title: '保存成功,请在相册中查看', + icon: 'success', + duration: 2000, + }); + }, // fail: () => wx.showToast({ // title: '保存涂色卡失败,请重试!', // icon: 'none', @@ -18,38 +24,37 @@ const doSaveImage = (canvas: Canvas) => { // }) }); }, - fail: (err: any) => { + fail: () => { wx.showToast({ title: '生成涂色卡失败,请重试!', icon: 'none', duration: 2000, - }) - } + }); + }, }); -} +}; const requestPhotoPermission = (canvas: Canvas) => { wx.authorize({ scope: 'scope.writePhotosAlbum', success: () => doSaveImage(canvas), - fail: () => wx.showToast({ - title: '请在“设置-添加到相册”中开启相册权限以保存涂色卡', - icon: 'none', - duration: 3000, - }) - }) -} - + fail: () => + wx.showToast({ + title: '请在“设置-添加到相册”中开启相册权限以保存涂色卡', + icon: 'none', + duration: 3000, + }), + }); +}; export const checkAndSaveImage = (canvas: Canvas) => { wx.getSetting({ - success: res => { + success: (res) => { if (!res.authSetting['scope.writePhotosAlbum']) { - requestPhotoPermission(canvas) + requestPhotoPermission(canvas); } else { - doSaveImage(canvas) + doSaveImage(canvas); } - } - }) + }, + }); }; - diff --git a/miniprogram/utils/shareGuide.ts b/miniprogram/utils/shareGuide.ts new file mode 100644 index 0000000..514c303 --- /dev/null +++ b/miniprogram/utils/shareGuide.ts @@ -0,0 +1,71 @@ +/** + * 分享引导工具 + * 用于控制每日免费下载次数和分享引导 + */ + +const STORAGE_KEY_DOWNLOAD_DATE = 'downloadDate'; +const STORAGE_KEY_DOWNLOAD_COUNT = 'downloadCount'; +const STORAGE_KEY_SHARED_DATE = 'sharedDate'; + +/** + * 获取今天的日期字符串(YYYY-MM-DD格式) + */ +function getTodayDateString(): string { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const day = String(today.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +/** + * 检查今天是否已经下载过 + * @returns true表示今天已下载过,false表示今天未下载 + */ +export function hasDownloadedToday(): boolean { + const today = getTodayDateString(); + const downloadDate = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_DATE); + return downloadDate === today; +} + +/** + * 记录今天的下载成功 + */ +export function recordDownloadSuccess(): void { + const today = getTodayDateString(); + wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today); + const count = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_COUNT) || 0; + wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, count + 1); +} + +/** + * 检查今天是否已经分享过 + * @returns true表示今天已分享过,false表示今天未分享 + */ +export function hasSharedToday(): boolean { + const today = getTodayDateString(); + const sharedDate = wx.getStorageSync(STORAGE_KEY_SHARED_DATE); + return sharedDate === today; +} + +/** + * 记录今天的分享成功 + */ +export function recordShareSuccess(): void { + const today = getTodayDateString(); + wx.setStorageSync(STORAGE_KEY_SHARED_DATE, today); +} + +/** + * 检查是否需要显示分享引导 + * @returns true表示需要显示分享引导,false表示不需要 + */ +export function shouldShowShareGuide(): boolean { + // 如果今天已经分享过,不需要显示引导 + if (hasSharedToday()) { + return false; + } + // 如果今天已经下载过,需要显示引导 + return hasDownloadedToday(); +} +