diff --git a/src/hooks/useAd.ts b/src/hooks/useAd.ts index dcf08de..5446f45 100644 --- a/src/hooks/useAd.ts +++ b/src/hooks/useAd.ts @@ -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(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); diff --git a/src/pages/index/index.css b/src/pages/index/index.css index f9f6267..c7e8f5d 100644 --- a/src/pages/index/index.css +++ b/src/pages/index/index.css @@ -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; } \ No newline at end of file diff --git a/src/pages/index/index.tsx b/src/pages/index/index.tsx index df1ec08..5d85ed9 100644 --- a/src/pages/index/index.tsx +++ b/src/pages/index/index.tsx @@ -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 = () => ( @@ -84,6 +135,18 @@ export default function Index() { ))} + + {/* 下载按钮区域 */} + + + 观看完整广告即可免费下载所有图片 + )