Vuetify DataTable多层嵌套分组实现方案求助
Vuetify DataTable 多层嵌套分组实现方案
核心思路
多层分组的关键是先把数据按分组层级预处理成嵌套结构,再结合Vuetify的分组插槽(v-data-table的group.header插槽)实现嵌套展开/折叠逻辑。
一、调整后端数据结构
先把原始数据转换成嵌套层级结构,比如先按gluten(无麸质/含麸质)一级分组,再在gluten: false(无麸质)的组内按highCalorie(高热量/低热量)二级分组。示例数据结构如下:
const nestedData = [ { group: "无麸质", items: [ { subgroup: "高热量", items: [ { name: "坚果能量棒", gluten: false, highCalorie: true, calories: 450 }, { name: "牛油果沙拉碗", gluten: false, highCalorie: true, calories: 520 } ] }, { subgroup: "低热量", items: [ { name: "藜麦蔬菜饭", gluten: false, highCalorie: false, calories: 320 }, { name: "烤鸡胸沙拉", gluten: false, highCalorie: false, calories: 280 } ] } ] }, { group: "含麸质", items: [ { name: "全麦三明治", gluten: true, highCalorie: false, calories: 380 }, { name: "意大利面", gluten: true, highCalorie: true, calories: 550 } ] } ];
二、Vuetify 3.x 实现代码
1. 组件模板
利用v-data-table的group.header插槽自定义分组头部,同时在组内嵌套二级分组的表格或直接渲染子项:
<template> <v-data-table :items="flattenedItems" :headers="headers" group-by="group" show-group-by > <template #group.header="{ group, items, isOpen, toggle }"> <tr> <td colspan="4"> <v-btn icon @click="toggle" class="mr-2"> <v-icon>{{ isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon> </v-btn> <strong>{{ group }}</strong> <span class="ml-2 text-caption">({{ items.length }} 项)</span> </td> </tr> <!-- 二级分组:仅当一级分组是"无麸质"时渲染 --> <tr v-if="group === '无麸质' && isOpen"> <td colspan="4"> <v-data-table :items="items" :headers="headers" group-by="subgroup" show-group-by class="ml-6" > <template #group.header="{ group: subGroup, items: subItems, isOpen: subIsOpen, toggle: subToggle }"> <tr> <td colspan="4"> <v-btn icon @click="subToggle" class="mr-2"> <v-icon>{{ subIsOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon> </v-btn> <span>{{ subGroup }}</span> <span class="ml-2 text-caption">({{ subItems.length }} 项)</span> </td> </tr> </template> <template #item="{ item }"> <tr> <td>{{ item.name }}</td> <td>{{ item.gluten ? '是' : '否' }}</td> <td>{{ item.highCalorie ? '是' : '否' }}</td> <td>{{ item.calories }}</td> </tr> </template> </v-data-table> </td> </tr> </template> <!-- 非无麸质组直接渲染子项 --> <template #item="{ item }" v-if="item.group !== '无麸质'"> <tr> <td>{{ item.name }}</td> <td>{{ item.gluten ? '是' : '否' }}</td> <td>{{ item.highCalorie ? '是' : '否' }}</td> <td>{{ item.calories }}</td> </tr> </template> </v-data-table> </template>
2. 脚本部分
需要把嵌套数据扁平化,适配Vuetify的group-by要求:
<script setup> import { computed } from 'vue'; const headers = [ { title: '食物名称', key: 'name' }, { title: '含麸质', key: 'gluten' }, { title: '高热量', key: 'highCalorie' }, { title: '热量', key: 'calories' } ]; const nestedData = [ // 上面定义的嵌套数据 ]; // 扁平化数据:给每个子项添加一级分组标识 const flattenedItems = computed(() => { return nestedData.flatMap(group => { if (group.group === '无麸质') { return group.items.flatMap(subgroup => { return subgroup.items.map(item => ({ ...item, group: group.group, subgroup: subgroup.subgroup })); }); } else { return group.items.map(item => ({ ...item, group: group.group })); } }); }); </script>
三、Vuetify 2.x 实现代码
Vuetify 2的v-data-table分组逻辑类似,主要差异是插槽命名和部分API:
1. 组件模板
<template> <v-data-table :items="flattenedItems" :headers="headers" group-by="group" show-group-by > <template v-slot:group.header="{ group, items, isOpen, toggle }"> <tr> <td colspan="4"> <v-btn icon @click="toggle" class="mr-2"> <v-icon>{{ isOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon> </v-btn> <strong>{{ group }}</strong> <span class="ml-2 text-caption">({{ items.length }} 项)</span> </td> </tr> <tr v-if="group === '无麸质' && isOpen"> <td colspan="4"> <v-data-table :items="items" :headers="headers" group-by="subgroup" show-group-by class="ml-6" > <template v-slot:group.header="{ group: subGroup, items: subItems, isOpen: subIsOpen, toggle: subToggle }"> <tr> <td colspan="4"> <v-btn icon @click="subToggle" class="mr-2"> <v-icon>{{ subIsOpen ? 'mdi-chevron-down' : 'mdi-chevron-right' }}</v-icon> </v-btn> <span>{{ subGroup }}</span> <span class="ml-2 text-caption">({{ subItems.length }} 项)</span> </td> </tr> </template> <template v-slot:item="{ item }"> <tr> <td>{{ item.name }}</td> <td>{{ item.gluten ? '是' : '否' }}</td> <td>{{ item.highCalorie ? '是' : '否' }}</td> <td>{{ item.calories }}</td> </tr> </template> </v-data-table> </td> </tr> </template> <template v-slot:item="{ item }" v-if="item.group !== '无麸质'"> <tr> <td>{{ item.name }}</td> <td>{{ item.gluten ? '是' : '否' }}</td> <td>{{ item.highCalorie ? '是' : '否' }}</td> <td>{{ item.calories }}</td> </tr> </template> </v-data-table> </template>
2. 脚本部分
<script> export default { data() { return { headers: [ { text: '食物名称', value: 'name' }, { text: '含麸质', value: 'gluten' }, { text: '高热量', value: 'highCalorie' }, { text: '热量', value: 'calories' } ], nestedData: [ // 嵌套数据同前 ] }; }, computed: { flattenedItems() { return this.nestedData.flatMap(group => { if (group.group === '无麸质') { return group.items.flatMap(subgroup => { return subgroup.items.map(item => ({ ...item, group: group.group, subgroup: subgroup.subgroup })); }); } else { return group.items.map(item => ({ ...item, group: group.group })); } }); } } }; </script>
关键注意事项
- 数据预处理:必须给每个子项添加对应层级的分组标识(
group、subgroup),确保Vuetify的group-by能正确识别。 - 嵌套表格样式:通过
class="ml-6"给二级分组添加缩进,区分层级。 - 条件渲染:可以根据业务需求控制哪些一级分组需要展开二级分组,不需要的直接渲染子项。
内容的提问来源于stack exchange,提问作者Hardik Shah
相关产品推荐
相关产品推荐

