• elmenu:浩瀚无垠


    el-menu:浩瀚无垠

    0. 缘起

    遇到了一个需要展现多级列表的需求,之前写的版本就是我嗯写死的二级菜单,现在想来个Updated版本,所以需要我这里调整。抄了组长整的无极列表组件,不过我还需要对项目兼容进行一些调整。

    1. v-if与v-for的循环产生el-menu-item与el-submenu

    这个版本的可以实现想要的无极效果,不过时不时element.js爆个堆栈溢出的错误,看的那叫个不爽啊。组长表示可以用高端的函数式组件,直接生成对应render模板就好。事实上那个方法确实比这个版本的好很多~~

    Element NavMenu 无限级菜单 - tianyawhl的个人空间 - OSCHINA - 中文开源技术交流社区

    2. 函数式组件

    函数式组件,我们可以理解为没有内部状态,没有生命周期钩子函数,没有 this(不需要实例化的组件)

    Vue JSX、自定义 v-model - 你的美好,我都记得 (ainyi.com)

    2.1 何时用到函数式组件?

    在作为包装组件时它们也同样非常有用。比如,当你需要做这些时:

    • 程序化地在多个组件中选择一个来代为渲染;
    • 在将 childrenpropsdata 传递给子组件之前操作它们。

    2.2 数据传输

    组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:

    • props:提供所有 prop 的对象
    • children:VNode 子节点的数组
    • slots:一个函数,返回了包含所有插槽的对象
    • scopedSlots:(2.6.0+) 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。
    • data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
    • parent:对父组件的引用
    • listeners:(2.3.0+) 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。
    • injections:(2.3.0+) 如果使用了 inject 选项,则该对象包含了应当被注入的 property。

    渲染函数 & JSX — Vue.js (vuejs.org)

    2.3 封装组件

    <script>
    import Vue from "vue";
    import "element-ui/lib/theme-chalk/index.css";
    
    import { Menu, MenuItem, Submenu, Icon } from "element-ui";
    
    Vue.use(Menu).use(MenuItem).use(Submenu).use(Icon);
    
    /// 渲染导航栏
    const renderNavBar = (h, context) => {
      // routers格式自定义,menu中有description标题文字,hidden是否隐藏
      // ATTENTION: 注意下方routerChildren是内部子路由,根据项目修改
      // option是传入的样式,这里的不全,只是定义透明背景与文字颜色
      // 还需要加入全局menu样式,来控制hover与focus时候的背景色(直接用了!important)
      const { routers, option, maxLength, className, id } = context.props;
      let routersCopy = JSON.parse(JSON.stringify(routers));
      if (routers.length > maxLength) {
        let emptyRoute = {
          path: "",
          name: "emptyRoute",
          children: routersCopy.slice(maxLength, routers.length),
        };
        // 隐藏路由黏在正常显示的后面
        routersCopy = [...routersCopy.slice(0, maxLength), emptyRoute];
      }
      return h(
        "el-menu",
        {
          class: {
            "navbar-menu": true,
    
            [`${className}`]: true,
          },
          attrs: {
            id: id,
          },
          props: {
            mode: option.mode || "horizontal",
            router: true,
            backgroundColor: option.backgroundColor || "#545c64",
            activeTextColor: option.activeTextColor || "#ffd04b",
            textColor: option.textColor || "#fff",
            uniqueOpened: option.uniqueOpened || false,
          },
        },
        (routersCopy || [])
          .map((item) => {
            // 大菜单完了,到小菜单的生成
            return renderChildItem(h, item, context);
          })
          .filter((it) => it)
      );
    };
    
    /// 渲染子菜单
    const renderChildItem = (h, item, context, popperClass = "menu-popup") => {
    
      // popper-class是element中对el-menu下拉出现的选项框的自定义类名
      // const { popperClass } = context.props;
      let haveRouterChildren =
        Array.isArray(item.routerChildren) && item.routerChildren.length;
        // 将有路由节点的且非隐藏情况的显示
      if (haveRouterChildren && !item.meta?.hidden) {
        return !(item.meta && item.meta.hidden)
          ? h(
              "el-submenu",
              {
                props: {
                  // el-menu的路由模式,根据index跳转对应地址,这里item.path例如'/home'
                  index: item.path || "",
                  popperClass: popperClass,
                },
                class: {
                  "empty-menu": item.name === "emptyRoute",
    
                },
              },
              [
                h(
                  "template",
                  {
                    slot: "title",
                  },
                  [
                    item.meta && item.meta.icon
                    // 存在Icon时显示对应图标
                      ? h("em", { class: { [item.meta.icon]: true } }, "")
                      : null,
                    h(
                      "span",
                      { slot: "title" },
                      // 显示标题span
                      item.meta && item.meta.description
                        ? item.meta.description
                        : ""
                    ),
                  ].filter((it) => it)
                ),
                // 子路由进行下一次循环
                ...item.routerChildren.map((child) => {
                  return renderChildItem(h, child, context);
                }),
              ]
            )
          : null;
      } else {
        // !(item.meta && item.meta.hidden)
        //   ?
        return h(
          "el-menu-item",
          {
            props: {
              index: `${item.path || ""}`,
            },
          },
          [
            item.meta && item.meta.icon
              ? h("em", { class: { [item.meta.icon]: true } }, "")
              : null,
            h(
              "span",
              { slot: "title" },
              item.meta && item.meta.description ? item.meta.description : ""
            ),
          ].filter((it) => it)
        );
        // : null;
      }
    };
    
    export default {
      name: "MenuBar",
    
      functional: true,
      props: {
        id: {
          type: String,
          default: () => "",
        },
        className: {
          type: String,
          default: () => "",
        },
        // 子菜单弹窗类
        popperClass: {
          type: String,
          default: () => "",
        },
        // 路由列表
        routers: {
          type: Array,
          default: () => [],
        },
        // el-menu option
        option: {
          type: Object,
          default: () => {
            return {};
          },
        },
        // 允许展示的导航数最大值
        maxLength: {
          type: Number,
          default: () => 5,
        },
      },
      render(createElement, context) {
        return renderNavBar(createElement, context);
      },
    };
    </script>
    
    <style lang="scss">
    .navbar-menu {
    
      min-height: 76px;
    
      .empty-menu {
    
        .el-icon-arrow-down {
          font-size: 20px;
          transform: rotate(-90deg);
        }
    
        .el-icon-arrow-down:before {
          content: "\e7a9";
        }
    
        .el-submenu__title {
          padding: 0 10px 0 0;
          text-align: center;
        }
    
        &.is-opened {
          .el-submenu__title .el-icon-arrow-down {
            transform: rotate(-270deg);
          }
        }
      }
    }
    </style>
    
    

    2.4 使用组件

    注意这里需要将扁平结构转化为树形

    JS树结构操作:查找、遍历、筛选、树结构和列表结构相互转换 - 沐码小站 (wintc.top)

       routers: {
          get() {
            let routers = this.$store.state.routers.map((key) => ({
              name: key.router.toUpperCase(),
              path: "/" + key.router,
              id: key.uiWebId,
              parentId: key.parentId,
              meta: {
                hidden: false,
                link: true,
                description: key.menuName,
              },
            }));
            let transferRouters = convertMenus(routers, "parentId");
            console.log('transferRouters: ', transferRouters);
            return transferRouters;
    
            function convertMenus(list, pid = "pid", id = "id") {
              let info = list.reduce(
                (map, node) => (
                  (map[node[`${id}`]] = node), (node.routerChildren = []), map
                ),
                {}
              );
              return list.filter((node) => {
                info[node[`${pid}`]] &&
                  info[node[`${pid}`]].routerChildren.push(node);
                return !node[`${pid}`];
              });
            }
          },
        },
    

    2.5 补充的样式代码

    CSS苦手,写这个真的头皮发麻,所以用了! important,丑陋至极!

    .frame_window_out {
    
      .el-menu-demo {
        margin-left: 34%;
      }
    
      .el-menu.el-menu--horizontal {
        border: 0;
      }
    
      .el-submenu:focus .el-submenu__title,
      .el-submenu:hover .el-submenu__title {
        color: #ffffff;
        background-color: transparent;
      }
    
      .el-menu {
        background-color: transparent;
    
        .el-submenu:focus .el-submenu__title,
        .el-submenu:hover .el-submenu__title {
          background-color: rgba(23, 78, 112, 0.7) !important;
        }
        .el-menu-item,
        .el-submenu .el-submenu__title {
          color: #ffffff;
          height: 40px;
          margin: 8px;
          border-radius: 4px;
          line-height: 40px;
    
          i {
            color: #ffffff;
          }
        }
    
        .el-menu-item:hover,
        .el-menu-item:focus {
          background-color: transparent !important;
          background-position: center;
        }
    
        .el-menu-item-group {
          .el-menu-item-group__title {
            color: #ffffff;
          }
        }
    
        .el-menu--horizontal .el-menu-item:not(.is-disabled):focus,
        .el-menu--horizontal .el-menu-item:not(.is-disabled):hover {
          background-color: #088dc1;
        }
      }
    }
    
    .menu-popup .el-menu--popup .el-menu-item {
    
      background-color: rgba(23, 78, 112, 0.7) !important;
      &:hover,
      &:focus {
        background-color: rgba(16, 130, 201, 0.7) !important;
      }
    }
    
    

    3. JSX

    在Vue中使用JSX,很easy的 - 掘金 (juejin.cn)

    Vue JSX、自定义 v-model - 你的美好,我都记得 (ainyi.com)

  • 相关阅读:
    房价
    Jsrender初体验
    GCD XOR UVA
    GCD
    Aladdin and the Flying Carpet LightOJ
    HDU6035 2017多校第一场1003 树形DP
    F
    C
    B
    An Easy Physics Problem HDU
  • 原文地址:https://www.cnblogs.com/lepanyou/p/16056121.html
Copyright © 2020-2023  润新知