feat: 添加广告激励下载功能

- 优化useAd Hook支持奖励和关闭回调
- 在预览页面添加看广告下载按钮
- 实现下载图片到本地相册功能
- 添加完整的中文注释和业务逻辑处理
This commit is contained in:
imeepos 2025-09-01 16:15:56 +08:00
parent 82e01276be
commit e7c1743bd5
3 changed files with 187 additions and 4 deletions

View File

@ -1,37 +1,85 @@
import { createPlatformFactory, RewardedVideoAd, RewardedVideoCloseCb, RewardedVideoErrorCb } from "../platforms";
import { useState, useCallback, useRef, useEffect } from 'react';
// 广告奖励回调函数类型
type AdRewardCallback = () => void;
// 广告 Hook 返回接口
interface UseAdReturn {
showAd: () => void;
loadAd: () => void;
loading: boolean;
}
export function useAd(): UseAdReturn {
// 广告 Hook 配置选项
interface UseAdOptions {
onReward?: AdRewardCallback; // 观看完整广告后的奖励回调
onClose?: (isEnded: boolean) => void; // 广告关闭回调,传入是否完整观看
}
export function useAd(options?: UseAdOptions): UseAdReturn {
const [loading, setLoading] = useState(true);
const adRef = useRef<RewardedVideoAd | null>(null);
useEffect(() => {
// 创建平台广告实例
const factory = createPlatformFactory()
adRef.current = factory.createRewardedVideoAd({
adUnitId: 'gncb4kr2b6kwp0uacr'
});
const ad = adRef.current!;
// 广告关闭回调处理
const onClose: RewardedVideoCloseCb = (res) => {
console.log('广告关闭:', res);
setLoading(false);
// 判断用户是否完成播放
const isEnded = Boolean(res?.isEnded);
if (isEnded) {
// 播放完成的业务逻辑
console.log('用户观看完整广告,给予奖励');
// 执行奖励回调
options?.onReward?.();
// 可以在这里处理以下业务:
// 1. 发放奖励(积分、道具等)
// 2. 解锁功能或内容
// 3. 统计完成观看数据
// 4. 触发下一步操作
} else {
// 未完成播放的处理
console.log('用户未完整观看广告');
// 可以处理以下场景:
// 1. 提示用户观看完整广告才能获得奖励
// 2. 记录未完成播放的统计
// 3. 可能的重试提示
}
// 执行关闭回调,传入是否完整观看
options?.onClose?.(isEnded);
}
// 广告加载错误回调处理
const onError: RewardedVideoErrorCb = (res) => {
console.error('广告错误:', res);
setLoading(false);
}
// 广告加载成功回调处理
const onLoad = () => {
console.log(`广告加载`)
console.log('广告加载成功');
setLoading(false);
}
// 绑定广告事件监听
ad.onLoad(onLoad);
ad.onClose(onClose);
ad.onError(onError);
// 清理函数:组件卸载时移除事件监听和销毁广告实例
return () => {
if (adRef.current) {
adRef.current.offClose(onClose)
@ -40,8 +88,9 @@ export function useAd(): UseAdReturn {
adRef.current.destroy();
}
};
}, []);
}, [options]);
// 显示广告方法
const showAd = useCallback(() => {
if (adRef.current) {
setLoading(true);
@ -49,6 +98,7 @@ export function useAd(): UseAdReturn {
}
}, []);
// 加载广告方法
const loadAd = useCallback(() => {
if (adRef.current) {
setLoading(true);

View File

@ -404,4 +404,74 @@
50% {
opacity: 1;
}
}
/* 下载区域样式 */
.download-section {
position: absolute;
bottom: 60rpx;
left: 50%;
transform: translateX(-50%);
width: 100%;
padding: 0 40rpx;
box-sizing: border-box;
z-index: 1001;
text-align: center;
}
/* 下载按钮样式 */
.download-btn {
background: linear-gradient(135deg, #ff6b9d, #ff9a56);
border: none;
border-radius: 50rpx;
padding: 20rpx 40rpx;
font-size: 28rpx;
font-weight: bold;
color: #fff;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
box-shadow: 0 8rpx 25rpx rgba(255, 107, 157, 0.4);
min-width: 320rpx;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.download-btn::before {
content: "";
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
transition: left 0.5s;
}
.download-btn:hover::before {
left: 100%;
}
.download-btn:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 15rpx rgba(255, 107, 157, 0.3);
}
.download-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* 下载提示文字 */
.download-tip {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
margin-top: 20rpx;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
line-height: 1.4;
display: block;
}

View File

@ -1,8 +1,9 @@
import { View, Text, Button, Image, Swiper, SwiperItem } from '@tarojs/components'
import { useLoad } from '@tarojs/taro'
import { useLoad, saveImageToPhotosAlbum, downloadFile, showToast } from '@tarojs/taro'
import { useState } from 'react'
import './index.css'
import { useSdk } from '../../hooks/index'
import { useAd } from '../../hooks/useAd'
type PageStep = 'upload' | 'loading' | 'result' | 'error'
@ -19,6 +20,19 @@ export default function Index() {
images: [],
error: null
})
// 广告激励下载功能
const { showAd, loading: adLoading } = useAd({
onReward: () => {
// 观看完整广告后下载图片
handleDownloadImages()
},
onClose: (isEnded) => {
if (!isEnded) {
console.log('需要观看完整广告才能下载')
}
}
})
useLoad(() => {
return () => { }
@ -43,6 +57,43 @@ export default function Index() {
setState({ step: 'upload', images: [], error: null })
}
// 下载图片到本地相册
const handleDownloadImages = async () => {
try {
showToast({ title: '开始下载...', icon: 'loading' })
for (const imageUrl of state.images) {
// 先下载图片到临时文件
const downloadRes = await downloadFile({ url: imageUrl })
// 保存到相册
await saveImageToPhotosAlbum({ filePath: downloadRes.tempFilePath })
}
showToast({
title: `成功下载${state.images.length}张图片`,
icon: 'success',
duration: 2000
})
} catch (error) {
console.error('下载失败:', error)
showToast({
title: '下载失败,请重试',
icon: 'error',
duration: 2000
})
}
}
// 触发看广告下载
const handleWatchAdToDownload = () => {
if (adLoading) {
showToast({ title: '广告加载中...', icon: 'loading' })
return
}
showAd()
}
// 1. 传图片环节
const renderUploadStep = () => (
<View className="button-container">
@ -84,6 +135,18 @@ export default function Index() {
</SwiperItem>
))}
</Swiper>
{/* 下载按钮区域 */}
<View className="download-section">
<Button
className="download-btn"
onClick={handleWatchAdToDownload}
disabled={adLoading}
>
{adLoading ? '广告加载中...' : '📱 看广告下载到相册'}
</Button>
<Text className="download-tip">广</Text>
</View>
</View>
)