feat: 添加广告激励下载功能
- 优化useAd Hook支持奖励和关闭回调 - 在预览页面添加看广告下载按钮 - 实现下载图片到本地相册功能 - 添加完整的中文注释和业务逻辑处理
This commit is contained in:
parent
82e01276be
commit
e7c1743bd5
|
|
@ -1,37 +1,85 @@
|
||||||
import { createPlatformFactory, RewardedVideoAd, RewardedVideoCloseCb, RewardedVideoErrorCb } from "../platforms";
|
import { createPlatformFactory, RewardedVideoAd, RewardedVideoCloseCb, RewardedVideoErrorCb } from "../platforms";
|
||||||
import { useState, useCallback, useRef, useEffect } from 'react';
|
import { useState, useCallback, useRef, useEffect } from 'react';
|
||||||
|
|
||||||
|
// 广告奖励回调函数类型
|
||||||
|
type AdRewardCallback = () => void;
|
||||||
|
|
||||||
|
// 广告 Hook 返回接口
|
||||||
interface UseAdReturn {
|
interface UseAdReturn {
|
||||||
showAd: () => void;
|
showAd: () => void;
|
||||||
loadAd: () => void;
|
loadAd: () => void;
|
||||||
loading: boolean;
|
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 [loading, setLoading] = useState(true);
|
||||||
const adRef = useRef<RewardedVideoAd | null>(null);
|
const adRef = useRef<RewardedVideoAd | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 创建平台广告实例
|
||||||
const factory = createPlatformFactory()
|
const factory = createPlatformFactory()
|
||||||
adRef.current = factory.createRewardedVideoAd({
|
adRef.current = factory.createRewardedVideoAd({
|
||||||
adUnitId: 'gncb4kr2b6kwp0uacr'
|
adUnitId: 'gncb4kr2b6kwp0uacr'
|
||||||
});
|
});
|
||||||
const ad = adRef.current!;
|
const ad = adRef.current!;
|
||||||
|
|
||||||
|
// 广告关闭回调处理
|
||||||
const onClose: RewardedVideoCloseCb = (res) => {
|
const onClose: RewardedVideoCloseCb = (res) => {
|
||||||
console.log('广告关闭:', res);
|
console.log('广告关闭:', res);
|
||||||
setLoading(false);
|
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) => {
|
const onError: RewardedVideoErrorCb = (res) => {
|
||||||
console.error('广告错误:', res);
|
console.error('广告错误:', res);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 广告加载成功回调处理
|
||||||
const onLoad = () => {
|
const onLoad = () => {
|
||||||
console.log(`广告加载`)
|
console.log('广告加载成功');
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
// 绑定广告事件监听
|
||||||
ad.onLoad(onLoad);
|
ad.onLoad(onLoad);
|
||||||
ad.onClose(onClose);
|
ad.onClose(onClose);
|
||||||
ad.onError(onError);
|
ad.onError(onError);
|
||||||
|
|
||||||
|
// 清理函数:组件卸载时移除事件监听和销毁广告实例
|
||||||
return () => {
|
return () => {
|
||||||
if (adRef.current) {
|
if (adRef.current) {
|
||||||
adRef.current.offClose(onClose)
|
adRef.current.offClose(onClose)
|
||||||
|
|
@ -40,8 +88,9 @@ export function useAd(): UseAdReturn {
|
||||||
adRef.current.destroy();
|
adRef.current.destroy();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, [options]);
|
||||||
|
|
||||||
|
// 显示广告方法
|
||||||
const showAd = useCallback(() => {
|
const showAd = useCallback(() => {
|
||||||
if (adRef.current) {
|
if (adRef.current) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -49,6 +98,7 @@ export function useAd(): UseAdReturn {
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 加载广告方法
|
||||||
const loadAd = useCallback(() => {
|
const loadAd = useCallback(() => {
|
||||||
if (adRef.current) {
|
if (adRef.current) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
|
||||||
|
|
@ -404,4 +404,74 @@
|
||||||
50% {
|
50% {
|
||||||
opacity: 1;
|
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;
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import { View, Text, Button, Image, Swiper, SwiperItem } from '@tarojs/components'
|
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 { useState } from 'react'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { useSdk } from '../../hooks/index'
|
import { useSdk } from '../../hooks/index'
|
||||||
|
import { useAd } from '../../hooks/useAd'
|
||||||
|
|
||||||
type PageStep = 'upload' | 'loading' | 'result' | 'error'
|
type PageStep = 'upload' | 'loading' | 'result' | 'error'
|
||||||
|
|
||||||
|
|
@ -19,6 +20,19 @@ export default function Index() {
|
||||||
images: [],
|
images: [],
|
||||||
error: null
|
error: null
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 广告激励下载功能
|
||||||
|
const { showAd, loading: adLoading } = useAd({
|
||||||
|
onReward: () => {
|
||||||
|
// 观看完整广告后下载图片
|
||||||
|
handleDownloadImages()
|
||||||
|
},
|
||||||
|
onClose: (isEnded) => {
|
||||||
|
if (!isEnded) {
|
||||||
|
console.log('需要观看完整广告才能下载')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
useLoad(() => {
|
useLoad(() => {
|
||||||
return () => { }
|
return () => { }
|
||||||
|
|
@ -43,6 +57,43 @@ export default function Index() {
|
||||||
setState({ step: 'upload', images: [], error: null })
|
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. 传图片环节
|
// 1. 传图片环节
|
||||||
const renderUploadStep = () => (
|
const renderUploadStep = () => (
|
||||||
<View className="button-container">
|
<View className="button-container">
|
||||||
|
|
@ -84,6 +135,18 @@ export default function Index() {
|
||||||
</SwiperItem>
|
</SwiperItem>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
|
|
||||||
|
{/* 下载按钮区域 */}
|
||||||
|
<View className="download-section">
|
||||||
|
<Button
|
||||||
|
className="download-btn"
|
||||||
|
onClick={handleWatchAdToDownload}
|
||||||
|
disabled={adLoading}
|
||||||
|
>
|
||||||
|
{adLoading ? '广告加载中...' : '📱 看广告下载到相册'}
|
||||||
|
</Button>
|
||||||
|
<Text className="download-tip">观看完整广告即可免费下载所有图片</Text>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue