171 lines
4.2 KiB
Vue
171 lines
4.2 KiB
Vue
<template>
|
|
<div
|
|
v-if="showing"
|
|
class="ai-drama-float-bubble"
|
|
:style="{ '--tab-count': String(tabCount) }"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="ai-drama-float-bubble__hit"
|
|
aria-label="AI爽剧来袭"
|
|
@click.stop="onClick"
|
|
>
|
|
<img
|
|
class="ai-drama-float-bubble__img"
|
|
src="@/assets/images/png/short-drama/ai-drama-float-bubble.png"
|
|
alt="AI爽剧来袭!"
|
|
width="131"
|
|
height="34"
|
|
/>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
import { useConfigStore } from '@/stores/config';
|
|
import { unlockShortVideoAudio } from '@/utils/shortVideoAudioUnlock';
|
|
|
|
/** 终身只展示 1 次(含老用户更新后的首次启动) */
|
|
const STORAGE_KEY = 'ai_drama_float_bubble_once_v1';
|
|
|
|
const props = defineProps<{
|
|
/** 底部 Tab 数量(含暗网时为 6,否则 5) */
|
|
tabCount?: number;
|
|
/** 当前是否已在短剧 Tab */
|
|
activeOnShortVideo?: boolean;
|
|
}>();
|
|
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
const configStore = useConfigStore();
|
|
|
|
const tabCount = computed(() => Math.max(5, Number(props.tabCount) || 6));
|
|
|
|
const isOnShortVideo = () =>
|
|
Boolean(props.activeOnShortVideo) || route.name === 'shortVideo';
|
|
|
|
const readOnceFlag = () => {
|
|
try {
|
|
return localStorage.getItem(STORAGE_KEY) === '1';
|
|
} catch {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const writeOnceFlag = () => {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, '1');
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
};
|
|
|
|
/** 本次是否正在展示(仅首次启动且非短剧 Tab) */
|
|
const showing = ref(false);
|
|
|
|
onMounted(() => {
|
|
// 已消耗过首次机会 → 永不出现
|
|
if (readOnceFlag()) {
|
|
showing.value = false;
|
|
return;
|
|
}
|
|
|
|
// Ping 运营全局关闭:不展示,也不消耗本地 once(日后重新开启仍可出现一次)
|
|
if (configStore.appConfig?.entryPopupEnabled === false) {
|
|
showing.value = false;
|
|
return;
|
|
}
|
|
|
|
// 消耗首次机会(老用户更新后第一次进 App 也算)
|
|
writeOnceFlag();
|
|
|
|
// 启动时默认就在短剧 Tab → 不出现
|
|
if (isOnShortVideo()) {
|
|
showing.value = false;
|
|
return;
|
|
}
|
|
|
|
showing.value = true;
|
|
});
|
|
|
|
// 进入短剧 Tab 后立刻消失
|
|
watch(
|
|
() => [props.activeOnShortVideo, route.name] as const,
|
|
() => {
|
|
if (isOnShortVideo()) {
|
|
showing.value = false;
|
|
}
|
|
},
|
|
);
|
|
|
|
const onClick = () => {
|
|
unlockShortVideoAudio();
|
|
showing.value = false;
|
|
writeOnceFlag();
|
|
void router.replace({ path: '/shortVideo', query: { type: '4' } });
|
|
};
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
/*
|
|
* 与 TabBar 同坐标系:fixed + max-width:780 居中。
|
|
* 原先 absolute 相对 tab_wrapper,和 fixed 底栏/安全区错位,真机易乱。
|
|
* Figma 23190:99055:三角 Vector x=46 w=12,尖端约在气泡左侧 52px,对准第 2 个 Tab「短剧」中心。
|
|
*/
|
|
.ai-drama-float-bubble {
|
|
--tab-count: 6;
|
|
/* 气泡左缘 → 三角尖端(46 + 12/2) */
|
|
--arrow-offset: 6px;
|
|
position: fixed;
|
|
left: 0;
|
|
right: 0;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
max-width: 780px;
|
|
/* 与 :deep(.van-tabbar) height:55px + safe-area 对齐,再上抬 6px */
|
|
bottom: calc(55px + constant(safe-area-inset-bottom) + 6px);
|
|
bottom: calc(55px + env(safe-area-inset-bottom, 0px) + 6px);
|
|
z-index: 1002;
|
|
height: 34px;
|
|
pointer-events: none;
|
|
|
|
&__hit {
|
|
position: absolute;
|
|
/* 第 2 个 Tab 中心:兼容部分 WebView 对 calc 除法顺序的限制 */
|
|
left: calc(100% * 1.5 / var(--tab-count));
|
|
bottom: 0;
|
|
width: 50px;
|
|
height: 34px;
|
|
padding: 0;
|
|
border: 0;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
pointer-events: auto;
|
|
-webkit-tap-highlight-color: transparent;
|
|
transform: translateX(calc(-1 * var(--arrow-offset)));
|
|
animation: ai-drama-bubble-bob 1.8s ease-in-out infinite;
|
|
}
|
|
|
|
&__img {
|
|
display: block;
|
|
width: 50px;
|
|
height: 50px;
|
|
object-fit: contain;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
}
|
|
}
|
|
|
|
@keyframes ai-drama-bubble-bob {
|
|
0%,
|
|
100% {
|
|
transform: translateX(calc(-1 * var(--arrow-offset))) translateY(0);
|
|
}
|
|
50% {
|
|
transform: translateX(calc(-1 * var(--arrow-offset))) translateY(-3px);
|
|
}
|
|
}
|
|
</style>
|