You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 16:15:34