列表拖拽排序

41 阅读1分钟

image.png

父组件:

<view class="padding" style="height: calc(100vh - 150px);" >
        <drag-list
           :itemHeight="itemHeight" 
           :list="navList" 
           @change="sortChange">
           <template slot-scope="{ item }">
                   <view class="drag-item">
                           <view style="text-align: center;" >
                                   {{ item.name }}
                           </view>
                   </view>
           </template>
        </drag-list>
</view>

<script>
	import dragList from '../../components/gzz-drag/drag-list.vue';
	export default {
		components: {
		            dragList
		        },
		data() {
		    return {
			    itemHeight: 96,
				scrollHeight: 0,
				startY: 0,
				currentIndex: -1
		    }
		  },
		  onLoad(options) {
		    if(options.navs) {
		      this.navList = JSON.parse(decodeURIComponent(options.navs));
		    }
		  },
		  methods: {
			sortChange(navList){
				this.navList = navList.map((item, index) => {
					item.name = item.title
					item.index = index
					return item
				  });
			    
			},
			async saveSort() {
				 uni.$emit('navSortUpdated', this.navList)
				 
				 // todo 调用接口,保存navList
				   // uni.showToast({ 
				   //   title: '排序已保存',
				   //   icon: 'success',
				   //   duration: 1500
				   // });
				 
				   setTimeout(() => {
				     uni.navigateBack({
				       delta: 1,
				       success: () => {
				         console.log('返回成功');
				       }
				     });
				   }, 1600);
			}
		}   
	}
</script>

子组件:


<template>
  <view class="drag-box">
    <view 
      v-for="(item,index) in dataList"
      :key="index"
      :style="{
        top: item.top +'px',
        height: (itemHeight - 1)+'rpx',
        marginBottom: '16px'
      }"
      class="drag-item"
      :class="{'drag-active': item.isActive}"
      @longtap="longtap(item)"
      @touchstart="touchstart"
      @touchmove.stop.prevent="touchmove"
      @touchend="touchend(item)">
        <slot :item="item"></slot>
    </view>
  </view>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      default: () => ([])
    },
    itemHeight: {
      type: [Number],
      default: 70
    },
    gap: {
      type: Number,
      default: 16
    }
  },
  data() {
    return {
      activeItem: null,
      isDrag: false,
      dragTargetY: 0,
      dataList: [],
      sortIndexList: [],
    }
  },
  watch: {
    list: {
      immediate: true,
      deep: true,
      handler(list) {
        this.setList(list)
      }
    }
  },
  methods: {
    touchstart(e) {
      this.dragTargetY = e.touches[0].pageY;
    },
    longtap(item) {
      this.activeItem = item;
      this.isDrag = true;
      item.isActive = true;
    },
    touchmove(e) {
      if(!this.isDrag) return;
      
      const newY = e.touches[0].pageY;
      const d = newY - this.dragTargetY;
      this.activeItem.top += d;
      
      const currentIndex = this.sortIndexList[this.activeItem.index];
      const prevItem = this.getItemByIndex(currentIndex - 1);
      const nextItem = this.getItemByIndex(currentIndex + 1);

      if (prevItem && d < 0) {
        const threshold = prevItem.top + this.rowHeight + this.gap;
        if (this.activeItem.top < threshold) {
          this.swapItems(prevItem);
        }
      } else if (nextItem && d > 0) {
        const threshold = nextItem.top - this.rowHeight - this.gap;
        if (this.activeItem.top > threshold) {
          this.swapItems(nextItem);
        }
      }
      this.dragTargetY = newY;
    },
    touchend(item) {
      if(!this.isDrag) return;
      
      this.isDrag = false;
      item.isActive = false;
      this.activeItem.top = this.sortIndexList[item.index] * (this.rowHeight + this.gap);
      
      const sortedList = this.dataList
        .sort((a, b) => this.sortIndexList[a.index] - this.sortIndexList[b.index])
        .map(item => {
          const { isActive, top, index, ...rest } = item;
          return rest;
        });
      
      this.$emit('change', sortedList);
    },
    getItemByIndex(index) {
      const target = this.dataList.find(
        item => this.sortIndexList[item.index] === index
      );
      return target || null;
    },
    swapItems(targetItem) {
      const currentIndex = this.sortIndexList[this.activeItem.index];
      const targetIndex = this.sortIndexList[targetItem.index];
      
      this.sortIndexList[this.activeItem.index] = targetIndex;
      this.sortIndexList[targetItem.index] = currentIndex;
      
      targetItem.top = currentIndex * (this.rowHeight + this.gap);
    },
    setList(list) {
      this.dataList = list.map((item, index) => ({
        ...item,
        isActive: false,
        top: index * (this.rowHeight + this.gap),
        index: index
      }));
      this.sortIndexList = list.map((_, index) => index);
    }
  },
  computed: {
    rowHeight() {
      const res = uni.getSystemInfoSync();
      return this.itemHeight * res.screenWidth / 750;
    }
  }
}
</script>

<style lang="less">
.drag-box {
  position: relative;
  width: 100%;
  height: 100%;
  
  .drag-item {
    position: absolute;
    width: 100%;
    line-height: 48px;
    text-align: center;
    background-color: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 16px 0 rgba(94, 139, 255, 0.08);
    border-bottom: 1rpx solid #F5F5F5;
    transition: all 0.3s ease;
    box-sizing: border-box;
    z-index: 1;
    
    &.drag-active {
      z-index: 9;
      transform: scale(1.05);
      box-shadow: 0 8px 20px 0 #e6e6e6;
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }
  }
}
</style>