动态导航栏

本文介绍了一种响应式侧边栏菜单的设计方案,包括不同状态下的样式变化、鼠标悬停效果、子菜单展开动画等细节。适用于需要实现复杂导航功能的Web应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

<template>
  <div
    class="sidebar gey-bg-2"
    :class="{
      'sm-sidebar': !isExpand,
      'hover-sidebar': !isExpand && isHover,
      'isOrder-sidebar': $route.meta.isOrder
    }"
    @mouseenter="overSidebar"
    @mouseleave="leaveSidebar">
    <div class="container">
      <template v-for="(item, index) in menu">
        <!-- 有子菜单 -->
        <div class="item  gey-hover gey-sidebar-font" :class="!isExpand ?'additional-width':''" :key="item.name">
          <div
            @click="toggleMenu(index, item)"
            @mouseenter="enterItem(index)"
            @mouseleave="leaveItem(index)"
            class="item-inner"
            :class="{
              'active':
                curHoverItemIndex === index || item.active
               }">
            <span class="left">
              <span class="icon-box">
              <navigation-icon :name="item.icon"></navigation-icon>
             <ul class="additional"  :class="{ 'item-hover': curHoverItemIndex === index}"  v-if="!isExpand && item.subMenu.length>0">
              <template v-for="subItem in item.subMenu">
                <li :key="subItem.title" @click="clickMenu(subItem)" class="gey-sidebar-sub-font" v-if="!subItem.hidden">
                  <div  class="link"
                    :class="{
                      'router-active': subItem.route.name === $route.name ||subItem.route.name === $route.meta.fromCategory }">
                    <span class="sub-name">{{subItem.name}}</span>
                    <span class="new-flag" v-if="subItem.isNew">new</span>
                    <span class="new-flag" v-if="subItem.isHot">hot</span>
                  </div>
                </li>
              </template>
          </ul>
              </span>
              <span class="title" v-if="isExpand">{{item.name}}</span>
            </span>
            <i class="iconfont icon-tubiaozhizuo-" :class="{'active': item.toggle}" v-if="item.subMenu.length > 0 && isExpand"></i>
          </div>
          <ul
          class="submenu"
          v-if="isExpand"
          :style="{
          'height': item.toggle ? item.subMenu.length * 44 + 'px' : '0px',
          'opacity': item.toggle ? 1 : 0
          }">
          <template v-for="subItem in item.subMenu">
          <li :key="subItem.title" @click="clickMenu(subItem)" class="gey-sidebar-sub-font" v-if="!subItem.hidden">
          <div
          class="link"
          :class="{
          'router-active':
          subItem.route.name === $route.name ||
          subItem.route.name === $route.meta.fromCategory
          }"
          >
          <span class="sub-name">{{subItem.name}}</span>
          <span class="new-flag" v-if="subItem.isNew">new</span>
          <span class="new-flag" v-if="subItem.isHot">hot</span>
          </div>
          </li>
          </template>
          </ul>
        </div>
      </template>
    </div>
  </div>
</template>
<script>
import allMenu from './newMenu.js'
import navigationIcon from '@/components/frontZone/navigationIcon' // 直播带货看板

export default {
  data () {
    return {
      isOver: false,
      timeout: null,
      menu: [],
      interval: null,
      curRouteName: '',
      curHoverItemIndex: 1
    }
  },
  mounted () {
    this.getMenu()
    let menu = this.menu
    for (let k = 0; k < menu.length; k++) {
      if (this.$route.name === menu[k].route.name) {
        menu[k].toggle = true
        break
      }
      for (let i = 0; i < menu[k].subMenu.length; i++) {
        if (this.$route.name === menu[k].subMenu[i].route.name || this.$route.meta.fromCategory === menu[k].subMenu[i].route.name) {
          menu[k].toggle = true
          break
        } else {
          menu[k].toggle = false
        }
      }
    }
  },
  beforeDestroy () {
    clearInterval(this.interval)
  },
  computed: {
    isExpand () {
      return this.$store.state.common.isExpand
    },
    isHover () {
      return this.$store.state.common.isHover
    },
    userInfo () {
      return this.$store.state.user.userInfo
    },
    fdRoleCode () {
      return this.$store.state.user.userInfo && this.$store.state.user.userInfo.fdRoleCode
    }
  },
  watch: {
    '$route' (newVal, oldVal) {
      if (newVal !== oldVal) {
        let menu = this.menu
        this.menu.forEach((item) => {
          item.active = false
        })
        for (let k = 0; k < menu.length; k++) {
          if (this.$route.name === menu[k].route.name) {
            menu[k].toggle = true
            menu[k].active = true
            break
          }
          for (let i = 0; i < menu[k].subMenu.length; i++) {
            if (this.$route.name === menu[k].subMenu[i].route.name || this.$route.meta.fromCategory === menu[k].subMenu[i].route.name) {
              menu[k].toggle = true
              menu[k].active = true
              break
            } else {
              menu[k].toggle = false
              menu[k].active = false
            }
          }
        }
      }
    }
  },
  methods: {
    getMenu () {
      let menu = allMenu()
      for (let k = 0; k < menu.length; k++) {
        if (this.$route.name === menu[k].route.name) {
          menu[k].active = true
          break
        }
        for (let i = 0; i < menu[k].subMenu.length; i++) {
          if (this.$route.name === menu[k].subMenu[i].route.name) {
            menu[k].active = true
            break
          } else {
            menu[k].active = false
          }
        }
      }
      this.menu = menu
      this.manuScroll()
    },
    clickMenu (item, index) {
      if (item.route.name === 'smartSearch') {
        let {href} = this.$router.resolve({name: 'smartSearch'})
        window.open(href, '_blank')
      } else {
        this.$router.push(item.route)
      }
      this.curRouteName = item.route.name
    },
    manuScroll () {
      this.interval = setTimeout(() => {
        if (document.querySelector('.item .router-active')) {
          document.querySelector('.item .router-active').scrollIntoView(false)
          clearTimeout(this.interval)
        } else {
          this.manuScroll()
        }
      }, 200)
    },
    toggleMenu (index, item) {
      if (item.subMenu.length > 0) {
        this.$set(this.menu, index, Object.assign({}, item, {toggle: !this.menu[index].toggle}))
      } else {
        this.$router.push(item.route)
      }
    },
    overSidebar () {
      this.$store.dispatch('hoverSidebar', true)
    },
    leaveSidebar () {
      this.$store.dispatch('hoverSidebar', false)
    },
    enterItem (index) {
      this.curHoverItemIndex = index
    },
    leaveItem (index) {
      this.curHoverItemIndex = 1000
    }
  },
  components: {
    navigationIcon
  }
}
</script>
<style lang="scss" scoped>
  @import 'assets/styles/theme-mixin.scss';

  .sub-name {
    font-size: 14px;
  }

  .isOrder-sidebar {
    .link {
      &.router-active, &:hover {
        .sub-name {
          font-size: 14px;
          font-weight: bold;
        }
        &::before {
          .sub-name {
            font-size: 14px;
            font-weight: bold;
          }
        }
      }
    }
  }

  .new-flag {
    position: absolute;
    width: 30px;
    height: 16px;
    line-height: 16px;
    font-size: 12px;
    color: #ffffff;
    text-align: center;
    border-radius: 4px;
  }

  .sidebar {
    position: fixed;
    top: 50px;
    left: 0;
    width: 210px;
    bottom: 0;
    z-index: 11;
    transition: all .8s cubic-bezier(.21, .83, .49, 1);
    &.sm-sidebar {
      transform: translateX(-159px);
      .container {
        .item {
          transform: translate3d(142px, 0, 0);
        }
        .additional-width{
          width: 50px;
        }
      }
      &.hover-sidebar {
        /*transform: translateX(0px);*/
        /*.container {*/
        /*.item {*/
        /*transform: translate3d(0px,0,0);*/
        /*}*/
        /*}*/
      }
    }
    .container {
      padding-top: 24px;
      height: 100%;
      .item {
        width: 200px;
        position: relative;
      }
      .item-inner {
        height: 45px;
        padding: 0 12px 0 30px;
        margin-bottom: 4px;
        display: flex;
        align-items: center;
        justify-content: space-between;
        transition: all .6s cubic-bezier(.21, .83, .49, 1);
        cursor: pointer;
        .left {
          position: relative;
          display: flex;
          align-items: center;
          .new-flag {
            right: -29px;
            top: -2px;
          }
          .icon-box {
            width: 30px;
            margin-right: 8px;
            text-align: center;
            position: relative;
            .additional {
              transition: all 0.3s ease;
              background: #FFFFFF;
              box-shadow: 6px 2px 20px rgba(0, 0, 0, 0.1);
              position: absolute;
              transform: translateX(38px);
              top: -15px;
              display: none;
              padding: 20px;
              & li{
              margin-bottom: 24px;
              font-size: 14px;
              text-align: left;
              white-space: nowrap;
            }
              & li:last-child{
                margin-bottom: 0px;
              }
            }
            .item-hover{
              display: block;
            }
          }
          .title {
            font-size: 16px;
            font-weight: bold;
            &.no-submenu-title {
              position: relative;
              .new-flag {
                top: -14px;
                right: -30px;
              }
            }
          }
        }
        .iconfont {
          display: inline-block;
          color: #B8BBBF;
          transition: all 0.4s;
          transform-origin: center center;
          font-size: 12px;
          &.active {
            transform: rotate(180deg);
          }
        }
        &.active, &.router-active {
          .icon-img {
            animation: iconPop 1s ease-in-out;
          }
        }
      }
      .router-active .sub-name {
        font-size: 14px;
        font-weight: bold;
      }
      .router-active:hover .sub-name {
        font-size: 14px;
        font-weight: bold;
      }
      .submenu {
        transition: all 0.4s;
        li {
          .link {
            position: relative;
            width: 100%;
            display: inline-block;
            padding-left: 68px;
            height: 42px;
            line-height: 42px;
            cursor: pointer;
            .title {
              vertical-align: middle;
            }
            &:before {
              display: none;
              position: absolute;
              content: "";
              width: 4px;
              left: 0;
              top: 0;
              height: 100%;
            }
            &.router-active, &:hover {
              /*background-color: #d9d4d4;*/
              /*//    color: var(--th-sidebar-font-active-color) !important;*/
              /*.sub-name{*/
              /*}*/
              &:before {
                display: inline-block;
              }
            }
          }
        }
      }
    }
  }

  @keyframes iconPop {
    0% {
      transform: scale(1);
      opacity: 1;
    }
    50% {
      transform: scale(0.2);
      opacity: 0;
    }
    75% {
      transform: scale(1.2);
      opacity: 1;
    }
    100% {
      transform: scale(1);
      opacity: 1;
    }
  }

  .en-app {
    .sidebar .container, .hover-sidebar .container {
      .item-inner {
        padding-left: 20px;
      }
      .submenu li .link {
        padding-left: 50px;
        .new-flag {
          left: 22px;
        }
      }
    }
    .sidebar .container .item-inner {
      padding-left: 20px;
    }
    .sidebar.sm-sidebar {
      .container .item-inner .left .icon-box {
        margin: 0 15px 0 10px;
      }
    }
  }

  .scroll-bar-box {
    @include scroll-bar-box(100vh);
    height: 100vh;
  }

  .title, .sub-name, .svg-icons {
    transition: 0.1s ease;
  }
</style>

在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值