【问题标题】:How to implement sub menu of the current route's children with vue.js and vue router如何使用 vue.js 和 vue 路由器实现当前路由的子菜单的子菜单
【发布时间】:2025-11-30 18:15:02
【问题描述】:

我正在尝试在我的 Vue 应用程序上进行导航,以在顶部填充所有根级路由的主菜单,如果路由有任何子级,则为侧菜单。像这样:

我目前的设计有主导航,顶部有路由链接,下面是路由器视图。我已经能够使侧面菜单正常工作,因此它仅在我选择 Travelers 并正确更新组件/内容时才会显示。但是,当我单击子菜单中的链接之一时,我无法正确路由内容,它不会附加到当前路径。因此,当我在 localhost/Traveler 中单击 View 并单击 View 时,url 更改为 localhost/View/ 而不是 >本地主机/旅行者/视图。当我在子菜单中选择某些内容时,顶部菜单上的选择也会被取消选择。

而且我无法通过 localhost/Traveler/View 之类的方式访问页面,只有 localhost/View

当我开始发表这篇文章时,我开始重新阅读nested routes 上的文档,我想我意识到我应该在每个级别创建一个新路由器,这不是我在下面的代码中所做的事情。

我也不确定如何访问当前路由的子级。我试图像这样显示它们:

  <h2>Route: {{ $route.name }}</h2>
  <ul id="example-1">
    <li v-for="child in $route.children">
      {{ child.name }}
    </li>
  </ul>

但我什么也得不到。他们应该作为参数传递还是什么?还是不是那么容易访问?

我们将不胜感激任何建议或帮助。

包含带有路由器链接的顶部菜单导航和下面的路由器视图。

<template>
  <div id="app" class="container-fluid">
    <div class="row">
      <div style="width:100%">
        <nav-menu params="route: route"></nav-menu>
      </div>

    </div>

    <div class="row">
      <div>
        <router-view></router-view>
      </div>
    </div>
  </div>
</template>
<script>
  import NavMenu from './nav-menu'

  export default {
    components: {
      'nav-menu': NavMenu
    },

    data() {
      return {}
    }
  }
</script>

顶部导航菜单

使用路线填充

<template>
  <nav class="site-header sticky-top py-1">
    <div class="container d-flex flex-column flex-md-row justify-content-between">
      <a class="nav-item" v-for="(route, index) in routes" :key="index">
        <router-link :to="{path: route.path, params: { idk: 1 }}" exact-active-class="active">
          <icon :icon="route.icon" class="mr-2" /><span>{{ route.display }}</span>
        </router-link>
      </a>
    </div>
  </nav>
</template>
<script>
  import { routes } from '../router/routes'

  export default {
    data() {
      return {
        routes,
        collapsed: true
      }
    },
    methods: {
      toggleCollapsed: function (event) {
        this.collapsed = !this.collapsed
      }
    }
  }
</script>

旅行者页面/视图

目前的旅行者页面有一个侧边栏菜单和另一个内容的路由视图:

<template>
  <div id="app" class="container-fluid">
    <div class="wrapper">
      <traveler-menu params="route: route"></traveler-menu>

      <div id="content">
        <router-view name="travlerview"></router-view>
        </div>
      </div>
    </div>
</template>
<script>
  import TravelerMenu from './traveler-menu'
  export default {
    components: {
      'traveler-menu': TravelerMenu
    },
    data() {
      return {}
    }
  }
</script>

侧边栏/旅行者菜单

<template>
    <nav id="sidebar">
      <div class="sidebar-header">
        <h3>Route's Children:</h3>
      </div>

      <ul class="list-unstyled components">
        <li>
          <a class="nav-item" v-for="(route, index) in travelerroutes" :key="index">
            <router-link :to="{path: route.path, params: { idk: 1 }}" exact-active-class="active">
              <icon :icon="route.icon" class="mr-2" /><span>{{ route.display }}</span>
            </router-link>
          </a>
        </li>
      </ul>

    </nav>

</template>

<script>
    import { travelerroutes } from '../../router/travelerroutes'
    export default {
    data() {
      console.log(travelerroutes);
        return {
          travelerroutes,
          collapsed: true
        }
      },
      methods: {
        toggleCollapsed: function (event) {
          this.collapsed = !this.collapsed
        }
      }
    }
</script>

路线

import CounterExample from 'components/counter-example'
import FetchData from 'components/fetch-data'
import HomePage from 'components/home-page'
import TestPage from 'components/test-page'
import Travelers from 'components/Traveler/traveler-root'
import { travelerroutes } from './travelerroutes'

export const routes = [
  { name: 'home', path: '/', component: HomePage, display: 'Home', icon: 'home' },
  { name: 'counter', path: '/counter', component: CounterExample, display: 'Counter', icon: 'graduation-cap' },
  { name: 'fetch-data', path: '/fetch-data', component: FetchData, display: 'Fetch data', icon: 'list' },
  { name: 'test-page', path: '/test-page', component: TestPage, display: 'Test Page', icon: 'list' },
  {
    name: 'traveler-root', path: '/traveler', component: Travelers, display: 'Travelers', icon: 'list', children: travelerroutes
  }
]

旅行者路线 (travelerroutes.js)

import TestPage from 'components/test-page'
import ViewTravelers from 'components/Traveler/TravelerPages/view-travelers'

export const travelerroutes = [{
  name: 'View',
  path: '/View',
  display: 'View', icon: 'list',
  components: {
    travlerview: TestPage
  }
},
  {
    name: 'Create',
    path: '/Create',
    display: 'Create', icon: 'list',
    components: {
      travlerview: ViewTravelers
    }
  },
  {
    name: 'Edit',
    path: '/Edit',
    display: 'Edit', icon: 'list',
    components: {
      travlerview: ViewTravelers
    }
  }];

路由器/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import { routes } from './routes'

Vue.use(VueRouter)

let router = new VueRouter({
  mode: 'history',
  routes
})

export default router

app.js

import Vue from 'vue'
import axios from 'axios'
import router from './router/index'
import store from './store'
import { sync } from 'vuex-router-sync'
import App from 'components/app-root'
import { FontAwesomeIcon } from './icons'

// Registration of global components
Vue.component('icon', FontAwesomeIcon)

Vue.prototype.$http = axios

sync(store, router)

const app = new Vue({
  store,
  router,
  ...App
})

export {
  app,
  router,
  store
}

如果您需要更多详细信息、上下文或代码,请告诉我。

【问题讨论】:

    标签: javascript user-interface vue.js routing


    【解决方案1】:

    我对 DigitalDrifter 的答案进行了一些修改,在那里我使用模板语法找到了孩子。我很确定我的答案会在另一个级别的孩子之后停止工作,所以我会说 DigitalDrifter 仍然有更好的答案。

    此外,我不确定如何保留子菜单路由 (localhost/Traveller),因此当我单击视图之类的东西时,它不会搜索 (locathost/Traveller/View) 的子菜单。现在我正在以一种笨拙的方式做这件事:

    v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path||$route.path.includes(x.path))"
    

    没有样式的完整 SFC 在这里:

    <template>
      <nav id="sidebar">
        <div class="sidebar-header">
          <h3>Bootstrap Sidebar</h3>
        </div>
    
        <ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.path||$route.path.includes(x.path))">
          <li v-for="child in route.children">
            <a class="nav-item" :key="index">
              <router-link :to="{path: route.path+'/'+child.path}" exact-active-class="active">
                <icon :icon="route.icon" class="mr-2" /><span>{{ child.path }}</span>
              </router-link>
            </a>
          </li>
        </ul>
    
      </nav>
    
    </template>
    
    <script>
      export default {
        data() {
          return {
          }
        },
        methods: {
        }
      }
    </script>
    

    编辑:我知道 vue 有我想要的与路由器视图相关联的数据,如下所示,

    我只是不确定如何访问这些属性。

    2019 年 1 月 29 日更新:

    我发现你需要做的就是使用 $Route.matched[0].path you can learn more here.

    现在我已经能够将菜单简化为这样的 SFC:

    <template>
        <nav id="sidebar">
          <div class="sidebar-header">
            <h3>Bootstrap Sidebar</h3>
          </div>
    
          <ul class="list-unstyled components" v-for="(route, index) in $router.options.routes.filter(x=>x.path==$route.matched[0].path)">
            <li v-for="child in route.children">
              <a class="nav-item"  :key="index">
                <router-link :to="route.path+'/'+child.path" exact-active-class="active">
                  <icon :icon="route.icon" class="mr-2" /><span>{{ child.name }}</span>
                </router-link>
              </a>
            </li>
          </ul>
    
        </nav>
    
    </template>
    
    <script>
        export default {
        data() {
            return {
            }
          }
        }
    </script>
    

    不包括 CSS。

    编辑:我现在将此作为我的首选答案,因为它允许我通过引用组件在任何地方使用侧边菜单。我不需要向其中传递任何东西,它应该在任何地方都可以工作。不过,我确实喜欢 digitaldrifter 的回答,因为他对此很感兴趣。

    【讨论】:

      【解决方案2】:

      您无需创建新的路由器实例,而是观察$route 属性并在其更改时创建侧边栏导航菜单。您需要从$router.options.routes 中提取子路由。这是一个例子:

      const router = new VueRouter({
        routes: [{
            name: 'home',
            path: '/',
            component: {
              template: '<div>Home</div>'
            }
          },
          {
            name: 'foo',
            path: '/foo',
            component: {
              template: '<div>Foo<router-view></router-view></div>'
            },
            children: [{
              name: 'foo.baz',
              path: '/baz',
              component: {
                template: '<div>Baz</div>'
              }
            }, {
              name: 'foo.tar',
              path: '/tar',
              component: {
                template: '<div>Tar</div>'
              }
            }]
          },
          {
            name: 'bar',
            path: '/bar',
            component: {
              template: '<div>Bar<router-view></router-view></div>'
            },
            children: [{
              name: 'bar.zim',
              path: '/zim',
              component: {
                template: '<div>Zim</div>'
              }
            }, {
              name: 'bar.zug',
              path: '/zug',
              component: {
                template: '<div>Zug</div>'
              }
            }]
          }
        ]
      })
      
      new Vue({
        el: '#app',
        router,
        data() {
          return {
            children: []
          }
        },
        watch: {
          $route: function(current) {
            const route = this.$router.options.routes.find(route => route.path === current.path)
      
            if (route && Array.isArray(route.children)) {
              this.children = route.children
            } else if (route) {
              this.children = []
            }
          }
        }
      })
      * {
        margin: 0;
        padding: 0;
      }
      
      html,
      body,
      #app {
        width: 100%;
        height: 100%;
      }
      
      #top {
        border: 1px solid black;
      }
      
      #top ul {
        display: flex;
        flex-direction: row;
        justify-content: flex-start;
        list-style-type: none;
      }
      
      li {
        padding: 1rem;
        text-align: center;
        text-transform: uppercase;
      }
      
      li a {
        text-decoration: none;
        font-weight: bold;
        color: black;
      }
      
      #sidebar {
        height: 50%;
        width: 100px;
        border: 1px solid black;
      }
      
      #content {
        width: 50%;
      }
      
      #content,
      #content>div {
        display: flex;
        flex-direction: column;
        justify-content: space-around;
        align-items: center;
      }
      <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
      <script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
      <div id="app">
        <div id="top">
          <ul>
            <li v-for="item in $router.options.routes" :key="item.path" :style="{ 'background-color': $route.name.includes(item.name) ? 'rgba(197,225,165,1)' : 'white' }">
              <router-link :to="item.path">{{ item.name }}</router-link>
            </li>
          </ul>
        </div>
        <div style="display: flex; flex-direction: row; height: 100%; width: 100%;">
          <div id="sidebar">
            <ul>
              <li v-for="item in children" :key="item.path" :style="{ 'background-color': $route.name === item.name ? 'rgba(197,225,165,1)' : 'white' }">
                <router-link :to="item.path">{{ item.name.split('.').pop() }}</router-link>
              </li>
            </ul>
          </div>
          <div id="content">
            <router-view></router-view>
          </div>
        </div>
      </div>

      【讨论】:

      • 谢谢!如果我这个周末有时间我会试试这个。或者星期一。
      • 我使用模板语法添加了另一个答案。不过我不确定它和你的一样好。