vue-element-admin学习笔记--权限加载及自定义布局(2)

接着昨天的笔记,整个布局和权限的结合基本搞定。后续开始看下Navbar等其他布局的设计。

根据element-vue-admin中的说明,这里的Sidebar是基于element-ui的NavMenu来实现的。于是先看了下element-ui的NavMenu。在官网找了个例子,主要代码如下:

<div id="app">
<el-row class="tac">
  <el-col :span="12">
    <h5>默认颜色</h5>
    <el-menu
      default-active="2"
      class="el-menu-vertical-demo"
      @open="handleOpen"
      @close="handleClose">
      <el-submenu index="1">
        <template slot="title">
          <i class="el-icon-location"></i>
          <span>导航一</span>
        </template>
        <el-menu-item-group>
          <template slot="title">分组一</template>
          <el-menu-item index="1-1">选项1</el-menu-item>
          <el-menu-item index="1-2">选项2</el-menu-item>
        </el-menu-item-group>
        <el-menu-item-group title="分组2">
          <el-menu-item index="1-3">选项3</el-menu-item>
        </el-menu-item-group>
        <el-submenu index="1-4">
          <template slot="title">选项4</template>
          <el-menu-item index="1-4-1">选项1</el-menu-item>
        </el-submenu>
      </el-submenu>
      <el-menu-item index="2">
        <i class="el-icon-menu"></i>
        <span slot="title">导航二</span>
      </el-menu-item>
      <el-menu-item index="3" disabled>
        <i class="el-icon-document"></i>
        <span slot="title">导航三</span>
      </el-menu-item>
      <el-menu-item index="4">
        <i class="el-icon-setting"></i>
        <span slot="title">导航四</span>
      </el-menu-item>
    </el-menu>
  </el-col>
 
</el-row>
</div>

层级结构主要为el-row>el-col>el-menu>el-submenu>el-menu-item。如果是多层级菜单使用了el-menu-item-group。其他属性可以参看element-ui的API。然后看下目前项目中的Sidebar。

这里Sidebar使用了index.vue/SidebarItem.vue/Item.vue/Link.vue

index.vue

在此组件中主要初始化菜单的el-menu,同时将用户的路由传到子组件SidebarItem.vue中进行判断逻辑。这里没有使用,而是使用了,这个是在官方文档中没有但是在element-ui的源码中的。其余的一些参数配置暂时默认为了false。代码如下:

<template>
  <div :class="{ 'has-logo': showLogo }">
    <logo v-if="showLogo" :collapse="isCollapse" />
    <el-scrollbar wrap-class="scrollbar-wrapper">
      <el-menu
        :default-active="activeMenu"
        :collapse="isCollapse"
        :background-color="variables.menuBg"
        :text-color="variables.menuText"
        :unique-opened="false"
        :active-text-color="variables.menuActiveText"
        :collapse-transition="false"
        mode="vertical"
      >
        <sidebar-item
          v-for="route in permission_routes"
          :key="route.path"
          :item="route"
          :base-path="route.path"
        />
      </el-menu>
    </el-scrollbar>
  </div>
</template>

<script>
import { mapGetters } from "vuex";
// import Logo from "./Logo";
import SidebarItem from "./SidebarItem";
import variables from "@/styles/variables.scss";

export default {
  components: { SidebarItem },
  computed: {
    ...mapGetters(["permission_routes"]),
    activeMenu() {
      const route = this.$route;
      const { meta, path } = route;
      // if set path, the sidebar will highlight the path you set
      if (meta.activeMenu) {
        return meta.activeMenu;
      }
      return path;
    },
    showLogo() {
      return false;
    },
    variables() {
      return variables;
    },
    isCollapse() {
      return false;
    }
  }
};
</script>

SidebarItem.vue/Item.vue/Link.vue

SidebarItem主要是路由的递归,然后加载相应的菜单。这里的Item.vue采用了函数式组件的方式,并通过render函数返回创建的节点。Link.vue则是判断是否外链。在SidebarItem中还有个逻辑,如果一个路由只有一级子节点,在没有配置alwaysShow属性时,默认就会合并为一个菜单。其它的逻辑如:判断菜单(router)是否是隐藏的,每个菜单都有icon、path、name等。

SidebarItem.vue

<template>
  <div v-if="!item.hidden">
    <template
      v-if="
        hasOneShowingChild(item.children, item) &&
          (!onlyOneChild.children || onlyOneChild.noShowingChildren) &&
          !item.alwaysShow
      "
    >
      <app-link v-if="onlyOneChild.meta" :to="resolvePath(onlyOneChild.path)">
        <el-menu-item
          :index="resolvePath(onlyOneChild.path)"
          :class="{ 'submenu-title-noDropdown': !isNest }"
        >
          <item
            :icon="onlyOneChild.meta.icon || (item.meta && item.meta.icon)"
            :title="item.meta.title"
          />
        </el-menu-item>
      </app-link>
    </template>

    <el-submenu
      v-else
      ref="subMenu"
      :index="resolvePath(item.path)"
      popper-append-to-body
    >
      <template slot="title">
        <item
          v-if="item.meta"
          :icon="item.meta && item.meta.icon"
          :title="item.meta.title"
        />
      </template>
      <sidebar-item
        v-for="child in item.children"
        :key="child.path"
        :is-nest="true"
        :item="child"
        :base-path="resolvePath(child.path)"
        class="nest-menu"
      />
    </el-submenu>
  </div>
</template>

<script>
import path from "path";
import Item from "./Item";
import AppLink from "./Link";
import { isExternal } from "@/utils/validate";
export default {
  name: "SidebarItem",
  components: { Item, AppLink },
  props: {
    // route object
    item: {
      type: Object,
      required: true
    },
    isNest: {
      type: Boolean,
      default: false
    },
    basePath: {
      type: String,
      default: ""
    }
  },
  data() {
    // To fix https://github.com/PanJiaChen/vue-admin-template/issues/237
    // TODO: refactor with render function
    this.onlyOneChild = null;
    return {};
  },
  methods: {
    hasOneShowingChild(children = [], parent) {
      const showingChildren = children.filter(item => {
        if (item.hidden) {
          return false;
        } else {
          // Temp set(will be used if only has one showing child)
          this.onlyOneChild = item;
          return true;
        }
      });

      // When there is only one child router, the child router is displayed by default
      if (showingChildren.length === 1) {
        return true;
      }

      // Show parent if there are no child router to display
      if (showingChildren.length === 0) {
        this.onlyOneChild = { ...parent, path: "", noShowingChildren: true };
        return true;
      }

      return false;
    },
    resolvePath(routePath) {
      if (isExternal(routePath)) {
        return routePath;
      }
      if (isExternal(this.basePath)) {
        return this.basePath;
      }
      return path.resolve(this.basePath, routePath);
    }
  }
};
</script>

<style scoped>
.svg-icon {
  margin-right: 16px;
}
</style>

Item.vue

<script>
export default {
  name: "MenuItem",
  functional: true,
  props: {
    icon: {
      type: String,
      default: ""
    },
    title: {
      type: String,
      default: ""
    }
  },
  render(h, context) {
    const { icon, title } = context.props;
    const vnodes = [];

    if (icon) {
      vnodes.push(<svg-icon icon-class={icon} />);
    }

    if (title) {
      vnodes.push(<span slot="title">{title}</span>);
    }
    return vnodes;
  }
};
</script>

Link.vue

<template>
  <!-- eslint-disable vue/require-component-is -->
  <component v-bind="linkProps(to)">
    <slot />
  </component>
</template>

<script>
import { isExternal } from "@/utils/validate";

export default {
  props: {
    to: {
      type: String,
      required: true
    }
  },
  methods: {
    linkProps(url) {
      if (isExternal(url)) {
        return {
          is: "a",
          href: url,
          target: "_blank",
          rel: "noopener"
        };
      }
      return {
        is: "router-link",
        to: url
      };
    }
  }
};
</script>

初次登陆不加载菜单

最后测试的时候发现第一次登录后不加载菜单,必须刷新页面后才加载。排查了下,发现是当初测试时,登陆后将用户的role存放到了store中,所以导致权限逻辑加载时逻辑路径错误,没有去动态的加载菜单。修改用户登录中的submitLogin方法,将返回值中的SET_ROLE去掉即可。如下所示:

  submitlogin({ commit }, { payload }) {
    const { username, password } = payload;
    return new Promise((resolve, reject) => {
      login(username.trim(), password)
        .then(response => {
          if (response.data.userInfo.token != "error") {
            //commit("SET_ROLES", response.data.userInfo.roles);//去掉此行
            commit("SET_NAME", response.data.userInfo.name);
            commit("SET_TOKEN", response.data.userInfo.token);
            setToken(response.data.userInfo.token);
            resolve();
            router.push("/");
          } else {
            console.log(response.data.userInfo.token);
            resolve();
            router.push("/404");
          }
        })
        .catch(error => {
          reject(error);
        });
    });
  }
原文地址:https://www.cnblogs.com/GYoungBean/p/12470033.html