前端封装el-table及注意点

table封装文件

html 复制代码
<template>
  <div class="tableBox">
    <el-table :data="props.tData" style="width: 100%" height="100%" :border="props.border" 
      stripe highlight-current-row ref="tableRef" 
      @selection-change="handleSelectionChange">
      <!-- 多选列 -->
      <el-table-column type="selection" width="55" v-if="props.showSelection" />
      <!-- 序号 -->
      <el-table-column label="序号" width="60" align="center" v-if="props.showIndex">
        <template #default="{$index}">
          {{ (props.pageNum -1) * props.pageSize + $index +1 }}
        </template>
      </el-table-column>

      <el-table-column v-for="(item, index) in props.configColumns" :key="index" 
        :prop="item.prop" :label="item.label" :width="item.width" show-overflow-tooltip align="center">
        <!-- 自定义列 -->
         <template #default="{row}">
          <slot :name="`col-${item.prop}`" :row="row">{{ row[item.prop] }}</slot>
         </template>
      </el-table-column>
      <!-- 操作列 -->
      <el-table-column label="操作" v-if="$slots['action']" :width="props.actionWidth || 180" align="center">
        <template #default="{row}">
          <slot name="action" :row="row"></slot>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup>
import { ref, onMounted, reactive, } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus';

const tableRef = ref()

const emit = defineEmits(['selection-change'])

const props = defineProps({
  tData:{
    type: Array,
    default: () => []
  },
  configColumns:{
    type: Array,
    default: () => []
  },
  border:{
    type: Boolean,
    default: false,
  },
  actionWidth:{
    type: Number,
    default: 180,
  },
  showIndex:{
    type:Boolean,
    default: false,
  },
  showSelection:{
    type:Boolean,
    default: false,
  },
  pageNum:{
    type: Number,
    default: 1,
  },
  pageSize:{
    type: Number,
    default: 10,
  },
})

const handleSelectionChange = (selectedRows)=>{
  emit('selection-change', selectedRows)
}

const tableClearSelection =()=>{
  tableRef.value.clearSelection()
}

defineExpose({ tableClearSelection })

const state = reactive({})

onMounted(()=>{})
</script>

<style lang="scss" scoped>
.tableBox{
  margin-bottom: 1%;
}

</style>

组件中使用

html 复制代码
<el-button type="info" plain @click="resetHandle">重置</el-button>
<el-button type="primary" @click.stop="deleteBatchHandle">批量删除</el-button>


<div class="tableBox">
  <fe-table :tData="tableData" :configColumns="csOptions" border    
    :showIndex="true" :showSelection="true" :pageNum="page.pageNum" :pageSize="page.pageSize"
    @selection-change="onSelectionChange" ref="feTableRef">
    <!-- 自定义列 -->
    <template #col-cslb="{row}">
      <el-tag :type="row.cslb === 0 ? 'success' : 'danger'">
        {{ row.cslb === 0 ? '启用' : '停用' }}
      </el-tag>
    </template>
     <!-- 自定义列(添加颜色) -->
    <template #col-csmc="{ row }">
      <span style="color:#1890ff; font-weight: bold;">
        {{ row.csmc }}
      </span>
    </template>

    <!-- 操作列 -->
     <template #action="{row}" :width="props.actionWidth || 180">
      <el-button type="primary" link @click="deleteHandle(row)">删除</el-button>
      <el-button type="primary" link @click="seeHandle(row)">查看</el-button>
     </template>
  </fe-table>
</div>

<div class="pageBox">
  <el-pagination background @size-change="sizeChange" @current-change="currentChange"
    :current-page="page.pageNum" :page-size="page.pageSize" :page-sizes="[10, 20, 50, 100,]"
    layout="total,sizes,prev,pager,next,jumper" :total="total">
  </el-pagination>
</div>

<script setup>
import { ref, onMounted, reactive, } from 'vue'
import FeTable from '@/components/fe-table/fe-table.vue'

const feTableRef = ref()
const tableData = ref([
  { id:'0', csmc:'名称', cslb: 0, csbh:'编号', ssfj:'单位', },
  { id:'1', csmc:'名称', cslb: 1, csbh:'编号', ssfj:'单位', },
  { id:'2', csmc:'名称', cslb: 0, csbh:'编号', ssfj:'单位', },
  { id:'3', csmc:'名称', cslb: 1, csbh:'编号', ssfj:'单位', },
  { id:'4', csmc:'名称', cslb: 1, csbh:'编号', ssfj:'单位', },
  { id:'5', csmc:'名称', cslb: 1, csbh:'编号', ssfj:'单位', },
  { id:'6', csmc:'名称', cslb: 0, csbh:'编号', ssfj:'单位', },
])

const csOptions = ref([
  { prop: 'csmc', label: '场所名称' },
  { prop: 'cslb', label: '场所类别' },
  { prop:'csbh', label:'场所编号' },
  { prop: 'ssfj', label: '所属单位' },
])

const total = ref(0)
const page = reactive({
  pageSize: 10,
  pageNum: 1,
})

const state = reactive({
  selectList:[]
})

const sizeChange = (val) => {
  page.pageSize = val
}

const currentChange = (val) => {
  page.pageNum = val
}

const onSelectionChange = (selectList)=>{
  console.log(selectList, '选中行')
  state.selectList = selectList
}

// 清空表格多选(比如切换分页、查询重置时调用)
const resetHandle = () => {
  feTableRef.value.tableClearSelection() // 清空表格多选
}

//批量删除
const deleteBatchHandle = () => {
  if(state.selectList.length == 0){
    ElMessage.warning('请选择至少一条数据')
    return 
  }
  const selectIds = state.selectList.map(el=> el.id)
  
  ElMessageBox.confirm('此操作将删除该数据,是否继续?','提示',{
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type:'warning'
  }).then(()=>{
    console.log('可以发请求了')
    // xxxxAPI({ "idList": ids }).then(res=>{
    //   console.log(res.data, 'ddd')
    //   if(res.data.code == 200){
    //     ElMessage.success(res.data.msg)
    //     getList()
    //   }else{
    //     ElMessage.error(res.data.msg)
    //   }
    // })    
  })
}

</script>

涉及的知识点:

  1. 连续序号
    (pageNum‑1)*pageSize + $index +1
    切换分页时,序号不会从 1 重新开始,分页组件里修改 pageNum、pageSize 传给 fe‑table 即可。
  2. 不同页面不同单元格渲染(动态命名插槽
    插槽命名规则:#col-字段名 ,哪个页面需要特殊渲染,就写对应插槽,不需要就不写,自动展示原始值。
  3. 操作列每个页面独立
    封装组件只预留插槽,按钮、事件全部在业务页面写;
    多个业务页面引入 fe‑table,各自写自己的 #action,互不干扰。
一、详解 动态命名插槽:
html 复制代码
<!-- fe-table 组件封装里的写法 -->
<!-- 自定义列 -->
<template #default="{row}">
  <slot :name="`col-${item.prop}`" :row="row">{{ row[item.prop] }}</slot>
</template>


<!-- 业务组件中使用时的写法 -->
<!-- 只针对 effectFlag 字段进行单元格渲染 -->
<template #col-effectFlag="{ row }">
  <span>{{ row.effectFlag === '0' ? '启用' : '停用' }}</span>
</template>
  • col-${item.prop} 是动态插槽名(col是前缀,根据需求可以自主命名)
  • item.prop 就是列配置里的字段名,例如 effectFlag、name、createTime
    当 item.prop = "name" → 实际插槽名字:col-name
    当 item.prop = "effectFlag" → 实际插槽名字:col-effectFlag
  • { row } 是插槽传出来的行对象,可以拿到当前行全部数据。
    name、createTime 没有写对应插槽,就会走组件内部默认 {{ row.name }},原样输出。

坑点: 插槽名严格匹配,col‑effectFlag,不能写错大小写;prop 是什么,插槽后缀就是什么。

二、外部传入操作列插槽 action

场景:表格的操作列,每个页面按钮完全不一样

页面 A:编辑、删除

页面 B:查看、复制、导出

页面 C:审核、驳回

使用方法:

  1. fe-table组件里写
html 复制代码
<!-- 如果外部使用了 table‑operate 插槽,才渲染这一列 -->
<el-table-column label="操作" :width="props.actionWidth || 180" v-if="$slots['action']" align="center">
  <template #default="{ row }">
    <!-- 把 row 行数据透传给外部插槽,外部拿row做按钮事件 -->
    <slot name="action" :row="row"></slot>
  </template>
</el-table-column>

action 是自己起的名字,你也可以叫 table-operate 或者其他;

$slots['action'] 是Vue内置,判断父组件有没有传入这个名字的插槽。没有就不渲染操作列。

如果某个页面不需要操作列:直接不写 #action 模板,组件内部 v‑if="$slots['action']" 自动隐藏操作列

  1. 业务页面使用
html 复制代码
<!-- A页面使用 -->
<fe-table :tData="tableData" :configColumns="csOptions">
   ......
  <!-- 操作列,当前页面自己写按钮和事件 -->
  <template #action="{ row }">
    <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
    <el-button link type="danger" @click="handleDel(row)">删除</el-button>
  </template>
</fe-table>

<!-- B页面使用 -->
<fe-table :tData="tableData2" :configColumns="csOptions2">
	......
  <template #action="{ row }">
    <el-button link>查看</el-button>
    <el-button link>复制</el-button>
    <el-button link>审核</el-button>
  </template>
</fe-table>

最后,两者对比总结

类型 动态字段插槽 column‑xxx 操作列插槽 action
用途 普通单元格自定义渲染(状态转换、标签、格式化) 整列操作按钮(编辑 / 删除 / 审核)
插槽命名 根据 prop 动态生成:#column‑prop值 固定名字 #action
触发条件 需要特殊格式化的列才写,不写用默认值 需要操作按钮才写,不写整列消失
传参 { row } 当前行数据 { row } 当前行数据
组件改动 组件只写兜底显示,业务接管覆盖 组件只渲染外壳列,按钮全部交给业务

常见踩坑

  • prop 大小写要和插槽后缀完全一致:prop:effectFlag →插槽 #column-effectFlag,不能写成 #column‑EffectFlag。
  • 动态插槽语法:v-slot:[`column-${item.prop}`],里面是反引号,不是单引号。
  • 插槽解构 {row} 不要丢,否则拿不到行数据。
  • 操作列:不写插槽模板,就不会出现操作列,不需要手动加 v‑if 控制。
相关推荐
计算机魔术师6 分钟前
德国Wiki被黑后两周,OpenAI终于把模型失控的账本摊开了
前端
kyriewen36 分钟前
我让 AI 当面试官面了我一轮:第 3 个追问我就卡住了(附 10 道追问清单)
前端·面试·ai编程
IT_陈寒1 小时前
Python的GIL把我坑惨了,多线程跑得比单线程还慢
前端·人工智能·后端
前端snow1 小时前
ai agent --- 多agent框架之图编排引擎-langgraph
前端
竹林8181 小时前
OmniPic Studio v3.2.1 核心技术架构与全平台发版解析文档
前端·浏览器
JamesZhang800781 小时前
页面内存只涨不跌? 一次泄漏排查, 牵出 WeakMap 的诞生
前端
Z小明1 小时前
第 6 章 组件进阶
前端·vue.js
江华森1 小时前
HTTP请求的完整过程详解:从DNS解析到TCP挥手的微秒级实战分析
前端
南青1 小时前
Vue 3 中后台实战:我踩过的 10 个坑和最佳实践
前端
江华森1 小时前
HTTPS协议详解——SSL/TLS握手、证书与加密通信
前端