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

VueJS 2+BootstrapVue可复用折叠组件优化及动态内容传递咨询

优化可复用BootstrapVue折叠组件(支持动态内容与数据联动)

针对你的需求,我们可以通过Vue的**插槽(Slot)和作用域插槽(Scoped Slot)**实现组件高度复用,同时借助props与事件传递保证数据和方法的双向联动。以下是具体优化方案:

核心优化点

  • 用默认插槽替换固定的DataTable,让父组件能自由传入任意内容(表单、表格等)
  • 用作用域插槽实现动态新增面板的内容自定义,同时传递面板上下文数据
  • 为每个折叠面板单独维护展开状态,避免全局状态冲突
  • 通过props传递初始数据,子组件触发事件通知父组件更新数据,保证双向联动

优化后的CollapseSection.vue代码

<template>
  <div class="accordion" role="tablist">
    <!-- 主折叠面板(支持自定义内容) -->
    <b-button block v-b-toggle="`accordion-${baseId}`" class="collapse-btn" align-h="between">
      {{ selectText }}
      <b-icon :icon="panelStates[baseId] ? 'caret-down' : 'caret-up'" class="icon"></b-icon>
    </b-button>
    <b-card no-body class="mb-1">
      <b-collapse :id="`accordion-${baseId}`" v-model="panelStates[baseId]" accordion="my-accordion" role="tabpanel">
        <b-card-body>
          <!-- 默认插槽:父组件传入的自定义内容,同时传递操作方法 -->
          <slot 
            :addPanel="addPanel" 
            :updateData="handleDataUpdate"
          ></slot>
        </b-card-body>
      </b-collapse>
    </b-card>

    <!-- 动态新增的折叠面板(支持作用域插槽自定义内容) -->
    <div v-for="(item, index) in dynamicPanels" :key="item.id">
      <b-button block v-b-toggle="`accordion-${item.id}`" class="collapse-btn" align-h="between">
        {{ item.name }}
        <b-icon :icon="panelStates[item.id] ? 'caret-down' : 'caret-up'" class="icon"></b-icon>
        <b-button size="sm" variant="danger" @click="removePanel(item.id)">×</b-button>
      </b-button>
      <b-card no-body class="mb-1">
        <b-collapse :id="`accordion-${item.id}`" v-model="panelStates[item.id]" accordion="my-accordion" role="tabpanel">
          <b-card-body>
            <!-- 作用域插槽:传递当前面板的item数据和更新方法 -->
            <slot 
              name="dynamic-panel" 
              :item="item" 
              @update-item="handleItemUpdate"
            ></slot>
          </b-card-body>
        </b-collapse>
      </b-card>
    </div>
  </div>
</template>

<script>
export default {
  name: "CollapseSection",
  props: {
    selectText: {
      type: String,
      default: "Select"
    },
    // 父组件传递的主面板数据,支持.sync双向绑定
    parentData: {
      type: Array,
      default: () => []
    },
    // 初始动态面板数据(可选)
    initialDynamicPanels: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      baseId: 1,
      dynamicPanels: [...this.initialDynamicPanels],
      // 为每个折叠面板单独维护展开状态
      panelStates: {
        1: false
      }
    };
  },
  methods: {
    // 添加新折叠面板
    addPanel(panelName = `New Panel ${this.dynamicPanels.length + 2}`) {
      const newId = this.baseId + this.dynamicPanels.length + 1;
      const newPanel = {
        id: newId,
        name: panelName,
        data: {} // 预留自定义数据字段
      };
      this.dynamicPanels.push(newPanel);
      this.panelStates[newId] = false;
      this.$emit("panel-added", newPanel);
    },
    // 删除折叠面板
    removePanel(panelId) {
      this.dynamicPanels = this.dynamicPanels.filter(item => item.id !== panelId);
      delete this.panelStates[panelId];
      this.$emit("panel-removed", panelId);
    },
    // 处理主面板数据更新,通知父组件
    handleDataUpdate(newData) {
      this.$emit("update:parentData", newData);
    },
    // 处理动态面板数据更新,同步并通知父组件
    handleItemUpdate(updatedItem) {
      const index = this.dynamicPanels.findIndex(item => item.id === updatedItem.id);
      if (index !== -1) {
        this.dynamicPanels.splice(index, 1, updatedItem);
        this.$emit("item-updated", updatedItem);
      }
    }
  }
};
</script>

调用示例(NewForm.vue)

这里展示如何传入不同内容(主面板用DataTable,动态面板用表单),并实现数据联动:

<template>
  <CollapseSection 
    select-text="主折叠面板"
    :parentData.sync="tableData"
    :initialDynamicPanels="initialPanels"
    @panel-added="logPanelAdded"
    @item-updated="logItemUpdated"
  >
    <!-- 主面板自定义内容:DataTable -->
    <template #default="{ addPanel, updateData }">
      <DataTable 
        :labels="tableLabels" 
        :data="tableData" 
        :showAdd="true"
        @add-item="addTableItem"
        @update-data="updateData"
      />
      <b-button variant="primary" class="mt-3" @click="addPanel('自定义新面板')">+ 添加折叠面板</b-button>
    </template>

    <!-- 动态面板自定义内容:表单 -->
    <template #dynamic-panel="{ item, updateItem }">
      <b-form @submit.prevent="savePanelForm(item)">
        <b-form-group label="面板名称">
          <b-form-input v-model="item.name" @change="updateItem(item)"></b-form-input>
        </b-form-group>
        <b-form-group label="自定义字段">
          <b-form-input v-model="item.data.customField"></b-form-input>
        </b-form-group>
        <b-button type="submit" variant="success">保存</b-button>
      </b-form>
    </template>
  </CollapseSection>
</template>

<script>
import CollapseSection from "./CollapseSection.vue";
import DataTable from "./DataTable.vue";

export default {
  name: "NewForm",
  components: { CollapseSection, DataTable },
  data() {
    return {
      tableLabels: ["姓名", "年龄"],
      tableData: [
        { name: "张三", age: 25 },
        { name: "李四", age: 30 }
      ],
      initialPanels: [
        { id: 2, name: "初始面板1", data: { customField: "初始值1" } }
      ]
    };
  },
  methods: {
    addTableItem(newRow) {
      this.tableData.push(newRow);
    },
    logPanelAdded(newPanel) {
      console.log("新增面板:", newPanel);
    },
    logItemUpdated(updatedItem) {
      console.log("更新面板数据:", updatedItem);
    },
    savePanelForm(item) {
      console.log("保存表单数据:", item);
      // 这里可添加接口提交逻辑
    }
  }
};
</script>

关键说明

  1. 插槽复用性:默认插槽负责主面板的自定义内容,作用域插槽dynamic-panel负责动态面板的内容定制,同时通过插槽传递方法(如addPanel、updateItem)实现父子组件方法联动。
  2. 数据双向绑定:通过.sync修饰符实现parentData的双向同步,子组件触发update:parentData事件更新父组件数据;动态面板的数据更新通过handleItemUpdate同步并通知父组件。
  3. 独立状态管理:panelStates对象为每个折叠面板单独维护展开/收起状态,解决了原代码中单个visible变量导致所有面板状态同步的问题。
  4. 扩展性:可根据需求扩展更多props(如默认展开状态、面板样式)或事件(如面板展开/收起回调),进一步提升组件复用性。

内容的提问来源于stack exchange,提问作者Temka

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 05:05:04