vue2+element-UI表格封装

针对表格进行封装,在列表页面直接传入字段数组就可以展示数据表:

javascript 复制代码
<template>
  <div class="table-container" :class="{ 'show-vertical-lines': showVerticalLines }">
    <!-- 数据表格 -->
    <el-table 
      ref="tableRef"
      :data="tableData" 
      border 
      size="mini" 
      style="width: 100%" 
      :max-height="tableHeight" 
      row-key="id" 
      :row-class-name="rowClassName"
      highlight-current-row
    >
      <!-- 单选框列 -->
      <el-table-column v-if="showRadio" width="55" align="center">
        <template slot-scope="scope">
          <el-radio v-model="radioSelection" :label="scope.row.id" @change="handleRadioChange(scope.row)">
            &nbsp;
          </el-radio>
        </template>
      </el-table-column>
      
      <!-- 多选框列 -->
      <el-table-column
        v-if="showSelection"
        type="selection"
        width="55"
        align="center"
      ></el-table-column>
      
      <!-- 序号列 -->
      <el-table-column
        v-if="showIndex"
        type="index"
        label="序号"
        width="60"
        align="center"
      >
        <template slot-scope="scope">
          {{ scope.$index + 1 }}
        </template>
      </el-table-column>
      
      <!-- 动态列 -->
      <el-table-column
        v-for="(column, index) in columns"
        :key="column.prop || index"
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        :min-width="column.minWidth || 80"
        :header-align="column.headerAlign || 'left'"
        :align="column.align || 'left'"
        :fixed="column.fixed"
      >
        <template slot-scope="scope">
          <!-- 作用域插槽:允许父组件自定义内容 -->
          <slot :name="column.prop" :row="scope.row" :editable="isRowEditable(scope.row)">
            <!-- 默认渲染逻辑 -->
            <template v-if="isRowEditable(scope.row) && column.editable === true">
              <el-input 
                v-model="scope.row[column.prop]" 
                size="small" 
                @blur="handleInputBlur(scope.row, column.prop)"
                @click.native.stop
              ></el-input>
            </template>
            <template v-else>
              <!-- 超长文本省略 -->
              <el-tooltip
                v-if="scope.row[column.prop] && scope.row[column.prop].toString().length > 100"
                :content="scope.row[column.prop]"
                placement="top"
                effect="dark"
              >
                <div class="ellipsis-text">{{ scope.row[column.prop] }}</div>
              </el-tooltip>
              <span v-else>{{ scope.row[column.prop] }}</span>
            </template>
          </slot>
        </template>
      </el-table-column>
    </el-table>
    
    <!-- 分页 -->
    <div class="pagination-fixed">
      <el-pagination
        @size-change="$emit('size-change', $event)"
        @current-change="$emit('current-change', $event)"
        :current-page="currentPage"
        :page-sizes="[10, 20, 50, 100]"
        :page-size="pageSize"
        layout="total, sizes, prev, pager, next, jumper"
        :total="total"
        background
        style="text-align:right;"
      ></el-pagination>
    </div>
  </div>
</template>

<script>
export default {
  name: "TableContainer",
  props: {
    tableData: { type: Array, default: () => [] },
    columns: { type: Array, default: () => [] },
    currentPage: { type: Number, default: 1 },
    pageSize: { type: Number, default: 10 },
    total: { type: Number, default: 0 },
    showSelection: { type: Boolean, default: false },
    showRadio: { type: Boolean, default: false },
    showIndex: { type: Boolean, default: false },
    showVerticalLines: { type: Boolean, default: false },
    // 新增:允许父组件传入表格距离顶部的偏移量,默认 260px
    offsetTop: { type: Number, default: 260 } 
  },
  data() {
    return {
      radioSelection: null,
      tempBackup: null,        // 新增:用于存储当前正在编辑行的快照
      tableHeight: 400
    };
  },
  mounted() {
    this.$nextTick(() => {
      this.calculateTableHeight();
    });
    window.addEventListener('resize', this.calculateTableHeight);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.calculateTableHeight);
  },
  methods: {
    rowClassName({ row }) {
      return this.radioSelection === row.id ? 'radio-selected' : '';
    },
    isRowEditable(row) {
      return this.radioSelection === row.id;
    },
    // 核心逻辑:处理单选框切换
    handleRadioChange(currentRow) {
      // 1. 恢复上一行的数据 (如果存在上一行且备份存在)
      if (this.tempBackup) {
        // 找到表格数据中对应的那一行对象
        const prevRowData = this.tableData.find(item => item.id === this.tempBackup.id);
        if (prevRowData) {
          // 用备份的数据覆盖当前表格中的数据(撤销修改)
          Object.assign(prevRowData, JSON.parse(JSON.stringify(this.tempBackup)));
        }
      }

      // 2. 为当前点击的这一行创建新的备份
      // 注意:这里必须深拷贝,否则 tempBackup 会跟着表格数据一起变
      this.tempBackup = JSON.parse(JSON.stringify(currentRow));

      // 3. 触发外部事件
      this.$emit('radio-change', currentRow);
    },
    handleInputBlur(row, prop) {
      this.$emit('input-blur', row, prop);
    },
    calculateTableHeight() {
      // 方式一:基于窗口高度减去固定偏移量(简单粗暴)
      // this.tableHeight = window.innerHeight - this.offsetTop;

      // 方式二:更精准的计算(推荐)
      // 获取表格容器相对于视口的位置
      const rect = this.$el.getBoundingClientRect();
      // 视口高度 - 表格顶部距离 - 分页高度(约50) - 底部留白(20)
      this.tableHeight = window.innerHeight - rect.top - 70;

      // 设置最小高度防止过小
      if (this.tableHeight < 200) this.tableHeight = 200;
    }
  },
  emits: ['size-change', 'current-change', 'radio-change', 'input-blur']
};
</script>

<style scoped>
.table-container {
  width: 100%;
  position: relative;
  background-color: #fff;
  /* border-radius: 4px; */
  /* box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); */
  overflow: hidden;
}

.el-table {
  width: 100%;
  margin: 0;
}

/* 核心调整:行高缩小至 24px,与文字大小一致 */
.el-table__row {
  height: 24px !important;
}

.el-table__cell {
  padding: 0 12px; /* 上下padding设为0,仅保留左右 */
  line-height: 1.2;
  vertical-align: middle; /* 确保垂直居中 */
}

/* 确保表头高度也同步缩小 */
::v-deep .el-table th {
  height: 24px !important;
  line-height: 24px !important;
  background-color: #f0f2f5 !important;
  font-weight: 600 !important;
  user-select: text !important;
}

/* 消除单元格底部多余边框间距 */
::v-deep .el-table--border::after, 
::v-deep .el-table--group::after, 
::v-deep .el-table::before {
  height: 0;
}

.pagination-fixed {
  margin-top: 0;
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 6px 0;
  background-color: #fff;
}

.banner-image {
  max-width: 100px;
  max-height: 50px;
}

.radio-selected {
  background-color: #f0f9eb !important;
}

.ellipsis-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
  display: inline-block;
}

/* 隐藏表格竖线 */
.table-container:not(.show-vertical-lines) ::v-deep .el-table th,
.table-container:not(.show-vertical-lines) ::v-deep .el-table td {
  border-right: none !important;
  border-left: none !important;
}

.table-container:not(.show-vertical-lines) ::v-deep .el-table--border, 
.table-container:not(.show-vertical-lines) ::v-deep .el-table--group {
  border-right: none !important;
  border-left: none !important;
}
</style>

传入字段数组格式:

javascript 复制代码
data() {
    return {
      tableData: [],
      columns: [
        { prop: 'title', label: '标题', minWidth: 80 },
        { prop: 'introduction', label: '内容简介', minWidth: 200 },
        { prop: 'publishTime', label: '发布时间', minWidth: 80 },
        { prop: 'status', label: '状态', minWidth: 50 },
        { prop: 'createTime', label: '创建时间', minWidth: 80 },
        { prop: 'createBy', label: '创建人', minWidth: 80 },
        { prop: 'action', label: '操作', minWidth: 80, headerAlign: 'center', align: 'center' }
      ],
      currentPage: 1,
      pageSize: 10,
      total: 0,
    };
  },

调用方式:

javascript 复制代码
<table-container
        :table-data="tableData"
        :columns="columns"
        :current-page="currentPage"
        :page-size="pageSize"
        :total="total"
        :base-url="baseUrl"
        :show-selection="false"
        :show-index="true"
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
      >
        <template #status="{ row }">
          <span v-if="row.status === 1" style="color:#13ce66">已发布</span>
          <span v-else style="color:#ff4949">未发布</span>
        </template>
        <template #createBy="{ row }">
          <span v-if="row.createBy">
            {{ getCreateUser(row) }}
          </span>
        </template>
        <template #action="{ row }">
          <el-button v-if="row.status === 0" type="text" @click="handleEdit(row)">编辑</el-button>
          <span v-if="row.status === 0" style="margin:0 8px;color:#ddd">|</span>
          <el-button v-if="row.status === 0" type="text" @click="handlePublish(row)">发布</el-button>
          <span v-if="row.status === 0" style="margin:0 8px;color:#ddd">|</span>
          <el-button v-if="row.status !== 0" type="text" @click="handlePreview(row)">预览</el-button>
          <span v-if="row.status !== 0" style="margin:0 8px;color:#ddd">|</span>
          <el-button type="text" @click="handleDelete(row.id)">删除</el-button>
        </template>
      </table-container>
    </div>
相关推荐
这儿有一堆花2 小时前
深入解析 Video.js:现代 Web 视频播放的工程实践
前端·javascript·音视频
烤麻辣烫2 小时前
JS基础
开发语言·前端·javascript·学习
猫猫不是喵喵.4 小时前
layui表单项次大数据量导入并提交
前端·javascript·layui
Hello--_--World5 小时前
ES13:类私有属性和方法、顶层 await、at() 方法、Object.hasOwnProperty()、类静态块 相关知识点
开发语言·javascript·es13
comerzhang6555 小时前
Web 性能的架构边界:跨线程信令通道的确定性分析
javascript·webassembly
Ruihong5 小时前
Vue v-bind 转 React:VuReact 怎么处理?
vue.js·react.js·面试
zhensherlock6 小时前
Protocol Launcher 系列:Overcast 一键订阅播客
前端·javascript·typescript·node.js·自动化·github·js
px不是xp7 小时前
DeepSeek API集成:让小程序拥有AI大脑
javascript·人工智能·小程序
前端那点事8 小时前
Vue插槽用法全解析(Vue2+Vue3适配)| 组件复用必备
vue.js