Vue 2 draggable绑定父对象属性时拖拽后重置问题排查
Vue 2 + Vuex拖拽组件重置问题排查与解决
问题
使用Vue 2结合Vuex开发项目,将zoneInfoClasses数组按categoryLocalised字段分组后,给draggable组件绑定分组对象的classes属性作为v-model。拖拽时视觉上有效果,但松开鼠标后所有元素都会回到初始位置,怀疑是v-model绑定父对象属性导致无响应。
模板代码
<div class="class-draggable__group" v-for="group in groupedClasses" :key="group.categoryLocalised"> <label class="class-draggable__label__category">{{ group.categoryLocalised }}</label> <draggable v-model="group.classes" :options="{ group: 'classes', dragClass: 'drag', ghostClass: 'ghost' }"> <div v-for="classItem in group.classes" :key="classItem.class" class="class-draggable__draggable__item"> <div class="d-flex align-items-center"> <svg-icon name="drag" class="mr-8 move item"></svg-icon> <div>{{ classItem.classLocalised }}</div> </div> <div class="class-info d-flex align-items-center"> <checkbox class="class-draggable__draggable__item__checkbox" :view="1" v-model="classItem.enabled" @select="toggleClassEnabledValue(classItem.class, $event)" /> <div class="icon-wrapper"> <svg-icon v-if="classItem.boolVal1Enabled" name="boolVal1Icon"></svg-icon> </div> <div class="icon-wrapper"> <svg-icon v-if="classItem.boolVal2Enabled" name="boolVal2Icon"></svg-icon> </div> <btn :content="{ icon: 'edit' }" class="class-draggable__draggable__item__button mr-8" round="circle" @click="editClassHandler(classItem.class)" /> </div> </div> </draggable> </div>
分组逻辑(计算属性)
groupedClasses() { const groups = {}; this.zoneInfoClasses.forEach(classItem => { if (!groups[classItem.categoryLocalised]) { groups[classItem.categoryLocalised] = []; } groups[classItem.categoryLocalised].push(classItem); }); return Object.keys(groups).map(categoryLocalised => ({ categoryLocalised, classes: groups[categoryLocalised], })); },
zoneInfoClasses结构
[ { "class": "classa", "classLocalised": "classA", "categoryLocalised": "category1", "enabled": true, "boolVal1Enabled": false, "boolVal2Enabled": false }, { "class": "classb", "classLocalised": "classB", "categoryLocalised": "category2", "enabled": false, "boolVal1Enabled": false, "boolVal2Enabled": false }, { "class": "classc", "classLocalised": "classC", "categoryLocalised": "category1", "enabled": false, "boolVal1Enabled": false, "boolVal2Enabled": false }, { "class": "classd", "classLocalised": "classD", "categoryLocalised": "category2", "enabled": false, "boolVal1Enabled": false, "boolVal2Enabled": false } ]
原因分析
- 计算属性的临时特性:
groupedClasses是计算属性,每次组件渲染时都会基于原始的zoneInfoClasses重新生成分组对象和数组引用。draggable组件修改的只是计算属性返回的临时数组,当组件重新渲染时,计算属性会再次生成原始顺序的分组,导致拖拽结果被重置。 - 数据源未同步更新:v-model绑定的是计算属性生成的临时对象的
classes数组,修改这个数组不会触发原始数据源zoneInfoClasses的更新,因此无法保留拖拽后的顺序。
解决方案
方案1:改用响应式数据属性维护分组
将分组逻辑从计算属性移到数据属性,通过监听zoneInfoClasses的变化来更新分组数组,确保分组后的数组是响应式的:
data() { return { groupedClasses: [] }; }, watch: { zoneInfoClasses: { immediate: true, handler(newVal) { const groups = {}; newVal.forEach(classItem => { if (!groups[classItem.categoryLocalised]) { groups[classItem.categoryLocalised] = []; } groups[classItem.categoryLocalised].push(classItem); }); this.groupedClasses = Object.keys(groups).map(categoryLocalised => ({ categoryLocalised, classes: groups[categoryLocalised], })); } } }
此时draggable组件修改的是groupedClasses这个响应式数据里的数组,拖拽后的顺序会被保留,不会被重置。
方案2:通过拖拽事件更新原始数据源
如果需要保留计算属性的方式,可利用draggable的@end事件,手动更新原始的zoneInfoClasses数组(若存储在Vuex中,需通过mutation修改):
<draggable v-model="group.classes" :options="{ group: 'classes', dragClass: 'drag', ghostClass: 'ghost' }" @end="handleDragEnd">
methods: { handleDragEnd(event) { if (event.oldIndex !== event.newIndex) { // 重新构建原始数组,按分组后的顺序拼接 const newZoneInfoClasses = []; this.groupedClasses.forEach(group => { newZoneInfoClasses.push(...group.classes); }); // 若zoneInfoClasses来自Vuex,调用mutation更新 this.$store.commit('UPDATE_ZONE_INFO_CLASSES', newZoneInfoClasses); // 若为组件本地数据,直接赋值 // this.zoneInfoClasses = newZoneInfoClasses; } } }
更新原始数据源后,计算属性会依赖变化重新生成正确顺序的分组,从而保留拖拽结果。
额外注意点
确保draggable项的:key使用唯一且稳定的值(当前代码中的classItem.class符合要求),避免因key不稳定导致的渲染异常。
内容的提问来源于stack exchange,提问作者nsa3679
相关产品推荐
相关产品推荐

