【问题标题】:Vuetify Tabs with Router and dynamic nested routes带有路由器和动态嵌套路由的 Vuetify 选项卡
【发布时间】:2021-08-08 09:14:10
【问题描述】:

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

TreeAndTab.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 布局。

TreeAndTab.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>

Requirement.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 上发布代码。

【问题讨论】:

    标签: vuetify.js vue-router vuetify-tabs


    【解决方案1】:

    最后我能够修复它。看起来选项卡上的路由设置不正确。这是我所做的更改:

    1. 将 tabProps 移至 compute() 以即时更新路线。
    2. 从子组件触发事件以更新路由,该事件被更新路由的父组件捕获。
    3. 我没有使用 this.$route 动态更新选项卡路由,因为如果在树上选择了子节点但用户切换到包含父母。 (我知道它令人困惑)。因此,它就像一个文件资源管理器,其中第一个选项卡显示文件夹的详细信息,第二个选项卡显示该文件夹中所选子项的详细信息。

    现在维护选项卡状态。

    这是相关的代码(不是最有效的,但它有效)。希望它可以帮助面临类似问题的人。

    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
    
    

    pages/Requirement.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>
    
    

    TreeAndTab.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> 
    
    

    【讨论】:

      猜你喜欢
      • 2020-09-28
      • 2019-07-16
      • 2019-08-28
      • 1970-01-01
      • 2022-06-11
      • 2020-03-04
      • 2020-12-13
      • 2017-07-23
      • 2020-01-20
      相关资源
      最近更新 更多