实现支持作用域插槽的通用数据表格组件

实现支持作用域插槽的通用数据表格组件

在 Vue 中,作用域插槽(Scoped Slots) 是让父组件自定义子组件内部渲染的利器。下面实现一个 <DataTable> 组件,它接收 data(行数据)和 columns(列配置)数组,并在每个 <td> 中提供插槽,让父组件按列自定义单元格内容。


1. 子组件:DataTable.vue(Vue 3 + <script setup>
  • 核心逻辑
    • 遍历 columns,为每一列生成 <th>
    • 遍历 data 生成 <tr>,再遍历 columns 生成 <td>
    • 每个 <td> 内,优先检查父组件是否传递了对应列的具名插槽 (以列字段 field 命名),若存在则使用插槽并传递 rowcolumnindex 等作用域数据;否则显示默认的 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>