组件封装 - Tabs组件

news2024/11/20 10:41:56

首先我们先来看一下 Element UI 里面的 Tabs 是怎么样的

<template>
  <el-tabs v-model="activeName" class="demo-tabs" @tab-click="handleClick">
    <el-tab-pane label="User" name="first">User</el-tab-pane>
    <el-tab-pane label="Config" name="second">Config</el-tab-pane>
    <el-tab-pane label="Role" name="third">Role</el-tab-pane>
    <el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
  </el-tabs>
</template>

<script lang="ts" setup>
import { ref } from 'vue'
import type { TabsPaneContext } from 'element-plus'

const activeName = ref('first')

const handleClick = (tab: TabsPaneContext, event: Event) => {
  console.log(tab, event)
}
</script>

首先, 我们来分析一下细节:

1. tabs 组件由 el-tabs 和 el-tab-pane 组件来共同完成

2. 在父组件中对 tabs 组件传入了一个 v-model 数据 activeName

3. 在父组件中监听了 tabs 组件抛出的自定义事件 @tab-click

4. 对 el-tab-pane 组件传入了 label 和 name 数据

为什么需要两个组件来共同完成?

因为 tabs 主要用于规定 tab 栏的数据显示和操作, 而 tab-pane 组件主要用于规定 tabs 组件显示什么内容.

在父组件中为什么要传入一个 v-model 数据?

因为我们对 tab 栏进行点击的时候, 我们需要给用户一个反馈; 也就是高亮的效果.

所以, 父组件传入的 v-model 数据就是用来修改 tab 栏中按钮的高亮效果的数据.

然后, 用户点击不同的 tab 栏按钮, 下面对应的数据也需要发生对应的变化(通过 v-model 数据来控制显示和隐藏)

在父组件中为什么要去监听 @tab-click 自定义事件?

因为 tabs 组件是一个上下结构, 上面是按钮, 下面就是按钮所对应的数据.

这个数据是需要发送请求获取过来的, 所以, 当用户在点击不同的按钮的时候, 我们既要去修改高亮数据; 也要去修改发送请求后端所需的参数数据.

给 el-tab-pane 组件中的 label 和 name 参数起到了什么作用?

label 数据主要是给到按钮的, 因为 tab 栏需要显示不同的按钮数据; 这一个数据是由外部来决定的.

name 数据也是给到按钮的, 因为我们需要给每一个按钮一个唯一值; 因为我们需要通过这一个唯一值去修改按钮的高亮效果.


好了根据我的说明, 你有没有一点点的明白了; 现在我们也就来逐步的将代码部分展现.

第一步:

1. 首先创建 tabs 和 tab-panel 组件

在 tabs 组件中需要去规定结构和 tab 栏的数据

<script>
export default {
  name: 'Tabs',
  render () {
      
    // return出去什么, 引用页面就会显示什么
    return 'tabs'
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>

 tab-panel 组件主要是负责接收外部传入的数据, 丢到默认插槽中

<template>
  <div class="tabs-panel">
    <slot />
  </div>
</template>

<script>
export default {
  name: 'TabsPanel',
}
</script>

第二步:

1. 规定 tabs 组件结构

2. tab-panel 组件接收 name 和 label 数据

3. 在父组件中进行应用

<script>
export default {
  name: 'Tabs',
  render () {
    // 规定结构会使用到jsx语法
    
    // 定义tab栏结构
    const nav = <nav>
      <a href="javascript:;">选项卡一</a>
      <a href="javascript:;">选项卡二</a>
      <a href="javascript:;">选项卡三</a>
    </nav>
          
    // 定义内容数据
    const content = <div>
      <div>内容一</div>
      <div>内容二</div>
      <div>内容三</div>
    </div>

    return <div class="tabs">{[nav, content]}</div>
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>
<template>
  <div class="tabs-panel">
    <slot />
  </div>
</template>

<script>
export default {
  name: 'TabsPanel',
  props: {
    label: {
      type: String,
      default: ''
    },
    name: {
      type: [String, Number],
      default: ''
    }
  }
}
</script>
<template>
 <div class="member-order">
  <Tabs>
    <TabsPanel label="选项卡一" name="first"></TabsPanel>
    <TabsPanel label="选项卡二" name="second"></TabsPanel>
    <TabsPanel label="选项卡三" name="third"></TabsPanel>
  </Tabs>
 </div>
</template>

第三步:

1. 根据 tab-panel 组件接收到的 label 数据去动态的渲染 tabs 组件的 nav 数据

通过 $slots.default 方法获取插槽数据, 此方法调用会返回一个数组; 数据里面包含若干个对象, 每一个对象就是每一个 tab-panel

<script>
export default {
  name: 'Tabs',
  render () {
    const panels = this.$slots.default()

    const nav = <nav>{
      panels.map((item, index) => {
        return <a href="javascript:;">{ item.props.label }</a>
      })
    }</nav>

    return <div class="tabs">{[nav, panels]}</div>
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>
<template>
 <div class="member-order">
  <Tabs>
    <TabsPanel label="选项卡一" name="first">选项一</TabsPanel >
    <TabsPanel label="选项卡二" name="second">选项二</TabsPanel >
    <TabsPanel label="选项卡三" name="third">选项三</TabsPanel >
  </Tabs>
 </div>
</template>

虽然效果还是一样的, 但是 nav 里面的数据是动态的了 

第四步:

1. 兼容对 tab-panel 组件书写死数据的情况(上面已经实现, 父组件传入的就是死数据)和对 tab-panel 进行 v-for 遍历的情况

这两种情况, 使用 $slots.default 方法获取的数据是不一样的

所以我们可以根据 $slots.default 方法返回的数组中每一个对象中的 type 值来进行判断

到底传入的是静态数据, 还是 v-for 遍历的动态数据(使用 v-for 是因为数据来源是后端)

<script>
export default {
  name: 'Tabs',
  render () {
    const panels = this.$slots.default()

    const dynamicPanels = []
    panels.forEach(item => {
      // 提取静态的组件内容
      if (item.type.name === 'TabsPanel') {
        dynamicPanels.push(item)
      } else {
        // 提取动态的组件内容
        item.children.forEach(i => {
          dynamicPanels.push(i)
        })
      }
    })

    const nav = <nav>{
      dynamicPanels.map((item, index) => {
        return <a href="javascript:;">{ item.props.label }</a>
      })
    }</nav>

    return <div class="tabs">{[nav, panels]}</div>
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>

那么现在父组件中既可以使用静态的数据, 也可以使用 v-for 遍历的的动态数据了 

<template>
 <div class="member-order">
  <Tabs>
    <TabsPanel label="选项卡零" name="zero">选项零</TabsPanel >
    <TabsPanel v-for="item in 4" :key="item" :label="`选项卡${item}`" :name="`name${item}`">内容{{item}}</TabsPanel >
  </Tabs>
 </div>
</template>

第五步:

1. 实现 tab 栏的交互(也就是完成 tab 栏的点击高亮, 和点击不同的按钮显示对应的内容数据)

首先是在父组件中传入 v-model 的数据

<template>
 <div class="member-order">
  <Tabs v-model="activeName">
    <TabsPanel label="选项卡零" name="zero">选项零</TabsPanel >
    <TabsPanel v-for="item in 4" :key="item" :label="`选项卡${item}`" :name="`name${item}`">内容{{item}}</TabsPanel >
  </Tabs>
 </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'MemberOrder',
  setup () {
    const activeName = ref('zero')
    return { activeName }
  }
}
</script>

然后在 tabs 组件中进行接收, 给 nav 标签中的每一个 a 标签添加的点击事件

点击事件触发的同时去修改父组件的 v-model 数据

再通过 provide 给 tab-panel 组件传入当前高亮的按钮信息, v-show 控制内容的显示隐藏(通过 provide的原因是, tab 和 tab-panel 不是父子关系)

<script>
import { provide } from 'vue'
import { useVModel } from '@vueuse/core'
export default {
  name: 'Tabs',
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    }
  },
  setup (props, { emit }) {
    const activeName = useVModel(props, 'modelValue', emit)
    
    // 定义点击事件的事件处理函数
    const clickCheckName = (name) => {
      activeName.value = name
    }
    provide('activeName', activeName)

    return { activeName, clickCheckName }
  },
  render () {
    const panels = this.$slots.default()

    const dynamicPanels = []
    panels.forEach(item => {
      if (item.type.name === 'TabsPanel') {
        dynamicPanels.push(item)
      } else {
        item.children.forEach(i => {
          dynamicPanels.push(i)
        })
      }
    })

    const nav = <nav>{
      dynamicPanels.map((item, index) => {
        return <a onClick={() => this.clickCheckName(item.props.name)} class={{ active: item.props.name === this.modelValue }} href="javascript:;">{ item.props.label }</a>
      })
    }</nav>

    return <div class="tabs">{[nav, panels]}</div>
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>
<template>
  <div class="tabs-panel" v-show="name === activeName">
    <slot />
  </div>
</template>

<script>
import { inject } from 'vue'
export default {
  name: 'TabsPanel',
  props: {
    label: {
      type: String,
      default: ''
    },
    name: {
      type: [String, Number],
      default: ''
    }
  },
  setup () {
    const activeName = inject('activeName')

    return { activeName }
  }
}
</script>

第六步:

1. 实现 @tab-click 自定事件

也就是在点击 a 标签的时候, 传出父组件发送请求所需的数据

<script>
import { provide } from 'vue'
import { useVModel } from '@vueuse/core'
export default {
  name: 'Tabs',
  props: {
    modelValue: {
      type: [String, Number],
      default: ''
    }
  },
  setup (props, { emit }) {
    const activeName = useVModel(props, 'modelValue', emit)
    
    // 定义点击事件的事件处理函数
    const clickCheckName = (name, index) => {
      activeName.value = name
      emit('tab-click', { name, index })
    }
    provide('activeName', activeName)

    return { activeName, clickCheckName }
  },
  render () {
    const panels = this.$slots.default()

    const dynamicPanels = []
    panels.forEach(item => {
      if (item.type.name === 'TabsPanel') {
        dynamicPanels.push(item)
      } else {
        item.children.forEach(i => {
          dynamicPanels.push(i)
        })
      }
    })

    const nav = <nav>{
      dynamicPanels.map((item, index) => {
        return <a onClick={() => this.clickCheckName(item.props.name, index)} class={{ active: item.props.name === this.modelValue }} href="javascript:;">{ item.props.label }</a>
      })
    }</nav>

    return <div class="tabs">{[nav, panels]}</div>
  }
}
</script>

<style scoped lang="less">
.tabs {
  background: #fff;
  > nav {
    height: 60px;
    line-height: 60px;
    display: flex;
    border-bottom: 1px solid #f5f5f5;
    > a {
      width: 110px;
      border-right: 1px solid #f5f5f5;
      text-align: center;
      font-size: 16px;
      &.active {
        border-top: 2px solid @xtxColor;
        height: 60px;
        background: #fff;
        line-height: 56px;
      }
    }
  }
}
</style>

emit 出去之后, 在父组件中进行监听

<template>
 <div class="member-order">
  <Tabs v-model="activeName" @tab-click="changeTab">
    <TabsPanel label="选项卡零" name="zero">选项零</TabsPanel >
    <TabsPanel v-for="item in 4" :key="item" :label="`选项卡${item}`" :name="`name${item}`">内容{{item}}</TabsPanel >
  </Tabs>
 </div>
</template>

<script>
import { ref } from 'vue'
export default {
  name: 'MemberOrder',
  setup () {
    const activeName = ref('zero')
    const changeTab = (obj) => {
      console.log(obj)
    }

    return { activeName, changeTab }
  }
}
</script>

这样父组件中就可以拿着后端返回的数据进行 v-for 渲染, 然后每一次点击的时候; 子组件返回父组件所需的数据, 显示不同的组件内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/163033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于matlab的汽车牌照识别程序详细教程

设计一个基于matlab的汽车牌照识别程序&#xff0c;能够实现车牌图像预处理&#xff0c;车牌定位&#xff0c;字符分割&#xff0c;然后通过神经网络对车牌进行字符识别&#xff0c;最终从一幅图像中提取车牌中的字母和数字&#xff0c;给出文本形式的车牌号码。关键词&#xf…

C语言 一个特殊的数组【柔性数组】

文章目录前言柔性数组的特点柔性数组的使用柔性数组的优势写在最后前言 也许你从来就没有听过柔性数组&#xff08;flexible array&#xff09;这个概念&#xff0c;但他是真的存在&#xff1b;柔性数组的概念存在于C99标准当中&#xff0c;C99标准表示&#xff1a;结构体的最后…

Linux-进程概念

目录 进程状态&#xff1a; 操作系统简图 调用进程bin.exe的详细过程 cpu运行队列的结构 R进程和阻塞进程 进程状态&#xff1a;挂起&#xff1a; Linux操作&#xff1a; ​编辑 R运行状态 S休眠状态 T暂停状态&#xff1a; kill kill -18 表示继续 kill -9 杀死进程…

肿瘤内微生物群在癌症转移中的新作用

谷禾健康 癌症是一种复杂的疾病&#xff0c;归因于多因素变化&#xff0c;导致治疗策略困难。 90%的癌症患者死于复发或转移。癌症转移是恶性肿瘤进展的关键步骤&#xff0c;由癌细胞内在特性和外在环境因素决定。 一些微生物组通过诱导癌性上皮细胞和慢性炎症促进癌发生、癌症…

光耦合器:类型及其应用

光耦合器&#xff1a;类型及其应用 介绍 光耦合器&#xff08;也称为光隔离器&#xff09;是一种在两个隔离电路之间传输电信号的半导体器件。光耦合器由两部分组成&#xff1a;发射红外光的LED和检测LED光的光敏器件。这两个部件都装在一个带有连接销的黑匣子中。输入电路接收…

数据可视化笔记记录(二)pandas

笔记内容是根据B站上面的一位老师的视频而记录了&#xff0c;因为我也还在学习&#xff0c;所以个人理解可能会出现错误&#xff0c;若有错误请指出。另外这篇文章会很长 B站视频连接、 numpy,matplotlib的学习记录 pandas 学习记录 SerIes结构&#xff0c;一图胜千言 Seri…

实用工具:FastDoc 文档提取工具

实用工具&#xff1a;FastDoc 文档提取工具 简单、实用的 HTTP API 文档生成工具&#xff0c;支持 Spring MVC/Spring Boot 下的控制器信息提取&#xff0c;类似 Swagger 但稍有不同的机制。 在线演示地址在 https://framework.ajaxjs.com/demo/fast-doc/。 关于研发者这工具…

二三层转发原理

二层转发原理 数据帧 二层即数据链路层的转发是以数据帧的格式进行转发&#xff0c;数据帧的格式如下&#xff1a; 目的地址(Destination Address,DA) &#xff1a;可以是单独的地址,或者是广播或组播MAC地址。 源地址(Source Address,SA) &#xff1a;用来识别发送没备,在S…

听说你想用开发者工具调试我的网站?挺可以的啊。25

本篇博客重点为大家介绍&#xff0c;如何禁止用户在浏览器中查看源码&#xff0c;禁用开发者工具调试等前端需求 案例已更新到 爬虫训练场 文章目录禁用右键&#xff0c;禁用 F12禁用 ctrl U 查看源代码&#xff0c;禁用 ctrl shift i 打开开发者工具实现开发者工具无限 deb…

arcgis使用Python脚本进行批量截图

arcgis使用Python脚本进行批量截图 介绍 最近公司数据部那边有个需求&#xff0c;需要结合矢量数据和影像数据&#xff0c;进行批量截图&#xff0c;并且截图中只能有一个图斑&#xff0c;还要添加上相应的水印。 思路 一开始我是准备使用QGIS直接进行批量出图&#xff0c;…

Ant Design在处理业务表单中的一些实践

文章目录前言表单处理实践Modal 清空旧数据使用Form.create和getFieldDecorator对Form进行包装表单控件是switch自定义表单控件Table列表SelectshowSearch 用于在选择框中显示搜索框其他需要注意的组件Tabs其它前言 目前&#xff0c;很多中小企业前端实现采用了react&#xff…

二、总线控制

文章目录一、引子二、总线判优控制1.基本概念2.方式&#xff08;1&#xff09;集中式①链式查询方式②计数器定时查询方式③独立请求方式&#xff08;2&#xff09;分布式三、总线通信控制1.基本概念2.方式&#xff08;1&#xff09;同步通信①介绍②同步式数据输入③同步数据输…

数据结构与算法学习推荐

推荐&#xff1a;官网地址&#xff1a;http://localhost:8000/algorithm/github&#xff1a;https://github.com/paiDaXing-web/You-Dont-Know-Algorithm 点star 大话搜索搜索一般指在有限的状态空间中进行枚举&#xff0c;通过穷尽所有的可能来找到符合条件的解或者解的个数。…

佳能C5235彩色激光复印机复印有底灰

故障描述&#xff1a; 佳能C5235彩色激光复印机&#xff0c;复印的时候有不同颜色的底灰问题&#xff1b; 检测分析&#xff1a; 打印不同纯色的纸张进行测试发现每张纸的上面都有不同颜色的底灰&#xff1b; 拆机进行检测吧&#xff0c;先打开前盖&#xff0c;拧下左右两边的螺…

CMake基础教程

CMake最大优势是跨平台&#xff0c;可以根据不同的平台生成Makefile文件&#xff0c;看下CMake的基础使用。 cmake&#xff08;单目录单文件&#xff09; 第一个最简单的cmake构建。 demo1.cpp代码&#xff1a; #include <iostream>using namespace std;int main(int…

【基于机械臂触觉伺服的物体操控研究】UR5e动力学建模及代码实现

我的毕设题目定为《基于机械臂触觉伺服的物体操控研究》&#xff0c;这个系列主要用于记录做毕设的过程。 前言&#xff1a;UR系列是优傲公司的代表产品&#xff0c;也是目前比较通用的产品级机械臂。所以我打算用该机械臂进行毕设的仿真实现。关于其动力学建模&#xff0c;网…

台阶问题-

台阶问题 题目描述 有NNN级的台阶&#xff0c;你一开始在底部&#xff0c;每次可以向上迈最多KKK级台阶&#xff08;最少111级&#xff09;&#xff0c;问到达第NNN级台阶有多少种不同方式。 输入格式 两个正整数N&#xff0c;K。 输出格式 一个正整数&#xff0c;为不同…

JVM(一)——架构基础

JVM java虚拟机 java gc 主要回收的是 方法区 和 堆中的内容&#xff0c;以下架构图是重点&#xff1a; 方法区和堆是线程共享&#xff0c;java栈、本机方法栈、程序计数器是线程私有。 运行时数据区可以用Runtime.getRuntime()获取 字节码执行引擎&#xff0c;修改程序计数器…

不要怀疑了,个人也是可以做好跨境电商的!

近几年随着跨境电商卖家们赚得盆满钵满&#xff0c;许多人都想从中分一杯羹&#xff0c;进而入住了跨境电商市场&#xff0c;有人与一些公司企业合作&#xff0c;也有人选择了自己做跨境电商平台&#xff0c;个人做的优劣势又有哪些呢&#xff1f; 个人做跨境电商平台最明显突…

A V L树

概念 在之前介绍了搜索二叉树&#xff0c;但是当我们插入的数据若是有序或者接近于有序&#xff0c;那么此时查找的效率底下&#xff0c;于是俄罗斯的数学家G.M.Adelson-Velskii和E.M.Landis在1962年发明了一种解决上述问题的方法&#xff1a;当向二叉搜索树中插入新结点后&am…