feat: 首页弹窗广告(HOME_POPUP 类型,每次冷启动弹一次,点击跳内部页面,tabBar 页自动 switchTab,失败 toast 兜底)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
bba2571f25
commit
32f3db7e85
+175
-2
@@ -125,12 +125,34 @@
|
|||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
|
<!-- 首页弹窗广告:每次冷启动弹一次,取「首页弹窗」类型排序最前一条 -->
|
||||||
|
<view
|
||||||
|
v-if="homePopupVisible"
|
||||||
|
class="popup-overlay"
|
||||||
|
:class="{ visible: homePopupAnim }"
|
||||||
|
@click="closeHomePopup"
|
||||||
|
>
|
||||||
|
<image
|
||||||
|
class="popup-img"
|
||||||
|
:src="homePopupData?.imageUrl || ''"
|
||||||
|
mode="widthFix"
|
||||||
|
@click.stop="onHomePopupClick"
|
||||||
|
@error="onHomePopupImgError"
|
||||||
|
/>
|
||||||
|
<view class="popup-close" @click.stop="closeHomePopup">
|
||||||
|
<view class="close-x">
|
||||||
|
<view class="close-line line-a" />
|
||||||
|
<view class="close-line line-b" />
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted, nextTick } from 'vue'
|
||||||
import { onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
import { onShow, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||||
import { post } from '../../api/request'
|
import { post } from '../../api/request'
|
||||||
import { BASE_URL } from '../../config'
|
import { BASE_URL } from '../../config'
|
||||||
import type { BannerItem } from '../../api/types'
|
import type { BannerItem } from '../../api/types'
|
||||||
@@ -224,6 +246,71 @@ async function fetchSpots() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ====== 首页弹窗广告(HOME_POPUP) ====== */
|
||||||
|
// xuchao: 首页为 tabBar 页,实例与进程同生命周期,实例级标志即「每次冷启动弹一次」
|
||||||
|
let homePopupHandled = false
|
||||||
|
let homePopupFetching = false
|
||||||
|
const homePopupData = ref<BannerItem | null>(null)
|
||||||
|
const homePopupVisible = ref(false)
|
||||||
|
const homePopupAnim = ref(false)
|
||||||
|
|
||||||
|
onShow(() => { checkHomePopup() })
|
||||||
|
|
||||||
|
/** 首页每次显示时检查;本次启动已弹过/请求中则跳过;接口失败不置标志,下次进首页重试 */
|
||||||
|
async function checkHomePopup() {
|
||||||
|
if (homePopupHandled || homePopupFetching) return
|
||||||
|
homePopupFetching = true
|
||||||
|
try {
|
||||||
|
const list = await post<BannerItem[]>('/mpContent/queryList', { channel: 'pingxiang', type: 'HOME_POPUP' })
|
||||||
|
homePopupHandled = true
|
||||||
|
const item = (list || []).find(c => c.imageUrl)
|
||||||
|
if (item) {
|
||||||
|
homePopupData.value = item
|
||||||
|
homePopupVisible.value = true
|
||||||
|
nextTick(() => { homePopupAnim.value = true })
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 静默失败,下次进首页再试
|
||||||
|
} finally {
|
||||||
|
homePopupFetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击广告图:先关弹窗,再跳配置的内部页面 */
|
||||||
|
function onHomePopupClick() {
|
||||||
|
const url = homePopupData.value?.linkUrl
|
||||||
|
homePopupAnim.value = false
|
||||||
|
homePopupVisible.value = false
|
||||||
|
if (url) navigateInnerPage(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 关闭弹窗(离场动画结束后移除节点) */
|
||||||
|
function closeHomePopup() {
|
||||||
|
homePopupAnim.value = false
|
||||||
|
setTimeout(() => { homePopupVisible.value = false }, 280)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图片加载失败直接移除弹窗 */
|
||||||
|
function onHomePopupImgError() {
|
||||||
|
homePopupVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// xuchao: tabBar 页 navigateTo 会失败,必须走 switchTab
|
||||||
|
const TAB_PAGE_PATHS = ['/pages/index/index', '/pages/qrcode/index', '/pages/profile/index']
|
||||||
|
|
||||||
|
/** xuchao: 小程序内部页面智能跳转(tabBar 走 switchTab,其余 navigateTo,失败兜底提示) */
|
||||||
|
function navigateInnerPage(url: string) {
|
||||||
|
const path = url.startsWith('/') ? url : `/${url}`
|
||||||
|
if (TAB_PAGE_PATHS.includes(path.split('?')[0])) {
|
||||||
|
uni.switchTab({ url: path })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uni.navigateTo({
|
||||||
|
url: path,
|
||||||
|
fail: () => uni.showToast({ title: '页面暂未开放', icon: 'none' }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
onShareAppMessage(() => ({
|
onShareAppMessage(() => ({
|
||||||
title: '爱上萍乡旅游年卡',
|
title: '爱上萍乡旅游年卡',
|
||||||
path: '/pages/index/index',
|
path: '/pages/index/index',
|
||||||
@@ -823,4 +910,90 @@ function goMore() {
|
|||||||
.main-scroll {
|
.main-scroll {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ============================================
|
||||||
|
首页弹窗广告
|
||||||
|
============================================ */
|
||||||
|
.popup-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: $z-modal;
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 280ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
|
&.visible {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
.popup-img {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-close {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 广告图:宽 600rpx 固定,高按原图比例(widthFix)自适应 */
|
||||||
|
.popup-img {
|
||||||
|
width: 600rpx;
|
||||||
|
border-radius: $radius-2xl;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0.88);
|
||||||
|
opacity: 0;
|
||||||
|
transition: transform 320ms cubic-bezier(0.16, 1, 0.3, 1),
|
||||||
|
opacity 260ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-close {
|
||||||
|
width: 72rpx;
|
||||||
|
height: 72rpx;
|
||||||
|
border-radius: $radius-full;
|
||||||
|
border: 2rpx solid rgba(255, 255, 255, 0.35);
|
||||||
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: $spacing-4;
|
||||||
|
flex-shrink: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
transition: opacity 320ms cubic-bezier(0.16, 1, 0.3, 1) 120ms,
|
||||||
|
transform 320ms cubic-bezier(0.16, 1, 0.3, 1) 120ms;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-x {
|
||||||
|
width: 24rpx;
|
||||||
|
height: 24rpx;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-line {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 28rpx;
|
||||||
|
height: 2rpx;
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 2rpx;
|
||||||
|
|
||||||
|
&.line-a {
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.line-b {
|
||||||
|
transform: translate(-50%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user