├── php-api/ # 改造后的PHP接口层 ├── java-ad-service/ # 若依框架微服务(广告+VIP+分账) ├── uniapp-reader/ # UniApp前端项目 │ ├── pages/ # 各端页面 │ └──
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import { ref } from 'vue'
  2. import { useUserStore } from '@/stores/user'
  3. import { useVipStore } from '@/stores/vip'
  4. // 广告平台配置
  5. const AD_PLATFORMS = {
  6. WECHAT: {
  7. reward: (adUnitId) => {
  8. const ad = wx.createRewardedVideoAd({
  9. adUnitId,
  10. multiton: true
  11. })
  12. ad.onError(err => console.error('微信激励广告错误:', err))
  13. return ad
  14. },
  15. feed: (adUnitId) => {
  16. return wx.createBannerAd({
  17. adUnitId,
  18. adIntervals: 30,
  19. style: {
  20. left: 0,
  21. top: 0,
  22. width: 320
  23. }
  24. })
  25. },
  26. interstitial: (adUnitId) => {
  27. return wx.createInterstitialAd({ adUnitId })
  28. }
  29. },
  30. DOUYIN: {
  31. reward: (adUnitId) => {
  32. const ad = tt.createRewardedVideoAd({ adUnitId })
  33. ad.onError(err => console.error('抖音激励广告错误:', err))
  34. return ad
  35. },
  36. feed: (adUnitId) => {
  37. return tt.createBannerAd({
  38. adUnitId,
  39. style: {
  40. left: 0,
  41. top: 0,
  42. width: 300
  43. }
  44. })
  45. },
  46. interstitial: (adUnitId) => {
  47. return tt.createInterstitialAd({ adUnitId })
  48. }
  49. },
  50. H5: {
  51. reward: (adUnitId) => {
  52. console.log('H5激励广告:', adUnitId)
  53. return {
  54. show: () => {
  55. console.log('展示激励广告')
  56. return Promise.resolve()
  57. },
  58. onClose: (callback) => {
  59. // 模拟关闭事件
  60. setTimeout(() => callback({ isEnded: true }), 3000)
  61. }
  62. }
  63. },
  64. feed: (adUnitId) => {
  65. console.log('H5信息流广告:', adUnitId)
  66. return {
  67. show: () => console.log('展示信息流广告'),
  68. destroy: () => {}
  69. }
  70. },
  71. interstitial: (adUnitId) => {
  72. console.log('H5插屏广告:', adUnitId)
  73. return {
  74. show: () => console.log('展示插屏广告')
  75. }
  76. }
  77. }
  78. }
  79. // 广告触发控制器
  80. export function useAdManager() {
  81. const userStore = useUserStore()
  82. const chapterCount = ref(0)
  83. const lastAdShownTime = ref(0)
  84. // 获取当前平台适配器
  85. const getPlatformAdapter = () => {
  86. // #ifdef MP-WEIXIN
  87. return AD_PLATFORMS.WECHAT
  88. // #endif
  89. // #ifdef MP-TOUTIAO
  90. return AD_PLATFORMS.DOUYIN
  91. // #endif
  92. return AD_PLATFORMS.H5
  93. }
  94. // 展示激励广告(每5章触发)
  95. const showRewardAd = async (chapterId) => {
  96. // VIP用户跳过广告
  97. if (vipStore.hasPrivilege('免广告')) return
  98. if (userStore.isVIP) return
  99. // 广告冷却时间(至少30秒)
  100. const now = Date.now()
  101. if (now - lastAdShownTime.value < 30000) return
  102. chapterCount.value++
  103. if (chapterCount.value % 5 !== 0) return
  104. const platform = getCurrentPlatform()
  105. const adapter = getPlatformAdapter()
  106. const adUnitId = getAdUnitId(platform, 'reward')
  107. if (!adUnitId) {
  108. console.error('未配置广告单元ID')
  109. return
  110. }
  111. const ad = adapter.reward(adUnitId)
  112. try {
  113. // 监听广告关闭事件
  114. return new Promise((resolve) => {
  115. ad.onClose(res => {
  116. if (res && res.isEnded) {
  117. // 完整观看,记录广告
  118. logAdView(chapterId, 'reward', platform)
  119. resolve(true)
  120. } else {
  121. resolve(false)
  122. }
  123. })
  124. // 展示广告
  125. ad.show().catch(err => {
  126. console.error('广告展示失败:', err)
  127. resolve(false)
  128. })
  129. lastAdShownTime.value = Date.now()
  130. })
  131. } catch (err) {
  132. console.error('广告展示失败:', err)
  133. return false
  134. }
  135. }
  136. // 展示底部信息流广告
  137. const showFeedAd = (adUnitId) => {
  138. // VIP用户跳过广告
  139. if (vipStore.hasPrivilege('免广告')) return null
  140. if (userStore.isVIP) return null
  141. const platform = getCurrentPlatform()
  142. const adapter = getPlatformAdapter()
  143. if (!adUnitId) {
  144. adUnitId = getAdUnitId(platform, 'feed')
  145. }
  146. if (!adUnitId) {
  147. console.error('未配置信息流广告单元ID')
  148. return null
  149. }
  150. const ad = adapter.feed(adUnitId)
  151. try {
  152. ad.show()
  153. return ad
  154. } catch (err) {
  155. console.error('信息流广告展示失败:', err)
  156. return null
  157. }
  158. }
  159. // 展示插屏广告
  160. const showInterstitialAd = () => {
  161. if (userStore.isVIP) return
  162. // 广告冷却时间(至少60秒)
  163. const now = Date.now()
  164. if (now - lastAdShownTime.value < 60000) return
  165. const platform = getCurrentPlatform()
  166. const adapter = getPlatformAdapter()
  167. const adUnitId = getAdUnitId(platform, 'interstitial')
  168. if (!adUnitId) {
  169. console.error('未配置插屏广告单元ID')
  170. return
  171. }
  172. const ad = adapter.interstitial(adUnitId)
  173. try {
  174. ad.show()
  175. lastAdShownTime.value = Date.now()
  176. return true
  177. } catch (err) {
  178. console.error('插屏广告展示失败:', err)
  179. return false
  180. }
  181. }
  182. // 广告日志记录
  183. const logAdView = (chapterId, adType, platform) => {
  184. uni.request({
  185. url: 'https://api.aiyadianzi.ltd/ad/log',
  186. method: 'POST',
  187. data: {
  188. userId: userStore.userId,
  189. chapterId,
  190. adType,
  191. platform
  192. },
  193. header: {
  194. 'Authorization': `Bearer ${uni.getStorageSync('token')}`
  195. }
  196. })
  197. }
  198. // 获取当前平台
  199. const getCurrentPlatform = () => {
  200. // #ifdef MP-WEIXIN
  201. return 'wechat'
  202. // #endif
  203. // #ifdef MP-TOUTIAO
  204. return 'douyin'
  205. // #endif
  206. return 'h5'
  207. }
  208. // 获取广告单元ID
  209. const getAdUnitId = (platform, adType) => {
  210. const config = {
  211. wechat: {
  212. reward: 'wechat_reward_ad_123456',
  213. feed: 'wechat_feed_ad_654321',
  214. interstitial: 'wechat_interstitial_ad_789012'
  215. },
  216. douyin: {
  217. reward: 'douyin_reward_ad_abcdef',
  218. feed: 'douyin_feed_ad_fedcba',
  219. interstitial: 'douyin_interstitial_ad_123abc'
  220. },
  221. h5: {
  222. reward: 'h5_reward_ad_123',
  223. feed: 'h5_feed_ad_456',
  224. interstitial: 'h5_interstitial_ad_789'
  225. }
  226. }
  227. return config[platform]?.[adType]
  228. }
  229. return {
  230. showRewardAd,
  231. showFeedAd,
  232. showInterstitialAd
  233. }
  234. }