uni-app 实现类似手机桌面应用拖拽的功能

1.组件封装

image

1px.less 文件


.setLine(@c: #eee) {

  content: " ";

  position: absolute;

  left: 0;

  top: 0;

  width: 200%;

  // border: 1rpx solid @c;

  color: @c;

  height: 200%;

  transform-origin: left top;

  transform: scale(0.5);

}

.vh-1px, .vh-1px-t, .vh-1px-b, .vh-1px-tb, .vh-1px-l, .vh-1px-r {

  position: relative;

}

.vh-1px {

  &:before {

    .setLine();

  }

}

.vh-1px-t {

  &:before {

    .setTopLine();

  }

}

.vh-1px-b {

  &:after {

    .setBottomLine();

  }

}

.vh-1px-tb {

  &:before {

    .setTopLine();

  }

  &:after {

    .setBottomLine();

  }

}

.vh-1px-l {

  &:before {

    .setLeftLine();

  }

}

.vh-1px-r {

  &:after {

    .setRightLine();

  }

}

.setTopLine(@c: #eee) {

  content: " ";

  position: absolute;

  left: 0;

  top: 0;

  right: 0;

  height: 1rpx;

  // border-top: 1rpx solid @c;

  color: @c;

  transform-origin: 0 0;

  transform: scaleY(0.5);

}

.setBottomLine(@c: #eee) {

  content: " ";

  position: absolute;

  left: 0;

  bottom: 0;

  right: 0;

  height: 1rpx;

  // border-bottom: 1rpx solid @c;

  color: @c;

  transform-origin: 0 100%;

  transform: scaleY(0.5);

}

.setLeftLine(@c: #eee) {

  content: " ";

  position: absolute;

  left: 0;

  top: 0;

  width: 1rpx;

  bottom: 0;

  // border-left: 1rpx solid @c;

  color: @c;

  transform-origin: 0 0;

  transform: scaleX(0.5);

}

.setRightLine(@c: #eee) {

  content: " ";

  position: absolute;

  right: 0;

  top: 0;

  width: 1rpx;

  bottom: 0;

  // border-right: 1rpx solid @c;

  color: @c;

  transform-origin: 100% 0;

  transform: scaleX(0.5);

}

index.vue 文件

<template>
    <view class="">
        <movable-area class="drag-sort" :style="{height:boxHeight }" id="drag">
            <movable-view v-for="(item, index) in currentList" :key="index" :x="item.x" v-if="item.isShow === 1" :data-index="index"
             @touchstart="touchstart" @touchmove.stop="touchmove" @touchend="touchend" @click="openTool(item)" :y="item.y"
             :direction="direction" disabled damping="40" :animation="item.animation" class="drag-sort-item" :style="styleObject"
             :class="{'active': active == index, 'vh-1px-t': item.index > 0}">
                <view class="item">
                    <image :src="item.scr" mode="" style="width: 60rpx;height: 60rpx;"></image>
                    <view>{{item[props.label]}}</view>
                </view>
            </movable-view>
        </movable-area>
    </view>

</template>

<script>
    export default {
        name: 'drag-sort',
        mixins: [],
        components: {},
        data() {
            return {
                styleObject: {
                    color: 'red',
                    fontSize: '13px'
                },
                style: {
                    background: 'red'
                },
                direction: "all",
                windowWidth: 350, //屏幕宽度
                height: 100, // 高度
                currentList: [],
                active: -1, // 当前激活的item
                index: 0, // 当前激活的item的原index
                topY: 0, // 距离顶部的距离
                topX: 0, // 距离左侧偏移位置
                deviationX: 0,
                deviationY: 0 // 偏移量
            }
        },
        computed: {
            boxHeight() {
                return (Math.ceil((Number(this.list.length) + 1) / 4)) * this.height + 'px'
            }
        },
        props: {
            list: {
                type: Array,
                default: () => {
                    return []
                }
            },
            boxStyle: {
                type: Object,
                default: () => {
                    return {}
                }
            },
            props: {
                type: Object,
                default: () => {
                    return {
                        label: 'label',
                        value: 'value'
                    }
                }
            }
        },
        watch: {
            list: {
                handler() {
                    this.onUpdateCurrentList(this.list)
                },
                deep: true

            }
        },
        created() {},
        mounted() {
            let that = this
            //获取屏幕宽度
            uni.getSystemInfo({
                success: function(res) {
                    that.windowWidth = res.windowWidth
                }
            });
            this.getLocalstorageList()
        },
        updated() {},
        filters: {},
        methods: {
            //获取缓存中的应用顺序
            getLocalstorageList() {
                let that = this
                uni.getStorage({
                    key: 'appList',
                    success: function(res) {
                        that.onUpdateCurrentList(res.data)
                    },
                    fail: function() {
                        that.onUpdateCurrentList(that.list)
                    }
                });
            },
            //存储图标位置
            localstorageList(currentList) {
                //对象数组根据属性进行排序
                currentList.sort(function sortId(a, b) {
                    return a.SortNumber - b.SortNumber
                });
                //存储当前应用程序顺序
                uni.setStorage({
                    key: 'appList',
                    data: currentList,
                    success: function() {}
                });
            },
            //打开对应的工具
            openTool(tools) {
                console.log('currentclick', tools)
                if (tools.url == 'd') {
                    //跳转外部小程序
                    uni.navigateToMiniProgram({
                        appId: 'wx63fe2171954ab185',
                        path: '',
                        extraData: {
                            'data1': 'test'
                        },
                        envVersion: 'develop',
                        success(res) {
                            console.log('外部小程序打开成功')
                            // 打开成功
                        },
                        fail(res) {
                            console.log('外部小程序打开失败')
                            // 打开失败
                        }
                    })
                } else {
                    uni.navigateTo({
                        //url: `../${tools.value}/index/index`,
                        url: tools.url,
                        success: res => {},
                        fail: () => {},
                        complete: () => {}
                    });
                }
            },
            onUpdateCurrentList(list) {
                let arr = []
                for (const key in list) {
                    let height = Math.ceil((Number(key) + 1) / 4) - 1
                    let x = 0
                    if (key <= 3) {
                        x = key * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this.windowWidth * 0.04
                    } else {
                        if ((Number(key) + 1) % 4 === 0) {
                            x = 3 * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this.windowWidth * 0.04
                        } else {
                            x = ((Number(key) + 1) % 4 - 1) * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this.windowWidth * 0.04
                        }
                    }
                    arr.push({
                        ...list[key],
                        isShow: 1,
                        index: Number(key),
                        SortNumber: Number(key),
                        y: height * this.height,
                        x,
                        animation: true
                    })
                }
                this.currentList = arr
            },
            // 根据排序进行重新计算位置
            moveUpdateCurrentList(index) {
                for (const i in this.currentList) {
                    let key
                    if (this.currentList[i].SortNumber || this.currentList[i].SortNumber === 0) {
                        key = this.currentList[i].SortNumber
                    } else {
                        key = Number(i)
                    }
                    let temobj = { ...this.currentList[i]
                    }
                    this.currentList[i].y = (Math.ceil((Number(key) + 1) / 4) - 1) * this.height
                    if (index == key) {
                        continue
                    } else {
                        if (key <= 3) {
                            this.currentList[i].x = key * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this.windowWidth * 0.04
                        } else {
                            if ((Number(key) + 1) % 4 === 0) {
                                this.currentList[i].x = 3 * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this.windowWidth * 0.04
                            } else {
                                this.currentList[i].x = ((Number(key) + 1) % 4 - 1) * this.windowWidth * 0.24 + this.windowWidth * 0.04 || this
                                    .windowWidth * 0.04
                            }
                        }
                    }
                }
                this.$emit('change', this.currentList)
            },
            touchstart(e) {
                // 计算 x y 轴点击位置
                var query = uni.createSelectorQuery().in(this)
                query.select('#drag').boundingClientRect()
                query.exec((res) => {
                    this.topY = res[0].top
                    this.topX = res[0].left
                    let touchY = e.mp.touches[0].clientY - res[0].top
                    let touchX = e.mp.touches[0].clientX - res[0].left
                    this.deviationY = touchY % this.height
                    this.deviationX = touchX % (this.windowWidth * 0.2)
                    this.active = Number(e.currentTarget.dataset.index)
                    this.index = Number(e.currentTarget.dataset.index)
                })
            },
            touchmove(e) {
                if (this.active < 0) return
                let temY = e.mp.touches[0].clientY - this.topY
                let temX = e.mp.touches[0].clientX - this.topX
                let touchY = temY - 15
                let touchX = temX - this.windowWidth * 0.1
                this.currentList[this.active].y = touchY
                this.currentList[this.active].x = touchX
                this.currentList[this.active].animation = false
                this.currentList.every((res, index) => {
                    let absX = Math.abs(touchX - res.x)
                    let absY = Math.abs(touchY - res.y)
                    // 设置元素定点距离多少进行重排
                    if (0 < absX && absX <= 10 && absY > 0 && absY <= 10 && this.active != index) {
                        // debugger
                        let temNumber = this.currentList[index].SortNumber
                        this.currentList.every((_res, _index) => {
                            // 判断从大像小移还是从小向大移
                            if (this.currentList[this.active].SortNumber < this.currentList[index].SortNumber) {
                                // 移动元素比目标元素所在位置小,之间元素排序--
                                if (this.currentList[_index].SortNumber > this.currentList[this.active].SortNumber && this.currentList[
                                        _index].SortNumber <= this.currentList[index].SortNumber) {
                                    _res.SortNumber--
                                }
                            } else {
                                // 反之++
                                if (this.currentList[_index].SortNumber < this.currentList[this.active].SortNumber && this.currentList[
                                        _index].SortNumber >= this.currentList[index].SortNumber) {
                                    _res.SortNumber++
                                }
                            }
                            return true
                        }, this)
                        this.currentList[this.active].SortNumber = temNumber
                        this.moveUpdateCurrentList(temNumber)
                        return false
                    } else {
                        return true
                    }
                }, this)
            },
            touchend(e) {
                if (this.currentList[this.active]) {
                    this.currentList[this.active].animation = true
                }
                this.moveUpdateCurrentList(-1)
                this.active = -1
                //当前顺序本地存储(一定要深拷贝啊啊啊!!!)
                this.localstorageList(JSON.parse(JSON.stringify(this.currentList)))
            }
        }
    }
</script>

<style lang='less' scoped>
    @import "~./1px.less";

    .drag-sort {
        width: calc(100% - 20rpx);
    }

    .drag-sort-item {
        position: absolute !important;
        display: flex;
        height: auto;
        align-items: center;
        width: 25%;
        text-align: center;
        color: #333;
        box-sizing: border-box;

        .item {
            position: relative;
            flex: 1;
            font-size: 24rpx;
        }
    }

    .active {
        z-index: 99;
    }
</style>

2.页面引用


image.png

image.png

3.预览


image.png

可拖拽排序

备注:这版做的是微信小程序端的,app可自行试试

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容

  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,144评论 0 1
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    柴东啊阅读 15,850评论 2 140
  • 简说Vue (组件库) https://github.com/ElemeFE/element" 饿了么出品的VUE...
    Estrus丶阅读 1,601评论 0 1
  • 标签部分 元数据 重要:meta示例代码 次要:title base link style 章节 重要:secti...
    ItsYaeji阅读 2,143评论 0 1
  • 苍穹独醉 “又是美好的一天。” 南宫引舒展着纳气了一夜的身体,顿时骨头关节之处传来咔咔的响声,修士就是修士,与凡...
    浅歌一笑阅读 276评论 0 1