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

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
    }
]

原因分析

  1. 计算属性的临时特性:groupedClasses是计算属性,每次组件渲染时都会基于原始的zoneInfoClasses重新生成分组对象和数组引用。draggable组件修改的只是计算属性返回的临时数组,当组件重新渲染时,计算属性会再次生成原始顺序的分组,导致拖拽结果被重置。
  2. 数据源未同步更新: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 23:55:25