vue3 + Element-plus 开发后台管理系统(28)

290 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第28天,点击查看活动详情

后台项目前端综合解决方案之通用功能开发

tagsView 原理及方案分析

tagsView 其实可以分为两个部分,一个是 tags,另一个则是 view

我们先来看一看 tags,那么什么是 tags 呢?其实很简单,它就是位于 appmain 之上的标签

知道了 tags,我们再来看看 views,其实脱离开 tagsviews 指的就是一个用来渲染组件的位置,就像我们之前的 AppMain 一样,只不过这里的 views 可能稍微复杂一点,因为他要在渲染的基础上增加动画以及缓存这两个额外的功能

加上这两个功能可能回略显复杂,但是官网已经帮助我们处理了这个问题

明确了原理之后,我们就来看看实现方案

1、创建 tagsView 组件,用来处理 tags 的展示

2、处理基于路由的动态过渡,在 AppMain 中进行,用于处理 view 部分

整个的方案就是这么两部分,但是其中我们还需要处理一些相关的细节

1、监听路由变化,组成用于渲染的 tags 数据源

2、创建 tags 组件,根据数据源渲染 tag,渲染出来的 tags 需要同时具备

  • 国际化
  • 路由跳转

3、处理鼠标右键效果,根据右键处理对应数据源

4、处理基于路由的动态过度

创建 tags 数据源

tags 数据源分为两部分

1、保存数据:appmain 组件中进行

2、展示数据:tags 组件中进行

所以 tags 的数据我们最好把它保存到 vuex

constant 中新建常量

// tags
export const TAGS_VIEW = 'tagsView'

store/app 中创建 tagsViewList

import { LANG, TAGS_VIEW } from '@/constant'
import { getItem, setItem } from '@/utils/storage'
export default {
  namespaced: true,
  state: () => ({
    tagsViewList: getItem(TAGS_VIEW) || []
  }),
  mutations: {
    /**
     * 添加 tags
     */
    addTagsViewList(state, tag) {
      const isFind = state.tagsViewList.find(item => {
        return item.path === tag.path
      })
    // 处理重复
      if (!isFind) {
        state.tagsViewList.push(tag)
        setItem(TAGS_VIEW, state.tagsViewList)
      }
    }
  },
  actions: {}
}

appmain 中监听路由变化

<script setup>
import { watch } from 'vue'
import { isTags } from '@/utils/tags'
import { generateTitle } from '@/utils/i18n'
import { useRoute } from 'vue-router'
import { useStore } from 'vuex'

const route = useRoute()

/**
 * 生成 title
 */
const getTitle = route => {
  let title = ''
  if (!route.meta) {
    // 处理无 meta 的路由
    const pathArr = route.path.split('/')
    title = pathArr[pathArr.length - 1]
  } else {
    title = generateTitle(route.meta.title)
  }
  return title
}

/**
 * 监听路由变化
 */
const store = useStore()
watch(
  route,
  (to, from) => {
    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
  }
)
</script>

创建 utils/tags

const whiteList = ['/login', '/import', '/404', '/401']

/**
 * path 是否需要被缓存
 * @param {*} path
 * @returns
 */
export function isTags(path) {
  return !whiteList.includes(path)
}
生成 tagsView

目前数据已经被保存到 store 中,那么接下来我们就依赖数据渲染 tags

创建 store/apptagsViewList 的快捷访问

  tagsViewList: state => state.app.tagsViewList

创建 compenent/tagsview

<template>
  <div class="tags-view-container">
      <router-link
        class="tags-view-item"
        :class="isActive(tag) ? 'active' : ''"
        :style="{
          backgroundColor: isActive(tag) ? $store.getters.cssVar.menuBg : '',
          borderColor: isActive(tag) ? $store.getters.cssVar.menuBg : ''
        }"
        v-for="(tag, index) in $store.getters.tagsViewList"
        :key="tag.fullPath"
        :to="{ path: tag.fullPath }"
      >
        {{ tag.title }}
        <i
          v-show="!isActive(tag)"
          class="el-icon-close"
          @click.prevent.stop="onCloseClick(index)"
        />
      </router-link>
  </div>
</template>

<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()

/**
 * 是否被选中
 */
const isActive = tag => {
  return tag.path === route.path
}

/**
 * 关闭 tag 的点击事件
 */
const onCloseClick = index => {}
</script>

<style lang="scss" scoped>
.tags-view-container {
  height: 34px;
  width: 100%;
  background: #fff;
  border-bottom: 1px solid #d8dce5;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.12), 0 0 3px 0 rgba(0, 0, 0, 0.04);
    .tags-view-item {
      display: inline-block;
      position: relative;
      cursor: pointer;
      height: 26px;
      line-height: 26px;
      border: 1px solid #d8dce5;
      color: #495060;
      background: #fff;
      padding: 0 8px;
      font-size: 12px;
      margin-left: 5px;
      margin-top: 4px;
      &:first-of-type {
        margin-left: 15px;
      }
      &:last-of-type {
        margin-right: 15px;
      }
      &.active {
        color: #fff;
        &::before {
          content: '';
          background: #fff;
          display: inline-block;
          width: 8px;
          height: 8px;
          border-radius: 50%;
          position: relative;
          margin-right: 4px;
        }
      }
      // close 按钮
      .el-icon-close {
        width: 16px;
        height: 16px;
        line-height: 10px;
        vertical-align: 2px;
        border-radius: 50%;
        text-align: center;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        transform-origin: 100% 50%;
        &:before {
          transform: scale(0.6);
          display: inline-block;
          vertical-align: -3px;
        }
        &:hover {
          background-color: #b4bccc;
          color: #fff;
        }
      }
    
  }
}
</style>

layout/index 中导入

<div class="fixed-header">
    <!-- 顶部的 navbar -->
    <navbar />
    <!-- tags -->
    <tags-view></tags-view>
</div>
import TagsView from '@/components/TagsView'