123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- <template>
- <view class="u-dragsort"
- :class="[direction == 'horizontal' ? 'u-dragsort--horizontal' : '', direction == 'all' ? 'u-dragsort--all' : '']">
- <movable-area class="u-dragsort-area" :style="movableAreaStyle">
- <movable-view v-for="(item, index) in list" :key="item.id" :id="`u-dragsort-item-${index}`"
- class="u-dragsort-item" :class="{ 'dragging': dragIndex === index }"
- :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">
- <view class="u-dragsort-item-content">
- <slot :item="item" :index="index">
- {{ item.label }}
- </slot>
- </view>
- </movable-view>
- </movable-area>
- </view>
- </template>
- <script>
- import { mpMixin } from '../../libs/mixin/mpMixin';
- import { mixin } from '../../libs/mixin/mixin';
- import { addStyle, addUnit, sleep } from '../../libs/function/index';
- export default {
- name: 'u-dragsort',
- // #ifdef MP
- mixins: [mpMixin, mixin,],
- // #endif
- // #ifndef MP
- mixins: [mixin],
- // #endif
- props: {
- initialList: {
- type: Array,
- required: true,
- default: () => []
- },
- draggable: {
- type: Boolean,
- default: true
- },
- direction: {
- type: String,
- default: 'vertical',
- validator: value => ['vertical', 'horizontal', 'all'].includes(value)
- },
- // 新增列数属性,用于all模式
- columns: {
- type: Number,
- default: 3
- }
- },
- data() {
- return {
- list: [],
- dragIndex: -1,
- itemHeight: 40,
- itemWidth: 80,
- areaWidth: 0, // 可拖动区域宽度
- areaHeight: 0, // 可拖动区域高度
- originalPositions: [], // 保存原始位置
- currentPosition: {
- x: 0,
- y: 0
- }
- };
- },
- computed: {
- movableAreaStyle() {
- if (this.direction === 'vertical') {
- return {
- height: `${this.list.length * this.itemHeight}px`,
- width: '100%'
- };
- } else if (this.direction === 'horizontal') {
- return {
- height: '100%',
- width: `${this.list.length * this.itemWidth}px`
- };
- } else {
- // all模式,计算网格布局所需的高度
- const rows = Math.ceil(this.list.length / this.columns);
- return {
- height: `${rows * this.itemHeight}px`,
- width: '100%'
- };
- }
- }
- },
- emits: ['drag-end'],
- async mounted() {
- await this.$nextTick();
- this.initList();
- this.calculateItemSize();
- this.calculateAreaSize();
- },
- methods: {
- initList() {
- // 初始化列表项的位置
- this.list = this.initialList.map((item, index) => {
- let x = 0, y = 0;
- if (this.direction === 'horizontal') {
- x = index * this.itemWidth;
- y = 0;
- } else if (this.direction === 'vertical') {
- x = 0;
- y = index * this.itemHeight;
- } else {
- // all模式,网格布局
- 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() {
- // 计算项目尺寸
- await sleep(30);
- return new Promise((resolve) => {
- uni.createSelectorQuery()
- .in(this)
- .select('.u-dragsort-item-content')
- .boundingClientRect(res => {
- if (res) {
- this.itemHeight = res.height || 40;
- this.itemWidth = res.width || 80;
- // 更新所有项目的位置
- this.updatePositions();
- // 保存原始位置
- this.saveOriginalPositions();
- }
- resolve(res);
- })
- .exec();
- });
- },
- async calculateAreaSize() {
- // 计算可拖动区域尺寸
- await sleep(30);
- return new Promise((resolve) => {
- uni.createSelectorQuery()
- .in(this)
- .select('.u-dragsort-area')
- .boundingClientRect(res => {
- if (res) {
- this.areaWidth = res.width || 300;
- this.areaHeight = res.height || 300;
- }
- resolve(res);
- })
- .exec();
- });
- },
- updatePositions() {
- // 更新所有项目的位置
- this.list.forEach((item, index) => {
- if (this.direction === 'vertical') {
- item.y = index * this.itemHeight;
- item.x = 0;
- } else if (this.direction === 'horizontal') {
- item.x = index * this.itemWidth;
- item.y = 0;
- } else {
- // all模式,网格布局
- const col = index % this.columns;
- const row = Math.floor(index / this.columns);
- item.x = col * this.itemWidth;
- item.y = row * this.itemHeight;
- }
- });
- },
- onTouchStart(index) {
- this.dragIndex = index;
- // 保存当前位置作为原始位置
- this.saveOriginalPositions();
- },
- onChange(index, event) {
- if (!event.detail.source || event.detail.source !== 'touch') return;
- this.currentPosition.x = event.detail.x;
- this.currentPosition.y = event.detail.y;
- // all模式下使用更智能的位置计算
- if (this.direction === 'all') {
- this.handleAllModeChange(index);
- } else {
- // 原有的垂直和水平模式逻辑
- let itemSize = 0;
- let targetIndex = -1;
- if (this.direction === 'vertical') {
- itemSize = this.itemHeight;
- targetIndex = Math.max(0, Math.min(
- Math.round(this.currentPosition.y / itemSize),
- this.list.length - 1
- ));
- } else if (this.direction === 'horizontal') {
- itemSize = this.itemWidth;
- targetIndex = Math.max(0, Math.min(
- Math.round(this.currentPosition.x / itemSize),
- this.list.length - 1
- ));
- }
- // 如果位置发生变化,则重新排序
- if (targetIndex !== index) {
- this.reorderItems(index, targetIndex);
- }
- }
- },
- handleAllModeChange(index) {
- // 在all模式下,根据当前位置计算最近的网格位置
- const col = Math.max(0, Math.min(Math.round(this.currentPosition.x / this.itemWidth), this.columns - 1));
- const row = Math.max(0, Math.round(this.currentPosition.y / this.itemHeight));
- // 计算目标索引
- let targetIndex = row * this.columns + col;
- targetIndex = Math.max(0, Math.min(targetIndex, this.list.length - 1));
- // 如果位置发生变化,则重新排序
- if (targetIndex !== index) {
- this.reorderItems(index, targetIndex);
- }
- },
- reorderItems(fromIndex, toIndex) {
- const movedItem = this.list.splice(fromIndex, 1)[0];
- this.list.splice(toIndex, 0, movedItem);
- // 震动反馈
- if (uni.vibrateShort) {
- uni.vibrateShort();
- }
- // 更新当前拖拽项目的新索引
- this.dragIndex = toIndex;
- // 更新所有项目的位置
- this.updatePositions();
- // 保存当前位置作为原始位置
- this.saveOriginalPositions();
- },
- onTouchEnd() {
- // 0.001是为了解决拖动过快等某些极限场景下位置还原不生效问题
- if (this.direction === 'horizontal') {
- this.list[this.dragIndex].x = this.currentPosition.x + 0.001;
- } else if (this.direction === 'vertical' || this.direction === 'all') {
- this.list[this.dragIndex].y = this.currentPosition.y + 0.001;
- 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]);
- });
- }
- },
- watch: {
- initialList: {
- handler() {
- this.$nextTick(() => {
- this.initList();
- });
- },
- deep: true
- },
- direction: {
- handler() {
- this.$nextTick(() => {
- this.initList();
- this.calculateItemSize();
- this.calculateAreaSize();
- });
- }
- },
- columns: {
- handler() {
- if (this.direction === 'all') {
- this.$nextTick(() => {
- this.initList();
- this.updatePositions();
- this.saveOriginalPositions();
- });
- }
- }
- }
- }
- };
- </script>
- <style scoped lang="scss">
- .u-dragsort {
- width: 100%;
- .u-dragsort-area {
- width: 100%;
- position: relative;
- }
- .u-dragsort-item {
- position: absolute;
- width: 100%;
- &.dragging {
- z-index: 1000;
- box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
- }
- .u-dragsort-item-content {
- padding: 0px;
- text-align: center;
- box-sizing: border-box;
- padding-bottom: 6px;
- border-radius: 8rpx;
- transition: all 0.3s ease;
- }
- }
- &.u-dragsort--horizontal {
- .u-dragsort-area {
- display: flex;
- white-space: nowrap;
- height: auto;
- }
- .u-dragsort-item {
- display: flex;
- width: auto;
- height: 100%;
- }
- }
- &.u-dragsort--all {
- .u-dragsort-area {
- height: auto;
- }
- .u-dragsort-item {
- width: auto;
- height: auto;
- }
- }
- }
- </style>
|