Vue3寫一個后臺管理系統(tǒng)(6)導(dǎo)航欄標(biāo)簽頁的實現(xiàn)

先看看最終的效果吧,這樣更有助于后面代碼邏輯的實現(xiàn)。

布局實現(xiàn)效果分析

image.png

我們看第一張圖片,可以分析得出 整個導(dǎo)航標(biāo)簽組件(我們最終會封裝成一個組件,便于后期其他項目的移植使用),由標(biāo)簽按鈕右側(cè)彈窗層組合成。只要通過css布局 ,就可以實現(xiàn)右側(cè)彈出層的效果,很簡單。

image.png

image.png

從上面兩張圖片可以看出,整個導(dǎo)航標(biāo)簽組件,是實現(xiàn)了標(biāo)簽按鈕過多后,上線可移動的效果的。

功能實現(xiàn)效果分析

image.png

右側(cè)彈窗層 實現(xiàn)了1.刷新 2.關(guān)閉右側(cè) 3.關(guān)閉其他 4.關(guān)閉全部四個功能

  • 刷新:只要通過 router.go(0) 刷新當(dāng)前頁就行
  • 關(guān)閉右側(cè):因為我們整個導(dǎo)航標(biāo)簽肯定是由一個數(shù)組渲染的,通過數(shù)組的splice就能實現(xiàn)數(shù)據(jù)的“切割”。
  • 關(guān)閉其他:通過也是通過實現(xiàn)的,只不過切割了當(dāng)前位置的一前一后的數(shù)據(jù),但這里需要注意一個點,就是我們這個項目一進來是自動定位到首頁標(biāo)簽的,所以關(guān)閉其他和關(guān)閉全部是不能關(guān)閉到首頁hone標(biāo)簽按鈕的
  • 關(guān)閉全部:把數(shù)組置未空就行了,但要定位到首頁,其他按鈕標(biāo)簽是由關(guān)閉功能的,但首頁是沒有關(guān)閉功能的,也就是首頁是沒有關(guān)閉當(dāng)前頁的功能的,


    image.png
image.png

其他問題分析

  • 路由過渡動畫的實現(xiàn)
    使用vue官方的過渡動畫的方式
    html
<!-- 使用動態(tài)過渡名稱 -->
<router-view v-slot="{ Component, route }">
  <transition :name="route.meta.transition">
    <component :is="Component" />
  </transition>
</router-view>
  • 數(shù)據(jù)的緩存
    我們使用keep-alive結(jié)合vuex的實現(xiàn)方式
<template>
  <div class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive :include="cachedViews">
          <component :is="Component" :key="route.name" />
        </keep-alive>
      </transition>
    </router-view>
  </div>
</template>

好了,總體的實現(xiàn)思路到講完了,我們直接看代碼吧

監(jiān)聽路由,往數(shù)組里里增加標(biāo)簽,再通過循環(huán)router-view 實現(xiàn)渲染

 /**
   * 監(jiān)聽路由變化
   */
  watch(
    route,
    (to, from) => {
      // const cachedViews = store.getters.tagsViewList
      // console.log("store.getters.tagsViewList",cachedViews)
      if (!isTags(to.path)) return
      const { fullPath, meta, name, params, path, query } = to
      store.commit('app/addTagsViewList', {
        fullPath,
        meta,
        name,
        params,
        path,
        query,
        title: getTitle(to)
      })
    },
    {
      immediate: true
    }
  )
<template>
  <div class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive :include="cachedViews">
          <component :is="Component" :key="route.name" />
        </keep-alive>
      </transition>
    </router-view>
  </div>
</template>

<script setup>

  const cachedViews = computed(() => {
    console.log(store.getters.tagsViewList.map((x) => x.name))
    return  store.getters.tagsViewList.map((x) => x.name)
  })

</script>


tagsViewList的數(shù)據(jù)和操作動作全都封裝到vuex里

import {TAGS_VIEW} from '@/constant'
import {getItem, setItem} from '@/utils/storage'

export default {
  namespaced: true,
  state: () => ({
    sidebarOpened: true,
    tagsViewList: getItem(TAGS_VIEW) || []
  }),
  mutations: {
    triggerSidebarOpened(state) {
      state.sidebarOpened = !state.sidebarOpened
    },
    /**
     * 添加 tags
     */
    addTagsViewList(state, tag) {
      const isFind = state.tagsViewList.find(item => {
        return item.path === tag.path
      })
      // 處理重復(fù)
      if (!isFind) {
        state.tagsViewList.push(tag)
        setItem(TAGS_VIEW, state.tagsViewList)
      }
    },
    /**
     * 刪除 tag
     * @param {type: 'other'||'right'||'index', index: index} payload
     */
    removeTagsView(state, payload) {
      if (payload.type === 'index') {
        state.tagsViewList.splice(payload.index, 1)
      } else if (payload.type === 'other') {
        state.tagsViewList.splice(
          payload.index + 1,
          state.tagsViewList.length - payload.index + 1
        )
        state.tagsViewList.splice(0, payload.index)
        if(payload.index !=0){
          //list第一位加入刪除了的首頁tag
          state.tagsViewList.unshift({
            fullPath:'/home',
            meta:{title: '首頁', affix: true},
            name:'home',
            params:{},
            path:'/home',
            query:{},
            title: "首頁"
          })
        }
      } else if (payload.type === 'right') {
        state.tagsViewList.splice(
          payload.index + 1,
          state.tagsViewList.length - payload.index + 1
        )
      } else if (payload.type === 'all') {
        state.tagsViewList = []
      }
      setItem(TAGS_VIEW, state.tagsViewList)
    },


  },
  actions: {}
}

右側(cè)彈窗組件的實現(xiàn)

<template>
  <ul class="context-menu-container">
    <li @click="onRefreshClick">
    刷新
    </li>
    <li @click="onCloseRightClick">
    關(guān)閉右側(cè)
    </li>
    <li @click="onCloseOtherClick">
    關(guān)閉其他
    </li>
    <li @click="onCloseAllClick">
      關(guān)閉全部
    </li>
  </ul>
</template>

<script setup>
  import { defineProps } from 'vue'
  import { useRouter } from 'vue-router'
  import { useStore } from 'vuex'

  const props = defineProps({
    index: {
      type: Number,
      required: true
    }
  })

  const router = useRouter()
  const store = useStore()
  const onRefreshClick = () => {
    router.go(0)
  }

  const onCloseRightClick = () => {
    store.commit('app/removeTagsView', {
      type: 'right',
      index: props.index
    })
  }

  const onCloseOtherClick = () => {
    store.commit('app/removeTagsView', {
      type: 'other',
      index: props.index
    })



  }

  const onCloseAllClick = () => {
    store.commit('app/removeTagsView', {
      type: 'all',
      index: props.index
    })

    router.push('/')

  }



</script>

具體的css代碼很簡單,就展示了,需要的私信我

整個tagView組件的實現(xiàn)

<template>
  <div class="tags-view-container">
    <el-scrollbar class="tags-view-wrapper">
    <router-link
      class="tags-view-item"
      :class="isActive(tag) ? 'active' : ''"
      :style="{
          backgroundColor: isActive(tag) ? $store.getters.cssVar.menuActiveText : '',
          borderColor: isActive(tag) ? $store.getters.cssVar.menuActiveText : ''
        }"
      v-for="(tag, index) in $store.getters.tagsViewList"
      :key="tag.fullPath"
      :to="{ path: tag.fullPath }"

      @contextmenu.prevent="openMenu($event, index)"
    >
      {{ tag.title }}
      <template v-if="!isAffiix(tag)">
        <i
          class="el-icon-close"
          @click.prevent.stop="onCloseClick(index,tag)"
        />
      </template>

    </router-link>
    </el-scrollbar>
    <context-menu
      v-show="visible"
      :style="menuStyle"
      :index="selectIndex"
    ></context-menu>
  </div>
</template>

<script setup>
  import ContextMenu from './ContextMenu.vue'
  import { ref, reactive, watch } from 'vue'
  import { useRoute,useRouter } from 'vue-router'
  import { useStore } from 'vuex'

  const route = useRoute()

  /**
   * 是否被選中
   */
  const isActive = tag => {
    return tag.path === route.path
  }
  const isAffiix = tag =>{
    return tag.meta && tag.meta.affix
  }
  // contextMenu 相關(guān)
  const selectIndex = ref(0)
  const visible = ref(false)
  const menuStyle = reactive({
    left: 0,
    top: 0
  })
  /**
   * 展示 menu
   */
  const openMenu = (e, index) => {
    const { x, y } = e
    menuStyle.left = x + 'px'
    menuStyle.top = y + 'px'
    selectIndex.value = index
    visible.value = true
  }

  /**
   * 關(guān)閉 tag 的點擊事件
   */
  const store = useStore()
  const router = useRouter()

  const onCloseClick = (index,tag) => {

    store.commit('app/removeTagsView', {
      type: 'index',
      index: index
    })

    //如果刪除的是當(dāng)前頁面,自動定位到上一個頁面
    if (isActive(tag)) {
      let tagsViewList = store.getters.tagsViewList
      if(index ==0 && tagsViewList.length>=1){
        let pre_index = 0
        let pre_page =tagsViewList[pre_index]
        router.push(pre_page.fullPath)
      }else if(tagsViewList.length == 0){//如果是最后一個,定位到首頁
        router.push('/')
      }else{
        let pre_index = index-1
        let pre_page =tagsViewList[pre_index]
        router.push(pre_page.fullPath)
      }


    }
  }


  /**
   * 關(guān)閉 menu
   */
  const closeMenu = () => {
    visible.value = false
  }

  /**
   * 監(jiān)聽變化
   */
  watch(visible, val => {
    if (val) {
      document.body.addEventListener('click', closeMenu)
    } else {
      document.body.removeEventListener('click', closeMenu)
    }
  })



</script>

需要注意的是,,如果刪除的是當(dāng)前頁,自動定位到上一個頁面,刪除的如果是最后一個,自動定位到首頁

tagsView 方案總結(jié)

那么到這里關(guān)于 tagsView 的內(nèi)容我們就已經(jīng)處理完成了。

整個 tagsView 就像我們之前說的,拆開來看之后,會顯得明確很多。

整個 tagsView 整體來看就是三塊大的內(nèi)容:

  1. tagstagsView 組件
  2. contextMenucontextMenu 組件
  3. view:頁面路由處理

再加上一部分的數(shù)據(jù)處理即可。

到這里,我們整個后臺管理系統(tǒng)的框架就全部搭建完畢了,剩下就是一些細(xì)節(jié),根據(jù)自己公司的業(yè)務(wù)需求,自行修改就行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容