阅读 5626

Vue 2.x折腾记 - (1)写一个不大靠谱的二级侧边栏

前言

本来想写个新手系列教程,发现这种东西一搜索一大把。 那就写点实战类的吧,这篇文章你能学点什么? 当然是一些常见内置指令的用法,组件过渡,遍历的思路等等

效果图

实现思路

  • 过渡用css -
  • 遍历循环判断(比对路由,点击的项名等)
  • 增加标记位来默认展开刷新页面当前所在项
  • 尽量减少DOM的改动,能用v-show的区域绝不用v-if
  • 自定义菜单JSON尽量格式简洁,没有一大坨标记位这些干扰物(越简单越方便后期)

代码

菜单menuList.js

export const MENULIST = [
  {
    menuName: "客户管理",
    menuIcon: "fz-ad-icon-test",
    menuSubLink: [
      {
        menuName: "广告主",
        menuUrl: "/customer/adhost"
      },
      {
        menuName: "渠道",
        menuUrl: "/customer/channel"
      }
    ]
  },
  {
    menuName: "广告管理",
    menuIcon: "fz-ad-guanggao",
    menuSubLink: [
      {
        menuName: "广告新增",
        menuUrl: "/ad/add"
      },
      {
        menuName: "广告审核",
        menuUrl: "/ad/check"
      }
    ]
  },
  {
    menuName: "投放管理",
    menuIcon: "fz-ad-toufang",
    menuSubLink: [
      {
        menuName: "广告位",
        menuUrl: "/puton/area"
      },
      {
        menuName: "广告规格",
        menuUrl: "/puton/regular"
      }
    ]
  },
  {
    menuName: "数据统计",
    menuIcon: "fz-ad-statistics",
    menuSubLink: [
      {
        menuName: "广告主",
        menuUrl: "/status/adhost"
      },
      {
        menuName: "渠道",
        menuUrl: "/status/channel"
      }
    ]
  },
  {
    menuName: "管理者",
    menuIcon: "fz-ad-guanli",
    menuUrl: "/manager"
  },
  {
    menuName: "操作日志",
    menuIcon: "fz-ad-rizhi",
    menuUrl: "/logger"
  }
];

复制代码

Sidebar.vue

<template>
  <div class="sidebar" >
    <ul class="sidebar-menu ">`
      <template v-for="(item ,index) in menulist">
        <li :class="currentUrl === item.menuUrl ? 'active':''">
          <template v-if="item.menuUrl">
            <router-link :to="item.menuUrl" @click.native="toggleName=''">
              <i :class="['fzicon',item.menuIcon]"></i>
              <span>{{item.menuName}}</span>
            </router-link>
          </template>
          <template v-else-if="item.menuSubLink">
            <a href="javascript:;" @click="isToggle(item.menuName,item.defaultActive)">
              <i :class="['fzicon',item.menuIcon]"></i>
              <span>{{item.menuName}}</span>
              <i class="trangle" :class="[config.iconfont, (item.menuName === toggleName) || item.defaultActive? config.icon_expand: config.icon_collapse]">
              </i>
            </a>
            <transition name="sliderToggle" mode="out-in">
              <ul class="tree-menu" v-show="item.menuName === toggleName || item.defaultActive">
                <li v-for="(subitem,subindex) in item.menuSubLink" :key="subitem" :class="currentUrl === subitem.menuUrl ? 'active':''">
                  <router-link :to="subitem.menuUrl">
                    <i :class="subitem.menuIcon"></i>
                    <span>{{subitem.menuName}}</span>
                  </router-link>
                </li>
              </ul>
            </transition>
          </template>
        </li>
      </template>
    </ul>
  </div>
</template>

<script>
import { MENULIST } from './menuList'; // 引入的自定义菜单数据
export default {
  name: 'layout-sidebar',
  data: function () {
    return {
      menulist: MENULIST, // 自定义菜单数据
      currentUrl: '', // 当前浏览器的url
      toggleName: '',  // 菜单子项目名称
      config: {
        'iconfont': 'fzicon', // iconfont的字体
        'icon_collapse': 'fz-ad-jiantou', // 箭头
        'icon_expand': 'fz-ad-jiantou1' // 箭头
      }
    }
  },
  props: ['toggle', 'padMode'], // 这里是用来构成布局响应传递的props,单一组件不用管他
  watch: {
    '$route'() {
      this.currentUrl = this.$route.fullPath; // 实时监测当前路由的变化并且赋值
    }

  },
  methods: {
    isToggle(name, defaultActive) {
      this.clearDefaultActive(); // 清除标记位,是否当前为默认展开
      defaultActive ? false : name !== this.toggleName ? this.toggleName = name : this.toggleName = ''; // 判断展开收缩的核心
    },
    clearDefaultActive() {
      this.menulist.forEach(item => {
        this.$delete(item, 'defaultActive')
      })
    }
  },
  created: function () {
    this.currentUrl = this.$route.fullPath;
    this.$nextTick(() => {
      this.menulist.forEach((item, index) => { // 增加标记位,判断当前url然后自动展开或者激活对应项(刷新默认展开当前url的项)
        if (!item.menuSubLink && item.menuUrl) {
          this.currentUrl === item.menuUrl ? this.$set(item, 'defaultActive', true) : '';
        } else {
          if (item.menuSubLink) {
            item.menuSubLink.forEach((subitem, index) => {
              this.currentUrl === subitem.menuUrl ? this.$set(item, 'defaultActive', true) : '';
            })
          }
        }
      })
    })
  },
  mounted: function () {
  }
}
</script>

<style scoped lang="scss">
// 自定义过渡效果
.sliderToggle-enter-active,
.sliderToggle-leave-active {
  transition: all 0.5s linear;
  height: 100%;
  height: auto;

  overflow: hidden;
}

.sliderToggle-enter,
.sliderToggle-leave-to {
  overflow: hidden;
  padding-top: 0;
  padding-bottom: 0;
  height: 0;
  opacity: 0;
}

// 侧边栏全局样式
.sidebar {
  position: absolute;
  top: 0;
  left: 0;
  padding-top: 68px;
  min-height: 100%;
  z-index: 810;
  transition: all 0.3s linear;
  background-color: #222d32;
  .sidebar-menu {
    list-style: none;
    margin: 0;
    padding: 0;
    white-space: nowrap;
    span {
      cursor: pointer;
    }
    a {
      text-decoration: none;
      color: #8aa4af;
      &:hover {
        color: #fff;
      }
    }
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
      white-space: nowrap;
      overflow: hidden;
    }
    >li {
      position: relative;
      margin: 0;
      padding: 0;
      text-align: left;
      &.active {
        color: #fff;
      }
      >a {
        border-left: 3px solid transparent;
        position: relative;
        padding: 15px 5px 15px 19px;
        display: block;
        font-size: 14px;
        >i {
          padding-right: 4px;
        }
        .trangle {
          float: right;
          padding-right: 10px;
        }
      }
      &.active {
        >a {
          border-left: 3px solid #3c8dbc;
          color: #fff;
        }
      }
      >.tree-menu {
        margin: 0 1px;
        background: #2c3b41;
        list-style: none;
        padding: 0;
        margin: 0;
        .tree-menu {
          padding-left: 20px;
        }
        >li {
          margin: 0;
          &.active {
            >a {
              border-left: 3px solid #3c8dbc;
              color: #fff;
            }
          }
          >a {
            padding: 15px 10px 15px 40px;
            display: block;
            font-size: 14px;
            border-left: 3px solid transparent;
          }
        }
        &.active {
          display: block;
        }
      }
    }
  }

  &.expand {
    width: 230px;
  }

  &.collapse {
    width: 50px;
    .sidebar-menu {
      >li {
        >a {
          padding: 15px 5px 15px 15px;
          span,
          i:last-child {
            display: none;
          }
        }
        .tree-menu {
          display: none;
        }
        &:hover {
          >a {
            display: block;
            span {
              display: block;
              position: absolute;
              left: 47px;
              width: 153px;
              padding: 15px 5px 15px 19px;
              background-color: #222d32;
              color: #fff;
              width: 177px;
              top: 0;
              border-radius: 0 5px 0 0;
            }
            i:last-child {
              display: inline-block;
              position: absolute;
              right: -165px;
              top: 50%;
              transform: translateY(-50%);
              color: #fff;
            }
          }
          >.tree-menu {
            display: block;
            position: absolute;
            left: 47px;
            width: 180px;
            background-color: #222d32;
            color: #fff;
            top: 46px;
            width: 180px;
            border-radius: 0 0 5px 5px;
          }
        }
      }
    }
  }
}
</style>

复制代码

总结

实际上这货就是把Admin Lte的风格用vue实现了,还有变形金刚版本,整个响应涉及几个到组件间的通讯;

就不把全部代码丢出来了,一大坨,效果是这样的