当前位置: 首页 > backend >正文

uniapp+vue3 微信小程序全屏广告组件功能

概述

全屏广告组件是一个用于在应用中展示全屏广告的Vue组件,支持自动显示、倒计时关闭、点击跳转等功能。该组件主要用于应用启动时或特定场景下展示推广内容。

效果

在这里插入图片描述

组件结构

主要文件

  • src/components/fullscreen-ad/index.vue - 主组件文件
  • src/components/fullscreen-ad/examples/ad-example.vue - 广告内容示例组件

功能特性

1. 自动显示控制

  • 自动显示: 支持组件加载后自动显示广告
  • 显示频率控制: 基于本地存储控制每日显示频率,避免重复打扰用户
  • 延迟显示: 支持延迟1秒后显示,确保页面加载完成

2. 倒计时功能

  • 自动倒计时: 广告显示后开始倒计时,到达指定时间后自动关闭
  • 倒计时显示: 在广告界面显示剩余时间,用户可清楚了解关闭时间
  • 可配置时长: 支持自定义倒计时时长(默认30秒)

3. 交互功能

  • 手动关闭: 用户可通过"跳过"按钮手动关闭广告
  • 点击跳转: 点击广告内容可跳转到指定的微信小程序
  • 背景关闭: 点击广告外部区域可关闭广告

4. 小程序跳转

  • 微信小程序跳转: 支持跳转到指定的微信小程序
  • 跳转参数配置: 支持配置目标小程序的AppID和页面路径
  • 跳转状态处理: 处理跳转成功、失败、取消等不同状态
  • 自动关闭: 跳转成功后自动关闭广告

5. 图片处理

  • 动态图片: 支持配置广告图片URL
  • 加载状态: 监听图片加载成功和失败状态
  • 错误处理: 图片加载失败时显示占位内容
  • 点击响应: 图片和占位内容都支持点击跳转

6. 响应式设计

  • 全屏适配: 支持不同屏幕尺寸的全屏显示
  • 安全区域适配: 自动适配设备的安全区域(刘海屏、底部指示器等)
  • 动画效果: 包含淡入动画和滑入动画效果
  • 毛玻璃效果: 背景支持毛玻璃模糊效果

7. 样式特性

  • 渐变背景: 使用线性渐变背景提升视觉效果
  • 现代化UI: 采用圆角、阴影等现代化设计元素
  • 服务特色展示: 展示服务特色图标和文字说明
  • 品牌展示: 包含应用logo、名称和slogan展示

组件属性 (Props)

属性名类型默认值说明
imageUrlString"https://picsum.photos/400/600"广告图片URL
appIdString"wx1234567890abcdef"目标微信小程序AppID
pathString"pages/index/index"目标小程序页面路径
countdownNumber30倒计时时长(秒)
autoShowBooleantrue是否自动显示广告

组件事件 (Events)

事件名说明回调参数
close广告关闭时触发
click广告被点击时触发

使用示例

需要的位置调用,一般是首页

<template><!-- 全屏广告组件 --><fullscreen-ad :imageUrl="adConfig.imageUrl":appId="adConfig.appId":path="adConfig.path":countdown="adConfig.countdown":autoShow="true"@close="handleAdClose"@click="handleAdClick"/>
</template><script setup>
import FullscreenAd from '@/components/fullscreen-ad/index.vue'// 广告配置
const adConfig = {imageUrl: 'https://example.com/ad-image.jpg',appId: 'wx1234567890abcdef',path: 'pages/index/index',countdown: 30
}// 处理广告关闭
const handleAdClose = () => {console.log('广告已关闭')
}// 处理广告点击
const handleAdClick = () => {console.log('用户点击了广告')
}
</script>

广告的核心逻辑

  • src/components/fullscreen-ad/index.vue
<template><!-- 全屏广告弹出层 --><viewclass="fullscreen-ad-overlay"v-if="adData.show"@click="closeAd":style="containerStyle"><ad-example:adData="adData"@close="closeAd"@click="handleAdClick"style="width: 100%"></ad-example></view>
</template><script lang="ts" setup>
import { reactive, onUnmounted, ref, computed } from "vue";
import { storages } from "@/support/storages";
import adExample from "./examples/ad-example.vue";// 定义组件属性
interface Props {imageUrl?: string;appId?: string;path?: string;countdown?: number;autoShow?: boolean;
}// 定义事件
interface Emits {(e: "close"): void;(e: "click"): void;
}const props = withDefaults(defineProps<Props>(), {imageUrl: "https://picsum.photos/400/600",appId: "wx1234567890abcdef",path: "pages/index/index",countdown: 30,autoShow: true,
});const emit = defineEmits<Emits>();// 广告数据
const adData = reactive({show: false,countdown: props.countdown,imageUrl: props.imageUrl,imageError: false,appId: props.appId,path: props.path,timer: null as any,// 显示广告showAd: () => {console.log("🚀 [广告组件] 开始检查是否显示广告");// 检查是否已经显示过广告const today = new Date().toDateString();const lastShownDate = storages.get("fullscreen_ad_shown_date");console.log("🚀 [广告组件] 今天日期:", today);console.log("🚀 [广告组件] 上次显示日期:", lastShownDate);// 临时注释掉日期检查,确保每次都显示广告用于调试if (true) {// 临时改为总是显示console.log("🚀 [广告组件] 准备显示广告");adData.show = true;adData.countdown = props.countdown;adData.startCountdown();// 记录今天已显示过广告storages.set("fullscreen_ad_shown_date", today);console.log("🚀 [广告组件] 广告已显示,倒计时开始");} else {console.log("🚀 [广告组件] 今天已显示过广告,跳过");}},// 开始倒计时startCountdown: () => {console.log("🚀 [广告组件] 开始倒计时,初始值:", adData.countdown);adData.timer = setInterval(() => {adData.countdown--;console.log("🚀 [广告组件] 倒计时:", adData.countdown);if (adData.countdown <= 0) {console.log("🚀 [广告组件] 倒计时结束,自动关闭广告");adData.closeAd();}}, 1000);},// 关闭广告closeAd: () => {console.log("🚀 [广告组件] 执行关闭广告操作");adData.show = false;if (adData.timer) {console.log("🚀 [广告组件] 清理倒计时定时器");clearInterval(adData.timer);adData.timer = null;}console.log("🚀 [广告组件] 广告已关闭");emit("close");},
});// 关闭广告
const closeAd = () => {console.log("🚀 [广告组件] 用户手动关闭广告");adData.closeAd();
};// 处理广告点击
const handleAdClick = () => {console.log("🚀 [广告组件] 用户点击了广告");emit("click");// 跳转到微信小程序uni.navigateToMiniProgram({appId: adData.appId,path: adData.path,success: (res) => {console.log("🚀 [广告组件] 跳转小程序成功", res);// 跳转成功后关闭广告adData.closeAd();},fail: (err) => {console.error("🚀 [广告组件] 跳转小程序失败", err);// 如果是用户取消操作,不显示失败提示if (err.errMsg && err.errMsg.includes("cancel")) {console.log("🚀 [广告组件] 用户取消跳转");return;}uni.showToast({title: "跳转失败",icon: "none",});},});
};// 处理图片加载错误
const handleImageError = (e: any) => {console.log("🚀 [广告组件] 图片加载失败", e);adData.imageError = true;
};// 处理图片加载成功
const handleImageLoad = (e: any) => {console.log("🚀 [广告组件] 图片加载成功", e);adData.imageError = false;
};// 暴露方法给父组件
defineExpose({showAd: adData.showAd,closeAd: adData.closeAd,
});// 组件卸载时清理定时器
onUnmounted(() => {if (adData.timer) {clearInterval(adData.timer);adData.timer = null;}
});// 获取系统信息,用于安全区域适配
const systemInfo = ref<any>({});// 获取系统信息
const getSystemInfo = () => {try {const info = uni.getSystemInfoSync();systemInfo.value = info;console.log("🚀 [广告组件] 系统信息:", info);// 在微信小程序中,直接使用系统信息来计算安全区域if (info.safeAreaInsets) {const { top, bottom, left, right } = info.safeAreaInsets;console.log("🚀 [广告组件] 安全区域:", { top, bottom, left, right });// 将安全区域信息保存到响应式数据中safeAreaInsets.value = { top, bottom, left, right };} else if (info.statusBarHeight) {// 兼容旧版本,使用状态栏高度console.log("🚀 [广告组件] 状态栏高度:", info.statusBarHeight);safeAreaInsets.value = {top: info.statusBarHeight,bottom: 0,left: 0,right: 0,};} else {// 默认值safeAreaInsets.value = { top: 0, bottom: 0, left: 0, right: 0 };}} catch (error) {console.error("🚀 [广告组件] 获取系统信息失败:", error);// 设置默认值safeAreaInsets.value = { top: 0, bottom: 0, left: 0, right: 0 };}
};// 安全区域数据
const safeAreaInsets = ref({ top: 0, bottom: 0, left: 0, right: 0 });// 计算样式
const containerStyle = computed(() => ({// paddingTop: `${safeAreaInsets.value.top}px`,paddingBottom: `${safeAreaInsets.value.bottom}px`,paddingLeft: `${safeAreaInsets.value.left}px`,paddingRight: `${safeAreaInsets.value.right}px`,
}));// const adContainerStyle = computed(() => ({
//   height: `calc(100vh - ${safeAreaInsets.value.top}px - ${safeAreaInsets.value.bottom}px)`
// }));const headerStyle = computed(() => ({paddingTop: `${30 + safeAreaInsets.value.top}px`, // 60rpx ≈ 30px
}));const countdownStyle = computed(() => ({top: `${20 + safeAreaInsets.value.top}px`, // 40rpx ≈ 20px
}));const closeBtnStyle = computed(() => ({top: `${20 + safeAreaInsets.value.top}px`, // 40rpx ≈ 20px
}));// 初始化时获取系统信息
getSystemInfo();// 如果设置了自动显示,则延迟显示广告
if (props.autoShow) {setTimeout(() => {adData.showAd();}, 1000);
}
</script><style lang="scss" scoped>
/* 全屏广告样式 */
.fullscreen-ad-overlay {position: fixed;top: 0;left: 0;width: 100vw;height: 100vh;// background: linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.9) 100%);z-index: 9999;display: flex;align-items: center;justify-content: center;animation: fadeIn 0.4s ease-in-out;backdrop-filter: blur(10rpx);/* 安全区域通过JavaScript动态设置 */box-sizing: border-box;
}
</style>

广告的交互页面

  • src/components/fullscreen-ad/examples/ad-example.vue - 广告内容示例组件
<template><!-- 广告内容 --><view class="fullscreen-ad-container" @click.stop><!-- 头部区域 --><view class="ad-header"><view class="ad-title">高价回收手机</view><view class="ad-subtitle">30分钟免费上门回收</view></view><!-- 内容区域 --><view class="ad-content"><!-- 广告图片 --><imageclass="banner-image":src="adData.imageUrl"mode="aspectFill"@click="handleAdClick"@error="handleImageError"@load="handleImageLoad"/><!-- 图片加载失败时的占位内容 --><viewclass="ad-placeholder"v-if="adData.imageError"@click="handleAdClick"><text class="placeholder-text">广告图片加载失败</text><text class="placeholder-subtitle">点击此处跳转小程序</text></view><!-- 服务特色 --><view class="service-features"><view class="feature-item"><view class="feature-icon"></view><text class="feature-text">30分钟上门</text></view><view class="feature-item"><view class="feature-icon">🔒</view><text class="feature-text">安全保障</text></view><view class="feature-item"><view class="feature-icon"></view><text class="feature-text">极速打款</text></view></view></view><!-- 底部操作区 --><view class="action-bar"><view class="submit-btn" @click="handleAdClick"> 戳我换钱 </view></view><!-- 底部导航 --><view class="bottom-nav"><view class="nav-left"><text class="page-number">{{ adData.countdown }}</text></view><view class="nav-center"><view class="app-logo">🦆</view><view class="app-info"><text class="app-name">出手鸭</text><text class="app-slogan">该出手时就出手</text></view></view><view class="nav-right" @click="closeAd"><text class="skip-text">跳过</text></view></view></view>
</template><script lang="ts" setup>
import { reactive, onUnmounted, ref, computed } from "vue";
import { storages } from "@/support/storages";
// 定义组件属性
interface Props {adData?: boolean;
}// 定义事件
interface Emits {(e: "close"): void;(e: "click"): void;
}const props = withDefaults(defineProps<Props>(), {adData: {},
});const emit = defineEmits<Emits>();
// 关闭广告
const closeAd = () => {console.log("🚀 [广告组件] 用户手动关闭广告");emit("close");
};// 处理广告点击
const handleAdClick = () => {console.log("🚀 [广告组件] 用户点击了广告");emit("click");
};// 处理图片加载错误
const handleImageError = (e: any) => {console.log("🚀 [广告组件] 图片加载失败", e);// adData.imageError = true;
};// 处理图片加载成功
const handleImageLoad = (e: any) => {console.log("🚀 [广告组件] 图片加载成功", e);// adData.imageError = false;
};
</script><style lang="scss">
.fullscreen-ad-container {position: relative;width: 100%;/* 高度通过JavaScript动态设置 */height: 100vh;border-radius: 0;overflow: hidden;background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);box-shadow: 0 20rpx 60rpx rgba(0, 0, 0, 0.3);animation: slideIn 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);display: flex;flex-direction: column;
}/* 头部区域 */
.ad-header {/* padding通过JavaScript动态设置 */padding: 200rpx 40rpx 40rpx;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);position: relative;overflow: hidden;
}.ad-header::before {content: "";position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="20" cy="20" r="2" fill="rgba(255,255,255,0.1)"/><circle cx="80" cy="40" r="1" fill="rgba(255,255,255,0.1)"/><circle cx="40" cy="80" r="1.5" fill="rgba(255,255,255,0.1)"/></svg>')repeat;opacity: 0.3;
}.ad-title {font-size: 64rpx;font-weight: 800;color: #ffffff;text-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.3);letter-spacing: 2rpx;position: relative;z-index: 1;
}.ad-subtitle {font-size: 32rpx;color: rgba(255, 255, 255, 0.9);margin-top: 16rpx;font-weight: 500;position: relative;z-index: 1;
}.countdown-container {position: absolute;/* top通过JavaScript动态设置 */top: 40rpx;right: 40rpx;background: rgba(255, 255, 255, 0.2);border-radius: 40rpx;padding: 12rpx 24rpx;backdrop-filter: blur(10rpx);border: 1rpx solid rgba(255, 255, 255, 0.3);
}.countdown-text {color: #fff;font-size: 24rpx;font-weight: 600;
}/* 内容区域 */
.ad-content {flex: 1;padding: 40rpx;background: #ffffff;position: relative;
}.banner-image {width: 100%;height: 600rpx;border-radius: 24rpx;margin-bottom: 40rpx;box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);object-fit: cover;
}.ad-placeholder {width: 100%;height: 360rpx;border-radius: 24rpx;margin-bottom: 40rpx;display: flex;flex-direction: column;align-items: center;justify-content: center;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);cursor: pointer;box-shadow: 0 12rpx 40rpx rgba(0, 0, 0, 0.15);position: relative;overflow: hidden;
}.ad-placeholder::before {content: "";position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: radial-gradient(circle at 30% 30%,rgba(255, 255, 255, 0.1) 0%,transparent 50%);
}.placeholder-text {color: #fff;font-size: 36rpx;font-weight: 700;margin-bottom: 16rpx;position: relative;z-index: 1;
}.placeholder-subtitle {color: rgba(255, 255, 255, 0.8);font-size: 28rpx;position: relative;z-index: 1;
}/* 服务特色 */
.service-features {display: flex;justify-content: space-around;margin-bottom: 40rpx;background: #f8f9ff;border-radius: 20rpx;padding: 30rpx 20rpx;
}.feature-item {display: flex;flex-direction: column;align-items: center;flex: 1;
}.feature-icon {font-size: 48rpx;margin-bottom: 12rpx;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;
}.feature-text {font-size: 24rpx;color: #666666;font-weight: 500;text-align: center;
}/* 底部操作区 */
.action-bar {padding: 30rpx 40rpx;background: #ffffff;
}.submit-btn {width: 100%;height: 96rpx;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);border-radius: 48rpx;color: #ffffff;font-size: 32rpx;font-weight: 700;border: none;display: flex;align-items: center;justify-content: center;cursor: pointer;box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.4);transition: all 0.3s ease;position: relative;overflow: hidden;
}.submit-btn::before {content: "";position: absolute;top: 0;left: -100%;width: 100%;height: 100%;background: linear-gradient(90deg,transparent,rgba(255, 255, 255, 0.2),transparent);transition: left 0.5s ease;
}.submit-btn:active::before {left: 100%;
}/* 底部导航 */
.bottom-nav {display: flex;justify-content: space-between;align-items: center;padding: 24rpx 40rpx;background: #ffffff;border-top: 1rpx solid #f0f0f0;height: 180rpx;
}.nav-left {width: 56rpx;height: 56rpx;background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);border-radius: 28rpx;display: flex;align-items: center;justify-content: center;box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);
}.page-number {font-size: 24rpx;color: #666666;font-weight: 600;
}.nav-center {display: flex;align-items: center;
}.app-logo {font-size: 48rpx;margin-right: 16rpx;filter: drop-shadow(0 2rpx 4rpx rgba(0, 0, 0, 0.1));
}.app-info {display: flex;flex-direction: column;
}.app-name {font-size: 28rpx;font-weight: 700;color: #333333;background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);-webkit-background-clip: text;-webkit-text-fill-color: transparent;background-clip: text;
}.app-slogan {font-size: 20rpx;color: #999999;margin-top: 4rpx;font-weight: 500;
}.nav-right {width: 100rpx;height: 56rpx;background: linear-gradient(135deg, #f0f0f0 0%, #e0e0e0 100%);border-radius: 28rpx;display: flex;align-items: center;justify-content: center;cursor: pointer;box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.1);transition: all 0.3s ease;
}.nav-right:active {transform: scale(0.95);
}.skip-text {font-size: 24rpx;color: #666666;font-weight: 600;
}/* 关闭按钮 */
.close-btn {position: absolute;/* top通过JavaScript动态设置 */top: 40rpx;right: 40rpx;width: 56rpx;height: 56rpx;background: rgba(255, 255, 255, 0.2);border-radius: 50%;display: flex;align-items: center;justify-content: center;z-index: 30;backdrop-filter: blur(10rpx);border: 1rpx solid rgba(255, 255, 255, 0.3);transition: all 0.3s ease;
}.close-btn:active {transform: scale(0.9);background: rgba(255, 255, 255, 0.3);
}.close-icon {color: #fff;font-size: 32rpx;font-weight: 700;line-height: 1;
}/* 动画效果 */
@keyframes fadeIn {from {opacity: 0;backdrop-filter: blur(0);}to {opacity: 1;backdrop-filter: blur(10rpx);}
}@keyframes slideIn {from {transform: scale(0.9) translateY(60rpx);opacity: 0;}to {transform: scale(1) translateY(0);opacity: 1;}
}/* 响应式适配 */
@media screen and (max-width: 750rpx) {.ad-title {font-size: 56rpx;}.ad-subtitle {font-size: 28rpx;}.banner-image {height: 320rpx;}.ad-placeholder {height: 320rpx;}.submit-btn {height: 88rpx;font-size: 30rpx;}
}@media screen and (max-width: 600rpx) {.ad-header {/* padding通过JavaScript动态设置 */padding: 50rpx 30rpx 30rpx;}.ad-content {padding: 30rpx;}.action-bar {padding: 24rpx 30rpx;}.bottom-nav {padding: 20rpx 30rpx;}.countdown-container {/* top通过JavaScript动态设置 */top: 30rpx;right: 30rpx;}.close-btn {/* top通过JavaScript动态设置 */top: 30rpx;right: 30rpx;}
}
</style>

技术实现

核心技术栈

  • Vue 3: 使用Composition API
  • TypeScript: 类型安全的开发体验
  • uni-app: 跨平台开发框架
  • SCSS: CSS预处理器

关键实现

  1. 状态管理: 使用reactive响应式数据管理广告状态
  2. 定时器管理: 使用setInterval实现倒计时,组件卸载时自动清理
  3. 本地存储: 使用uni-app的存储API记录广告显示状态
  4. 系统信息获取: 获取设备信息进行安全区域适配
  5. 小程序跳转: 使用uni.navigateToMiniProgram API实现跳转

生命周期管理

  • 组件挂载: 自动获取系统信息,设置自动显示
  • 组件卸载: 自动清理定时器,防止内存泄漏
  • 错误处理: 完善的错误捕获和用户提示

注意事项

  1. 权限要求: 跳转微信小程序需要相应的平台权限配置
  2. 网络依赖: 广告图片加载依赖网络连接
  3. 存储空间: 组件会使用本地存储记录显示状态
  4. 性能考虑: 大图片可能影响加载性能,建议优化图片大小
  5. 用户体验: 建议合理设置显示频率,避免过度打扰用户

扩展建议

  1. 多广告支持: 支持配置多个广告轮播显示
  2. 统计功能: 添加广告展示和点击统计
  3. A/B测试: 支持不同广告内容的A/B测试
  4. 动态配置: 支持从服务端动态获取广告配置
  5. 更多跳转方式: 支持跳转到H5页面、应用内页面等
http://www.xdnf.cn/news/19775.html

相关文章:

  • AI IDE+AI 辅助编程,真能让程序员 “告别 996” 吗?
  • 【LeetCode_283】移动零
  • 技术小白如何快速的了解opentenbase?--把握四大特色
  • XE 旧版本 JSON 处理
  • 使用 Uni-app 打包 外链地址APK 及 iOS 注意事项
  • K8S-基础架构
  • 离开职场2个月,后知后觉的反思。
  • 素材合集!直播间带货音乐BGM合集,抖音直播间常用热门音乐合集,根据中文分类,方便查找
  • 力扣hot100:矩阵置零(73)(原地算法)
  • 【Python语法基础学习笔记】类的定义和使用
  • WSL + VSCode + Git + Node.js 开发环境配置文档
  • python数据分析 与spark、hive数据分析对比
  • 使用pyspark对上百亿行的hive表生成稀疏向量
  • 2025年COR IOTJ SCI2区,灾后通信无人机基站位置优化和移动充电无人机路径规划,深度解析+性能实测
  • Android aoap开发常见问题之package_allowed_list.txt导致的编译报错
  • 深度学习------模型的保存和使用
  • 深度学习篇---Adam优化器
  • Docker Pull 代理配置方法
  • 【正则表达式】 正则表达式有哪些语法?
  • Low-Light Image Enhancement via Structure Modeling and Guidance 论文阅读
  • AP5414:高效灵活的LED驱动解决方案,点亮创意生活
  • go大厂真实的面试经历与总结
  • 心路历程-初识Linux用户
  • EasyExcel 基础用法
  • 如何在FastAPI中巧妙隔离依赖项,让单元测试不再头疼?
  • 一文吃透 `protoc` 安装与落地
  • 【Spring Cloud微服务】10.王子、巨龙与Spring Cloud:用注解重塑微服务王国
  • 普通人也能走的自由之路
  • 科技赋能田园:数字化解决方案开启智慧农业新篇章
  • 告别 Hadoop,拥抱 StarRocks!政采云数据平台升级之路