Router and Navigation

Router and Nav are the key skeleton for organizing the system.

In this project, router and nav are bound together, so you only have to configure the route under @/router/index.js and the sidebar nav will be dynamically generated automatically. This greatly reduces the workload of manually editing the sidebar nav. Of course, you have to follow configurations of route item

Config

First let us know what configuration items are provided config route.

// if set to true, it will not appear in sidebar nav.
// e.g. login or 401 page or as some editing pages /edit/1 (Default: false)
hidden: true

// this route cannot be clicked in breadcrumb navigation when noredirect is set
redirect: noredirect

// when you route a children below the declaration of more than one route,
// it will automatically become a nested mode - such as the component page
// when there is only one, the child route will be displayed as the root route
// if you want to display your root route
// regardless of the number of children declarations under the route
// you can set alwaysShow: true
// so that it will ignore the previously defined rules and always show the root route
alwaysShow: true

// set router name. It must be set,in order to avoid problems with <keep-alive>.
name: 'router-name'

meta: {
  // required roles to access to this route, support multiple roles.
  // if not set, this route is accesible for everyone.
  roles: ['admin', 'editor'],
  // Required permissions to access this route, support multiple permissions
  // If not set, the permission will not be checked to access this route
  permissions: ['view menu zip', 'manage user'],

  // the title of the route to show in various components (e.g. sidebar, breadcrumbs).
  title: 'title'

  // svg icon class
  icon: 'svg-name'

  // when set true, the route will not be cached by <keep-alive>. Default false
  noCache: true

  // if false, the item will hidden in breadcrumb(default is true)
  breadcrumb: false
}

Example:

{
  path: '/permission',
  component: Layout,
  redirect: '/permission/index',
  alwaysShow: true, // will always show the root menu
  meta: {
    title: 'permission',
    icon: 'lock',
    permissions: ['view menu permission'], // Only accessible for role/user which has 'view menu permission' permission
  },
  children: [...],
}

Router

There are two types of routes: constantRoutes and asyncRoutes.

constantRoutes: represents routes which do not require authorized access such as login page, 404, general pages...

asyncRoutes: represents pages which require dynamic permissions/roles and are dynamically added through addRoutes. The details will be introduced on the Roles and Permissions.

TIP

All routing pages here use the router lazy loading, as described in document

If you want to know more about browserHistory and hashHistory, please refer to Build & Deploy.

The other configurations are no different from the vue-router official, so check the documentation for yourself.

WARNING

The 404 page must be the latest one to be loaded in the constantRoutes. Later declared pages will be blocked to 404, see the details of the problem: addRoutes when you've got a wildcard route for 404s does not work

The project sidebar is mainly based on the el-menu of element-ui.

As mentioned before, the sidebar is dynamically generated by getting the routes and combining with the role/permission system, but also need to support the multi-level of nested routes, so here is also used to the recursive components.

Code: @/layout/components/Sidebar

Many default sidebar styles of element-ui have been modified. All css can be found in @/styles/sidebar.scss and can be modified to suit your needs.

Here need to pay attention. The general sidebar has two forms, submenu andel-menu-item. One is a nested submenu, the other is a direct link. As shown below:

The sidebar has already helped you to create nested menu. If you define more than one route in children, it will automaticly become the nested mode. If the children has only one route item, root menu will be shown instead. If you do not want to, you can disable this feature by setting alwaysShow: true in the root route. Such as:

// no submenu, because children.length===1
{
  path: '/icon',
  component: Layout,
  children: [{
    path: 'index',
    component: ()=>import('svg-icons/index'),
    name: 'icons',
    meta: { title: 'icons', icon: 'icon'},
  }],
},

// Has submenu with children.length===1, because alwaysShow===true
{
  path: '/icon',
  component: Layout,
  alwaysShow: true,
  children: [{
    path: 'index',
    component: ()=>import('svg-icons/index'),
    name: 'icons',
    meta: { title: 'icons', icon: 'icon'},
  }],
},

// has submenu, because children.length>1
{
  path: '/components',
  component: Layout,
  name: 'component-demo',
  meta: {
    title: 'components',
    icon: 'component',
  },
  children: [
    { path: 'tinymce', component: () =>import('components-demo/tinymce'), name: 'tinymce-demo', meta: { title: 'tinymce' }},
    { path: 'markdown', component: () =>import('components-demo/markdown'), name: 'markdown-demo', meta: { title: 'markdown' }},
  ],
}

Click the sidebar to refresh the current route

With traditional web appication before SPA (Single Page Application), when user clicks on the sidebar, it will request the page again. This behaviour keeps user clicking on the menu to refresh the page. But in SPA, this behaviour is not the same. vue-router will intercept your routing, it determines your URL does not change, so it will not trigger any hook or view changes. There are many discussions related to this issue.

yyx990803also said that he wanted to add a way to brighten the view, but later he changed his mind again/(ㄒ o ㄒ)/~~ But demand is here, what should we do? He said it would not trigger anything without changing the current URL, so can I force the trigger? The hack is simple. By changing the URL query to trigger the view changes。We listen to each link's click event on the sidebar, each click will push a different query for the router to ensure that the view is refreshed.

clickLink(path) {
  this.$router.push({
    path,
    query: {
      //Ensure that each click, query is not the same
      //to ensure that refresh the view
      t: +new Date(),
    }
  })
}

Note: Don't forget to add a unique key to router-view, such as <router-view :key="$route.path"></router-view>.

We could apply the ugly query suffix behide the URL, such as xxx.com/article/list?t=1496832345025 to create unique URL when clicking on the sidebar Or we can use redirect method to jump to the same page when detecting the current clicked route is same as the current route.

Example

Click on the global size switch button shown in the image and you will see that the page of app-main has been refreshed. It uses the method of redirecting to the Redirect page and then redirecting back to the original page.

Redirect page to /redirect when clicking

const { fullPath } = this.$route
this.$nextTick(() => {
  this.$router.replace({
    path: '/redirect' + fullPath,
  });
});

The redirect page is redirected back to the original page

// redirect.vue
// https://github.com/tuandm/laravue/blob/master/resources/js/views/redirect/index.vue
export default {
  beforeCreate() {
    const { params, query } = this.$route;
    const { path } = params;
    this.$router.replace({ path: '/' + path, query });
  },
  render: function(h) {
    return h(); // avoid warning message
  },
};

This project also provides a breadcrumb navigation, which is also dynamically generated by watching $route change. It is the same with the menu, you can also config it in the routing. You can also add some custom attributes in route.meta attr for your logic. For example, you can declare breadcrumb:false in the route so that it is not displayed in breadcrumb.

Corresponding code: @/components/Breadcrumb

You can also configure an external-link in the sidebar. As long as you fill in the legal url path in path, you will be able to open this page when you click on the sidebar.

E.g.

{
  path: 'external-link',
  component: Layout,
  children: [
    {
      path: 'https://github.com/tuandm/laravue',
      meta: { title: 'externalLink', icon: 'link' },
    },
  ],
},