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

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

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

image.png

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

image.png

image.png

從上面兩張圖片可以看出瑞凑,整個(gè)導(dǎo)航標(biāo)簽組件末秃,是實(shí)現(xiàn)了標(biāo)簽按鈕過多后,上線可移動(dòng)的效果的拨黔。

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

image.png

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

  • 刷新:只要通過 router.go(0) 刷新當(dāng)前頁就行
  • 關(guān)閉右側(cè):因?yàn)槲覀冋麄€(gè)導(dǎo)航標(biāo)簽肯定是由一個(gè)數(shù)組渲染的蛔溃,通過數(shù)組的splice就能實(shí)現(xiàn)數(shù)據(jù)的“切割”绰沥。
  • 關(guān)閉其他:通過也是通過實(shí)現(xiàn)的,只不過切割了當(dāng)前位置的一前一后的數(shù)據(jù)贺待,但這里需要注意一個(gè)點(diǎn)徽曲,就是我們這個(gè)項(xiàng)目一進(jìn)來是自動(dòng)定位到首頁標(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

其他問題分析

  • 路由過渡動(dòng)畫的實(shí)現(xiàn)
    使用vue官方的過渡動(dòng)畫的方式
    html
<!-- 使用動(dòng)態(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的實(shí)現(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>

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

監(jiān)聽路由稚虎,往數(shù)組里里增加標(biāo)簽,再通過循環(huán)router-view 實(shí)現(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ù)和操作動(dòng)作全都封裝到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è)彈窗組件的實(shí)現(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代碼很簡(jiǎn)單偎捎,就展示了蠢终,需要的私信我

整個(gè)tagView組件的實(shí)現(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 的點(diǎn)擊事件
   */
  const store = useStore()
  const router = useRouter()

  const onCloseClick = (index,tag) => {

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

    //如果刪除的是當(dāng)前頁面,自動(dòng)定位到上一個(gè)頁面
    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){//如果是最后一個(gè)茴她,定位到首頁
        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)前頁丈牢,自動(dòng)定位到上一個(gè)頁面祭钉,刪除的如果是最后一個(gè),自動(dòng)定位到首頁

tagsView 方案總結(jié)

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

整個(gè) tagsView 就像我們之前說的慌核,拆開來看之后,會(huì)顯得明確很多泛粹。

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

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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钾怔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蒙挑,更是在濱河造成了極大的恐慌宗侦,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忆蚀,死亡現(xiàn)場(chǎng)離奇詭異矾利,居然都是意外死亡姑裂,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門男旗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來舶斧,“玉大人,你說我怎么就攤上這事察皇≤罾鳎” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵什荣,是天一觀的道長(zhǎng)矾缓。 經(jīng)常有香客問我,道長(zhǎng)稻爬,這世上最難降的妖魔是什么嗜闻? 我笑而不...
    開封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮桅锄,結(jié)果婚禮上泞辐,老公的妹妹穿的比我還像新娘。我一直安慰自己竞滓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開白布吹缔。 她就那樣靜靜地躺著商佑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厢塘。 梳的紋絲不亂的頭發(fā)上茶没,一...
    開封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音晚碾,去河邊找鬼抓半。 笑死,一個(gè)胖子當(dāng)著我的面吹牛格嘁,可吹牛的內(nèi)容都是我干的笛求。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼糕簿,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼探入!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起懂诗,我...
    開封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蜂嗽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后殃恒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體植旧,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辱揭,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了病附。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片问窃。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖胖喳,靈堂內(nèi)的尸體忽然破棺而出泡躯,到底是詐尸還是另有隱情,我是刑警寧澤丽焊,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布较剃,位于F島的核電站,受9級(jí)特大地震影響技健,放射性物質(zhì)發(fā)生泄漏写穴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一雌贱、第九天 我趴在偏房一處隱蔽的房頂上張望啊送。 院中可真熱鬧,春花似錦欣孤、人聲如沸馋没。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽篷朵。三九已至,卻和暖如春婆排,著一層夾襖步出監(jiān)牢的瞬間声旺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來泰國打工段只, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腮猖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓赞枕,卻偏偏與公主長(zhǎng)得像澈缺,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炕婶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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