前端封装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 控制。
相关推荐
晨枫阳32 分钟前
Vue 前端构建工具链完全理解指南
前端·javascript·vue.js
言乐633 分钟前
Python关于老游复刻与创新2
前端·javascript·css·python·css3
ly768933 分钟前
前端测试完整指南:从单元测试、组件测试到端到端测试与 CI/CD
前端·ci/cd·单元测试
钛态9 小时前
Vite 中的 CSS 工程化:从 CSS Modules 到 UnoCSS 的渐进式迁移
前端·vue·react·web
Csvn11 小时前
原型链、this 与闭包内存:三座深水区一次打通(含内存泄漏排查实战)
前端·javascript
葡萄城技术团队11 小时前
如何通过 JSON 数据源 / Web API 快速构建轻量级报表(Report)?
前端·json·原型模式
万维易源11 小时前
趣味项目素材|免费脑筋急转弯查询接口实战接入教程
javascript·免费api·脑筋急转弯查询·脑筋急转弯接口
浪兎兎11 小时前
【Java Web】Servlet + JSP 笔记
java·前端·servlet