实现支持作用域插槽的通用数据表格组件
在 Vue 中,作用域插槽(Scoped Slots) 是让父组件自定义子组件内部渲染的利器。下面实现一个 <DataTable> 组件,它接收 data(行数据)和 columns(列配置)数组,并在每个 <td> 中提供插槽,让父组件按列自定义单元格内容。
1. 子组件:DataTable.vue(Vue 3 + <script setup>)
- 核心逻辑 :
- 遍历
columns,为每一列生成<th>。 - 遍历
data生成<tr>,再遍历columns生成<td>。 - 每个
<td>内,优先检查父组件是否传递了对应列的具名插槽 (以列字段field命名),若存在则使用插槽并传递row、column、index等作用域数据;否则显示默认的row[field]值。
- 遍历
vue
<template>
<table class="data-table">
<thead>
<tr>
<th v-for="col in columns" :key="col.field">
{{ col.title || col.field }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(row, rowIndex) in data" :key="rowIndex">
<td v-for="col in columns" :key="col.field">
<!-- 作用域插槽:使用列字段作为插槽名,同时提供后备内容 -->
<slot :name="col.field" :row="row" :column="col" :index="rowIndex">
<!-- 默认显示 -->
{{ row[col.field] }}
</slot>
</td>
</tr>
</tbody>
</table>
</template>
<script setup>
defineProps({
data: {
type: Array,
required: true,
default: () => []
},
columns: {
type: Array,
required: true,
// 列对象格式:{ field: 'name', title: '姓名' }
default: () => []
}
})
</script>
<style scoped>
.data-table {
border-collapse: collapse;
width: 100%;
}
.data-table th, .data-table td {
border: 1px solid #ddd;
padding: 8px 12px;
text-align: left;
}
.data-table th {
background-color: #f5f5f5;
}
</style>
2. 父组件使用示例
父组件通过 v-slot 指令(或 # 缩写)为指定列提供自定义渲染。作用域数据包括:
row:当前行的完整数据对象column:当前列配置index:行索引
vue
<template>
<div>
<DataTable :data="tableData" :columns="columns">
<!-- 自定义 'status' 列:显示不同颜色的标签 -->
<template #status="{ row }">
<span :class="row.status === 'active' ? 'tag-active' : 'tag-inactive'">
{{ row.status }}
</span>
</template>
<!-- 自定义 'actions' 列:放置操作按钮 -->
<template #actions="{ row, index }">
<button @click="editRow(row, index)">编辑</button>
<button @click="deleteRow(index)">删除</button>
</template>
<!-- 其他未自定义的列(如 name, age)会自动使用默认渲染 -->
</DataTable>
</div>
</template>
<script setup>
import { ref } from 'vue'
import DataTable from './DataTable.vue'
const columns = [
{ field: 'name', title: '姓名' },
{ field: 'age', title: '年龄' },
{ field: 'status', title: '状态' },
{ field: 'actions', title: '操作' }
]
const tableData = ref([
{ name: '张三', age: 28, status: 'active' },
{ name: '李四', age: 34, status: 'inactive' },
{ name: '王五', age: 22, status: 'active' }
])
const editRow = (row, index) => {
alert(`编辑第 ${index + 1} 行:${row.name}`)
}
const deleteRow = (index) => {
tableData.value.splice(index, 1)
}
</script>
<style scoped>
.tag-active { color: green; font-weight: bold; }
.tag-inactive { color: red; }
</style>