目标:
使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>