feat(广告): 添加广告遮罩组件并在欢迎页和首页集成
添加 AdOverlay 组件用于显示全屏广告,支持点击跳转和关闭功能 在 Welcome 和 Dashboard 页面集成该组件,并添加相关逻辑处理
Showing
4 changed files
with
313 additions
and
0 deletions
| ... | @@ -7,6 +7,7 @@ export {} | ... | @@ -7,6 +7,7 @@ export {} |
| 7 | 7 | ||
| 8 | declare module 'vue' { | 8 | declare module 'vue' { |
| 9 | export interface GlobalComponents { | 9 | export interface GlobalComponents { |
| 10 | + AdOverlay: typeof import('./src/components/AdOverlay.vue')['default'] | ||
| 10 | AppHeader: typeof import('./src/components/AppHeader.vue')['default'] | 11 | AppHeader: typeof import('./src/components/AppHeader.vue')['default'] |
| 11 | BackToTop: typeof import('./src/components/BackToTop.vue')['default'] | 12 | BackToTop: typeof import('./src/components/BackToTop.vue')['default'] |
| 12 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] | 13 | BottomNav: typeof import('./src/components/BottomNav.vue')['default'] |
| ... | @@ -19,6 +20,7 @@ declare module 'vue' { | ... | @@ -19,6 +20,7 @@ declare module 'vue' { |
| 19 | NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] | 20 | NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] |
| 20 | NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] | 21 | NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] |
| 21 | NutInput: typeof import('@nutui/nutui-taro')['Input'] | 22 | NutInput: typeof import('@nutui/nutui-taro')['Input'] |
| 23 | + NutOverlay: typeof import('@nutui/nutui-taro')['Overlay'] | ||
| 22 | NutPicker: typeof import('@nutui/nutui-taro')['Picker'] | 24 | NutPicker: typeof import('@nutui/nutui-taro')['Picker'] |
| 23 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] | 25 | NutPopup: typeof import('@nutui/nutui-taro')['Popup'] |
| 24 | NutRow: typeof import('@nutui/nutui-taro')['Row'] | 26 | NutRow: typeof import('@nutui/nutui-taro')['Row'] | ... | ... |
src/components/AdOverlay.vue
0 → 100644
| 1 | +<template> | ||
| 2 | + <nut-overlay | ||
| 3 | + v-model:visible="visible" | ||
| 4 | + :z-index="9999" | ||
| 5 | + :close-on-click-overlay="false" | ||
| 6 | + :lock-scroll="true" | ||
| 7 | + > | ||
| 8 | + <view class="overlay-body"> | ||
| 9 | + <view class="overlay-content"> | ||
| 10 | + <!-- 广告图片 --> | ||
| 11 | + <view class="ad-image-container" @click="handleAdClick"> | ||
| 12 | + <image | ||
| 13 | + :src="adImageUrl" | ||
| 14 | + mode="aspectFill" | ||
| 15 | + class="ad-image" | ||
| 16 | + alt="广告图片" | ||
| 17 | + /> | ||
| 18 | + </view> | ||
| 19 | + | ||
| 20 | + <!-- 关闭按钮 --> | ||
| 21 | + <view class="close-button" @click="handleClose"> | ||
| 22 | + <Close size="24" class="close-icon" /> | ||
| 23 | + </view> | ||
| 24 | + </view> | ||
| 25 | + </view> | ||
| 26 | + </nut-overlay> | ||
| 27 | +</template> | ||
| 28 | + | ||
| 29 | +<script setup> | ||
| 30 | +import { ref, onMounted } from 'vue' | ||
| 31 | +import Taro from '@tarojs/taro' | ||
| 32 | +import { Close } from '@nutui/icons-vue-taro' | ||
| 33 | + | ||
| 34 | +// Props | ||
| 35 | +const props = defineProps({ | ||
| 36 | + // 广告图片URL | ||
| 37 | + adImageUrl: { | ||
| 38 | + type: String, | ||
| 39 | + default: '' | ||
| 40 | + }, | ||
| 41 | + // 点击跳转的页面路径 | ||
| 42 | + targetPage: { | ||
| 43 | + type: String, | ||
| 44 | + default: '/pages/Dashboard/index' | ||
| 45 | + }, | ||
| 46 | + // 存储键名(用于区分不同的广告) | ||
| 47 | + storageKey: { | ||
| 48 | + type: String, | ||
| 49 | + default: 'ad_overlay_shown' | ||
| 50 | + } | ||
| 51 | +}) | ||
| 52 | + | ||
| 53 | +// Emits | ||
| 54 | +const emit = defineEmits(['close', 'click']) | ||
| 55 | + | ||
| 56 | +// 响应式数据 | ||
| 57 | +const visible = ref(false) | ||
| 58 | + | ||
| 59 | +/** | ||
| 60 | + * 获取今天的日期字符串(YYYY-MM-DD格式) | ||
| 61 | + * @returns {string} 今天的日期 | ||
| 62 | + */ | ||
| 63 | +const getTodayDateString = () => { | ||
| 64 | + const today = new Date() | ||
| 65 | + const year = today.getFullYear() | ||
| 66 | + const month = String(today.getMonth() + 1).padStart(2, '0') | ||
| 67 | + const day = String(today.getDate()).padStart(2, '0') | ||
| 68 | + return `${year}-${month}-${day}` | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +/** | ||
| 72 | + * 检查今天是否已经显示过广告 | ||
| 73 | + * @returns {boolean} 是否已显示 | ||
| 74 | + */ | ||
| 75 | +const hasShownToday = () => { | ||
| 76 | + try { | ||
| 77 | + const lastShownDate = Taro.getStorageSync(props.storageKey) | ||
| 78 | + const today = getTodayDateString() | ||
| 79 | + return lastShownDate === today | ||
| 80 | + } catch (error) { | ||
| 81 | + console.error('获取存储数据失败:', error) | ||
| 82 | + return false | ||
| 83 | + } | ||
| 84 | +} | ||
| 85 | + | ||
| 86 | +/** | ||
| 87 | + * 记录今天已显示广告 | ||
| 88 | + */ | ||
| 89 | +const markAsShownToday = () => { | ||
| 90 | + try { | ||
| 91 | + const today = getTodayDateString() | ||
| 92 | + Taro.setStorageSync(props.storageKey, today) | ||
| 93 | + } catch (error) { | ||
| 94 | + console.error('保存存储数据失败:', error) | ||
| 95 | + } | ||
| 96 | +} | ||
| 97 | + | ||
| 98 | +/** | ||
| 99 | + * 显示广告遮罩 | ||
| 100 | + */ | ||
| 101 | +const show = () => { | ||
| 102 | + if (!hasShownToday()) { | ||
| 103 | + visible.value = true | ||
| 104 | + } | ||
| 105 | +} | ||
| 106 | + | ||
| 107 | +/** | ||
| 108 | + * 隐藏广告遮罩 | ||
| 109 | + */ | ||
| 110 | +const hide = () => { | ||
| 111 | + visible.value = false | ||
| 112 | +} | ||
| 113 | + | ||
| 114 | +/** | ||
| 115 | + * 处理广告图片点击事件 | ||
| 116 | + */ | ||
| 117 | +const handleAdClick = () => { | ||
| 118 | + // 记录已显示 | ||
| 119 | + markAsShownToday() | ||
| 120 | + | ||
| 121 | + // 隐藏遮罩 | ||
| 122 | + hide() | ||
| 123 | + | ||
| 124 | + // 跳转到目标页面 | ||
| 125 | + Taro.navigateTo({ | ||
| 126 | + url: props.targetPage | ||
| 127 | + }).catch(error => { | ||
| 128 | + console.error('页面跳转失败:', error) | ||
| 129 | + Taro.showToast({ | ||
| 130 | + title: '页面跳转失败', | ||
| 131 | + icon: 'none' | ||
| 132 | + }) | ||
| 133 | + }) | ||
| 134 | + | ||
| 135 | + // 触发点击事件 | ||
| 136 | + emit('click', props.targetPage) | ||
| 137 | +} | ||
| 138 | + | ||
| 139 | +/** | ||
| 140 | + * 处理关闭按钮点击事件 | ||
| 141 | + */ | ||
| 142 | +const handleClose = () => { | ||
| 143 | + // 记录已显示 | ||
| 144 | + markAsShownToday() | ||
| 145 | + | ||
| 146 | + // 隐藏遮罩 | ||
| 147 | + hide() | ||
| 148 | + | ||
| 149 | + // 触发关闭事件 | ||
| 150 | + emit('close') | ||
| 151 | +} | ||
| 152 | + | ||
| 153 | +// 暴露方法给父组件 | ||
| 154 | +defineExpose({ | ||
| 155 | + show, | ||
| 156 | + hide, | ||
| 157 | + hasShownToday | ||
| 158 | +}) | ||
| 159 | + | ||
| 160 | +// 组件挂载时检查是否需要显示 | ||
| 161 | +onMounted(() => { | ||
| 162 | + // 延迟一点显示,避免页面加载时的闪烁 | ||
| 163 | + setTimeout(() => { | ||
| 164 | + show() | ||
| 165 | + }, 500) | ||
| 166 | +}) | ||
| 167 | +</script> | ||
| 168 | + | ||
| 169 | +<style lang="less"> | ||
| 170 | +.overlay-body { | ||
| 171 | + display: flex; | ||
| 172 | + height: 100%; | ||
| 173 | + align-items: center; | ||
| 174 | + justify-content: center; | ||
| 175 | +} | ||
| 176 | + | ||
| 177 | +.overlay-content { | ||
| 178 | + display: flex; | ||
| 179 | + flex-direction: column; | ||
| 180 | + align-items: center; | ||
| 181 | + justify-content: center; | ||
| 182 | + width: 80vw; | ||
| 183 | + max-width: 600rpx; | ||
| 184 | +} | ||
| 185 | + | ||
| 186 | +.ad-image-container { | ||
| 187 | + width: 100%; | ||
| 188 | + border-radius: 16rpx; | ||
| 189 | + overflow: hidden; | ||
| 190 | + box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.3); | ||
| 191 | + cursor: pointer; | ||
| 192 | + margin-bottom: 40rpx; | ||
| 193 | +} | ||
| 194 | + | ||
| 195 | +.ad-image { | ||
| 196 | + width: 100%; | ||
| 197 | + height: 50vh; | ||
| 198 | + display: block; | ||
| 199 | + border-radius: 16rpx; | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +.close-button { | ||
| 203 | + width: 60rpx; | ||
| 204 | + height: 60rpx; | ||
| 205 | + // background-color: rgba(0, 0, 0, 0.6); | ||
| 206 | + border-radius: 50%; | ||
| 207 | + display: flex; | ||
| 208 | + align-items: center; | ||
| 209 | + justify-content: center; | ||
| 210 | + cursor: pointer; | ||
| 211 | +} | ||
| 212 | + | ||
| 213 | +.close-icon { | ||
| 214 | + color: white; | ||
| 215 | +} | ||
| 216 | + | ||
| 217 | +/* 点击效果 */ | ||
| 218 | +.ad-image-container:active { | ||
| 219 | + transform: scale(0.98); | ||
| 220 | + transition: transform 0.1s ease; | ||
| 221 | +} | ||
| 222 | + | ||
| 223 | +.close-button:active { | ||
| 224 | + transform: scale(0.9); | ||
| 225 | + transition: transform 0.1s ease; | ||
| 226 | +} | ||
| 227 | +</style> |
| ... | @@ -169,6 +169,15 @@ | ... | @@ -169,6 +169,15 @@ |
| 169 | 169 | ||
| 170 | <BottomNav /> | 170 | <BottomNav /> |
| 171 | 171 | ||
| 172 | + <!-- 广告遮罩层 --> | ||
| 173 | + <AdOverlay | ||
| 174 | + :ad-image-url="adObj.adImageUrl" | ||
| 175 | + :target-page="adObj.targetPage" | ||
| 176 | + :storage-key="adObj.storageKey" | ||
| 177 | + @close="handleAdClose" | ||
| 178 | + @click="handleAdClick" | ||
| 179 | + /> | ||
| 180 | + | ||
| 172 | <!-- 图片预览 --> | 181 | <!-- 图片预览 --> |
| 173 | <nut-image-preview | 182 | <nut-image-preview |
| 174 | v-model:show="previewVisible" | 183 | v-model:show="previewVisible" |
| ... | @@ -226,6 +235,7 @@ import TotalPointsDisplay from '@/components/TotalPointsDisplay.vue'; | ... | @@ -226,6 +235,7 @@ import TotalPointsDisplay from '@/components/TotalPointsDisplay.vue'; |
| 226 | import PointsCollector from '@/components/PointsCollector.vue' | 235 | import PointsCollector from '@/components/PointsCollector.vue' |
| 227 | import WeRunAuth from '@/components/WeRunAuth.vue' | 236 | import WeRunAuth from '@/components/WeRunAuth.vue' |
| 228 | import RankingCard from '@/components/RankingCard.vue' | 237 | import RankingCard from '@/components/RankingCard.vue' |
| 238 | +import AdOverlay from '@/components/AdOverlay.vue' | ||
| 229 | import { useMediaPreview } from '@/composables/useMediaPreview'; | 239 | import { useMediaPreview } from '@/composables/useMediaPreview'; |
| 230 | // 默认家庭封面图 | 240 | // 默认家庭封面图 |
| 231 | const defaultFamilyCover = 'https://cdn.ipadbiz.cn/lls_prog/images/default-family-cover.png'; | 241 | const defaultFamilyCover = 'https://cdn.ipadbiz.cn/lls_prog/images/default-family-cover.png'; |
| ... | @@ -277,6 +287,9 @@ const albumData = ref([ | ... | @@ -277,6 +287,9 @@ const albumData = ref([ |
| 277 | } | 287 | } |
| 278 | ]); | 288 | ]); |
| 279 | 289 | ||
| 290 | +// 广告遮罩层数据 | ||
| 291 | +const adObj = ref({}) | ||
| 292 | + | ||
| 280 | /** | 293 | /** |
| 281 | * 触发积分收集组件的一键收取 | 294 | * 触发积分收集组件的一键收取 |
| 282 | */ | 295 | */ |
| ... | @@ -375,6 +388,28 @@ const handleGoToPointsRule = () => { | ... | @@ -375,6 +388,28 @@ const handleGoToPointsRule = () => { |
| 375 | }) | 388 | }) |
| 376 | } | 389 | } |
| 377 | 390 | ||
| 391 | +/** | ||
| 392 | + * 处理广告遮罩层关闭事件 | ||
| 393 | + */ | ||
| 394 | +const handleAdClose = () => { | ||
| 395 | + console.log('广告遮罩层已关闭') | ||
| 396 | +} | ||
| 397 | + | ||
| 398 | +/** | ||
| 399 | + * 处理广告遮罩层点击事件 | ||
| 400 | + * @param {string} targetPage - 跳转的目标页面 | ||
| 401 | + */ | ||
| 402 | +const handleAdClick = (targetPage) => { | ||
| 403 | + console.log('广告被点击,跳转到:', targetPage) | ||
| 404 | + // 如果跳转路径是欢迎页和首页,不跳转直接关闭弹框 | ||
| 405 | + if (targetPage === '/pages/Dashboard/index' || targetPage === '/pages/Welcome/index') { | ||
| 406 | + handleAdClose() | ||
| 407 | + return | ||
| 408 | + } | ||
| 409 | + // 跳转到目标页面 | ||
| 410 | + Taro.navigateTo({ url: targetPage }); | ||
| 411 | +} | ||
| 412 | + | ||
| 378 | const family_id = ref(''); | 413 | const family_id = ref(''); |
| 379 | const totalFamilySteps = ref(0); | 414 | const totalFamilySteps = ref(0); |
| 380 | 415 | ||
| ... | @@ -406,6 +441,13 @@ useDidShow(async () => { | ... | @@ -406,6 +441,13 @@ useDidShow(async () => { |
| 406 | Taro.redirectTo({ url: '/pages/Welcome/index' }); | 441 | Taro.redirectTo({ url: '/pages/Welcome/index' }); |
| 407 | return; // 直接返回,不执行后续逻辑 | 442 | return; // 直接返回,不执行后续逻辑 |
| 408 | } | 443 | } |
| 444 | + | ||
| 445 | + // TODO: 获取广告信息 | ||
| 446 | + adObj.value = { | ||
| 447 | + adImageUrl: 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%8D%97%E4%BA%AC%E8%B7%AF%E5%95%86%E5%9C%88.jpeg', | ||
| 448 | + targetPage: '/pages/Dashboard/index', | ||
| 449 | + storageKey: 'dashboard_ad_overlay', | ||
| 450 | + } | ||
| 409 | }) | 451 | }) |
| 410 | 452 | ||
| 411 | useReady(async () => { | 453 | useReady(async () => { | ... | ... |
| ... | @@ -132,6 +132,15 @@ | ... | @@ -132,6 +132,15 @@ |
| 132 | </nut-row> | 132 | </nut-row> |
| 133 | </template> | 133 | </template> |
| 134 | </nut-dialog> | 134 | </nut-dialog> |
| 135 | + | ||
| 136 | + <!-- 广告遮罩层 --> | ||
| 137 | + <AdOverlay | ||
| 138 | + :ad-image-url="adObj.adImageUrl" | ||
| 139 | + :target-page="adObj.targetPage" | ||
| 140 | + :storage-key="adObj.storageKey" | ||
| 141 | + @close="handleAdClose" | ||
| 142 | + @click="handleAdClick" | ||
| 143 | + /> | ||
| 135 | </view> | 144 | </view> |
| 136 | </template> | 145 | </template> |
| 137 | 146 | ||
| ... | @@ -139,6 +148,7 @@ | ... | @@ -139,6 +148,7 @@ |
| 139 | import { ref } from 'vue'; | 148 | import { ref } from 'vue'; |
| 140 | import Taro, { useDidShow } from '@tarojs/taro'; | 149 | import Taro, { useDidShow } from '@tarojs/taro'; |
| 141 | import BottomNav from '../../components/BottomNav.vue'; // 假设BottomNav组件已转换 | 150 | import BottomNav from '../../components/BottomNav.vue'; // 假设BottomNav组件已转换 |
| 151 | +import AdOverlay from '@/components/AdOverlay.vue' | ||
| 142 | // 获取接口信息 | 152 | // 获取接口信息 |
| 143 | import { getUserProfileAPI } from '@/api/user'; | 153 | import { getUserProfileAPI } from '@/api/user'; |
| 144 | 154 | ||
| ... | @@ -161,6 +171,31 @@ const navigateTo = (url) => { | ... | @@ -161,6 +171,31 @@ const navigateTo = (url) => { |
| 161 | Taro.navigateTo({ url }); | 171 | Taro.navigateTo({ url }); |
| 162 | }; | 172 | }; |
| 163 | 173 | ||
| 174 | +// 广告遮罩层数据 | ||
| 175 | +const adObj = ref({}) | ||
| 176 | + | ||
| 177 | +/** | ||
| 178 | + * 处理广告遮罩层关闭事件 | ||
| 179 | + */ | ||
| 180 | +const handleAdClose = () => { | ||
| 181 | + console.log('广告遮罩层已关闭') | ||
| 182 | +} | ||
| 183 | + | ||
| 184 | +/** | ||
| 185 | + * 处理广告遮罩层点击事件 | ||
| 186 | + * @param {string} targetPage - 跳转的目标页面 | ||
| 187 | + */ | ||
| 188 | +const handleAdClick = (targetPage) => { | ||
| 189 | + console.log('广告被点击,跳转到:', targetPage) | ||
| 190 | + // 如果跳转路径是欢迎页和首页,不跳转直接关闭弹框 | ||
| 191 | + if (targetPage === '/pages/Dashboard/index' || targetPage === '/pages/Welcome/index') { | ||
| 192 | + handleAdClose() | ||
| 193 | + return | ||
| 194 | + } | ||
| 195 | + // 跳转到目标页面 | ||
| 196 | + Taro.navigateTo({ url: targetPage }); | ||
| 197 | +} | ||
| 198 | + | ||
| 164 | useDidShow(async () => { | 199 | useDidShow(async () => { |
| 165 | // 获取用户的个人信息 | 200 | // 获取用户的个人信息 |
| 166 | const { code, data } = await getUserProfileAPI(); | 201 | const { code, data } = await getUserProfileAPI(); |
| ... | @@ -179,6 +214,13 @@ useDidShow(async () => { | ... | @@ -179,6 +214,13 @@ useDidShow(async () => { |
| 179 | hasProfile.value = userInfo.value.nickname && userInfo.value.birth_date && userInfo.value.wheelchair_needed !== null; | 214 | hasProfile.value = userInfo.value.nickname && userInfo.value.birth_date && userInfo.value.wheelchair_needed !== null; |
| 180 | userAge.value = userInfo.value.birthday; | 215 | userAge.value = userInfo.value.birthday; |
| 181 | } | 216 | } |
| 217 | + | ||
| 218 | + // TODO: 获取广告信息 | ||
| 219 | + adObj.value = { | ||
| 220 | + adImageUrl: 'https://cdn.ipadbiz.cn/lls_prog/images/%E5%8D%97%E4%BA%AC%E8%B7%AF%E5%95%86%E5%9C%88.jpeg', | ||
| 221 | + targetPage: '/pages/Dashboard/index', | ||
| 222 | + storageKey: 'dashboard_ad_overlay', | ||
| 223 | + } | ||
| 182 | }); | 224 | }); |
| 183 | 225 | ||
| 184 | const handleNavigate = (url) => { | 226 | const handleNavigate = (url) => { | ... | ... |
-
Please register or login to post a comment