vue项目标题、页面、tabbar保持一致

目标:

使vue项目能和微信小程序一样,点击tabbar菜单,页面切换,标题更新。或者路由变化,标题、页面、tabbar都更新。


自定义tabbar

store.js


import { createStore } from 'vuex'
import createPersistedState from 'vuex-persistedstate'

export default createStore({
  state: {
    curMenu: 0,
    menu: [
      { id: 0, name: '首页', img: 'home', title: '首页', child: ['scan', 'submit'] },
      { id: 1, name: '记录', img: 'records', title: '记录', child: ['records', 'details'] },
      { id: 2, name: '个人', img: 'individual', title: '个人', child: ['individual', 'edit', 'list'] },
    ],
   
  },
  getters,
  mutations: {
    SET_CURMENU(state, obj) {
      state.curMenu = obj
    },
    SET_USERINFO(state, obj) {
      state.userInfo = obj
    }
  },
  actions:{},
  modules: {},
  plugins: [
    createPersistedState({
      storage: window.localstorage,
      paths: ['curMenu']
    })
  ]
})

route.js

import { createRouter, createWebHistory } from 'vue-router'
import store from '@/store/index'

const routes = [
  {
    path: '/',
    name: 'index',
    component: () => import('../views/index'),
    meta: {
      title: '登录'
    }
  },

  {
    path: '/home',
    name: 'home',
    redirect: '/scan',
    component: () => import('../views/home/index'),
    meta: {
      title: '首页',
    },
    children: [
      {
        path: '/scan',
        name: 'scan',
        component: () => import('../views/home/scan'),
        meta: {
          title: '首页',
        },
      },
      {
        path: '/submit',
        name: 'submit',
        component: () => import('../views/home/submit'),
        meta: {
          title: '填报',
        },
      },
      {
        path: '/records',
        name: 'records',
        component: () => import('../views/records/index'),
        meta: {
          title: '记录',
        },
      },
      {
        path: '/details',
        name: 'details',
        component: () => import('../views/records/details'),
        meta: {
          title: '记录',
        },
      },
      {
        path: '/individual',
        name: 'individual',
        component: () => import('../views/individual/index'),
        meta: {
          title: '个人',
        },
      }
    ]
  },
  {
    path: '/edit',
    name: 'edit',
    component: () => import('../views/individual/edit'),
    meta: {
      title: '编辑'
    }
  },
  {
    path: '/list',
    name: 'list',
    component: () => import('../views/individual/list'),
    meta: {
      title: '列表'
    }
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

function onPopState(event) {
  // 检查当前路径是否是 '/'

  if (window.location.pathname === '/') {
    // 清空历史记录(实际上只是替换为一个新的条目)
    window.history.pushState(null, '', '/');
  } else {
    // 如果不是,移除 popstate 事件监听器
    window.removeEventListener('popstate', onPopState);
  }
}

let isRedirecting = false;

router.beforeEach((to, from, next) => {
 // 返回首页清除历史路由(window.history提供的方法无法实现或者效果不好,所以选择页面刷新)
  if (to.path === '/' && from.path === '/scan' && !isRedirecting) {
    isRedirecting = true;

    window.open('/', '_self')

    setTimeout(() => {
      isRedirecting = false;
    }, 0);
  } else {
    if (to.meta.title) {
      document.title = to.meta.title
    }
    if (store.state.menu.find(i => i.img == to.name)) {
      store.commit('SET_CURMENU', store.state.menu.find(i => i.img == to.name).id)
    } else {
      let resId
      store.state.menu.forEach(i => {
        if (i.child.findIndex(j => j == to.name) != -1) {
          resId = i.id
        }
      })
      store.commit('SET_CURMENU', resId)
    }
    next()
  }
  window.addEventListener('popstate', onPopState);
});

router.afterEach((to, from) => {
  if (to.meta.title) {
    document.title = to.meta.title
  }

  if (from.path != '/scan' && to.path === '/submit') {
    // 确保不能回退到scan页面
    router.back(); // 这里使用浏览器的历史记录回退
  }
})

export default router

views/home/index.vue

<template>
  <div class="main-wrap wid100 hei100 pos-relative ">
    <Header />
    <div class="main-page wid100">
      <router-view />
    </div>
    <Tabbar />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import Header from '@/components/header'
import Tabbar from '@/components/tabbar'
</script>

<style lang="scss" scoped>
.main-wrap {
  .main-page {
    height: calc(100% - 7.75rem);
    overflow-y: auto;
  }
}
</style>

components/header.vue

<template>
  <div class="header wid100 flex justify-center items-end ">{{title}}</div>
</template>

<script setup>
import { ref, reactive, onMounted, computed } from 'vue'
import { useStore } from 'vuex';

let store = useStore();

let title = computed(() => {
  return store.state.menu.find(i => i.id == store.state.curMenu)?.title
})

</script>

<style lang="scss" scoped>
.header {
  height: 4rem;
  background-color: #1d6be1;
  padding: 0.625rem;
  font-weight: 500;
  font-size: 1.125rem;
}
</style>

components/tabbar.vue

<template>
  <div class="tabbar wid100 flex justify-around pos-absolute">
    <div v-for="i in menu" :key="i.id" class="tabbar-item flex flex-column justify-end items-center" @click="changeMenu(i.id)">
      <img :src="require(`@/assets/img/home/${i.img}${store.state.curMenu == i.id?2:1}.png`)" alt="">
      <span :style="{color:store.state.curMenu == i.id ?'#1677FF':'#7f808b'}">{{ i.name }}</span>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useStore } from 'vuex';
import { useRouter } from 'vue-router'

let store = useStore();
let router = useRouter();

let menu = ref(store.state.menu)
const changeMenu = id => {
  store.commit('SET_CURMENU', id)
  router.push({ name: store.state.menu.find(i => i.id == store.state.curMenu).img })
}
</script>

<style lang="scss" scoped>
.tabbar {
  height: 3.75rem;
  background-color: #fff;
  padding-bottom: 0.875rem;
  bottom: 0;
  left: 0;
  .tabbar-item {
    cursor: pointer;
    img {
      margin-bottom: 0.0625rem;
    }
    span {
      font-weight: 500;
      font-size: 0.625rem;
    }
  }
}
</style>


vant :tabbar

安装vant

npm i vant

在main.js中引入

import Vant from 'vant'
import 'vant/lib/index.css';


const app = createApp(App);
app.use(Vant)
app.mount('#app');

代码示例

其他文件和上面一样

views/home/index.vue

<template>
  <div class="main-wrap wid100 hei100 pos-relative ">
    <Header />
    <div class="main-page wid100">
      <router-view />
    </div>
    <vantTabbar />
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import Header from '@/components/header'
import vantTabbar from '@/components/vant'
</script>

<style lang="scss" scoped>
.main-wrap {
  .main-page {
    height: calc(100% - 7.75rem);
    overflow-y: auto;
  }
}
</style>

components/vant.vue

<template>
  <van-tabbar :active="active" @change="onChange" route>
    <van-tabbar-item v-for="i in menu" :key="i.id" :to="i.img == 'home'?'/scan':'/'+i.img">
      <img :src="require(`@/assets/img/home/${i.img}${store.state.curMenu == i.id?2:1}.png`)" alt="">
      {{  i.name }}
    </van-tabbar-item>
  </van-tabbar>

</template>

<script setup>
import { ref, reactive, onMounted } from 'vue'
import { useStore } from 'vuex';

let store = useStore();

let menu = ref(store.state.menu)
let active = ref(0)
function onChange(index) {
  active.value = index
}
</script>

<style lang="scss" >
.van-tabbar-item__text {
  display: flex;
  flex-direction: column;
  img{
    margin-bottom: 0.25rem;
  }
}
</style>


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值