diff --git a/uni_modules/HM-dragSorts/changelog.md b/uni_modules/HM-dragSorts/changelog.md new file mode 100644 index 0000000..dbf616d --- /dev/null +++ b/uni_modules/HM-dragSorts/changelog.md @@ -0,0 +1,50 @@ +## 1.0.6(2023-12-19) +* 修复vue3app环境下,拖动丢失行的问题 +* UVUE版本支持了多行多列,可以去看看[传送门](https://ext.dcloud.net.cn/plugin?id=15953) +## 1.0.5(2023-01-09) +* 修复拖动后,@onclick返回错误行下标 +* 增加push(),unshift(),splice()返回值,现在通过这三个函数修改后,会返回修改后的列表数组 +## 1.0.4(2022-11-17) +* 新增push,unshift,splice函数,实现列表的删除插入 +* 重写了给wxs传递数据的逻辑 +## 1.0.3(2022-08-19) +* [重写] 拖拽排序的算法,提升渲染性能 +* [修复] 修复删除行,排序出错的问题 +* [兼容] 触发长按时长属性(longTouchTime)增加兼容微信小程序(微信基础库2.14.2或以上) +* [废弃] 因为微信循环插槽会无限警告,所以废弃插槽 +* [BUG] vue3下,只支持长按拖拽 +## 1.0.2(2022-08-19) +* [重写] 拖拽排序的算法,提升渲染性能 +* [废弃] 因为微信循环插槽会无限警告,所以废弃插槽 +* [兼容] 触发长按时长属性(longTouchTime)增加兼容微信小程序(微信基础库2.14.2或以上) +* [BUG] vue3下,只支持长按拖拽 +## 1.0.1(2022-08-19) +* [重写] 拖拽排序的算法,提升渲染性能 +* [废弃] 因为微信循环插槽会无限警告,所以废弃插槽 +* [兼容] 触发长按时长属性(longTouchTime)增加兼容微信小程序(微信基础库2.14.2或以上) +* [BUG] vue3下,只支持长按拖拽 +## 1.0.0(2022-08-19) +* [重写] 拖拽排序的算法,提升渲染性能 +* [废弃] 因为微信循环插槽会无限警告,所以废弃插槽 +* [兼容] 触发长按时长属性(longTouchTime)增加兼容微信小程序(微信基础库2.14.2或以上) +* [BUG] vue3下,只支持长按拖拽 +## 0.2.5(2021-09-09) +* 修复 wxs使用了es6语法导致编译到微信小程序出错 感谢 @小小贝 反馈 +## 0.2.4(2021-09-01) +* 修复 iOS在整行拖拽情况下,触感反馈与点击事件冲突的问题 感谢 @粲然 反馈 +## 0.2.3(2021-08-09) +* 修复 修改list导致拖拽报错 +## 0.2.2(2021-07-06) +更新confirm的bug问题,这是我手贱写出的bug。 +## 0.2.1(2021-07-02) +* 修复 数据中传入id导致不触发回调事件的问题 感谢@layu反馈 +* 优化 拖拽和位置交换动画使用translate3d 感谢@pwiz反馈 +## 0.2.0(2021-06-23) +* 修复 页面滚动后拖拽位置不正确问题 +* 修复 页面使用多个组件时,组件间互相影响问题 +* 修复 微信小程序设置列表高度不生效的问题 +## 0.1.2(2021-02-02) +* 修复moveRow取值错误问题 感谢@诗人的咸鱼 反馈 +## 0.1.1(2021-02-02) +* 增加开关触感反馈参数feedbackGeneratorState +* 发布uni_modules版本(需HX3.1.0以上) diff --git a/uni_modules/HM-dragSorts/components/HM-dragSorts/HM-dragSorts.vue b/uni_modules/HM-dragSorts/components/HM-dragSorts/HM-dragSorts.vue new file mode 100644 index 0000000..f490dfe --- /dev/null +++ b/uni_modules/HM-dragSorts/components/HM-dragSorts/HM-dragSorts.vue @@ -0,0 +1,823 @@ + + + + + + diff --git a/uni_modules/HM-dragSorts/components/HM-dragSorts/drag.wxs b/uni_modules/HM-dragSorts/components/HM-dragSorts/drag.wxs new file mode 100644 index 0000000..de7591f --- /dev/null +++ b/uni_modules/HM-dragSorts/components/HM-dragSorts/drag.wxs @@ -0,0 +1,371 @@ + + +var scrollTop = {}; //滚动条位置 +// 排序列表 +var sortList={}; +var isMove = false; //是否可拖动 长按事件控制切换这个状态 +var touchTimer = false; //长按事件定时器 +// 当页面有多个当前组件时,guid用来识别当前的列表的。因为一个页面内多个组件的wxs作用域相同。 + + +function setScrollTop(tmpGuid) { + if (typeof scrollTop[tmpGuid] == "undefined") { + scrollTop[tmpGuid] = 0; + } +} + +function scroll(event, instance) { + var dataView = instance.selectComponent('#dataView'); + var viewData = dataView.getDataset(); + setScrollTop(viewData.guid) + scrollTop[viewData.guid] = event.detail.scrollTop; +} + +function initVar(state, instance) { + var dataView = instance.selectComponent('#dataView'); + var viewData = dataView.getDataset(); + // 读取配置项 + // 获取scroll-view id + config = All_Config[viewData.guid]; + + setScrollTop(config.guid); + state.initscrollTop = scrollTop[config.guid]; + +} +function getRowSort(findId,instance){ + for (var i = 0; i < sortList[config.guid].length; i++) { + if(sortList[config.guid][i].id==findId){ + currentRowView = sortList[config.guid][i].rowView; + return sortList[config.guid][i].lastSort; + } + } +} +var shadowRowBoxView=null; +var shadowRowView = null; +var currentRowView=null; +var rowSort=0; +var sorting = false; +function touchstart(event, instance) { + if(sorting){ + return ; + } + sorting = true; + // 兼容setTimeout + if(typeof setTimeout ==="undefined" && typeof instance.setTimeout !== 'undefined'){ + setTimeout = instance.setTimeout; + clearTimeout = instance.clearTimeout; + } + + isMove = false; + var rowData = event.instance.getDataset(); + var state = instance.getState(); + if (event.touches.length == 1) { + state.point = event.touches[0]; + + state.islongTap = true; + state.rowData = rowData; + //读取数据 + initVar(state, instance); + } + var rowStyle = event.instance.getComputedStyle(['height']); + config.rowHeight = parseFloat(rowStyle.height); //获取行高 + // 计算shadowRow.style.top + + rowSort = getRowSort(rowData.id,instance); + var shadowRowTop = rowSort * config.rowHeight; + shadowRowTop = shadowRowTop - scrollTop[config.guid]; + // 加载shadowRow数据 + instance.callMethod("loadShadowRow", { + rowSort: rowSort + }); + state.shadowRowTop = shadowRowTop; + // 设置shadowRow初始位置 + shadowRowBoxView = instance.selectComponent('#shadowRowBox'); + shadowRowBoxView.setStyle({ + 'top': shadowRowTop + 'px' + }) + shadowRowView = instance.selectComponent('#shadowRow') + //长按事件 + if (config.longTouch) { + touchTimer && clearTimeout(touchTimer); + touchTimer = setTimeout(function() { + longpress(event, instance); + }, config.longTouchTime) + } +} + +function longpress(event, instance) { + if (config.longTouch) { + isMove = true; + moveRow(instance, 0) + } +} + +function touchmove(event, instance) { + var state = instance.getState(); + var rowData = event.instance.getDataset(); + var movePoint = event.touches[0]; + var initPoint = state.point; + var moveY = movePoint.pageY - initPoint.pageY; + if (config.longTouch) { + if (Math.abs(moveY) > 10) { + clearTimeout(touchTimer); + } + if (!isMove) { + return; + } + } + moveRow(instance, moveY); + //阻止滚动页面 + if (event.preventDefault) { + event.preventDefault(); + } + return false; +} +function touchend(event, instance) { + if (config.longTouch) { + clearTimeout(touchTimer); + if (!isMove) { + oldOffset = null; + sorting = false; + return; + } + } + if (lastCommand != "stop") { + lastCommand = "stop"; + config.autoScroll && instance.callMethod("pageScroll", { + 'guid': config.guid, + 'command': "stop" + }); + } + var state = instance.getState(); + // 把隐藏的行重新显示 + resetRowStyle(instance,state.rowData.id) + // 隐藏ShadowRow + resetShadowRowStyle(instance,state.offset) + if (typeof state.offset !== "undefined" && rowSort != state.offset && state.offset != null) { + var sortArray=[]; + for (var i = 0; i < sortList[config.guid].length; i++) { + sortList[config.guid][i].lastSort = sortList[config.guid][i].newSort; + sortArray.push(sortList[config.guid][i].newSort); + sortList[config.guid][i].rowView.removeClass('ani'); + } + instance.callMethod("sort", { + index: rowSort, + offset: state.offset, + sortArray:sortArray + }); + } else { + sorting = false; + triggerFeedbackGenerator(instance); //震动反馈 + return false; + } + state.offset = null; + oldOffset = null; + sorting = false; + triggerFeedbackGenerator(instance); //震动反馈 + return false; +} +// 重置列表行 +function resetRowStyle(instance,id) { + currentRowView.removeClass('hide'); +} +// 重置拖拽行 +function resetShadowRowStyle(instance,offset) { + shadowRowBoxView.removeClass('show'); + shadowRowBoxView.addClass('hide'); + shadowRowBoxView.setStyle({}); +} +var lastCommand = ''; + +// move Row +function moveRow(instance, moveY) { + var state = instance.getState(); + + // 显示shadowRowBox + shadowRowBoxView.removeClass('hide'); + shadowRowBoxView.hasClass('show') || shadowRowBoxView.addClass('show'); + // 移动shadowRowBox里面的shadowRow + shadowRowView.setStyle({ + 'transform': 'translate3d(0,' + moveY + 'px,10px)', + '-webkit-transform': 'translate3d(0,' + moveY + 'px,10px)' + }); + // 隐藏列表对应行 + currentRowView.hasClass('hide') || currentRowView.addClass('hide'); + currentRowView.removeClass('ani') + var listClientY = state.shadowRowTop + moveY + config.rowHeight/2; + var tmpscrollListTop = scrollTop[config.guid]; + + // 拖拽至边缘滚动视图 距离顶部距离1.5行高触发上滚动 下滚动同理 + var callMethodData = { + guid: config.guid, + command: listClientY < config.ListHeight * 0.2 ? "up" : listClientY > config.ListHeight - (config.ListHeight * 0.2) ? "down" : + "stop", + scrollTop: tmpscrollListTop, + } + // 把滚动指令发给逻辑层 + if (lastCommand != callMethodData.command) { + lastCommand = callMethodData.command; + config.autoScroll && instance.callMethod("pageScroll", callMethodData); + } + + var moveOffset = moveY + scrollTop[config.guid] - state.initscrollTop; + var offset = calcOffset(rowSort, moveOffset); + if (offset <= 2 || offset >= config.listLength - 2) { + callMethodData.command = 'stop'; + } + // 为减少卡顿,微信小程序端,在滚动视图期间不进行列表位置交换 + if (config.autoScroll && (!config.isAppH5) && callMethodData.command != 'stop') { + return; + } + oldOffset = oldOffset == null ? rowSort : oldOffset; + if (offset < 0 || offset >= config.listLength) { + return; + } + if (offset == oldOffset) { + return; + } + + oldOffset = offset; + state.offset = offset; + //触发change事件 并交换列表位置 + instance.callMethod("change", { + index: rowSort, + moveTo: state.offset + }); + for (var i = 0; i < sortList[config.guid].length; i++) { + var sort = sortList[config.guid][i].lastSort; + var newSort = sortList[config.guid][i].newSort; + if ((sort >= offset && sort <= rowSort) || (sort <= offset && sort >= rowSort)) { + if(sort == rowSort) { + newSort = offset; + }else{ + newSort = sort < rowSort ? sort+1 : sort-1; + } + }else{ + newSort = sort; + } + if(sortList[config.guid][i].newSort == newSort){ + continue; + } + sortList[config.guid][i].newSort = newSort; + var translateY = (sortList[config.guid][i].newSort-sortList[config.guid][i].sort) * 100; + sortList[config.guid][i].rowView.hasClass('ani') || sortList[config.guid][i].rowView.addClass('ani'); + sortList[config.guid][i].rowView.setStyle({ + 'transform': 'translate3d(0,' + translateY + '%,0)', + '-webkit-transform': 'translate3d(0,' + translateY + '%,0)' + }); + } + triggerFeedbackGenerator(instance); //震动反馈 +} +//计算偏移index +var oldOffset = null; +function calcOffset(initSort, moveY) { + var offset = initSort + parseInt(moveY / config.rowHeight); //偏移 行高的倍数 + var rest = moveY % config.rowHeight; + if (rest > 0) { + offset = offset + (rest / config.rowHeight >= 0.6 ? 1 : 0); + if (offset < oldOffset) { + offset = rest / config.rowHeight <= 0.4 ? offset : oldOffset; + } + } else + { + offset = offset + (rest / config.rowHeight <= -0.6 ? -1 : 0); + if (offset > oldOffset) { + offset = rest / config.rowHeight >= -0.4 ? offset : oldOffset; + } + } + return offset; +} + +//触感反馈 +//wxs 不支持条件编译,所以用此方法判断 +var isiOSAPP = typeof plus != "undefined" && plus.os.name == 'iOS'; +var UISelectionFeedbackGenerator; +var impact + +if (isiOSAPP) { + UISelectionFeedbackGenerator = plus.ios.importClass("UISelectionFeedbackGenerator"); + impact = new UISelectionFeedbackGenerator(); + impact.init(); +} +function triggerFeedbackGenerator(instance) { + if (!config.feedbackGenerator) { + //关闭触感反馈 + return; + } + if (isiOSAPP) { + //异步,避免与点击事件冲突 + setTimeout(function(){ + impact.selectionChanged(); + },0) + } else { + if (typeof plus != "undefined") { + plus.device.vibrate(12) + } else { + instance.callMethod("vibrate"); + } + } +} +var All_Config={}; +var config = {}; +function receiveData(e,state, instance){ + var data = JSON.parse(e); + var tmp_config = {}; + var hasGuid = false; + var sortArray=[]; + for(var i=0;i0){ + tmp_row.lastSort = sortArray[i]; + tmp_row.newSort = tmp_row.lastSort; + } + sortList[guid].push(tmp_row); + var translateY = (tmp_row.lastSort-tmp_row.sort) * 100; + tmp_row.rowView.setStyle({ + 'transform': 'translate3d(0,' + translateY + '%,0)', + '-webkit-transform': 'translate3d(0,' + translateY + '%,0)' + }); + } + +} +// 输出 +module.exports = { + receiveData:receiveData, + scroll: scroll, + longpress: longpress, + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend +} \ No newline at end of file diff --git a/uni_modules/HM-dragSorts/package.json b/uni_modules/HM-dragSorts/package.json new file mode 100644 index 0000000..32e0a05 --- /dev/null +++ b/uni_modules/HM-dragSorts/package.json @@ -0,0 +1,81 @@ +{ + "id": "HM-dragSorts", + "displayName": "拖动排序列表 HM-dragSorts", + "version": "1.0.6", + "description": "可拖动行,对列表进行排序,拖动触感反馈,兼容APP-VUE、H5、MP-WEIXIN", + "keywords": [ + "拖拽", + "拖动", + "拖动排序", + "drag", + "触感反馈" +], + "repository": "", + "engines": { + "HBuilderX": "^3.6.0" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "震动" + }, + "npmurl": "", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "n", + "IE": "n", + "Edge": "n", + "Firefox": "n", + "Safari": "n" + }, + "小程序": { + "微信": "y", + "阿里": "n", + "百度": "n", + "字节跳动": "n", + "QQ": "n" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "n" + } + } + } + } +} \ No newline at end of file diff --git a/uni_modules/HM-dragSorts/readme.md b/uni_modules/HM-dragSorts/readme.md new file mode 100644 index 0000000..1a50e98 --- /dev/null +++ b/uni_modules/HM-dragSorts/readme.md @@ -0,0 +1,117 @@ + + +##组件说明 + +> * 这是一个传入列表数据会生成一个可拖动排序列表的组件 +> * 此组件只支持单列多行,UVUE版本支持了多行多列,可以去看看[传送门](https://ext.dcloud.net.cn/plugin?id=15953) +> * 因为废弃了插槽,所以,行的样式需要自行到组件内部去修改 +> * 行内容可以自行到组件,组件只支持每行都相等高度的列表 +> * 拖动会有触感反馈,如果设备支持的话。 +> * 组件使用了wxs,兼容APP-VUE、H5、MP-WEIXIN,其他端未做兼容,不支持。 +> * [BUG] vue3下,只支持长按拖拽 +> * 下载示例并运行,你的很多疑问或得到答案。 + + +###属性说明 + +|属性名 |类型 |说明 | +|-- |-- |-- | +|list|ObjectArray |必填,列表数据,数据格式请参考示例,
注意:数据非双向绑定,拖动并不会直接修改list数据,排序过的数据在confirm中获取 | +|rowHeight|Int |选填,每一行的高度,单位:px,默认44px | +|listHeight|Int |选填,整个列表的高度,默认等于窗口高度 | +|listBackgroundColor|String |选填,列表底色,注意是列表的底色,不是行的底色,默认#FFFFFF | +|feedbackGenerator|Boolean |选填,是否开启拖动触感反馈,可选值true/false,默认true 开启 | +|longTouch|Boolean |选填,是否长按拖动,可选值true/false,默认false 关闭,如果是整行拖拽,请开启长按拖拽,不然页面不能滚动 | +|autoScroll|Boolean |选填,是否拖拽至边缘自动滚动列表,可选值true/false,默认true 开启 | +|longTouchTime|Int |选填,触发长按时长,单位:ms,默认350ms | +|@onclick|EventHandle |点击事件,返回被点击行的数据,event = {index:被点击行的下标,row:被点击行的数据} | +|@confirm|EventHandle |拖拽结束且行位置发生了改变,触发confirm事件,event = {index:'原始下标',moveTo:'被拖动到的下标',moveRow:'拖动行数据',list:'整个列表拖动后的数据'} | +|@change|EventHandle |拖拽过程中,行位置发生交换时,触发change事件,event = {index:'原始下标',moveTo:'被拖动到的下标',moveRow:'拖动行数据'} | + +###内置函数说明 + > ####push,unshift,splice函数 +内置了push,unshift,splice函数,组件设置ref属性,通过$refs调用,实现列表的删除插入,使用方法和数组的push,unshift,splice一致,push,unshift,splice函数修改后会返回修改后的值 +例如: + ``` + let tmplist = this.$refs.dragSorts.splice(5,1,...rows); + ``` + + +##使用示例 +页面: +``` + + +``` +script: + +``` + import dragSorts from '@/uni_modules/components/HM-dragSorts/HM-dragSorts.vue' // 组件符合easycom规范,默认这个可以不写 + export default { + components: {'HM-dragSorts':dragSorts},// 组件符合easycom规范,默认这个可以不写 + data() { + return { + list:[ + {"name": "花呗", "icon": "/static/img/1.png"}, + {"name": "余额宝","icon": "/static/img/2.png"}, + {"name": "账户余额","icon": "/static/img/3.png"}, + {"name": "交通银行信用卡(0001)""icon": "/static/img/4.png"}, + {"name": "中国建设银行信用卡(4401)","icon": "/static/img/5.png"}, + {"name": "网商储蓄卡(7223)","icon": "/static/img/6.png"} + ] + } + }, + methods: { + push(){ + // 和数组的push使用方法一致,可以push单行,也可以push多行 + this.$refs.dragSorts.push({ + "name": "push行", + "icon": "/static/img/2.png" + }); + }, + unshift(){ + // 和数组的unshift使用方法一致,可以unshift单行,也可以unshift多行 + this.$refs.dragSorts.unshift({ + "name": "unshift行", + "icon": "/static/img/2.png" + }); + }, + splice(){ + // 和数组的unshift使用方法一致 下标1开始删除1个并在下标1位置插入行 + this.$refs.dragSorts.splice(1,1,{ + "name": "splice行", + "icon": "/static/img/2.png" + }); + }, + onclick(e){ + console.log('=== onclick start ==='); + console.log("被点击行: " + JSON.stringify(e.value)); + console.log("被点击下标: " + JSON.stringify(e.index)); + console.log('=== onclick end ==='); + }, + change(e){ + console.log('=== change start ==='); + console.log("被拖动行: " + JSON.stringify(e.moveRow)); + console.log('原始下标:',e.index); + console.log('移动到:',e.moveTo); + console.log('=== change end ==='); + }, + confirm(e){ + console.log('=== confirm start ==='); + console.log("被拖动行: " + JSON.stringify(e.moveRow)); + console.log('原始下标:',e.index); + console.log('移动到:',e.moveTo); + console.log('=== confirm end ==='); + } + } + } +``` + +###更多的说明请下载示例运行查看,有示例对照注释更容易明白。