Vue3+element动态增减el-tabs

文章目录

  • overview
  • 设计
    • 容器
      • 容器主体
        • 样式文件
      • 容器左侧
  • over

overview

这个问题我想了很久,怎么能优雅地实现点击左侧边栏,右边动态增减。现在有点眉目了

设计

容器

容器主体

主要思路:分为三个部分,容器整体,容器左侧,容器右侧。左侧是按钮组,右侧是待渲染部分。
我把侧边栏单独抽出来做了个组件,不重要。然后重点是动态增减Tabs,最重要的就是用component的:is属性,根据按钮的callBack动态渲染每一个Tab的内容。
代码如下:

<div class="root-ele">
  <div class="root-title">
    你好, 世界!
  div>
  <div class="root-body">
    <div class="root-body root-body-item-left">
      
      <EventSide @openTab="toOpen">EventSide>
    div>
    <div class="root-body root-body-item-right">
      
      <el-tabs
        v-model="editableTabsValue"
        type="card"
        closable
        @tab-remove="removeTab"
      >
        <el-tab-pane
          v-for="(item, index) in allTabs.value"
          :key="index"
          :name="item.name"
          :label="item.label"
        >
          
          <Component :is="item.name">Component>
        el-tab-pane>
      el-tabs>
    div>
  div>
div>

<script lang="ts">
import { defineComponent, reactive, ref } from 'vue'
// 样式文件
import '../RootElementStyle.css'
import MyHead from '../MyHead.vue'
import EventSide from '../../components/eventComponent/EventSide.vue'
import AllEvent from '../../components/eventComponent/AllEvent.vue'
import EventDetail from '../../components/eventComponent/EventDetail.vue'

export default defineComponent({
    name: 'EventPage',
    components: {
      MyHead,
      EventSide,
      allEvent: AllEvent,
      eventDetail: EventDetail,
    },
    setup() {
      // 定义接口,用于规定数组对象的类型
      interface inter{
        name: string
        [propName: string]: any
      }
      const editableTabsValue = ref('')
      const allTabs = reactive({
        value: [] as inter[],
      })
      function toOpen(item: {name: string; label: string}) {
        for (let i = 0; i < allTabs.value.length; ++i) {
          if (allTabs.value[i].name === item.name) {
            return
          }
        }
        allTabs.value.push(item)
        editableTabsValue.value = item.name
      }
      function removeTab(targetName: string) {
        const tabs = allTabs.value
        let activeName = editableTabsValue.value
        if (activeName === targetName) {
          tabs.forEach((tab, index) => {
            if (tab.name === targetName) {
              const nextTab = tabs[index + 1] || tabs[index - 1]
              if (nextTab) {
                activeName = nextTab.name
              }
            }
          })
        }
        editableTabsValue.value = activeName
        allTabs.value = tabs.filter(tab => tab.name !== targetName)
      }
      return {
        toOpen,
        editableTabsValue,
        removeTab,
        allTabs,
      }
    },
  })
script>

样式文件

.root-ele {
    font-size: medium;
    position: absolute;
    height: 100%;
    width: 100%;
    font-family: "montserrat", sans-serif;
}
.root-title {
    min-height: 7%;
    max-height: 9%;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    background-color: #f4f4f5;
    backdrop-filter: blur(5px);
    font-size: x-large;
    font-weight: lighter;
    line-height: 230%;
    margin-bottom: 0.5%;
    color: white;
    border-left: black 4px solid;
    background-image: linear-gradient(125deg, #409EFF, #E6A23C, #F56C6C, #67C23A, #909399);
    animation: bganimation 15s infinite;
    background-size: 400%;
}
.root-body {
    height: 88.5%;
    overflow-y: auto;
    display: flex;
    border-radius: 4px;
}
.root-body .root-body-item-left {
    width: 15%;
    height: 100%;
    background-image: linear-gradient(125deg, #DCDFE6, #E4E7ED, #EBEEF5, #F2F6FC, #C0C4CC);
    animation: bganimation 15s infinite;
    background-size: 400%;
    box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
    overflow-x: hidden;
}

.root-body .root-body-item-right {
    display: block;
    padding-left: 10px;
    width: 84%;
    height: 100%;
    margin-left: 0.3%;
    background-image: linear-gradient(125deg, #DCDFE6, #E4E7ED, #EBEEF5, #F2F6FC, #C0C4CC);
    animation: bganimation 15s infinite;
    background-size: 400%;
}

容器左侧

不废话,代码如下

<template>
	<div
	  v-for="(item, index) in sides.sides"
	  :key="index"
	  class="event-side-item"
	  @click="handleOpenTab(item)"
	>
	  {{ item.label }}
	div>
template>

<script lang="ts">
  import { defineComponent, reactive } from 'vue'
  import eventSideConfig from './EventSideConfig'

  export default defineComponent({
    name: 'EventSide',
    emits: ['openTab'],
    setup(_, { emit }) {
      const sides = reactive({
        sides: eventSideConfig,
      })

      function handleOpenTab(item: any) {
        emit('openTab', item)
      }

      return {
        sides,
        handleOpenTab,
      }
    },
  })
script>

我把侧边栏和组件的对应关系放在EventSideConfig里面,直接读取,如果后期需要从后端动态读取,页面什么都不用改,直接赋个值就行。
定义文件如下:

const eventSideConfig = [
  {
    label: 'allEvent',
    name: 'allEvent',
  },
  {
    label: 'eventDetail',
    name: 'eventDetail',
  },
]

export default eventSideConfig

over

这就是我目前能想出来最优雅扩展性最强的解法。比之前那种强跳转的解法来说,更容易扩展,只要后端加载的数据格式约定好,直接就能由静态转动态。另外,没有强依赖跨组件的状态共享,耦合性低,更加安全

欢迎联系我 memeda

你可能感兴趣的:(vue前端,css,vue.js,html)