如何使用 b-table 将单元格模板传递给组件?

How to pass cell templates to a component with b-table?

我创建了一个显示各个页面的 table 数据的组件。该组件在内部使用 b-table 。现在,对于几个页面,我想自定义某些列的呈现,并且 Bootstrap 表允许使用具有特殊语法的作用域字段槽:

    <template #cell(field)="data">
            {{ data.item.value }}
    </template>

其中字段 - 列名,来自我的列数组,data.item - 要呈现的单元格项。

问题是我对不同的页面有不同的字段,所以这个自定义应该来自父组件,这些模板应该是动态创建的。

以下是我尝试解决的方法:

通过 属性 向 MyTableComponent 传递一个具有可自定义字段和唯一插槽名称的数组 在 MyTableComponent 中动态创建模板进行自定义,并在内部动态创建命名插槽

从父传递槽数据到命名槽

我的表格组件:

    <b-table>
        <template v-for="slot in myslots" v-bind="cellAttributes(slot)">
            <slot :name="slot.name"></slot>
        </template>
    </b-table>

    <script>
        ...
        computed: {
            cellAttributes(slot) {
                return ['#cell(' + slot.field + ')="data"'];
            }
        }
        ...
    </script>

家长:

    <MyTableComponent :myslots="myslots" :items="items" :fields="fields">
        <template slot="customSlot1">
                Hello1
        </template>
        <template slot="customSlot1">
                Hello2
        </template>
    </MyTableComponent>

<script>
    ...
    items: [...my items...],
    fields: [...my columns...],
    myslots: [
        { field: "field1", name: "customSlot1" },
        { field: "field2", name: "customSlot2" }
    ]
    ...
</script>

不幸的是,b-table 组件会忽略我的自定义插槽,就像没有提供它们一样。如果我直接在 MyTableComponent 中指定它,它会起作用:

    <b-table>
          <template #cell(field1)="data">
            {{ data.item.value }}
          </template>
    </b-table>

但我需要它通过组件属性动态完成。请帮忙。

您可以使用 Vue 2 的 Dynamic Slot Names 功能将所有(或部分)插槽从 parent 传递到 child 内的 <b-table>,如下所示:

Child:

<b-table>
  <template v-for="(_, slotName) of $scopedSlots" v-slot:[slotName]="scope">
    <slot :name="slotName" v-bind="scope"/>
  </template>
</b-table>

$scopedSlots 包含传递给您的组件的所有插槽。

现在这将起作用:

    <MyTableComponent :items="items" :fields="fields">
          <template #cell(field1)="data">
            {{ data.item.value }}
          </template>
    </ MyTableComponent>

更新 2 - Vue 3

要使上述代码在 Vue 3 中工作,只需按照 migration guide

的建议将 $scopedSlots 替换为 $slots

更新 1

You can filter $scopedSlots if you want (have some slot specific to your wrapper component you don't want to pass down to <b-table>) by creating computed

我在原来的回答中提到了这种可能性,但它有点问题,所以值得更好的解释...

  1. Scoped 插槽作为函数传递给组件(在调用时生成 VNode)。目标组件只执行她知道的(按名称)并忽略其余部分。因此,假设您的包装器内部有 b-table(或 Vuetify 的 v-data-table)和其他一些组件,比方说分页。您可以在两者中使用上面的代码,将所有插槽传递给每个插槽。除非有一些命名冲突(两个组件使用相同的插槽名称),否则它会工作得很好并且不会产生任何额外成本(所有插槽函数在传递给包装器组件时已经 compiled/created )。目标组件将仅使用(执行)它按名称知道的插槽。

  2. 如果可能存在命名冲突,可以通过使用一些命名约定来解决,例如在仅用于 b-table 的插槽名称前加上一些东西喜欢 table-- 并在内部进行过滤,但请注意 $scopedSlots object 确实包含一些必须沿复制的 Vue 内部属性 !! ([= Vue 2 的 25=]、$key$hasNormal - 请参阅 code)。所以下面的过滤代码即使它非常好并且不会抛出任何错误将不起作用b-table将无法识别和使用插槽)

<b-table>
  <template v-for="(_, slotName) of tableSlots" v-slot:[slotName]="scope">
    <slot :name="slotName" v-bind="scope"/>
  </template>
</b-table>
computed: {
    tableSlots() {
      const prefix = "table--";
      const raw = this.$scopedSlots;
      const filtered = Object.keys(raw)
        .filter((key) => key.startsWith(prefix))
        .reduce(
          (obj, key) => ({
            ...obj,
            [key.replace(prefix, "")]: raw[key],
          }),
          {}
        );
      return filtered;
    },
  },

这段代码可以通过包含上面提到的属性来修复,但是就我的口味而言,这对 Vue 内部实现的依赖太多,我不推荐它。如果可能的话,坚持方案 1...