【Vue 开发实战】实战篇 # 31:如何将菜单和路由结合

简介: 【Vue 开发实战】实战篇 # 31:如何将菜单和路由结合

说明

【Vue 开发实战】学习笔记。



实现效果4dcb655302b44506a16dda3b45613b15.png



BasicLayout.vue

<template>
    <div :class="[`nav-theme-${navTheme}`, `nav-layout-${navLayout}`]">
        <a-layout id="components-layout-demo-side" style="min-height: 100vh">
            <a-layout-sider
                v-if="navLayout === 'left'"
                v-model="collapsed"
                width="256px"
                collapsible
                :theme="navTheme"
                :trigger="null"
            >
                <div class="logo">Ant Design Vue Pro</div>
                <SiderMenu :theme="navTheme"/>
            </a-layout-sider>
            <a-layout>
                <a-layout-header style="background: #fff; padding: 0">
                    <a-icon
                        class="trigger"
                        :type="collapsed ? 'menu-unfold' : 'menu-fold'"
                        @click="collapsed = !collapsed"
                    ></a-icon>
                    <Header />
                </a-layout-header>
                <a-layout-content style="margin: 0 16px">
                    <router-view></router-view>
                </a-layout-content>
                <a-layout-footer style="text-align: center">
                    <Footer />
                </a-layout-footer>
            </a-layout>
        </a-layout>
        <SettingDrawer />
    </div>
</template>
<script>
import Header from "./Header";
import SiderMenu from "./SiderMenu";
import Footer from "./Footer";
import SettingDrawer from "../components/SettingDrawer";
export default {
    data() {
        return {
            collapsed: false,
        };
    },
    components: {
        Header,
        SiderMenu,
        Footer,
        SettingDrawer,
    },
    computed: {
        navTheme() {
            return this.$route.query.navTheme || "dark";
        },
        navLayout() {
            return this.$route.query.navLayout || "left";
        },
    },
};
</script>
<style lang="less" scoped>
.trigger {
    padding: 0 20px;
    line-height: 64px;
    font-size: 20px;
    &:hover {
        background-color: #eeeeee;
    }
}
.logo {
    height: 64px;
    line-height: 64px;
    text-align: center;
    overflow: hidden;
}
.nav-theme-dark {
    .logo {
        color: #fff;
    }
}
</style>


SiderMenu.vue

<template>
    <div style="width: 256px">
        <a-menu
            :selectedKeys="selectedKeys"
            :openKeys.sync="openKeys"
            mode="inline"
            :theme="theme"
        >
            <template v-for="item in menuData">
                <a-menu-item v-if="!item.children"
                    :key="item.path"
                    @click="() => $router.push({path: item.path, query: $router.query})"
                >
                    <a-icon v-if="item.meta.icon" :type="item.meta.icon" />
                    <span>{{ item.meta.title }}</span>
                </a-menu-item>
                <sub-menu v-else :key="item.path" :menu-info="item" />
            </template>
        </a-menu>
    </div>
</template>
<script>
import SubMenu from "./SubMenu.vue";
export default {
    props: {
        theme: {
            type: String,
            default: "dark"
        }
    },
    components: {
        "sub-menu": SubMenu
    },
    data() {
        this.selectedKeysMap = {};
        this.openKeysMap = {};
        const menuData = this.getMenuData(this.$router.options.routes)
        return {
            collapsed: false,
            menuData,
            selectedKeys: this.selectedKeysMap[this.$route.path],
            openKeys: this.collapsed ? [] : this.openKeysMap[this.$route.path]
        };
    },
    watch: {
        "$route.path": function(val) {
            this.selectedKeys = this.selectedKeysMap[val];
            this.openKeys = this.collapsed ? [] : this.openKeysMap[val];
        }
    },
    methods: {
        getMenuData(routes = [], parentKeys = [], selectedKeys) {
            const menuData = [];
            routes.forEach(item => {
                if(item.name && !item.hideInMenu) {
                    this.openKeysMap[item.path] = parentKeys;
                    this.selectedKeysMap[item.path] = [selectedKeys || item.path];
                    const newItem = {...item};
                    delete newItem.children;
                    if(item.children && !item.hideChildrenInMenu) {
                        newItem.children = this.getMenuData(item.children, [...parentKeys, item.path]);
                    }else{
                        this.getMenuData(
                            item.children, 
                            selectedKeys ? parentKeys : [...parentKeys, item.path],
                            selectedKeys || item.path
                        );
                    }
                    menuData.push(newItem);
                }else if(
                    !item.hideInMenu &&
                    !item.hideChildrenInMenu &&
                    item.children
                ) {
                    menuData.push(...this.getMenuData(item.children, [...parentKeys, item.path]));
                }
            });
            return menuData;
        }
    },
};
</script>


SubMenu.vue

<template functional>
    <a-sub-menu :key="props.menuInfo.path">
        <span slot="title">
            <a-icon v-if="props.menuInfo.meta.icon" :type="props.menuInfo.meta.icon" />
            <span>{{ props.menuInfo.meta.title }}</span>
        </span>
        <template v-for="item in props.menuInfo.children">
            <a-menu-item v-if="!item.children"
                :key="item.path"
                @click="() => parent.$router.push({path: item.path, query: parent.$router.query})"
            >
                <a-icon v-if="item.meta.icon" :type="item.meta.icon" />
                <span>{{ item.meta.title }}</span>
            </a-menu-item>
            <sub-menu v-else :key="item.path" :menu-info="item" />
        </template>
    </a-sub-menu>
</template>
<script>
export default {
    props: ["menuInfo"],
};
</script>


路由配置

import Vue from "vue";
import VueRouter from "vue-router";
import NProgress from "nprogress";
import "nprogress/nprogress.css";
import NotFound from "../views/404";
Vue.use(VueRouter);
const routes = [
  {
    path: "/user",
        hideInMenu: true,
    component: () =>
      import(/* webpackChunkName: "layout" */ "../layouts/UserLayout"),
    children: [
      {
        path: "/user",
        redirect: "/user/login"
      },
      {
        path: "/user/login",
        name: "login",
        component: () =>
          import(/* webpackChunkName: "user" */ "../views/User/Login"),
      },
      {
        path: "/user/register",
        name: "register",
        component: () =>
          import(/* webpackChunkName: "user" */ "../views/User/Register"),
      }
    ],
  },
  {
    path: "/",
    component: () =>
      import(/* webpackChunkName: "layout" */ "../layouts/BasicLayout"),
    children: [
      {
        path: "/",
        redirect: "/dashboard"
      },
      {
        path: "/dashboard",
        redirect: "/dashboard/analysis"
      },
      {
        path: "/dashboard",
        name: "dashboard",
                meta: { icon: "dashboard", title: "仪表盘" },
        component: { render: h => h("router-view")},
        children: [
          {
            path: "/dashboard/analysis",
            name: "analysis",
                        meta: { title: "分析页" },
            component: () =>
              import(/* webpackChunkName: "dashboard" */ "../views/Dashboard/Analysis"),
          },
        ]
      },
      {
        path: "/form",
        name: "form",
                meta: { icon: "form", title: "表单" },
        component: { render: h => h("router-view")},
        children: [
          {
            path: "/form",
            redirect: "/form/basic-form"
          },
          {
            path: "/form/basic-form",
            name: "basicform",
                        meta: { title: "基础表单" },
            component: () =>
              import(/* webpackChunkName: "form" */ "../views/Forms/BasicForm"),
          },
          {
            path: "/form/step-form",
            name: "stepform",
                        hideChildrenInMenu: true,
                        meta: { title: "分步表单" },
                        component: () =>
              import(/* webpackChunkName: "form" */ "../views/Forms/StepForm"),
            children: [
              {
                path: "/form/step-form",
                redirect: "/form/step-form/info"
              },
              {
                path: "/form/step-form/info",
                name: "info",
                component: () =>
                  import(/* webpackChunkName: "form" */ "../views/Forms/StepForm/Step1"),
              },
              {
                path: "/form/step-form/confirm",
                name: "confirm",
                component: () =>
                  import(/* webpackChunkName: "form" */ "../views/Forms/StepForm/Step2"),
              },
              {
                path: "/form/step-form/result",
                name: "result",
                component: () =>
                  import(/* webpackChunkName: "form" */ "../views/Forms/StepForm/Step3"),
              },
            ]
          },
        ]
      }
    ],
  },
  {
    path: "*",
    name: "404",
        hideInMenu: true,
    component: NotFound
  }
];
const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes
});
// 路由守卫
router.beforeEach((to, from, next) => {
    if(to.path !== from.path) {
      NProgress.start();
    }
  next();
})
router.afterEach((to, from) => {
  NProgress.done();
})
export default router;



目录
相关文章
|
3月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
324 2
|
2月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
302 137
|
6月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
814 0
|
6月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
5月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
433 1
|
5月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
273 0
|
6月前
|
JavaScript 前端开发 开发者
Vue 自定义进度条组件封装及使用方法详解
这是一篇关于自定义进度条组件的使用指南和开发文档。文章详细介绍了如何在Vue项目中引入、注册并使用该组件,包括基础与高级示例。组件支持分段配置(如颜色、文本)、动画效果及超出进度提示等功能。同时提供了完整的代码实现,支持全局注册,并提出了优化建议,如主题支持、响应式设计等,帮助开发者更灵活地集成和定制进度条组件。资源链接已提供,适合前端开发者参考学习。
483 17
|
6月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
287 1
|
6月前
|
存储 JavaScript 前端开发
如何高效实现 vue 文件批量下载及相关操作技巧
在Vue项目中,实现文件批量下载是常见需求。例如文档管理系统或图片库应用中,用户可能需要一次性下载多个文件。本文介绍了三种技术方案:1) 使用`file-saver`和`jszip`插件在前端打包文件为ZIP并下载;2) 借助后端接口完成文件压缩与传输;3) 使用`StreamSaver`解决大文件下载问题。同时,通过在线教育平台的实例详细说明了前后端的具体实现步骤,帮助开发者根据项目需求选择合适方案。
542 0
|
6月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
273 0