Vue项目使用Dragula拖拽时目标索引返回错误的问题
幻灯片侧边栏拖拽排序异常问题
我想给幻灯片侧边栏的卡片添加拖拽功能,用来调整幻灯片的位置,但目前拖拽时排序错误,原因是部分slide-card元素的data-index与DOM中的实际显示顺序不一致。
HTML代码
<div class="c-side-bar__opened__container__navbar" v-model="isSidebarOpen" ref="slideCards"> <template v-for="(slide, index) in slides"> <li class="c-side-bar__opened__container__navbar__slide" :class="{ 'c-side-bar__opened__container__navbar__slide--hover': index === activeSlideIndex, }" :data-index="index" :key="slide.ID" > <div class="c-side-bar__opened__container__navbar__slide__card" :class="{ 'c-side-bar__opened__container__navbar__slide__card--hover': index === activeSlideIndex, }" > <slide-card :slideID="slide.ID" :data-index="index" :index="index" :key="slide.ID" :totalCount="slides.length" :isSideBarComponent="isSideBarComponent" > </slide-card> </div> <span class="c-side-bar__opened__container__navbar__slide__index" :class="{'c-side-bar__opened__container__navbar__slide__index--hover': index === activeSlideIndex }" >{{ index + 1 }}</span > </li> </template> <li class="c-side-bar__opened__container__navbar__slide c-side-bar--add-slide"> <app-button type="icon" theme="secondary" size="large" @click="addSlide"> <app-icon name="plus"></app-icon> </app-button> </li> </div>
TS代码
this.dragula = dragula([this.$refs.slideCards as Element], { direction: 'horizantal', isContainer: (el: Element) => { if (!this.draggedSlide) { return false; } if (el.classList.contains('c-side-bar__opened__container__navbar__slide__card')) { const targetIndex = parseInt(el.getAttribute('data-index'), 10); // 错误点:HTML元素的data-index有时和DOM显示顺序不一致 this.draggedSlide.targetIndex = targetIndex; } return false; }, }) .on('drag', (draggingSlideCard: HTMLElement) => { this.isDragging = true; this.draggedSlide = { sourceIndex: parseInt(draggingSlideCard.getAttribute('data-index'), 10), targetIndex: parseInt(draggingSlideCard.getAttribute('data-index'), 10), }; }) .on('drop', () => { if (!this.draggedSlide) { return; } // 保存顺序变更后的新幻灯片列表 this.moveSlide(this.draggedSlide.sourceIndex, this.draggedSlide.targetIndex); this.draggedSlide = null; this.isDragging = false; });
问题根源与修复方案
问题根源
- Vue列表复用导致data-index过期:循环使用
slide.ID作为:key,当slides数组顺序改变时,Vue会复用已有DOM元素,不会重新渲染,导致data-index仍保留旧的索引值,和DOM实际顺序不匹配。 - 依赖data-index获取目标索引不可靠:Dragula逻辑中通过
data-index获取目标位置,而该值已过期,自然导致排序错误。 - 拼写错误:
direction: 'horizantal'应为'horizontal',可能影响拖拽方向判断。
修复步骤
1. 修改列表的key值,确保DOM随数组顺序更新
将循环的:key从slide.ID改为index(或结合slide.ID与index),这样数组顺序变化时,Vue会重新渲染列表,同步更新data-index:
<li class="c-side-bar__opened__container__navbar__slide" :class="{ 'c-side-bar__opened__container__navbar__slide--hover': index === activeSlideIndex, }" :data-index="index" :key="index" <!-- 此处修改 --> > <!-- slide-card的key也同步修改 --> <slide-card :slideID="slide.ID" :data-index="index" :index="index" :key="index" <!-- 或使用`${slide.ID}-${index}`确保唯一 --> :totalCount="slides.length" :isSideBarComponent="isSideBarComponent" > </slide-card>
2. 直接通过DOM位置计算索引,不依赖data-index
修改Dragula逻辑,通过元素在父容器中的位置获取真实索引:
this.dragula = dragula([this.$refs.slideCards as Element], { direction: 'horizontal', // 修正拼写错误 moves: (el, source, handle) => { // 指定只有卡片元素可拖拽 return el.classList.contains('c-side-bar__opened__container__navbar__slide__card'); } }) .on('drag', (draggingSlideCard: HTMLElement) => { this.isDragging = true; // 获取拖拽卡片的父li元素 const parentLi = draggingSlideCard.closest('.c-side-bar__opened__container__navbar__slide'); // 获取所有非添加按钮的slide项 const slideItems = Array.from((this.$refs.slideCards as Element).querySelectorAll('.c-side-bar__opened__container__navbar__slide:not(.c-side-bar--add-slide)')); this.draggedSlide = { sourceIndex: slideItems.indexOf(parentLi), targetIndex: slideItems.indexOf(parentLi), }; }) .on('drop', (el, target, source, sibling) => { if (!this.draggedSlide) return; // 获取所有slide项 const slideItems = Array.from((this.$refs.slideCards as Element).querySelectorAll('.c-side-bar__opened__container__navbar__slide:not(.c-side-bar--add-slide)')); let targetIndex; // 根据sibling元素计算目标位置 if (sibling) { const siblingLi = sibling.closest('.c-side-bar__opened__container__navbar__slide'); targetIndex = slideItems.indexOf(siblingLi); } else { // 没有sibling说明拖到了最后 targetIndex = slideItems.length - 1; } // 执行幻灯片移动逻辑 this.moveSlide(this.draggedSlide.sourceIndex, targetIndex); this.draggedSlide = null; this.isDragging = false; });
3. 验证moveSlide逻辑
确保moveSlide函数能正确修改slides数组的顺序,示例如下:
moveSlide(fromIndex: number, toIndex: number) { const [movedSlide] = this.slides.splice(fromIndex, 1); this.slides.splice(toIndex, 0, movedSlide); // 可选:更新当前激活的幻灯片索引 this.activeSlideIndex = toIndex; }
内容的提问来源于stack exchange,提问作者Ramazan Arslan
相关产品推荐
相关产品推荐

