hookehuyr

feat(广告): 添加广告遮罩组件并在欢迎页和首页集成

添加 AdOverlay 组件用于显示全屏广告,支持点击跳转和关闭功能
在 Welcome 和 Dashboard 页面集成该组件,并添加相关逻辑处理
...@@ -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']
......
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) => {
......