vue3二次封装element-ui中的table组件

为什么要做这件事

借助封装table组件的过程来巩固一下vue3相关知识点。

组件有哪些配置项

  1. options:表格的配置项
  2. data: 表格数据源
  3. elementLoadingText:加载文案
  4. elementLoadingSpinner:加载图标
  5. elementLoadingBackground:背景遮罩的颜色
  6. elementLoadingSvg:加载图标(svg)
  7. elementLoadingSvgViewBox:加载图标是svg的话,配置项
  8. editIcon:编辑图标
  9. isEditRow:是否支持编辑
  10. editRowIndex:编辑行的标识符
  11. pagination:是否支持分页
  12. paginationAlign:分页对齐方式
  13. currentPage:当前页数
  14. pageSize:每页显示条目个数
  15. pageSizes:每页显示个数选择器的选项设置
  16. total:总条目数

实现过程

首先,将一个普通的element-plus中的table组件引入进来,表格数据源就是我们通过父组件传递进来的data,所以我们使用defineProps来定义,并且它的数据类型是一个数组;同时我们遵循单向数据流的原则,使用lodash中的深拷贝方法将data拷贝一份出来赋值给变量tableData,将tableData传递给element-plus中的table组件,用来渲染数据。

// 子组件 m-table-copy

<template>
  <el-table :data="tableData">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
  </el-table>
</template>

<script setup>
import { ref } from 'vue'
import cloneDeep from 'lodash/cloneDeep'
let props = defineProps({
  data: {
    type: Array,
    required: true
  }
})

// 拷贝一份儿数据
let tableData = ref(cloneDeep(props.data))
</script>

 父组件在使用这个自定义组件的时候应该这么使用:

<!-- 父组件 -->
<template>
  <m-table-copy :data="tableData"></m-table-copy>
</template>

<script setup>
import { ref } from 'vue'

let tableData = ref([])

tableData.value = [
  {
    name: '张三',
    address: '杭州市',
    date: '1998-07-16'
  },
  {
    name: '李四',
    address: '石家庄市',
    date: '2013-09-02'
  }
]
</script>

 这样页面上就能够显示出来我们的数据了:

第一步完成了,我们接着再分析,还有什么是可以封装的呢?仔细看上面的代码,是不是有了想法?是的,label、width、prop这些也是可以放在一个配置项里面的,那我们继续来进行封装:

// 父组件准备好的数据结构
let options = [
  {
    prop: 'date',
    label: '日期',
    align: 'center',
    slot: 'date',
    editable: true,
    width: '230'
  },
  {
    prop: 'name',
    label: '姓名',
    align: 'center',
    slot: 'name'
  },
  {
    prop: 'address',
    label: '地址',
    align: 'center',
    editable: true
  },
  {
    label: '操作',
    align: 'center',
    action: true
  }
]

<template>
  <el-table :data="tableData">
    <template v-for="(item, index) in tableOption" :key="index">
      <el-table-column
        :label="item.label"
        :prop="item.prop"
        :align="item.align"
        :width="item.width" />
    </template>
  </el-table>
</template>

<script setup>
import { ref, computed } from 'vue'
let props = defineProps({
  // 表格配置项
  options: {
    type: Array,
    required: true
  }
})

// 过滤操作项之后的配置
let tableOption = computed(() => props.options.filter((item) => !item.action))
</script>

 一般来说,表格都会配置一下loading状态,所以我们继续封装,将loading相关的配置项也添加进来:

完整代码 

<template>
  <el-table
    :data="tableData"
    v-loading="isLoading"
    :element-loading-text="elementLoadingText"
    :element-loading-spinner="elementLoadingSpinner"
    :element-loading-svg="elementLoadingSvg"
    :element-loading-svg-view-box="elementLoadingSvgViewBox"
    :element-loading-background="elementLoadingBackground"
    @row-click="rowClick"
    v-bind="$attrs">
    <template v-for="(item, index) in tableOption" :key="index">
      <el-table-column
        :label="item.label"
        :prop="item.prop"
        :align="item.align"
        :width="item.width">
        <template #default="scope">
          <!-- 编辑模式 -->
          <template v-if="scope.row.rowEdit">
            <el-input v-model="scope.row[item.prop]"></el-input>
          </template>
          <template v-else>
            <template v-if="scope.$index + scope.column.id === currentEdit">
              <div style="display: flex">
                <el-input v-model="scope.row[item.prop]"></el-input>
                <div>
                  <slot
                    v-if="$slots.cellEdit"
                    name="cellEdit"
                    :scope="scope"></slot>
                  <div class="action-icon" v-else>
                    <el-icon-check
                      class="check"
                      @click.stop="check(scope)"></el-icon-check>
                    <el-icon-close
                      class="close"
                      @click.stop="close(scope)"></el-icon-close>
                  </div>
                </div>
              </div>
            </template>
            <template v-else>
              <!-- slot是一个插槽出口,表示了父元素提供的插槽内容将在哪里被渲染 -->
              <slot v-if="item.slot" :name="item.slot" :scope="scope"></slot>
              <span v-else>{{ scope.row[item.prop] }}</span>
              <component
                v-if="item.editable"
                :is="`el-icon-${toLine(editIcon)}`"
                class="edit"
                @click.stop="clickEditIcon(scope)"></component>
            </template>
          </template>
        </template>
      </el-table-column>
    </template>
    <el-table-column
      :align="actionOption.align"
      :label="actionOption.label"
      :width="actionOption.width">
      <template #default="scope">
        <!-- 编辑模式下显示确认和取消 -->
        <slot name="editRow" :scope="scope" v-if="scope.row.rowEdit"></slot>
        <!-- 正常状态下显示 编辑和删除 -->
        <slot name="action" :scope="scope" v-else></slot>
      </template>
    </el-table-column>
  </el-table>
  <div
    v-if="pagination && !isLoading"
    class="pagination"
    :style="{ justifyContent }">
    <el-pagination
      v-model:currentPage="currentPage"
      :page-size="pageSize"
      :page-sizes="pageSizes"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
      layout="total, sizes, prev, pager, next, jumper"
      :total="total" />
  </div>
</template>
<script setup>
import cloneDeep from 'lodash/cloneDeep'
import { computed, onMounted, ref, watch } from 'vue'
import { toLine } from '../../../utils'
let props = defineProps({
  // 表格配置项
  options: {
    type: Array,
    required: true
  },
  // 表格数据
  data: {
    type: Array,
    required: true
  },
  // 编辑图标
  editIcon: {
    type: String,
    default: 'Edit'
  },
  // 显示在加载图标下方的加载文案
  elementLoadingText: {
    type: String
  },
  // 自定义加载图标
  elementLoadingSpinner: {
    type: String
  },
  // 自定义加载图标(svg)
  elementLoadingSvg: {
    type: String
  },
  // 自定义加载图标(svg)的配置
  elementLoadingSvgViewBox: {
    type: String
  },
  // 背景遮罩的颜色
  elementLoadingBackground: {
    type: String
  },
  // 是否可用编辑行
  isEditRow: {
    type: Boolean,
    default: false
  },
  // 编辑行按钮的标识
  editRowIndex: {
    type: String,
    default: ''
  },
  // 是否显示分页
  pagination: {
    type: Boolean,
    default: false
  },
  // 分页的对齐方式
  paginationAlign: {
    type: String,
    default: 'left'
  },
  // 当前第几页
  currentPage: {
    type: Number,
    default: 1
  },
  // 显示分页数据多少条的选项
  pageSizes: {
    type: Array,
    default: () => [10, 20, 30, 40]
  },
  // 数据总条数
  total: {
    type: Number,
    default: 0
  }
})

// 深拷贝一份表格的数据
let tableData = ref(cloneDeep(props.data))

let cloneEditRowIndex = ref(props.editRowIndex)
// 过滤操作项之后的配置
let tableOption = computed(() => props.options.filter((item) => !item.action))

let actionOption = computed(() => props.options.find((item) => item.action))

// 监听的标识
let watchData = ref<boolean>(false)

// 如果data的数据变了 要重新给tableData赋值
// 只需要监听一次就可以了
let stopWatchData = watch(
  () => props.data,
  (val) => {
    watchData.value = true
    tableData.value = val
    tableData.value.map((item) => {
      item.rowEdit = false
    })
    if (watchData.value) stopWatchData()
  },
  { deep: true }
)

watch(
  () => props.editRowIndex,
  (val) => {
    if (val) cloneEditRowIndex.value = val
  }
)

// 当前被点击的单元格的标志
let currentEdit = ref('')
let currentPage = computed(() => props.currentPage)
let justifyContent = computed(() => {
  if (props.paginationAlign === 'left') return 'flex-start'
  else if (props.paginationAlign === 'right') return 'flex-end'
  else return 'center'
})
let isLoading = computed(() => !props.data || !props.data.length)

let emits = defineEmits([
  'confirm',
  'cancel',
  'update:editRowIndex',
  'size-change',
  'current-change'
])

onMounted(() => {
  tableData.value.map((item) => {
    item.rowEdit = false
  })
})

let clickEditIcon = (scope) => {
  currentEdit.value = scope.$index + scope.column.id
}

let handleSizeChange = (val) => {
  emits('size-change', val)
}

let handleCurrentChange = (val) => {
  emits('current-change', val)
}

let check = (scope) => {
  emits('confirm', scope)
  currentEdit.value = ''
}

let close = (scope) => {
  emits('cancel', scope)
  currentEdit.value = ''
}

let rowClick = (row, column) => {
  if (column.label === actionOption.value.label) {
    if (props.isEditRow && cloneEditRowIndex.value === props.editRowIndex) {
      row.rowEdit = !row.rowEdit
      tableData.value.map((item) => {
        if (item !== row) item.rowEdit = false
      })
      if (!row.rowEdit) emits('update:editRowIndex', '')
    }
  }
}
</script>

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【RabbitMQ】RabbitMQ高级:死信队列和延迟队列

目录 设置TTL&#xff08;过期时间&#xff09;概述RabbitMQ使用TTL原生API案例springboot案例 死信队列概述原生API案例springboot案例 延迟队列概述插件实现延迟队列安装插件代码 TTL实现延迟队列实现延迟队列优化 设置TTL&#xff08;过期时间&#xff09; 概述 在电商平台…

Netty开篇——NIO章下(五)

SelectionKey 表示 Selector 和网络通道的注册关系&#xff0c;共四种(全是常量): Int OP_ACCEPT:有新的网络连接可以接受&#xff0c;值为 16 &#xff08;1 << 4&#xff09;Int OP_CONNECT: 代表连接已经建立&#xff0c;值为 8 &#xff08;1 << 3&#xff09;…

#AIGC#text2video文生视频,开源DragNUWA:通过集成文本、图像和轨迹对视频生成进行细粒度控制

DragNUWA&#xff1a;通过集成文本、图像和轨迹对视频生成进行细粒度控制 论文地址&#xff1a;https://arxiv.org/abs/2308.08089 DragNUWA 使用户能够直接操纵图像中的背景或对象&#xff0c;模型将这些动作无缝地转换为相机运动或对象运动&#xff0c;生成相应的视频。 Drag…

linux基础学习(3):挂载

挂载可以理解为给磁盘空间一个可访问的入口&#xff0c;那个入口称为挂载点&#xff0c;相当于windows中的盘符。 1.挂载命令mount 1.1直接输入mount 查看系统已挂载的设备 1.2挂载与卸载命令 mount -t 文件系统名 设备文件名 挂载点 | umount 挂载点 或 umount 设…

机器学习——支持向量机SVM

1 摘要&#xff1a; 支持向量机&#xff08;SVM&#xff09;是一种二类分类模型&#xff0c;其基本模型是在特征空间上找到最佳的分离超平面使得训练集上正负样本间隔最大&#xff0c;间隔最大使它有别于感知机&#xff0c;支持向量机也可通过核技巧使它成为非线性分类器。支持…

防火墙部署安全区域

目录 为什么需要安全区域在防火墙上如何来区分不同的网络将接口划分到安全区域安全区域、受信任程度与安全级别安全域间、安全策略与报文流动的方向 安全区域配置案例 为什么需要安全区域 防火墙主要部署在网络边界起到隔离的作用 在防火墙上如何来区分不同的网络 防火墙通过安…

MobaXterm无法上传文件处理

ssh能成功通过mobaxterm连接虚拟机但sftp上传失败的解决办法 1、出现问题时&#xff0c;/etc/ssh/sshd_config的配置文件关于sftp的这行下图所示的情况 2、更改配置文件/etc/ssh/sshd_config的配置文件关于sftp为“internal-sftp”。 3、执行命令systemctl restart sshd&…

leetcode82. 删除排序链表中的重复元素 II

文章目录 题目思路1复杂度Code2 思路2复杂度2Code2 题目 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,3,4,4,5] 输出&#xff1a;…

10.云原生之在线开发调试

云原生专栏大纲 文章目录 vscode-server介绍VSCode Server 和云开发结合vscode-server安装code-server安装插件在线安装插件离线安装插件安装中文插件 配置开发环境在容器中安装开放环境Dockerfile制作镜像 git拉取项目 vscode-server介绍 VSCode Server&#xff08;Visual S…

C++ 编程需要什么样的开发环境?

C 编程需要什么样的开发环境&#xff1f; 在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#…

k8s之pod基础(下)

k8s之pod基础&#xff08;下&#xff09; 存活探针和就绪探针&#xff0c;会伴随整个pod的生命周期 就绪探针的特点&#xff1a;pod的状态是running&#xff0c;ready状态是notready&#xff0c;容器不可以提供正常的业务访问&#xff0c;就绪探针不会重启容器 就绪探针exec的…

闲鱼宝库亮相!闲鱼商品详情关键词搜索电商API接口助你畅享无尽好货!

随着互联网的快速发展&#xff0c;电商平台的崛起已经改变了人们的购物习惯。而在众多电商平台中&#xff0c;闲鱼作为一款社区二手交易平台&#xff0c;一直备受用户喜爱。如今&#xff0c;闲鱼宝库正式亮相&#xff0c;为用户带来了更加全面、详细的商品详情关键词搜索电商AP…

IP地址冲突警告!你的网络正在受到威胁

IP地址冲突是网络安全中的一个严重问题&#xff0c;可能导致网络不稳定、数据泄漏等严重后果。本文将深入探讨IP地址冲突的原因、影响以及如何应对&#xff0c;以提醒用户关注网络安全问题。 1. IP地址冲突的原因&#xff1a; 动态分配问题&#xff1a;在使用动态IP地址分配的…

开发需求总结9-el-tree获取选中节点,节点全选时返回被全选子级的父节点,未全选则返回被选中的节点

目录 需求描述 代码实现&#xff1a; 需求描述 需要获取树组件选中的节点&#xff0c;假如父节点被选中&#xff08;该节点全选&#xff09;&#xff0c;即只返回父节点的数据&#xff0c;如父节点未被全选&#xff0c;则正常返回被选中节点的数据。 示例一&#xff1a; 如上图…

大众点评评论采集软件使用教程

导出字段&#xff1a; 店铺ID 评论ID 发布时间 人均消费 评分 详情链接 点赞数 浏览数 评论数 最后更新时间 发布平台 推荐 评论详情 原始评论 图片数 图片链接 用户等级 用户名称 用户头像 VIP 私

农业无人机行业分析:单年内作业量突破13亿亩次

面对我国18亿亩的耕地植保市场需求&#xff0c;未来我国植保无人机将依然保持快速发展态势&#xff0c;预计2022年我国植保无人机销量将增长至8万架。 植保无人机市场呈现爆发式增长&#xff0c;同时也吸引了不少企业进入&#xff0c;我们从2022年植保无人机企业网络热度榜中可…

Linux学习记录——사십일 高级IO(2)--- Select型服务器

文章目录 1、思路2、select接口3、实现1、准备工作2、实现等待多个fd3、辨别连接和简单处理读事件4、简单处理写、读事件 4、特点 1、思路 select就是多路转接IO。select能以某种形式&#xff0c;等待多个文件描述符&#xff0c;只要有哪个fd有数据就可以读取并全部返回。就绪…

服务异步通讯——springcloud

服务异步通讯——springcloud 文章目录 服务异步通讯——springcloud初始MQRabbitMQ快速入门单机部署1.1.下载镜像安装MQ SpringAMQPwork Queue 工作队列Fanout Exchange广播模式DirectExchange路由模式TopicExchange话题模式 消息转换器 初始MQ RabbitMQ快速入门 官网https:/…

手把手教你SWOT分析!建议收藏

最近&#xff0c;我一直为一件事情感到困扰。那家位于市中心的西点店生意越来越好&#xff0c;甚至已经开了两家分店&#xff0c;但是挣来的钱还不足够买房子。于是最近&#xff0c;我被这如火如荼的奶茶市场所吸引&#xff0c;想要利用已有的资源开一家奶茶店。但是我不确定这…

计算机视觉开发工程师怎么考?报考难度大吗?证书含金量高吗?

为进一步贯彻落实中共中央印发《关于深化人才发展体制机制改革的意见》和国务院印发《关于“十四五”数字经济发展规划》等有关工作的部署要求&#xff0c;深入实施人才强国战略和创新驱动发展战略&#xff0c;加强全国数字化人才队伍建设&#xff0c;持续推进人工智能专业人员…
最新文章