vue+el-table:打造可展开收缩的表格组件

该文章已生成可运行项目,

前言

在前端开发中,表格是展示数据的常用组件。当表格数据量较大时,合理控制表格的展示方式,既能提升用户体验,又能优化页面布局。本文将基于 vueel-table 组件,详细剖析一个可展开、收起的表格组件的实现过程。


实现思路

以下代码就实现了一个可展开和收起的表格功能,主要用于展示数据列表,并在数据量较多时提供展开和收起的交互方式,以节省页面空间。以下是具体的实现思路:

  • 模板部分(html):
    1. 使用 el-table 组件来展示数据,通过 :data 绑定 tableData 数组;
    2. 通过 v-if 条件判断 shouldShowToggleButton 来决定是否显示展开 / 收起按钮;
    3. 按钮部分通过 @click 绑定 toggleExpand 方法来切换展开 / 收起状态,同时通过 @mouseenter@mouseleave 绑定事件来控制鼠标悬停状态;
    4. 根据 isExpanded 的值来显示不同的图标(el-icon-caret-topel-icon-caret-bottom),并在鼠标悬停时根据 hover 状态显示或隐藏文字提示。

  • 数据定义(data):
    1. tableData 是一个包含多个对象的数组,用于模拟表格数据;
    2. isExpanded 用于记录表格当前是展开还是收起状态,初始值为 false(收起);
    3. hover 用于记录鼠标是否悬停在按钮上,初始值为 false

  • 脚本部分(js):
    • 计算属性(computed):
      1. computedStyle 根据 isExpandedshouldShowToggleButton 的值来动态计算表格的样式,包括最大高度和底部内边距;
      2. shouldShowToggleButton 判断 tableData 的长度是否大于 3,决定是否显示展开 / 收起按钮。
    • 方法定义(methods):
      1. toggleExpand 方法用于切换 isExpanded 的值,从而实现表格的展开和收起。

  • 样式部分(css):

    1. 定义了 .chartTable.tableWrapper 的样式,用于布局和控制表格的滚动;
    2. 定义了 .toggleBtn 的样式,包括按钮的位置、颜色、背景等,以及鼠标悬停时的样式变化;
    3. 定义了按钮内图标和文字提示的样式,通过 transformopacity 属性实现动画效果;
    4. 使用 ::-webkit-scrollbar 隐藏滚动条,并通过 ::v-deep 修改 el-table 单元格的内边距。

完整代码

<template>
  <div class="chartTable">
    <div class="tableWrapper" :style="computedStyle">
      <el-table :data="tableData" border>
        <el-table-column prop="date" label="参数名称"></el-table-column>
        <el-table-column prop="name" label="总数量"></el-table-column>
        <el-table-column prop="address" label="超规格数量"></el-table-column>
      </el-table>
    </div>
    <div v-if="shouldShowToggleButton" class="toggleBtn" @click="toggleExpand" @mouseenter="hover = true" @mouseleave="hover = false">
      <i :class="isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i>
      <span class="text" :class="{ show: hover }"> {{ isExpanded ? "收起表格" : "展开表格" }}</span>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        {
          date: "2023-11-15",
          name: "李小红",
          address: "北京市朝阳区朝阳路 223 号",
        },
        {
          date: "2024-03-20",
          name: "张宇",
          address: "广州市天河区珠江新城花城大道 33 号",
        },
        {
          date: "2022-07-08",
          name: "王建国",
          address: "深圳市福田区深南大道 5002 号",
        },
        {
          date: "2025-01-25",
          name: "刘佳",
          address: "成都市武侯区武侯祠大街 231 号",
        },
        {
          date: "2023-09-12",
          name: "赵晓峰",
          address: "杭州市西湖区文三路 477 号",
        },
        {
          date: "2024-12-05",
          name: "孙丽",
          address: "南京市鼓楼区中山北路 200 号",
        },
        {
          date: "2025-02-18",
          name: "周阳",
          address: "武汉市武昌区中南路 14 号",
        },
      ], // 模拟数据
      isExpanded: false, // 表格展开/收起状态
      hover: false, // 鼠标悬停状态
    };
  },
  computed: {
    // 表格样式
    computedStyle() {
      return {
        maxHeight: this.isExpanded ? "none" : "200px",
        paddingBottom: this.shouldShowToggleButton ? "40px" : "0px",
      };
    },
    // 判断是否应显示展开/收起按钮
    shouldShowToggleButton() {
      return this.tableData.length > 3;
    },
  },
  methods: {
    // 切换表格展开/收起状态
    toggleExpand() {
      this.isExpanded = !this.isExpanded;
    },
  },
};
</script>
<style scoped>
.chartTable {
  margin: 16px;
  position: relative;
}
.tableWrapper {
  overflow: auto;
}
.toggleBtn {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: absolute;
  z-index: 99;
  height: 40px;
  line-height: 40px;
  width: 100%;
  bottom: 0px;
  color: #d3dce6;
  background: #f5f7fa;
  overflow: hidden;
  border: 1px solid #ebeef5;
  border-top: none;
}
.toggleBtn i {
  font-size: 18px;
  transition: all 0.3s ease;
  position: relative;
}
.toggleBtn:hover {
  color: rgb(64, 158, 255);
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5);
}
.toggleBtn:hover i {
  transform: translateX(-20px);
}
.text {
  position: absolute;
  bottom: -1px;
  opacity: 0;
  transform: translateX(30px);
  transition: all 0.3s ease;
  white-space: nowrap;
  left: calc(50%);
}
.text.show {
  opacity: 1;
  transform: translateX(0);
}
::-webkit-scrollbar {
  width: 0;
}
::v-deep .el-table .el-table__cell {
  padding: 8px 0;
}
</style>

实现效果
在这里插入图片描述


封装公共组件

考虑到项目中可能多个地方都需要此功能,到时候还需要重复编写相同的代码,所以不如直接将其封装成一个公共组件进行使用。

封装的公共文件

<template>
  <div class="tableExpandWrapper">
    <div class="tableWrapper" :style="computedStyle">
      <slot></slot>
    </div>
    <div v-if="shouldShowToggleButton" class="toggleBtn" @click="toggleExpand" @mouseenter="hover = true" @mouseleave="hover = false">
      <i :class="isExpanded ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"></i>
      <span class="text" :class="{ show: hover }">
        {{ isExpanded ? collapseText : expandText }}
      </span>
    </div>
  </div>
</template>

<script>
export default {
  name: "TableExpandWrapper",
  props: {
    // 表格数据长度,用于判断是否需要显示展开/收起按钮
    dataLength: {
      type: Number,
      default: 0,
    },
    // 折叠高度
    collapsedHeight: {
      type: String,
      default: "200px",
    },
    // 展开按钮文字
    expandText: {
      type: String,
      default: "展开表格",
    },
    // 收起按钮文字
    collapseText: {
      type: String,
      default: "收起表格",
    },
    // 显示按钮的阈值(数据长度超过此值才显示按钮)
    threshold: {
      type: Number,
      default: 3,
    },
  },
  data() {
    return {
      isExpanded: false, // 表格展开/收起状态
      hover: false, // 鼠标悬停状态
    };
  },
  computed: {
    // 表格样式
    computedStyle() {
      return {
        maxHeight: this.isExpanded ? "none" : this.collapsedHeight,
        paddingBottom: this.shouldShowToggleButton ? "40px" : "0px",
      };
    },
    // 判断是否应显示展开/收起按钮
    shouldShowToggleButton() {
      return this.dataLength > this.threshold;
    },
  },
  methods: {
    // 切换表格展开/收起状态
    toggleExpand() {
      this.isExpanded = !this.isExpanded;
      this.$emit("toggle", this.isExpanded);
    },
  },
};
</script>

<style scoped>
.tableExpandWrapper {
  margin: 16px;
  position: relative;
}

.tableWrapper {
  overflow: auto;
}

.toggleBtn {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: absolute;
  z-index: 99;
  height: 40px;
  line-height: 40px;
  width: 100%;
  bottom: 0px;
  color: #d3dce6;
  background: #f5f7fa;
  overflow: hidden;
  border: 1px solid #ebeef5;
  border-top: none;
}

.toggleBtn i {
  font-size: 18px;
  transition: all 0.3s ease;
  position: relative;
}

.toggleBtn:hover {
  color: rgb(64, 158, 255);
  box-shadow: 0 0 8px 0 rgba(232, 237, 250, 0.6), 0 2px 4px 0 rgba(232, 237, 250, 0.5);
}

.toggleBtn:hover i {
  transform: translateX(-20px);
}

.text {
  position: absolute;
  bottom: -1px;
  opacity: 0;
  transform: translateX(30px);
  transition: all 0.3s ease;
  white-space: nowrap;
  left: calc(50%);
}

.text.show {
  opacity: 1;
  transform: translateX(0);
}

.tableWrapper::-webkit-scrollbar {
  width: 0;
}
::v-deep .el-table .el-table__cell {
  padding: 8px 0;
}
</style>

任意使用文件

<template>
  <div>
    <TableExpandWrapper :data-length="tableData.length">
      <el-table :data="tableData" border>
        <el-table-column prop="date" label="日期"></el-table-column>
        <el-table-column prop="name" label="姓名"></el-table-column>
        <el-table-column prop="address" label="地址"></el-table-column>
      </el-table>
    </TableExpandWrapper>
  </div>
</template>

<script>
import TableExpandWrapper from '@/components/TableExpandWrapper.vue'
export default {
  components: {
    TableExpandWrapper,
  },
  data() {
    return {
      tableData: [
        {
          date: "2023-11-15",
          name: "李小红",
          address: "北京市朝阳区朝阳路 223 号",
        },
        {
          date: "2024-03-20",
          name: "张宇",
          address: "广州市天河区珠江新城花城大道 33 号",
        },
        {
          date: "2022-07-08",
          name: "王建国",
          address: "深圳市福田区深南大道 5002 号",
        },
        {
          date: "2025-01-25",
          name: "刘佳",
          address: "成都市武侯区武侯祠大街 231 号",
        },
        {
          date: "2023-09-12",
          name: "赵晓峰",
          address: "杭州市西湖区文三路 477 号",
        },
        {
          date: "2024-12-05",
          name: "孙丽",
          address: "南京市鼓楼区中山北路 200 号",
        },
        {
          date: "2025-02-18",
          name: "周阳",
          address: "武汉市武昌区中南路 14 号",
        },
      ],
    };
  },
};
</script>

封装成公共组件有以下几个重要原因:

  • 代码复用 (DRY 原则)

    1. 避免重复:项目中多个地方需要相同功能时,不需要重复编写相同代码;
    2. 一处修改全局生效:如果需要调整展开/收起逻辑或样式,只需修改公共组件一处。
  • 关注点分离

    1. 单一职责原则:组件只负责表格的展开/收起功能,不关心表格具体内容和业务逻辑;
    2. 降低耦合度:表格内容通过插槽注入,组件不依赖具体表格实现。
  • 统一用户体验

    1. 一致性:保证整个项目中所有可展开表格的行为和视觉风格一致;
    2. 标准化:提供统一的交互方式和动画效果。
  • 可维护性提升

    1. 逻辑集中:所有展开/收起相关代码集中管理,便于维护和调试;
    2. 接口清晰:通过明确的 props 和事件与父组件通信,降低理解成本。
  • 灵活性增强

    1. 可配置性:通过 props 暴露关键参数(如折叠高度、阈值等),适应不同场景需求;
    2. 扩展性:未来添加新功能(如动画效果、记住状态等)只需修改组件内部。
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

水星记_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值