| | |
| | | <template> |
| | | <view class="u-dragsort" |
| | | :class="[direction == 'horizontal' ? 'u-dragsort--horizontal' : '', direction == 'vertical' ? 'u-dragsort--vertical' : '', direction == 'all' ? 'u-dragsort--all' : '']"> |
| | | <movable-area class="u-dragsort-area" :style="movableAreaStyle"> |
| | | :class="[direction == 'horizontal' ? 'u-dragsort--horizontal' : '', direction == 'vertical' ? 'u-dragsort--vertical' : '', direction == 'all' ? 'u-dragsort--all' : '']" |
| | | :style="movableAreaStyle" |
| | | > |
| | | <movable-area class="u-dragsort-area"> |
| | | <movable-view v-for="(item, index) in list" :key="item.id" :id="`u-dragsort-item-${index}`" |
| | | class="u-dragsort-item" :class="{ 'dragging': dragIndex === index }" |
| | | class="u-dragsort-item" :class="{ 'dragging': dragIndex === index, disabled: !draggable || item.draggable === false }" |
| | | :direction="direction === 'all' ? 'all' : direction" :x="item.x" :y="item.y" :inertia="false" |
| | | :disabled="!draggable || (item.draggable === false)" @change="onChange(index, $event)" |
| | | @touchstart="onTouchStart(index)" @touchend="onTouchEnd" @touchcancel="onTouchEnd"> |
| | | :disabled="!draggable || dragIndex === -1 || item.draggable === false" @change="onChange(index, $event)" |
| | | @touchstart="onTouchStart(index, $event)" @touchend="onTouchEnd" @touchcancel="onTouchEnd" @touchmove="onTouchMove"> |
| | | <view class="u-dragsort-item-content"> |
| | | <view |
| | | class="ui-dragSort-item-handler" |
| | | v-if="$slots.handler" |
| | | data-action="handler" |
| | | @touchstart="onTouchStart(index, $event)" |
| | | > |
| | | <slot name="handler" :item="item" :index="index"></slot> |
| | | </view> |
| | | <slot :item="item" :index="index"> |
| | | {{ item.label }} |
| | | </slot> |
| | |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | vibrate: { |
| | | type: Boolean, |
| | | default: true |
| | | }, |
| | | direction: { |
| | | type: String, |
| | | default: 'vertical', |
| | |
| | | return { |
| | | list: [], |
| | | dragIndex: -1, |
| | | itemHeight: 40, |
| | | itemWidth: 80, |
| | | sortChanged: false, |
| | | itemHeight: 0, |
| | | itemWidth: 0, |
| | | areaWidth: 0, // 可拖动区域宽度 |
| | | areaHeight: 0, // 可拖动区域高度 |
| | | originalPositions: [], // 保存原始位置 |
| | | currentPosition: { |
| | | x: 0, |
| | | y: 0 |
| | |
| | | movableAreaStyle() { |
| | | if (this.direction === 'vertical') { |
| | | return { |
| | | height: `${this.list.length * this.itemHeight}px`, |
| | | height: this.itemHeight ? `${this.list.length * this.itemHeight}px` : 'auto', |
| | | width: '100%' |
| | | }; |
| | | } |
| | | } else if (this.direction === 'horizontal') { |
| | | return { |
| | | height: `${this.itemHeight}px`, |
| | | width: `${this.list.length * this.itemWidth}px` |
| | | }; |
| | | height: this.itemHeight ? `${this.itemHeight}px` : 'auto', |
| | | width: this.itemWidth ? `${this.list.length * this.itemWidth}px` : 'auto' |
| | | } |
| | | } else { |
| | | // all模式,计算网格布局所需的高度 |
| | | const rows = Math.ceil(this.list.length / this.columns); |
| | | const rows = Math.ceil(this.list.length / this.columns) |
| | | return { |
| | | height: `${rows * this.itemHeight}px`, |
| | | height: this.itemHeight ? `${rows * this.itemHeight}px` : 'auto', |
| | | width: '100%' |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | }, |
| | |
| | | initList() { |
| | | // 初始化列表项的位置 |
| | | this.list = this.initialList.map((item, index) => { |
| | | let x = 0, y = 0; |
| | | let x |
| | | let y |
| | | |
| | | if (this.direction === 'horizontal') { |
| | | x = index * this.itemWidth; |
| | | y = 0; |
| | | } else if (this.direction === 'vertical') { |
| | | x = 0; |
| | | y = index * this.itemHeight; |
| | | } else { |
| | | if (this.direction === 'horizontal' && this.itemWidth) { |
| | | x = index * this.itemWidth |
| | | y = 0 |
| | | } else if (this.direction === 'vertical' && this.itemHeight) { |
| | | x = 0 |
| | | y = index * this.itemHeight |
| | | } else if (this.itemWidth && this.itemHeight) { |
| | | // all模式,网格布局 |
| | | const col = index % this.columns; |
| | | const row = Math.floor(index / this.columns); |
| | | x = col * this.itemWidth; |
| | | y = row * this.itemHeight; |
| | | const col = index % this.columns |
| | | const row = Math.floor(index / this.columns) |
| | | x = col * this.itemWidth |
| | | y = row * this.itemHeight |
| | | } |
| | | |
| | | return { |
| | | ...item, |
| | | x, |
| | | y |
| | | }; |
| | | }); |
| | | // 保存初始位置 |
| | | this.saveOriginalPositions(); |
| | | }, |
| | | saveOriginalPositions() { |
| | | // 保存当前位置作为原始位置 |
| | | this.originalPositions = this.list.map(item => ({ |
| | | x: item.x, |
| | | y: item.y |
| | | })); |
| | | } |
| | | }) |
| | | }, |
| | | async calculateItemSize() { |
| | | // 计算项目尺寸 |
| | |
| | | |
| | | // 更新所有项目的位置 |
| | | this.updatePositions(); |
| | | // 保存原始位置 |
| | | this.saveOriginalPositions(); |
| | | } |
| | | resolve(res); |
| | | }) |
| | |
| | | .exec(); |
| | | }); |
| | | }, |
| | | updatePositions() { |
| | | updatePositions(isDragging) { |
| | | // 更新所有项目的位置 |
| | | this.list = this.list.map((item, index) => { |
| | | // 当前正在拖动的项目保持拖动位置不动,避免抖动 |
| | | if (this.dragIndex === index) { |
| | | return { |
| | | ...item, |
| | | x: this.currentPosition.x, |
| | | y: this.currentPosition.y |
| | | } |
| | | if (isDragging && this.dragIndex === index) { |
| | | return item |
| | | } |
| | | |
| | | if (this.direction === 'vertical') { |
| | |
| | | } |
| | | }) |
| | | }, |
| | | onTouchStart(index) { |
| | | onTouchStart(index, e) { |
| | | if (this.$slots.handler && e.currentTarget.dataset.action !== 'handler') { |
| | | return |
| | | } |
| | | if (this.list[index]?.draggable === false) return; |
| | | if (this.timer) clearTimeout(this.timer); |
| | | this.sortChanged = false; |
| | | this.dragIndex = index; |
| | | // 保存当前位置作为原始位置 |
| | | this.saveOriginalPositions(); |
| | | }, |
| | | onTouchMove(e) { |
| | | if (this.dragIndex !== -1) { |
| | | // 目前只对H5生效, 如果该组件放置在开启了下拉刷新的scroll-view中, 向下拖动item还是会触发下拉刷新 |
| | | e.stopPropagation() |
| | | e.preventDefault() |
| | | } |
| | | }, |
| | | onChange(index, event) { |
| | | if (!event.detail.source || event.detail.source !== 'touch') return; |
| | |
| | | const movedItem = this.list.splice(fromIndex, 1)[0]; |
| | | this.list.splice(toIndex, 0, movedItem); |
| | | |
| | | // 震动反馈 |
| | | if (uni.vibrateShort) { |
| | | uni.vibrateShort(); |
| | | } |
| | | |
| | | // 更新当前拖拽项目的新索引 |
| | | this.dragIndex = toIndex; |
| | | this.sortChanged = true; |
| | | |
| | | // 更新所有项目的位置 |
| | | this.updatePositions(); |
| | | this.updatePositions(true); |
| | | |
| | | // 保存当前位置作为原始位置 |
| | | this.saveOriginalPositions(); |
| | | // 震动反馈 |
| | | if (this.vibrate && uni.vibrateShort) { |
| | | uni.vibrateShort({ type: 'light' }); |
| | | } |
| | | }, |
| | | onTouchEnd() { |
| | | // 未发生位移 |
| | | if (this.dragIndex === -1) return |
| | | |
| | | // 0.001是为了解决拖动过快等某些极限场景下位置还原不生效问题 |
| | | if (this.direction === 'horizontal') { |
| | | this.list[this.dragIndex].x = this.currentPosition.x + 0.001; |
| | |
| | | |
| | | // 重置到位置,需要延迟触发动,否则无效。 |
| | | sleep(50).then(() => { |
| | | this.list.forEach((item, index) => { |
| | | item.x = this.originalPositions[index].x; |
| | | item.y = this.originalPositions[index].y; |
| | | }); |
| | | this.dragIndex = -1; |
| | | this.$emit('drag-end', [...this.list]); |
| | | this.updatePositions(); |
| | | if (this.sortChanged) { |
| | | this.$emit('drag-end', [...this.list]); |
| | | this.sortChanged = false; |
| | | } |
| | | this.timer = setTimeout(() => { |
| | | this.dragIndex = -1 |
| | | }, 600) |
| | | }); |
| | | } |
| | | }, |
| | |
| | | this.initList(); |
| | | }); |
| | | }, |
| | | deep: true |
| | | // deep: true |
| | | }, |
| | | direction: { |
| | | handler() { |
| | |
| | | this.$nextTick(() => { |
| | | this.initList(); |
| | | this.updatePositions(); |
| | | this.saveOriginalPositions(); |
| | | }); |
| | | } |
| | | } |
| | |
| | | <style scoped lang="scss"> |
| | | .u-dragsort { |
| | | width: 100%; |
| | | height: auto; |
| | | |
| | | .u-dragsort-area { |
| | | width: 100%; |
| | | height: 100%; |
| | | position: relative; |
| | | } |
| | | |
| | | .u-dragsort-item { |
| | | position: absolute; |
| | | width: 100%; |
| | | transition: box-shadow 0.45s ease-out; |
| | | cursor: pointer; |
| | | |
| | | &.dragging { |
| | | z-index: 1000; |
| | |
| | | } |
| | | |
| | | .u-dragsort-item-content { |
| | | position: relative; |
| | | padding: 0; |
| | | box-sizing: border-box; |
| | | } |
| | |
| | | .u-dragsort-area { |
| | | display: flex; |
| | | white-space: nowrap; |
| | | height: auto; |
| | | } |
| | | |
| | | .u-dragsort-item { |
| | |
| | | } |
| | | |
| | | &.u-dragsort--all { |
| | | .u-dragsort-area { |
| | | height: auto; |
| | | } |
| | | |
| | | .u-dragsort-item { |
| | | width: auto; |
| | | height: auto; |