| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242 |
- <template>
- <Teleport to="body">
- <div v-if="visible" class="footer-wrap">
- <nav class="footer" :class="{ isWhite }">
- <div
- v-for="item in tabs"
- :key="item.id"
- class="footer-item"
- :class="{ active: currentTab === item.id }"
- @click="refresh(item.id)"
- >
- <template v-if="!isRefreshing(item.id)">
- <div class="icon-placeholder">
- <img
- class="icon-img"
- :src="currentTab === item.id ? item.iconActive : item.icon"
- :alt="item.label"
- />
- </div>
- <span class="label" :class="{ active: currentTab === item.id }">{{ item.label }}</span>
- </template>
- </div>
- </nav>
- </div>
- </Teleport>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import bus, { EVENT_KEY } from '@/utils/bus'
- import tab1 from '@/assets/img/tabbar/tab-1.png'
- import tabAct1 from '@/assets/img/tabbar/tab-act-1.png'
- import tab2 from '@/assets/img/tabbar/tab-2.png'
- import tabAct2 from '@/assets/img/tabbar/tab-act-2.png'
- import tab3 from '@/assets/img/tabbar/tab-3.png'
- import tabAct3 from '@/assets/img/tabbar/tab-act-3.png'
- const props = defineProps({
- initTab: { type: Number, default: 1 },
- isWhite: { type: Boolean, default: false },
- })
- const router = useRouter()
- const route = useRoute()
- const currentTab = ref(props.initTab)
- /** douyin:bus 控制;扩展:仅 footerTab 路由 + 首页子层可隐藏 */
- const panelVisible = ref(true)
- const fullscreenHidden = ref(false)
- const hasFooterTab = computed(() => route.meta.footerTab != null)
- const visible = computed(
- () => hasFooterTab.value && panelVisible.value && !fullscreenHidden.value,
- )
- const tabs = [
- {
- id: 1,
- path: '/',
- label: '农场美景',
- icon: tab1,
- iconActive: tabAct1,
- },
- {
- id: 2,
- path: '/guard-map',
- label: '守护地图',
- icon: tab2,
- iconActive: tabAct2,
- },
- {
- id: 3,
- path: '/my-guard',
- label: '我的守护',
- icon: tab3,
- iconActive: tabAct3,
- },
- ]
- function syncTabFromRoute() {
- const tab = route.meta.footerTab as number | undefined
- if (tab) {
- currentTab.value = tab
- return
- }
- const item = tabs.find((t) => t.path === route.path)
- if (item) currentTab.value = item.id
- }
- const isRefresh1 = ref(false)
- const isRefresh2 = ref(false)
- const isRefresh3 = ref(false)
- function isRefreshing(index: number) {
- if (index === 1) return isRefresh1.value
- if (index === 2) return isRefresh2.value
- if (index === 3) return isRefresh3.value
- return false
- }
- function setRefreshing(index: number, val: boolean) {
- if (index === 1) isRefresh1.value = val
- else if (index === 2) isRefresh2.value = val
- else if (index === 3) isRefresh3.value = val
- }
- function tab(index: number) {
- const item = tabs.find((t) => t.id === index)
- if (!item) return
- currentTab.value = index
- if (route.path !== item.path) {
- router.push(item.path)
- }
- }
- /** 与 douyin-vue 一致:当前 Tab 再点进入 refresh 态,否则切换 Tab */
- function refresh(index: number) {
- if (currentTab.value === index) {
- setRefreshing(index, !isRefreshing(index))
- setTimeout(() => {
- setRefreshing(index, !isRefreshing(index))
- }, 2000)
- } else {
- tab(index)
- }
- }
- watch(
- () => route.path,
- () => {
- panelVisible.value = true
- fullscreenHidden.value = false
- syncTabFromRoute()
- },
- )
- onMounted(() => {
- syncTabFromRoute()
- bus.on('setFooterVisible', (e) => {
- panelVisible.value = e as boolean
- })
- bus.on(EVENT_KEY.ENTER_FULLSCREEN, () => {
- fullscreenHidden.value = true
- })
- bus.on(EVENT_KEY.EXIT_FULLSCREEN, () => {
- fullscreenHidden.value = false
- })
- })
- onUnmounted(() => {
- bus.off('setFooterVisible')
- bus.off(EVENT_KEY.ENTER_FULLSCREEN)
- bus.off(EVENT_KEY.EXIT_FULLSCREEN)
- })
- </script>
- <style scoped lang="less">
- .footer-wrap {
- position: fixed;
- left: 50%;
- bottom: var(--footer-bottom-offset, 30rem);
- z-index: 10;
- transform: translateX(-50%);
- width: calc(100vw - 32rem);
- max-width: 360rem;
- pointer-events: none;
- box-sizing: border-box;
- }
- .footer {
- pointer-events: auto;
- display: flex;
- align-items: center;
- justify-content: space-evenly;
- width: 100%;
- height: 56rem;
- padding: 8rem 12rem;
- box-sizing: border-box;
- background: rgba(0, 0, 0, 0.61);
- border: 1px solid rgba(255, 255, 255, 0.2);
- border-radius: 25rem;
- &.isWhite {
- background: #f5f5f5;
- border-color: rgba(0, 0, 0, 0.12);
- }
- }
- .footer-item {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 4rem;
- cursor: pointer;
- min-width: 0;
- text-align: center;
- }
- .icon-placeholder {
- width: 28rem;
- height: 28rem;
- display: flex;
- align-items: center;
- justify-content: center;
- flex-shrink: 0;
- }
- .icon-img {
- display: block;
- width: 100%;
- height: 100%;
- object-fit: contain;
- opacity: 0.4;
- }
- .footer-item.active .icon-img {
- opacity: 1;
- }
- .label {
- width: 100%;
- font-size: 11rem;
- line-height: 1.2;
- text-align: center;
- color: rgba(255, 255, 255, 0.35);
- white-space: nowrap;
- &.active {
- color: #ffb400;
- font-weight: 500;
- }
- }
- .footer.isWhite .label {
- color: rgba(0, 0, 0, 0.35);
- &.active {
- color: #e6a000;
- }
- }
- </style>
|