如何在 vue 路由器中使用 Vuetify 选项卡

我有下面的 Jsfiddle,它有两个 Vuetify 选项卡。

我发现这个 Medium.com 发布关于如何使用与 vue-router的 Vuetify,它有以下代码:

<div id="app">
<v-tabs grow light>
<v-tabs-bar>
<v-tabs-item href="/" router>
<v-icon>motorcycle</v-icon>
</v-tabs-item>
<v-tabs-item href="/dog" router>
<v-icon>pets</v-icon>
</v-tabs-item>
</v-tabs-bar>
</v-tabs>


<router-view />
</div>

然而,代码现在已经过时了,因为 Vuetify 1.0.13 Tabs 文档没有在 API 中指定 router道具,所以文章中的嵌入式示例不起作用。

我还发现这个 StackOverflow 回答有以下代码:

<v-tabs-item :to="{path:'/path/to/somewhere'}">

但是,使用 to道具不起作用,而且它也没有列在 Vuetify API 中。相比之下,v-button Vuetify 组件确实列出了一个 to道具并利用了 vue-router,所以我希望一个受 vue-router支持的组件能够支持 to道具。

在旧的 旧的 Vuetify 0.17文档挖掘周围,to道具是为 v-tabs-item指定的。似乎在1.0.13中已经删除了支持。

如何在 Vuetify 选项卡中使用 vue-router

76530 次浏览

Update

Holy wow! I asked the Vuetify community to add documentation to their api, and it looks like they just added the to prop as well as other vue-router props to the Vuetify tabs docs. Seriously, the community there is awesome.

Original Answer

The folks in the Vuetify community Discord were able to help me out. My updated jsfiddle now has the working code.

Essentially, v-tab is a wrapper for router-link, where I assume it uses slots to pass props to router-link, so putting the to prop on v-tab works fine.

The following code is an example of the working code:

html

<v-app dark>
<v-tabs fixed-tabs>
<v-tab to="/foo">Foo</v-tab>
<v-tab to="/bar">Bar</v-tab>
</v-tabs>
<router-view></router-view>
</v-app>

js

const Foo = {
template: '<div>Foo component!</div>'
};


const Bar = {
template: '<div>Bar component!</div>'
};


const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar },
];


const router = new VueRouter({ routes });


new Vue({
el: '#app',
router,
});

Result

Foo tab example Bar tab example

I'm just adding some animation-related tips here & clarifying the use of v-tab-items vs v-tab-item.

If you have noticed, using the working markup as follows prevents the v-tabs tab switch animation to work:

<v-tabs v-model="activeTab">
<v-tab key="tab-1" to="/tab-url-1">tab 1</v-tab>
<v-tab key="tab-2" to="/tab-url-2">tab 2</v-tab>
</v-tabs>
<router-view />

If you want to keep the tab switch animation, this is a way to do it.

<v-tabs v-model="activeTab">
<v-tab key="tab-1" to="/tab-url-1" ripple>
tab 1
</v-tab>
<v-tab key="tab-2" to="/tab-url-2" ripple>
tab 2
</v-tab>


<v-tab-item id="/tab-url-1">
<router-view v-if="activeTab === '/tab-url-1'" />
</v-tab-item>
<v-tab-item id="/tab-url-2">
<router-view v-if="activeTab === '/tab-url-2'" />
</v-tab-item>
</v-tabs>

Note that you can also use a v-for loop on your v-tab and v-tab-item tags as long as your to value is found among the id attribute of your v-tab-items.

If you need to place your tab contents in a different place than your tabs buttons, this is what v-tab-items is for. You can place the v-tab-items in a v-tab-items tag outside of the v-tabs component. Make sure you give it a v-model="activeTab" attribute.

The template part:

<div>
<v-tabs
class="tabs"
centered
grow
height="60px"
v-model="activeTab"
>
<v-tab v-for="tab in tabs" :key="tab.id" :to="tab.route" exact>
\{\{ tab.name }}
</v-tab>
</v-tabs>
<router-view></router-view>
</div>

And the js part:

  data() {
return {
activeTab: `/user/${this.id}`,
tabs: [
{ id: 1, name: "Task", route: `/user/${this.id}` },
{ id: 2, name: "Project", route: `/user/${this.id}/project` }
]
};
}

Routes:

{
path: "/user/:id",
component: User1,
props: true,
children: [
{
path: "", //selected tab by default
component: TaskTab
},
{
path: "project",
component: ProjectTab
}
]
}

See codesanbox example

The answers of @roli-roli and @antoni are currect but lacking of a tiny detail. In fact, using their methods (almost equivalent) there is an issue in mobile views; in fact, in such conditions tabs become swipeable. The problem is that swiping won't update the route as expected, passing from Tab A with component A to Tab B with component B; instead, an animation will be fired and the activeTab will change, but the router won't update.

TL;DR Swiping the v-tab-item does not update the router, and you get the same component under each tab.

Solutions could be either disable the swipeability of v-tab-item or listening to the change event of the tab component to update the router accordingly. I would advise this second solution (since swiping results pretty handy in some conditions), but I thik that this would trigger twice the router update when clicking on the tab's label.

Here's a working example based on @roli-roli answer

Template

<template>
<v-tabs v-model="activeTab">
<v-tab v-for="tab in tabs" :key="tab.id" :to="tab.route">\{\{ tab.name }}</v-tab>


<v-tabs-items v-model="activeTab" @change="updateRouter($event)">
<v-tab-item v-for="tab in tabs" :key="tab.id" :to="tab.route">
<router-view />
</v-tab-item>
</v-tabs-items>
</v-tabs>
</template>

Script

export default {
data: () => ({
activeTab: '',
tabs: [
{id: '1', name: 'Tab A', route: 'component-a'},
{id: '2', name: 'Tab B', route: 'component-b'}
]
}),
methods: {
updateRouter(val){
this.$router.push(val)
}
}
}

Router

Set up as in previous answers.

The following code works for me

  <v-tabs fixed-tabs>
<v-tab>Locations</v-tab>
<v-tab>Employees</v-tab>
<v-tab-item>Tab 1 content
<locations-data/>
</v-tab-item>
<v-tab-item>Tab 2 content
<employees-data/>
</v-tab-item>
</v-tabs>
<script>
import LocationsData from './Settings/Locations';
import EmployeesData from './Settings/Employees';
export default {
components: {
LocationsData,
EmployeesData
},
}
</script>
//urls in some componet
<v-btn @click="goToUrl({name: 'RouteName1'})">
.....
<v-list-item-title
@click="goToUrl({name: 'RouteName2'})"
>
Some tab link text
</v-list-item-title>
....




//tabs menu and content in other component
<v-tabs>
<v-tab key="key_name1" :to="{ name: 'RouteName1'}">Some link 1</v-tab>
<v-tab key="key_name2" :to="{ name: 'RouteName2'}">Some link 2</v-tab>


<v-tab-item key="key_name1" value="/url/for/route1">
....
<v-tab-item key="key_name2" value="/url/for/route2">
....

With animation and swipe enabled

And keep your existing query params: usefull e.g. if you have a :locale in your url:

Template

<v-tabs v-model="activeTab">
<v-tab v-for="tab in tabs" :key="tab.id" :to="tab.to">
\{\{ tab.name }}
</v-tab>
</v-tabs>


<v-tabs-items v-model="activeTab" @change="updateRouter($event)">
<v-tab-item v-for="tab in tabs" :key="tab.id" :value="tab.to">
<router-view />
</v-tab-item>
</v-tabs-items>

Script

export default {
data: () => ({
activeTab: '',
}),
computed: {
tabs() {
return [
{ id: 1, name: 'Tab one', to: this.getTabPath('routeName1') },
{ id: 2, name: 'Tab two', to: this.getTabPath('routeName2') },
{ id: 3, name: 'Tab three', to: this.getTabPath('routeName3') },
{ id: 4, name: 'Tab four', to: this.getTabPath('routeName4') },
]
},
},
methods: {
getTabPath(name) {
// Get the path without losing params. Usefull e.g. if you have a :locale in your url:
return this.$router.resolve({ name, params: this.$route.params }).href
},
updateRouter(path) {
// Gets called upon swipe.
this.$router.push(path)
},
},
}

Using $router.resolve to get the path from a route object as explained here.

I just want to add a fix for double mounting of the new component when a tab is switched to. You can see the question and answer here.

The TLDR is that if you use a v-for within the , you are going to run into a problem where the component you are switching to will be created, destroyed and then created again on each switch. If, like me, you do an ajax call on the create side of that, then you will be hitting your back end twice for every tab switch. You can wrap the with a to prevent that.

in the answer I go into depth about why this happens.