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

