Vue项目中使用vue-draggable拖拽时如何保留元素展开状态?
解决Vue嵌套拖拽时展开状态丢失的问题
核心问题是展开状态没有和数据源绑定,拖拽导致组件重新渲染后,局部维护的展开状态被重置。把每个元素的展开状态存在数据源自身的属性中,就能保持状态不丢失。
步骤1:修改数据源结构
给每个元素添加isExpanded属性,用于存储该元素的展开/折叠状态:
// 示例数据源(Vue3 ref示例) const elements = ref([ { id: 'A', children: [], isExpanded: true }, // 初始展开 { id: 'B', children: [{ id: 'B1', children: [], isExpanded: false }], isExpanded: true }, { id: 'C', children: [], isExpanded: false }, { id: 'E', children: [], isExpanded: false } ]);
如果是从后端获取数据,需要遍历数组给每个元素补上isExpanded属性。
步骤2:修改NestedElement组件
把展开状态从组件局部data移到数据源的isExpanded属性,通过props传递并同步更新:
Vue3 版本
<template> <div class="nested-element"> <div class="element-header" @click="toggleExpand"> {{ element.id }} <span v-if="hasChildren"> {{ element.isExpanded ? '▼' : '▶' }} </span> </div> <!-- 根据数据源的isExpanded控制显示 --> <div v-if="element.isExpanded && hasChildren" class="children-container"> <draggable v-model="element.children" group="elements" handle=".element-header" item-key="id" > <template #item="{ element: child }"> <NestedElement :element="child" @update:element="updateChild" /> </template> </draggable> </div> </div> </template> <script setup> import { computed, defineProps, defineEmits } from 'vue'; import draggable from 'vuedraggable'; const props = defineProps({ element: { type: Object, required: true } }); const emit = defineEmits(['update:element']); const hasChildren = computed(() => props.element.children?.length > 0); const toggleExpand = () => { // 更新数据源的isExpanded属性并同步到父组件 emit('update:element', { ...props.element, isExpanded: !props.element.isExpanded }); }; const updateChild = (updatedChild) => { // 子元素状态更新时,同步更新当前元素的children数组 const updatedChildren = props.element.children.map(child => child.id === updatedChild.id ? updatedChild : child ); emit('update:element', { ...props.element, children: updatedChildren }); }; </script>
Vue2 版本
<template> <div class="nested-element"> <div class="element-header" @click="toggleExpand"> {{ element.id }} <span v-if="hasChildren"> {{ element.isExpanded ? '▼' : '▶' }} </span> </div> <div v-if="element.isExpanded && hasChildren" class="children-container"> <draggable v-model="element.children" group="elements" handle=".element-header" item-key="id" > <template #item="{ element: child }"> <NestedElement :element.sync="child" /> </template> </draggable> </div> </div> </template> <script> import draggable from 'vuedraggable'; export default { name: 'NestedElement', components: { draggable }, props: { element: { type: Object, required: true } }, computed: { hasChildren() { return this.element.children?.length > 0; } }, methods: { toggleExpand() { this.$emit('update:element', { ...this.element, isExpanded: !this.element.isExpanded }); } } }; </script>
步骤3:在父组件中同步更新
父组件中使用NestedElement时,通过@update:element(Vue3)或.sync(Vue2)同步更新根数据源:
<!-- Vue3 父组件示例 --> <template> <draggable v-model="elements" group="elements" handle=".element-header" item-key="id" > <template #item="{ element }"> <NestedElement :element="element" @update:element="updateElement" /> </template> </draggable> </template> <script setup> import { ref } from 'vue'; import draggable from 'vuedraggable'; import NestedElement from './NestedElement.vue'; const elements = ref([/* 数据源 */]); const updateElement = (updatedElement) => { const index = elements.value.findIndex(el => el.id === updatedElement.id); if (index !== -1) { elements.value[index] = updatedElement; } }; </script>
原理说明
之前展开状态存在组件局部data中,拖拽时数据源结构变化会触发组件重新渲染,新的组件实例会使用默认的折叠状态。将展开状态绑定到数据源的元素属性后,状态会跟着数据走,拖拽操作只会改变元素的层级/顺序,不会丢失isExpanded属性,组件渲染时自然会保持原本的展开状态。
内容的提问来源于stack exchange,提问作者breking bed
相关产品推荐
相关产品推荐

