带有路由器和动态嵌套路由的Vuetify选项卡



我对Vue很陌生。我的应用程序有一个非常标准的布局,包括顶部导航、侧面导航、页脚和内容区。内容区域分为两部分,左边是一棵树,右边是一个Tabbed界面。我使用的是带有嵌套动态路由的vue路由器。

树和选项卡.vue

import Vue from 'vue'
import VueRouter from 'vue-router'
/* import DefaultLayout from '../layout/Default.vue' */
/* import TreeAndTabLayout from '../layout/TreeAndTab.vue' */
Vue.use(VueRouter)
const routes = [
{
path: '/home',
name: 'home',
meta: { layout: 'default' },
component: () => import('../pages/Home.vue')
},
{
path: '/about',
name: 'about',
meta: { layout: 'default' },
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../pages/About.vue')
},
{
path: '/dashboard',
name: 'dashboard',
meta: { layout: 'default' },
component: () => import('../pages/dashboard/dashboard.vue')
},
{
// Top level requirement goes to epic
path: '/r/:epic_id?',
//name: 'requirement',
meta: { layout: 'default' },
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../pages/requirement/Requirement.vue'),
children: [
{
path: '',
name: 'epic',
component: () => import('../pages/About.vue'),
props: true,
children: [
{
path: '/r/:epic_id/s/:story_id',
name: 'story',
component: () => import('../pages/Home.vue'),
props: true
}
]
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

我有两个布局-Default.vue和TreeAndTab.vue。当应用程序加载时,会使用Default.vue,页面会进一步加载TreeAndTab.vue布局。

树和选项卡.vue

<template>
<tree-and-tab-layout
:treeProps="treeProps"
:tabProps="tabProps"
:treeOptions="treeOptions"
>
</tree-and-tab-layout>
</template>
<script>
import TreeAndTabLayout from "../../layout/TreeAndTab.vue";
import RequirementService from "./RequirementService.js";
export default {
components: {
TreeAndTabLayout,
},
data: () => ({
treeProps: {},
tabProps: {
tabs: [
{
id: 1,
title: "epic",
route: { name: "epic" },
},
{
id: 2,
title: "story",
route: { name: "story" },
},
{
id: 3,
title: "mapping",
/* route: `/requirement/mapping/${this.$route.params.map_id}` */
},
],
},
treeOptions: {
propertyNames: {
text: "title",
},
},
}),
methods: {
getTabProps() {
return {};
},
},
created() {
this.treeProps = RequirementService.getAllRequirementsForApp();
//this.tabProps = this.getTabProps();
this.treeProps.activeNode = [
this.$route.params.epic_id || this.$route.params.story_id,
];
},
};
</script>

要求.vue

<template>
<tree-and-tab-layout
:treeProps="treeProps"
:tabProps="tabProps"
:treeOptions="treeOptions"
>
</tree-and-tab-layout>
</template>
<script>
import TreeAndTabLayout from "../../layout/TreeAndTab.vue";
import RequirementService from "./RequirementService.js";
export default {
components: {
TreeAndTabLayout,
},
data: () => ({
treeProps: {},
tabProps: {
tabs: [
{
id: 1,
title: "epic",
route: { name: "epic" },
},
{
id: 2,
title: "story",
route: { name: "story" },
},
{
id: 3,
title: "mapping",
/* route: `/requirement/mapping/${this.$route.params.map_id}` */
},
],
},
treeOptions: {
propertyNames: {
text: "title",
},
},
}),
methods: {
getTabProps() {
return {};
},
},
created() {
this.treeProps = RequirementService.getAllRequirementsForApp();
//this.tabProps = this.getTabProps();
this.treeProps.activeNode = [
this.$route.params.epic_id || this.$route.params.story_id,
];
},
};
</script>

我想要的流程如下:

  1. 加载页面时,会选择树中的第一个项目
  2. 当用户单击树中的父节点时,应选择右侧的第一个选项卡并加载适当的内容。它是路由器中的父路由
  3. 当用户点击子加载时,第二个选项卡应该根据路由器加载

我看到树运行正常,地址栏上显示了正确的路由。第一个选项卡的组件也正确加载。但是,当我单击叶节点时,即使正确创建了路由,选项卡也不会更新。选项卡既没有更改,也没有加载相应的组件。我尝试了各种选项,包括在选项卡中使用路由名称:to等,但似乎都不起作用。

非常感谢您的帮助。如果需要,我可以在GitHub上发布代码。

我终于修复了它。看起来选项卡上的路由设置不正确。以下是我更改的内容:

  1. 将tabProps移动到compute((以动态更新路由
  2. 从子组件激发了一个事件来更新路由,该事件被更新路由的父组件捕获
  3. 我没有用这个$路由以动态更新选项卡路由,因为如果在树上选择了子节点,但用户切换到包含父节点数据的第一个选项卡,我希望保留第二个选项卡的状态。(我知道这让人困惑(。所以它就像一个文件资源管理器,第一个选项卡显示文件夹的详细信息,第二个选项卡显示该文件夹中所选子项的详细信息

选项卡状态现在得到维护。

以下是相关的代码(不是最有效的,但它有效(。希望它能帮助那些面临类似问题的人。

route.js

/* eslint-disable no-unused-vars */
import Vue from 'vue'
import VueRouter from 'vue-router'
/* import DefaultLayout from '../layout/Default.vue' */
/* import TreeAndTabLayout from '../layout/TreeAndTab.vue' */
Vue.use(VueRouter)
const routes = [
{
path: '/home',
name: 'home',
meta: { layout: 'default' },
component: () => import('../pages/Home.vue')
},
{
path: '/about',
name: 'about',
meta: { layout: 'default' },
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../pages/About.vue')
},
{
path: '/dashboard',
name: 'dashboard',
meta: { layout: 'default' },
component: () => import('../pages/dashboard/dashboard.vue')
},
{
// Top level requirement goes to epic
path: '/plan',
//name: 'requirement',
meta: { layout: 'default' },
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../pages/requirement/Requirement.vue'),
children: [
{
path: 'e/:epic_id',
name: 'epic',
component: () => import('../pages/About.vue'),
props: true
},
{
path: 'e/:epic_id/s/:story_id',
name: 'story',
component: () => import('../pages/Home.vue'),
props: true
}
]
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router

页数/要求.vue

<template>
<tree-and-tab-layout
:treeProps="treeProps"
:tabProps="tabProps"
:treeOptions="treeOptions"
v-on:activateTreeNode="handleTreeNodeActivate"
>
</tree-and-tab-layout>
</template>
<script>
//
import TreeAndTabLayout from "../../layout/TreeAndTab.vue";
import RequirementService from "./RequirementService.js";
export default {
name: "RequirementPage",
components: {
TreeAndTabLayout,
},
data: () => ({
base_path: "/plan",
epic_id: "",
story_id: "",
epic_base_path: "/e/",
story_base_path: "/s/",
treeProps: {},
treeOptions: {
propertyNames: {
text: "title",
},
},
}),
computed: {
tabProps() {
return {
tabs: [
{
id: 1,
title: "Epic",
route:
this.base_path + this.epic_base_path + this.epic_id,
},
{
id: 2,
title: "Story",
route:
this.base_path +
this.epic_base_path +
this.epic_id +
this.story_base_path +
this.story_id,
},
],
};
},
},
methods: {
handleTreeNodeActivate(child_id, parent_id) {
this.story_id = child_id;
this.epic_id = parent_id;
},
},
created() {
this.treeProps = RequirementService.getAllRequirementsForApp();
// Does not work somehow. Handling it from the template
//this.$on("activateTreeNode", this.handleTreeNodeActivate);
},
};
</script>

树和选项卡.vue

<template>
<splitpanes>
<pane size="30">
<tree
:data="treeProps.items"
:options="treeOptions"
ref="tree"
@node:selected="onActive"
@node:expanded="onExpand"
/>
</pane>
<pane size="70">
<v-tabs v-model="activeTab" light>
<!-- <v-tabs-slider></v-tabs-slider> -->
<v-tab
v-for="tab in tabProps.tabs"
:key="tab.id"
:to="tab.route"
exact
>{{ tab.title }}</v-tab
>
</v-tabs>
<v-card flat tile>
<keep-alive>
<router-view />
</keep-alive>
</v-card>
</pane>
</splitpanes>
</template>
<script>
import { Splitpanes, Pane } from "splitpanes";
import LiquorTree from "liquor-tree";
import "splitpanes/dist/splitpanes.css";
export default {
name: "TreeAndTab",
components: {
Splitpanes,
Pane,
tree: LiquorTree,
},
props: {
treeProps: {
type: Object,
required: true,
},
tabProps: {
type: Object,
required: true,
},
treeOptions: {
type: Object,
},
},
data: () => ({
newEpic: {
id: "e_new",
title: "New Epic",
isFolder: true,
},
newStory: {
id: "s_new",
title: "New Story",
},
newCounter: 0,
activeTab: null,
}),
mounted() {

console.log("mounted Tree and Tab");
},
computed: {},
methods: {
onActive(node) {

if (node.parent != null) {
this.$emit("activateTreeNode", node.id, node.parent.id);
this.$router.push({
name: "story",
params: { epic_id: node.parent.id, story_id: node.id },
});
} else {

this.$emit("activateTreeNode", null, node.id);
this.$router.push({
name: "epic",
params: { epic_id: node.id },
});

}
},
onExpand(node) {
console.log("expand=", node);
},

getCurrentActiveNode() {
return this.$refs.tree.selected()[0];
},
},
};
</script>
<style scoped>
</style>