feat: 下载次数限制,免费额度用完后第一次需要分享,之后需要看广告
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import { PAPER_SIZE } from '../constants/colors';
|
import { PAPER_SIZE } from '../constants/colors';
|
||||||
import { downloadPrint } from '../utils/downloadPrint';
|
import { downloadPrint, tryUnlockWithRewardedAd } from '../utils/downloadPrint';
|
||||||
|
import {
|
||||||
|
shouldShowShareGuide,
|
||||||
|
shouldUnlockWithAd,
|
||||||
|
} from '../utils/shareGuide';
|
||||||
import { BaseDrawService } from '../core/draw/baseDraw';
|
import { BaseDrawService } from '../core/draw/baseDraw';
|
||||||
import tracker from '../utils/tracker';
|
import tracker from '../utils/tracker';
|
||||||
import { defaultShareConfig } from '../config/config';
|
import { defaultShareConfig } from '../config/config';
|
||||||
@@ -230,12 +234,28 @@ export function getPageCommonMethods(config: PageCommonMethodsConfig = {}) {
|
|||||||
typeof this.getWorksheetStatsId === 'function'
|
typeof this.getWorksheetStatsId === 'function'
|
||||||
? this.getWorksheetStatsId()
|
? this.getWorksheetStatsId()
|
||||||
: this.data.functionId;
|
: this.data.functionId;
|
||||||
await downloadPrint(this.canvas, {
|
const downloadOptions = {
|
||||||
errorToast: '请先生成内容',
|
errorToast: '请先生成内容',
|
||||||
trackerName: this.data.pageTitle,
|
trackerName: this.data.pageTitle,
|
||||||
trackerMode: this.data.currentMode,
|
trackerMode: this.data.currentMode,
|
||||||
worksheetId,
|
worksheetId,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
// 第 2 次及以后满批:直接拉激励广告
|
||||||
|
if (shouldUnlockWithAd()) {
|
||||||
|
const unlocked = await tryUnlockWithRewardedAd();
|
||||||
|
if (!unlocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当日首次满批:仅分享解锁,弹窗引导
|
||||||
|
if (shouldShowShareGuide()) {
|
||||||
|
this.setData({ showShareDialog: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await downloadPrint(this.canvas, downloadOptions);
|
||||||
},
|
},
|
||||||
|
|
||||||
getShareOptions(this: PageCanvasInstance): ShareOptions {
|
getShareOptions(this: PageCanvasInstance): ShareOptions {
|
||||||
@@ -288,7 +308,6 @@ export function getPageCommonMethods(config: PageCommonMethodsConfig = {}) {
|
|||||||
typeof this.getWorksheetStatsId === 'function'
|
typeof this.getWorksheetStatsId === 'function'
|
||||||
? this.getWorksheetStatsId()
|
? this.getWorksheetStatsId()
|
||||||
: this.data.functionId;
|
: this.data.functionId;
|
||||||
// 分享成功后下载(不限制次数)
|
|
||||||
await downloadPrint(this.canvas, {
|
await downloadPrint(this.canvas, {
|
||||||
errorToast: '请先生成内容',
|
errorToast: '请先生成内容',
|
||||||
trackerName: this.data.pageTitle,
|
trackerName: this.data.pageTitle,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import WordDrawService from './draw/wordDrawService';
|
import WordDrawService from './draw/wordDrawService';
|
||||||
import { downloadPrint } from '../../utils/downloadPrint';
|
|
||||||
import { getWordsSvgData } from '../../utils/getWordsSvgJson';
|
import { getWordsSvgData } from '../../utils/getWordsSvgJson';
|
||||||
import { WORDS } from '../../core/data/words';
|
import { WORDS } from '../../core/data/words';
|
||||||
import { CharacterItem } from '../../types/characterType';
|
import { CharacterItem } from '../../types/characterType';
|
||||||
@@ -396,13 +395,6 @@ createPage(
|
|||||||
return characterData;
|
return characterData;
|
||||||
},
|
},
|
||||||
|
|
||||||
async exportToPrint() {
|
|
||||||
await downloadPrint(this.canvas, {
|
|
||||||
errorToast: '请先生成练字贴',
|
|
||||||
trackerName: '练字贴',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onShare() {},
|
onShare() {},
|
||||||
|
|
||||||
onShareAppMessage() {
|
onShareAppMessage() {
|
||||||
@@ -421,14 +413,6 @@ createPage(
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
onCloseShareDialog() {
|
|
||||||
this.setData({ showShareDialog: false });
|
|
||||||
},
|
|
||||||
|
|
||||||
onShareSuccess() {
|
|
||||||
this.setData({ showShareDialog: false });
|
|
||||||
},
|
|
||||||
|
|
||||||
getPublishMeta(): DebugPublishMeta {
|
getPublishMeta(): DebugPublishMeta {
|
||||||
const meta = getPublishMetaByMode(HANDWRITING_SHEET_WORKSHEET_ID);
|
const meta = getPublishMetaByMode(HANDWRITING_SHEET_WORKSHEET_ID);
|
||||||
if (!meta) {
|
if (!meta) {
|
||||||
|
|||||||
@@ -55,5 +55,10 @@
|
|||||||
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--secondary {
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #323233;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
import { recordShareSuccess } from '../../utils/shareGuide';
|
import {
|
||||||
|
getShareGuidePopupCopy,
|
||||||
|
recordShareSuccess,
|
||||||
|
} from '../../utils/shareGuide';
|
||||||
|
|
||||||
|
const popupCopy = getShareGuidePopupCopy();
|
||||||
|
|
||||||
Component({
|
Component({
|
||||||
options: {},
|
options: {},
|
||||||
@@ -14,7 +19,10 @@ Component({
|
|||||||
/**
|
/**
|
||||||
* 组件的初始数据
|
* 组件的初始数据
|
||||||
*/
|
*/
|
||||||
data: {},
|
data: {
|
||||||
|
unlockMessage: popupCopy.message,
|
||||||
|
unlockHint: popupCopy.hint,
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* 组件的方法列表
|
* 组件的方法列表
|
||||||
*/
|
*/
|
||||||
@@ -31,9 +39,7 @@ Component({
|
|||||||
|
|
||||||
/** 点击分享按钮 */
|
/** 点击分享按钮 */
|
||||||
onShare() {
|
onShare() {
|
||||||
// 记录分享成功(用户点击了分享按钮,视为已分享)
|
|
||||||
recordShareSuccess();
|
recordShareSuccess();
|
||||||
// 关闭弹窗并通知父组件分享成功(不触发下载,用户需要重新点击下载按钮)
|
|
||||||
this.triggerEvent('onShareSuccess');
|
this.triggerEvent('onShareSuccess');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,11 +8,9 @@
|
|||||||
bind:click-overlay="onOverlayClick"
|
bind:click-overlay="onOverlayClick"
|
||||||
custom-class="share-guide-popup">
|
custom-class="share-guide-popup">
|
||||||
<view class="share-guide-popup-content">
|
<view class="share-guide-popup-content">
|
||||||
<view class="share-guide-popup-title">分享解锁下载</view>
|
<view class="share-guide-popup-title">解锁继续下载</view>
|
||||||
<view class="share-guide-popup-message"> 今日免费下载次数已用完 </view>
|
<view class="share-guide-popup-message">{{unlockMessage}}</view>
|
||||||
<view class="share-guide-popup-hint">
|
<view class="share-guide-popup-hint">{{unlockHint}}</view>
|
||||||
分享给好友后,可重新点击“下载打印”按钮继续下载
|
|
||||||
</view>
|
|
||||||
<view class="share-guide-popup-buttons">
|
<view class="share-guide-popup-buttons">
|
||||||
<button
|
<button
|
||||||
class="share-button share-button--primary"
|
class="share-button share-button--primary"
|
||||||
|
|||||||
@@ -19,3 +19,17 @@ export const defaultShareConfig = {
|
|||||||
path: '/pages/mathIndex/mathIndex',
|
path: '/pages/mathIndex/mathIndex',
|
||||||
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 每批可免费下载次数
|
||||||
|
* - 当日第 1 次满批:弹分享窗,须分享后才能继续下载
|
||||||
|
* - 当日第 2 次及以后满批:直接播放激励广告解锁
|
||||||
|
*/
|
||||||
|
export const downloadFreeBatchSize = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* develop 包下载限制开关
|
||||||
|
* - develop:为 true 时启用下载配额/分享/广告(便于联调);为 false 时不限制
|
||||||
|
* - 体验版 / 正式版:始终启用,不受此开关影响
|
||||||
|
*/
|
||||||
|
export const enableDownloadLimitInDevelop = true;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { SHAPES, ShapeCard } from '../../constants/shapes';
|
import { SHAPES, ShapeCard } from '../../constants/shapes';
|
||||||
import { WATER_COLORS, PAPER_SIZE } from '../../constants/colors';
|
import { WATER_COLORS, PAPER_SIZE } from '../../constants/colors';
|
||||||
import ShapeDrawService from '../../service/shapeDrawService';
|
import ShapeDrawService from '../../service/shapeDrawService';
|
||||||
import { downloadPrint } from '../../utils/downloadPrint';
|
import { downloadPrint, tryUnlockWithRewardedAd } from '../../utils/downloadPrint';
|
||||||
|
import {
|
||||||
|
shouldShowShareGuide,
|
||||||
|
shouldUnlockWithAd,
|
||||||
|
} from '../../utils/shareGuide';
|
||||||
|
|
||||||
Page({
|
Page({
|
||||||
canvas: null as Canvas | null,
|
canvas: null as Canvas | null,
|
||||||
@@ -289,6 +293,18 @@ Page({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldUnlockWithAd()) {
|
||||||
|
const unlocked = await tryUnlockWithRewardedAd();
|
||||||
|
if (!unlocked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldShowShareGuide()) {
|
||||||
|
this.setData({ showShareDialog: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await downloadPrint(this.canvas, {
|
await downloadPrint(this.canvas, {
|
||||||
errorToast: '请先生成图形',
|
errorToast: '请先生成图形',
|
||||||
trackerName: '图形涂色',
|
trackerName: '图形涂色',
|
||||||
@@ -324,9 +340,14 @@ Page({
|
|||||||
},
|
},
|
||||||
|
|
||||||
/** 分享成功回调(由组件触发) */
|
/** 分享成功回调(由组件触发) */
|
||||||
onShareSuccess() {
|
async onShareSuccess() {
|
||||||
// 只关闭弹窗,不触发下载
|
|
||||||
this.setData({ showShareDialog: false });
|
this.setData({ showShareDialog: false });
|
||||||
|
if (this.canvas && this.data.shapeList.length > 0) {
|
||||||
|
await downloadPrint(this.canvas, {
|
||||||
|
errorToast: '请先生成图形',
|
||||||
|
trackerName: '图形涂色',
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUnload() {
|
onUnload() {
|
||||||
|
|||||||
@@ -6,122 +6,51 @@
|
|||||||
import { checkAndSaveImage } from './saveImage';
|
import { checkAndSaveImage } from './saveImage';
|
||||||
import tracker from './tracker';
|
import tracker from './tracker';
|
||||||
import { addDownloadLog } from './downloadLogs';
|
import { addDownloadLog } from './downloadLogs';
|
||||||
|
import {
|
||||||
|
getUnlockMethod,
|
||||||
|
isDownloadLimitEnabled,
|
||||||
|
needsUnlockBeforeDownload,
|
||||||
|
recordAdUnlockSuccess,
|
||||||
|
recordDownloadSuccess,
|
||||||
|
} from './shareGuide';
|
||||||
|
|
||||||
// 存储键名
|
// 导出供个人中心等使用
|
||||||
const STORAGE_KEY_DOWNLOAD_COUNT = 'downloadCount';
|
export { getTodayDownloadCount } from './shareGuide';
|
||||||
const STORAGE_KEY_DOWNLOAD_DATE = 'downloadDate';
|
|
||||||
const STORAGE_KEY_WATCHED_AD_TODAY = 'watchedAdToday';
|
|
||||||
|
|
||||||
// 免费下载次数限制
|
/** 是否跳过下载配额与激励广告(与 shareGuide.isDownloadLimitEnabled 相反) */
|
||||||
const FREE_DOWNLOAD_LIMIT = 5;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开发调试环境:本地 develop 包或开发者工具(含桌面模拟器)
|
|
||||||
* 此环境下不弹广告、不限制下载次数
|
|
||||||
*/
|
|
||||||
function isDevBypassLimits(): boolean {
|
function isDevBypassLimits(): boolean {
|
||||||
try {
|
return !isDownloadLimitEnabled();
|
||||||
const env = wx.getAccountInfoSync().miniProgram.envVersion || 'release';
|
|
||||||
if (env === 'develop') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (wx.getSystemInfoSync().platform === 'devtools') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 激励视频广告单元ID
|
// 激励视频广告单元ID
|
||||||
const AD_UNIT_ID = 'adunit-eff5626f8b68c2e0';
|
const AD_UNIT_ID = 'adunit-eff5626f8b68c2e0';
|
||||||
|
|
||||||
/**
|
let rewardedVideoAd: WechatMiniprogram.RewardedVideoAd | null = null;
|
||||||
* 获取今天的日期字符串(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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
function getRewardedVideoAd(): WechatMiniprogram.RewardedVideoAd | null {
|
||||||
* 获取今日下载次数(供个人中心等展示;与内部计数逻辑一致)
|
|
||||||
*/
|
|
||||||
export function getTodayDownloadCount(): number {
|
|
||||||
const today = getTodayDateString();
|
|
||||||
const downloadDate = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_DATE);
|
|
||||||
|
|
||||||
// 如果不是今天,重置计数
|
|
||||||
if (downloadDate !== today) {
|
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, 0);
|
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today);
|
|
||||||
wx.setStorageSync(STORAGE_KEY_WATCHED_AD_TODAY, false);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wx.getStorageSync(STORAGE_KEY_DOWNLOAD_COUNT) || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 增加今日下载次数
|
|
||||||
*/
|
|
||||||
function incrementDownloadCount(): void {
|
|
||||||
const today = getTodayDateString();
|
|
||||||
const count = getTodayDownloadCount();
|
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, count + 1);
|
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查今日是否已观看广告
|
|
||||||
*/
|
|
||||||
function hasWatchedAdToday(): boolean {
|
|
||||||
const today = getTodayDateString();
|
|
||||||
const watchedDate = wx.getStorageSync(STORAGE_KEY_WATCHED_AD_TODAY);
|
|
||||||
return watchedDate === today;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 标记今日已观看广告
|
|
||||||
*/
|
|
||||||
function markWatchedAdToday(): void {
|
|
||||||
const today = getTodayDateString();
|
|
||||||
wx.setStorageSync(STORAGE_KEY_WATCHED_AD_TODAY, today);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建激励视频广告实例
|
|
||||||
*/
|
|
||||||
function createVideoAd() {
|
|
||||||
if (!wx.createRewardedVideoAd) {
|
if (!wx.createRewardedVideoAd) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!rewardedVideoAd) {
|
||||||
const videoAd = wx.createRewardedVideoAd({
|
rewardedVideoAd = wx.createRewardedVideoAd({
|
||||||
adUnitId: AD_UNIT_ID,
|
adUnitId: AD_UNIT_ID,
|
||||||
});
|
});
|
||||||
|
rewardedVideoAd.onLoad(() => {
|
||||||
videoAd.onLoad(() => {
|
console.log('激励视频广告加载成功');
|
||||||
console.log('激励视频广告加载成功');
|
});
|
||||||
});
|
rewardedVideoAd.onError((err) => {
|
||||||
|
console.error('激励视频广告加载失败', err);
|
||||||
videoAd.onError((err) => {
|
});
|
||||||
console.error('激励视频广告加载失败', err);
|
}
|
||||||
});
|
return rewardedVideoAd;
|
||||||
|
|
||||||
return videoAd;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示激励视频广告
|
* 显示激励视频广告(单例实例,与改版前 downloadPrint 内逻辑一致)
|
||||||
*/
|
*/
|
||||||
function showVideoAd(): Promise<boolean> {
|
function showVideoAd(): Promise<boolean> {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const videoAd = createVideoAd();
|
const videoAd = getRewardedVideoAd();
|
||||||
|
|
||||||
if (!videoAd) {
|
if (!videoAd) {
|
||||||
wx.showToast({
|
wx.showToast({
|
||||||
@@ -132,48 +61,33 @@ function showVideoAd(): Promise<boolean> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示 loading
|
|
||||||
wx.showLoading({
|
wx.showLoading({
|
||||||
title: '加载中...',
|
title: '加载中...',
|
||||||
mask: true,
|
mask: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听广告关闭事件
|
const onClose: WechatMiniprogram.RewardedVideoAdOnCloseCallback = (res) => {
|
||||||
videoAd.onClose((res) => {
|
videoAd.offClose(onClose);
|
||||||
// 隐藏 loading(如果还在显示)
|
|
||||||
wx.hideLoading();
|
wx.hideLoading();
|
||||||
|
resolve(!!(res && res.isEnded));
|
||||||
|
};
|
||||||
|
|
||||||
if (res && res.isEnded) {
|
videoAd.onClose(onClose);
|
||||||
// 用户完整观看了广告
|
|
||||||
markWatchedAdToday();
|
|
||||||
resolve(true);
|
|
||||||
} else {
|
|
||||||
// 用户提前关闭了广告
|
|
||||||
resolve(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 显示广告
|
|
||||||
videoAd
|
videoAd
|
||||||
.show()
|
.show()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 广告显示成功,隐藏 loading
|
|
||||||
wx.hideLoading();
|
wx.hideLoading();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// 失败重试,保持 loading 显示
|
|
||||||
videoAd
|
videoAd
|
||||||
.load()
|
.load()
|
||||||
|
.then(() => videoAd.show())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 加载成功,继续显示广告
|
|
||||||
return videoAd.show();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
// 广告显示成功,隐藏 loading
|
|
||||||
wx.hideLoading();
|
wx.hideLoading();
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
// 最终失败,隐藏 loading 并提示
|
videoAd.offClose(onClose);
|
||||||
wx.hideLoading();
|
wx.hideLoading();
|
||||||
console.error('激励视频广告显示失败', err);
|
console.error('激励视频广告显示失败', err);
|
||||||
wx.showToast({
|
wx.showToast({
|
||||||
@@ -186,6 +100,27 @@ function showVideoAd(): Promise<boolean> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 观看激励广告以解锁下一批下载(不执行保存)
|
||||||
|
*/
|
||||||
|
export async function tryUnlockWithRewardedAd(): Promise<boolean> {
|
||||||
|
if (isDevBypassLimits()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!needsUnlockBeforeDownload()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (getUnlockMethod() !== 'ad') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const watched = await showVideoAd();
|
||||||
|
if (watched) {
|
||||||
|
recordAdUnlockSuccess();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行下载
|
* 执行下载
|
||||||
*/
|
*/
|
||||||
@@ -199,7 +134,6 @@ function doDownload(
|
|||||||
worksheetId?: string;
|
worksheetId?: string;
|
||||||
},
|
},
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// 上报下载埋点
|
|
||||||
if (options.trackerName) {
|
if (options.trackerName) {
|
||||||
if (options.trackerMode) {
|
if (options.trackerMode) {
|
||||||
tracker.reportDownload(options.trackerName, options.trackerMode);
|
tracker.reportDownload(options.trackerName, options.trackerMode);
|
||||||
@@ -210,7 +144,7 @@ function doDownload(
|
|||||||
|
|
||||||
return checkAndSaveImage(canvas).then((saved) => {
|
return checkAndSaveImage(canvas).then((saved) => {
|
||||||
if (saved) {
|
if (saved) {
|
||||||
incrementDownloadCount();
|
recordDownloadSuccess();
|
||||||
if (options.worksheetId) {
|
if (options.worksheetId) {
|
||||||
addDownloadLog(options.worksheetId);
|
addDownloadLog(options.worksheetId);
|
||||||
}
|
}
|
||||||
@@ -221,13 +155,6 @@ function doDownload(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载打印(通用方法)
|
* 下载打印(通用方法)
|
||||||
* @param canvas Canvas 实例
|
|
||||||
* @param options 配置选项
|
|
||||||
* @param options.successToast 成功提示(可选,默认:'保存成功,请在相册中查看')
|
|
||||||
* @param options.errorToast 错误提示(可选,默认:'请先生成内容')
|
|
||||||
* @param options.trackerName 埋点名称(可选)
|
|
||||||
* @param options.trackerMode 埋点模式(可选)
|
|
||||||
* @param options.worksheetId worksheet ID(可选,保存成功后更新 downloads)
|
|
||||||
*/
|
*/
|
||||||
export async function downloadPrint(
|
export async function downloadPrint(
|
||||||
canvas: Canvas | null,
|
canvas: Canvas | null,
|
||||||
@@ -239,7 +166,6 @@ export async function downloadPrint(
|
|||||||
worksheetId?: string;
|
worksheetId?: string;
|
||||||
} = {},
|
} = {},
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// 检查 canvas 是否存在
|
|
||||||
if (!canvas) {
|
if (!canvas) {
|
||||||
wx.showToast({
|
wx.showToast({
|
||||||
title: options.errorToast || '请先生成内容',
|
title: options.errorToast || '请先生成内容',
|
||||||
@@ -252,33 +178,18 @@ export async function downloadPrint(
|
|||||||
return doDownload(canvas, options);
|
return doDownload(canvas, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取今日下载次数
|
if (needsUnlockBeforeDownload()) {
|
||||||
const downloadCount = getTodayDownloadCount();
|
const method = getUnlockMethod();
|
||||||
const hasWatchedAd = hasWatchedAdToday();
|
if (method === 'share') {
|
||||||
|
return false;
|
||||||
// 如果已观看广告,直接下载
|
}
|
||||||
if (hasWatchedAd) {
|
if (method === 'ad') {
|
||||||
return doDownload(canvas, options);
|
const unlocked = await tryUnlockWithRewardedAd();
|
||||||
|
if (!unlocked) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果未超过免费次数,直接下载
|
return doDownload(canvas, options);
|
||||||
if (downloadCount < FREE_DOWNLOAD_LIMIT) {
|
|
||||||
return doDownload(canvas, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 超过免费次数,需要观看广告
|
|
||||||
const watched = await showVideoAd();
|
|
||||||
if (watched) {
|
|
||||||
// 观看完成,可以下载
|
|
||||||
return doDownload(canvas, options);
|
|
||||||
}
|
|
||||||
/* else {
|
|
||||||
// 未观看完成,提示用户
|
|
||||||
wx.showToast({
|
|
||||||
title: '需要完整观看广告才能下载',
|
|
||||||
icon: 'none',
|
|
||||||
duration: 2000,
|
|
||||||
});
|
|
||||||
} */
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+135
-36
@@ -1,15 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* 分享引导工具
|
* 分享引导与下载配额
|
||||||
* 用于控制每日免费下载次数和分享引导
|
* - 当日第 1 次满批:须分享解锁,弹窗引导,不分享不可下载
|
||||||
|
* - 当日第 2 次及以后满批:直接播放激励广告解锁
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
downloadFreeBatchSize,
|
||||||
|
enableDownloadLimitInDevelop,
|
||||||
|
} from '../config/config';
|
||||||
|
|
||||||
const STORAGE_KEY_DOWNLOAD_DATE = 'downloadDate';
|
const STORAGE_KEY_DOWNLOAD_DATE = 'downloadDate';
|
||||||
const STORAGE_KEY_DOWNLOAD_COUNT = 'downloadCount';
|
const STORAGE_KEY_DOWNLOAD_COUNT = 'downloadCount';
|
||||||
const STORAGE_KEY_SHARED_DATE = 'sharedDate';
|
const STORAGE_KEY_DOWNLOADS_SINCE_UNLOCK = 'downloadsSinceUnlock';
|
||||||
|
/** 当日已完成解锁次数(分享或广告各计 1 次) */
|
||||||
|
const STORAGE_KEY_DAILY_UNLOCK_COUNT = 'dailyUnlockCount';
|
||||||
|
|
||||||
|
export type DownloadUnlockMethod = 'share' | 'ad';
|
||||||
|
|
||||||
|
/** 每批免费下载次数(来自 config,至少为 1) */
|
||||||
|
export function getFreeDownloadBatchSize(): number {
|
||||||
|
const n = Number(downloadFreeBatchSize);
|
||||||
|
return Number.isFinite(n) && n >= 1 ? Math.floor(n) : 1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取今天的日期字符串(YYYY-MM-DD格式)
|
|
||||||
*/
|
|
||||||
function getTodayDateString(): string {
|
function getTodayDateString(): string {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
const year = today.getFullYear();
|
const year = today.getFullYear();
|
||||||
@@ -18,54 +31,140 @@ function getTodayDateString(): string {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** 跨日时重置当日计数与解锁批次 */
|
||||||
* 检查今天是否已经下载过
|
function ensureDailyReset(): void {
|
||||||
* @returns true表示今天已下载过,false表示今天未下载
|
|
||||||
*/
|
|
||||||
export function hasDownloadedToday(): boolean {
|
|
||||||
const today = getTodayDateString();
|
const today = getTodayDateString();
|
||||||
const downloadDate = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_DATE);
|
const downloadDate = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_DATE);
|
||||||
return downloadDate === today;
|
if (downloadDate !== today) {
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today);
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, 0);
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DOWNLOADS_SINCE_UNLOCK, 0);
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DAILY_UNLOCK_COUNT, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当日已完成解锁次数 */
|
||||||
|
export function getDailyUnlockCount(): number {
|
||||||
|
ensureDailyReset();
|
||||||
|
return wx.getStorageSync(STORAGE_KEY_DAILY_UNLOCK_COUNT) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function incrementDailyUnlockCount(): void {
|
||||||
|
ensureDailyReset();
|
||||||
|
const count = getDailyUnlockCount();
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DAILY_UNLOCK_COUNT, count + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 记录今天的下载成功
|
* 获取今日总下载次数(供个人中心等展示)
|
||||||
|
*/
|
||||||
|
export function getTodayDownloadCount(): number {
|
||||||
|
ensureDailyReset();
|
||||||
|
return wx.getStorageSync(STORAGE_KEY_DOWNLOAD_COUNT) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当前批次内已下载次数 */
|
||||||
|
export function getDownloadsSinceUnlock(): number {
|
||||||
|
ensureDailyReset();
|
||||||
|
return wx.getStorageSync(STORAGE_KEY_DOWNLOADS_SINCE_UNLOCK) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否已达本批次上限,下载前需先解锁
|
||||||
|
*/
|
||||||
|
export function needsUnlockBeforeDownload(): boolean {
|
||||||
|
return getDownloadsSinceUnlock() >= getFreeDownloadBatchSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前满批时应使用的解锁方式
|
||||||
|
*/
|
||||||
|
export function getUnlockMethod(): DownloadUnlockMethod | null {
|
||||||
|
if (!isDownloadLimitEnabled() || !needsUnlockBeforeDownload()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return getDailyUnlockCount() === 0 ? 'share' : 'ad';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分享或看广告成功后,重置批次计数,可再免费下载一批
|
||||||
|
*/
|
||||||
|
export function resetUnlockQuota(): void {
|
||||||
|
ensureDailyReset();
|
||||||
|
wx.setStorageSync(STORAGE_KEY_DOWNLOADS_SINCE_UNLOCK, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录一次下载成功(总次数 + 当前批次次数各 +1)
|
||||||
*/
|
*/
|
||||||
export function recordDownloadSuccess(): void {
|
export function recordDownloadSuccess(): void {
|
||||||
|
ensureDailyReset();
|
||||||
const today = getTodayDateString();
|
const today = getTodayDateString();
|
||||||
|
const total = getTodayDownloadCount();
|
||||||
|
const sinceUnlock = getDownloadsSinceUnlock();
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today);
|
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_DATE, today);
|
||||||
const count = wx.getStorageSync(STORAGE_KEY_DOWNLOAD_COUNT) || 0;
|
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, total + 1);
|
||||||
wx.setStorageSync(STORAGE_KEY_DOWNLOAD_COUNT, count + 1);
|
wx.setStorageSync(STORAGE_KEY_DOWNLOADS_SINCE_UNLOCK, sinceUnlock + 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 {
|
export function recordShareSuccess(): void {
|
||||||
const today = getTodayDateString();
|
resetUnlockQuota();
|
||||||
wx.setStorageSync(STORAGE_KEY_SHARED_DATE, today);
|
incrementDailyUnlockCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查是否需要显示分享引导
|
* 记录激励广告解锁成功
|
||||||
* @returns true表示需要显示分享引导,false表示不需要
|
|
||||||
*/
|
*/
|
||||||
export function shouldShowShareGuide(): boolean {
|
export function recordAdUnlockSuccess(): void {
|
||||||
// 如果今天已经分享过,不需要显示引导
|
resetUnlockQuota();
|
||||||
const env = getApp().globalData.env;
|
incrementDailyUnlockCount();
|
||||||
if (env === 'develop' || hasSharedToday()) {
|
}
|
||||||
|
|
||||||
|
function isDevelopEnv(): boolean {
|
||||||
|
try {
|
||||||
|
const env = getApp().globalData.env;
|
||||||
|
if (env === 'develop') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const accountEnv =
|
||||||
|
wx.getAccountInfoSync().miniProgram.envVersion || 'release';
|
||||||
|
return accountEnv === 'develop';
|
||||||
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// 如果今天已经下载过,需要显示引导
|
}
|
||||||
return hasDownloadedToday();
|
|
||||||
|
/**
|
||||||
|
* 是否启用下载配额(分享引导、激励广告)
|
||||||
|
*/
|
||||||
|
export function isDownloadLimitEnabled(): boolean {
|
||||||
|
if (!isDevelopEnv()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return enableDownloadLimitInDevelop;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当日首次满批:须分享解锁,弹分享引导窗 */
|
||||||
|
export function shouldShowShareGuide(): boolean {
|
||||||
|
return getUnlockMethod() === 'share';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 当日第 2 次及以后满批:直接播放激励广告 */
|
||||||
|
export function shouldUnlockWithAd(): boolean {
|
||||||
|
return getUnlockMethod() === 'ad';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 分享解锁弹窗文案 */
|
||||||
|
export function getShareGuidePopupCopy(): {
|
||||||
|
message: string;
|
||||||
|
hint: string;
|
||||||
|
} {
|
||||||
|
const n = getFreeDownloadBatchSize();
|
||||||
|
return {
|
||||||
|
message: `免费下载次数已用完`,
|
||||||
|
hint: '分享给好友后即可继续下载',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user