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>
涉及的知识点:
- 连续序号
(pageNum‑1)*pageSize + $index +1
切换分页时,序号不会从 1 重新开始,分页组件里修改 pageNum、pageSize 传给 fe‑table 即可。 - 不同页面不同单元格渲染(动态命名插槽 )
插槽命名规则:#col-字段名,哪个页面需要特殊渲染,就写对应插槽,不需要就不写,自动展示原始值。 - 操作列每个页面独立
封装组件只预留插槽,按钮、事件全部在业务页面写;
多个业务页面引入 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:审核、驳回
使用方法:
- 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']" 自动隐藏操作列
- 业务页面使用
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 控制。