当前位置:   article > 正文

unnapp 小程序长按拖动排序

unnapp 小程序长按拖动排序

一、单列排序

1.组件 sort.vue

  1. <template>
  2. <view @touchmove.stop.prevent>
  3. <scroll-view
  4. :scroll-y="isTouchMove"
  5. :style="{
  6. height: windowHeight + 'px',
  7. }"
  8. >
  9. <view
  10. class="listItem"
  11. @longtap="longtap($event, index)"
  12. @touchstart="onTouchstart($event, index)"
  13. @touchmove="onTouchmove"
  14. @touchend="onTouchend"
  15. v-for="(item, index) in listArray"
  16. :key="index"
  17. :style="{
  18. height: lineHeight + 'px',
  19. 'margin-bottom': '15px',
  20. top: listPosition[index].top + 'px',
  21. transition: curretnItemIndex === index ? 'initial' : '.3s',
  22. }"
  23. :class="{ activeClass: index == curretnItemIndex }"
  24. >
  25. {{ item.name }}
  26. </view>
  27. </scroll-view>
  28. </view>
  29. </template>
  30. <script>
  31. export default {
  32. props: {
  33. //列表
  34. list: {
  35. type: Array,
  36. default: [],
  37. },
  38. // 列表每行高度
  39. lineHeight: {
  40. type: Number,
  41. default: 80,
  42. },
  43. },
  44. data() {
  45. return {
  46. listArray: [],
  47. // 所有元素定位位置
  48. listPosition: [],
  49. // 记录拖动前元素定位位置
  50. initListPosition: [],
  51. // 记录当前拖动元素的下标
  52. curretnItemIndex: -1,
  53. // 记录拖动前的位置
  54. recordPosition: {
  55. y: 0,
  56. },
  57. // 记录拖动前的定位位置
  58. recordCurrentPositionItem: {
  59. top: 0,
  60. },
  61. // 是否正在交换位置
  62. isChange: false,
  63. isDrag: false,
  64. isTouchMove: true,
  65. windowHeight: 0,
  66. };
  67. },
  68. created() {
  69. this.init();
  70. let windowHeight = uni.getSystemInfoSync().windowHeight;
  71. // 状态栏高度
  72. let statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
  73. const custom = wx.getMenuButtonBoundingClientRect();
  74. let navigationBarHeight =
  75. custom.height + (custom.top - statusBarHeight) * 2;
  76. let navHeight = navigationBarHeight + statusBarHeight;
  77. this.windowHeight = windowHeight - navHeight;
  78. },
  79. methods: {
  80. init() {
  81. this.listArray = [...this.list];
  82. const query = uni.createSelectorQuery().in(this);
  83. query.selectAll(".listItem").fields(
  84. {
  85. rect: true,
  86. size: true,
  87. },
  88. (data) => {
  89. data.forEach((item, index) => {
  90. this.listPosition.push({
  91. height: item.height,
  92. top: (item.height + 15) * index,
  93. });
  94. });
  95. this.initListPosition = [...this.listPosition];
  96. }
  97. );
  98. query.exec(); //执行所有请求
  99. },
  100. longtap(event, index) {
  101. this.isTouchMove = false;
  102. this.isDrag = true;
  103. const { pageY } = event.touches[0];
  104. // 记录当前拖动元素的下标
  105. this.curretnItemIndex = index;
  106. // 记录拖动前的位置
  107. this.recordPosition = {
  108. y: pageY,
  109. };
  110. // 记录拖动前的定位位置
  111. this.recordCurrentPositionItem = this.listPosition[index];
  112. },
  113. onTouchstart(event, index) {
  114. // const { pageY } = event.touches[0];
  115. // // 记录当前拖动元素的下标
  116. // this.curretnItemIndex = index;
  117. // // 记录拖动前的位置
  118. // this.recordPosition = {
  119. // y: pageY,
  120. // };
  121. // // 记录拖动前的定位位置
  122. // this.recordCurrentPositionItem = this.listPosition[index];
  123. },
  124. onTouchmove(event) {
  125. if (!this.isDrag) {
  126. return;
  127. }
  128. const { pageY } = event.touches[0];
  129. // 获取移动的差
  130. this.$set(this.listPosition, this.curretnItemIndex, {
  131. top:
  132. this.listPosition[this.curretnItemIndex].top +
  133. (pageY - this.recordPosition.y),
  134. });
  135. // 记录位置
  136. this.recordPosition = {
  137. y: pageY,
  138. };
  139. // 向下
  140. if (
  141. this.listPosition[this.curretnItemIndex].top >=
  142. this.listPosition[this.curretnItemIndex + 1]?.top -
  143. this.initListPosition[0].height / 2
  144. ) {
  145. if (this.isChange) return;
  146. this.isChange = true;
  147. let temp = this.listArray[this.curretnItemIndex];
  148. console.log(temp);
  149. this.listArray[this.curretnItemIndex] =
  150. this.listArray[this.curretnItemIndex + 1];
  151. this.listArray[this.curretnItemIndex + 1] = temp;
  152. this.listPosition[this.curretnItemIndex + 1] =
  153. this.listPosition[this.curretnItemIndex];
  154. this.listPosition[this.curretnItemIndex] =
  155. this.recordCurrentPositionItem;
  156. this.curretnItemIndex = this.curretnItemIndex + 1;
  157. this.recordCurrentPositionItem =
  158. this.initListPosition[this.curretnItemIndex];
  159. this.isChange = false;
  160. }
  161. // 向上
  162. if (
  163. this.listPosition[this.curretnItemIndex].top <=
  164. this.listPosition[this.curretnItemIndex - 1]?.top +
  165. this.initListPosition[0].height / 2
  166. ) {
  167. if (this.isChange) return;
  168. this.isChange = true;
  169. let temp = this.listArray[this.curretnItemIndex];
  170. console.log(temp);
  171. this.listArray[this.curretnItemIndex] =
  172. this.listArray[this.curretnItemIndex - 1];
  173. this.listArray[this.curretnItemIndex - 1] = temp;
  174. this.listPosition[this.curretnItemIndex - 1] =
  175. this.listPosition[this.curretnItemIndex];
  176. this.listPosition[this.curretnItemIndex] =
  177. this.recordCurrentPositionItem;
  178. this.curretnItemIndex = this.curretnItemIndex - 1;
  179. this.recordCurrentPositionItem =
  180. this.initListPosition[this.curretnItemIndex];
  181. this.isChange = false;
  182. }
  183. },
  184. onTouchend(event) {
  185. if (!this.isDrag) {
  186. return;
  187. }
  188. this.isTouchMove = true;
  189. this.isDrag = false;
  190. // 拖动元素归位
  191. this.listPosition[this.curretnItemIndex] =
  192. this.initListPosition[this.curretnItemIndex];
  193. this.curretnItemIndex = -1;
  194. this.$emit("change", [...this.listArray]);
  195. },
  196. },
  197. };
  198. </script>
  199. <style scoped lang="scss">
  200. .listItem {
  201. width: 100%;
  202. align-items: center;
  203. box-sizing: border-box;
  204. background-color: #fff;
  205. }
  206. .activeClass {
  207. box-shadow: 0 0px 50rpx #cfcfcf;
  208. z-index: 999;
  209. }
  210. </style>

2.使用 index.vue

  1. <template>
  2. <view>
  3. <sort
  4. ref="sort"
  5. v-if="sortList.length > 0"
  6. :list="sortList"
  7. :lineHeight="lineHeight"
  8. @change="changeSort"
  9. />
  10. </view>
  11. </template>
  12. <script>
  13. import sort from "./components/sort.vue";
  14. export default {
  15. components: {
  16. sort,
  17. },
  18. data() {
  19. return {
  20. sortList: [{ name: "1" }, { name: "2" }, { name: "3" }],
  21. lineHeight: 108,
  22. };
  23. },
  24. methods: {
  25. changeSort(e) {
  26. console.log(e, "修改后的新排序");
  27. },
  28. },
  29. };
  30. </script>

二、多列排序

1.sort.vue

  1. <template>
  2. <view>
  3. <scroll-view
  4. :scroll-y="isEdit"
  5. :style="{
  6. height: windowHeight + 'px',
  7. }"
  8. >
  9. <movable-area
  10. id="dragSortArea"
  11. :style="{ height: boxHeight + 'px' }"
  12. >
  13. <block v-for="(item, index) in cloneList" :key="item.id">
  14. <movable-view
  15. class="dragSort-view"
  16. direction="all"
  17. :class="{ 'is-touched': item.isTouched }"
  18. :x="item.x"
  19. :y="item.y"
  20. :damping="40"
  21. :disabled="!isEdit"
  22. @longtap="longtap($event, index)"
  23. @change="onChange($event, item)"
  24. @touchstart="onTouchstart(item)"
  25. @touchend="onTouchend(item, index)"
  26. :style="{
  27. width: columnWidth + 'px',
  28. height: rpxTopx(rowHeight) + 'px',
  29. zIndex: item.zIndex,
  30. }"
  31. >
  32. {{ item[label] }}
  33. </movable-view>
  34. </block>
  35. </movable-area>
  36. </scroll-view>
  37. </view>
  38. </template>
  39. <script>
  40. export default {
  41. data() {
  42. return {
  43. cloneList: [], //用来展示的数据列表
  44. cacheList: [], //用来在点击“编辑”文字按钮的时候,将当前list的数据缓存,以便在取消的时候用到
  45. positionList: [], //用来存储xy坐标定位的列表
  46. columnWidth: 0, //列宽,单位px
  47. rowNum: 1, //行数
  48. boxHeight: 10, //可拖动区域的高度,单位px
  49. windowWidth: 750, //系统获取到的窗口宽度,单位px
  50. curTouchPostionIndex: 0, //当前操作的移动块在positionList队列里的索引
  51. xMoveUnit: 0, //沿x轴移动时的单位距离,单位px
  52. yMoveUnit: 0, //沿y轴移动时的单位距离,单位px
  53. clearT: "", //onChange事件中使用
  54. clearF: "", //点击“完成”文字按钮时使用
  55. isEdit: false, //是否在编辑状态
  56. windowHeight: 0,
  57. };
  58. },
  59. props: {
  60. //props里属性Number的单位都为rpx,在操作的时候需要用rpxTopx进行转换
  61. list: {
  62. type: Array,
  63. default() {
  64. return [];
  65. },
  66. },
  67. label: {
  68. //list队列中的对象中要用来展示的key名
  69. type: String,
  70. default: "name",
  71. },
  72. rowHeight: {
  73. //行高,单位rpx
  74. type: Number,
  75. default: 60,
  76. },
  77. rowSpace: {
  78. //行间距,单位rpx
  79. type: Number,
  80. default: 15,
  81. },
  82. columnSpace: {
  83. //列间距,单位rpx
  84. type: Number,
  85. default: 15,
  86. },
  87. columnNum: {
  88. //列数
  89. type: Number,
  90. default: 4,
  91. },
  92. zIndex: {
  93. //可移动项的默认z-index
  94. type: Number,
  95. default: 1,
  96. },
  97. },
  98. created() {
  99. this.windowWidth = uni.getSystemInfoSync().windowWidth;
  100. let windowHeight = uni.getSystemInfoSync().windowHeight;
  101. // 状态栏高度
  102. let statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
  103. const custom = wx.getMenuButtonBoundingClientRect();
  104. let navigationBarHeight =
  105. custom.height + (custom.top - statusBarHeight) * 2;
  106. let navHeight = navigationBarHeight + statusBarHeight;
  107. this.windowHeight = windowHeight - navHeight;
  108. },
  109. mounted() {
  110. const query = uni.createSelectorQuery().in(this);
  111. query
  112. .select("#dragSortArea")
  113. .boundingClientRect((data) => {
  114. this.columnWidth =
  115. (data.width - (this.columnNum - 1) * this.rpxTopx(this.columnSpace)) /
  116. this.columnNum;
  117. this.handleListData();
  118. this.toggleEdit("edit");
  119. })
  120. .exec();
  121. },
  122. methods: {
  123. /* 切换编辑状态
  124. * [type] String 参数状态
  125. */
  126. toggleEdit(type) {
  127. if (type == "finish") {
  128. //点击“完成”
  129. this.isEdit = false;
  130. this.$emit("newDishList", this.getSortedIdArr());
  131. } else if (type == "cancel") {
  132. //点击“取消”,将数据恢复到最近一次编辑时的状态
  133. this.isEdit = false;
  134. this.updateList(this.cacheList);
  135. } else if (type == "edit") {
  136. //点击“编辑”
  137. this.isEdit = true;
  138. this.cacheList = JSON.parse(JSON.stringify(this.list));
  139. }
  140. },
  141. /* 更新父组件的list,并重新渲染布局
  142. * 有改变数组长度的操作内才需要调用此方法进行重新渲染布局进行更新,
  143. * 否则直接$emit('update:list')进行更新,无须调用此方法
  144. */
  145. updateList(arr) {
  146. this.$emit("update:list", arr);
  147. setTimeout(() => {
  148. this.handleListData();
  149. }, 100);
  150. },
  151. /* 处理源数据列表,生成展示用的cloneList和positionList布局位置信息 */
  152. handleListData() {
  153. this.cloneList = JSON.parse(JSON.stringify(this.list));
  154. this.positionList = [];
  155. this.rowNum = Math.ceil(this.cloneList.length / this.columnNum);
  156. this.boxHeight =
  157. this.rowNum * this.rpxTopx(this.rowHeight) +
  158. (this.rowNum - 1) * this.rpxTopx(this.rowSpace);
  159. this.xMoveUnit = this.columnWidth + this.rpxTopx(this.columnSpace);
  160. this.yMoveUnit =
  161. this.rpxTopx(this.rowHeight) + this.rpxTopx(this.rowSpace);
  162. this.cloneList.forEach((item, index) => {
  163. item.sortNumber = index;
  164. item.zIndex = this.zIndex;
  165. item.x = this.xMoveUnit * (index % this.columnNum); //单位px
  166. item.y = Math.floor(index / this.columnNum) * this.yMoveUnit; //单位px
  167. this.positionList.push({
  168. x: item.x,
  169. y: item.y,
  170. id: item.id,
  171. });
  172. });
  173. },
  174. /* 找到id在位置队列positionList里对应的索引 */
  175. findPositionIndex(id) {
  176. var resultIndex = 0;
  177. for (var i = 0, len = this.positionList.length; i < len; i++) {
  178. var item = this.positionList[i];
  179. if (item.id == id) {
  180. resultIndex = i;
  181. break;
  182. }
  183. }
  184. return resultIndex;
  185. },
  186. /* 触摸开始 */
  187. onTouchstart(obj) {
  188. if (!this.isEdit) {
  189. return false;
  190. }
  191. this.curTouchPostionIndex = this.findPositionIndex(obj.id);
  192. // 将当前拖动的模块zindex调成当前队列里的最大
  193. this.cloneList.forEach((item, index) => {
  194. if (item.id == obj.id) {
  195. item.zIndex = item.zIndex + 50;
  196. item.isTouched = true;
  197. } else {
  198. item.zIndex = this.zIndex + index + 1;
  199. item.isTouched = false;
  200. }
  201. });
  202. this.$set(this.cloneList, 0, this.cloneList[0]);
  203. },
  204. /* 触摸结束 */
  205. onTouchend(obj) {
  206. if (!this.isEdit) {
  207. return false;
  208. }
  209. this.startSort(this.curTouchPostionIndex, "onTouchend"); //再次调用并传参数‘onTouchend’,使拖动后且没有找到目标位置的滑块归位
  210. },
  211. /* 移动过程中触发的事件(所有移动块只要一有移动都会触发) */
  212. onChange(e, obj) {
  213. if (!this.isEdit) {
  214. return false;
  215. }
  216. var theX = e.detail.x,
  217. theY = e.detail.y,
  218. curCenterX = theX + this.columnWidth / 2,
  219. curCenterY = theY + this.rpxTopx(this.rowHeight) / 2;
  220. if (e.detail.source === "touch") {
  221. //表示由“拖动”触发
  222. var targetIndex = this.findTargetPostionIndex({
  223. curCenterX,
  224. curCenterY,
  225. });
  226. clearTimeout(this.clearT);
  227. this.clearT = setTimeout(() => {
  228. this.$nextTick(() => {
  229. this.startSort(targetIndex); //根据targetIndex将队列进行排序
  230. });
  231. }, 100);
  232. }
  233. },
  234. longtap() {
  235. this.isEdit = true;
  236. },
  237. /* 根据targetIndex将cloneList进行排序
  238. * [targetIndex] Number 当前拖动的模块拖动到positionList队列里的目标位置的索引
  239. * [type] String 值为onTouchend时,再次调用set方法
  240. */
  241. startSort(targetIndex, type) {
  242. var curTouchId = this.positionList[this.curTouchPostionIndex].id;
  243. if (this.curTouchPostionIndex < targetIndex) {
  244. for (var i = 0, len = this.positionList.length; i < len; i++) {
  245. var curItem = this.positionList[i];
  246. var nextItem =
  247. this.positionList[i + 1] ||
  248. this.positionList[this.positionList.length - 1];
  249. if (i >= this.curTouchPostionIndex && i <= targetIndex) {
  250. //找到要进行位移的索引集
  251. if (i == targetIndex) {
  252. curItem.id = curTouchId;
  253. } else {
  254. curItem.id = nextItem.id;
  255. }
  256. }
  257. }
  258. } else {
  259. var clonePostionList = JSON.parse(JSON.stringify(this.positionList));
  260. for (var i = 0, len = this.positionList.length; i < len; i++) {
  261. var curItem = this.positionList[i];
  262. var preItem = this.positionList[i - 1] || this.positionList[0];
  263. if (i >= targetIndex && i <= this.curTouchPostionIndex) {
  264. //找到要进行位移的索引集
  265. if (i == targetIndex) {
  266. curItem.id = curTouchId;
  267. } else {
  268. curItem.id = clonePostionList[i - 1].id;
  269. }
  270. }
  271. }
  272. }
  273. this.cloneList.forEach((item) => {
  274. item.x += 0.001;
  275. item.y += 0.001;
  276. });
  277. if (type == "onTouchend") {
  278. this.$set(this.cloneList, 0, this.cloneList[0]);
  279. }
  280. this.$nextTick(() => {
  281. this.cloneList.forEach((item) => {
  282. for (var i = 0, len = this.positionList.length; i < len; i++) {
  283. var item02 = this.positionList[i];
  284. if (item.id == item02.id) {
  285. item.x = item02.x;
  286. item.y = item02.y;
  287. }
  288. }
  289. });
  290. this.$set(this.cloneList, 0, this.cloneList[0]);
  291. this.curTouchPostionIndex = targetIndex;
  292. this.handleEmitData(); //需要在onChange事件里发射信息出去最稳妥,因为在快速拖动释放鼠标的时候该事件会再onTouchend后执行
  293. });
  294. },
  295. /* 处理要发射出去的数据队列,将排序后的结果同步到父组件的list */
  296. handleEmitData() {
  297. var idArr = this.getSortedIdArr();
  298. var emitList = [];
  299. idArr.forEach((id) => {
  300. for (var i = 0, len = this.list.length; i < len; i++) {
  301. var item = this.list[i];
  302. if (id == item.id) {
  303. emitList.push(item);
  304. break;
  305. }
  306. }
  307. });
  308. this.$emit("update:list", emitList);
  309. },
  310. /* 获取最后的排序完的id队列集 */
  311. getSortedIdArr() {
  312. return this.positionList.map((item) => item.id);
  313. },
  314. /* 找出拖动到positionList队列里的哪个目标索引
  315. * [curObj.curCenterX], Number 当前拖动的模块的中心点x轴坐标
  316. * [curObj.curCenterY], Number 当前拖动的模块的中心点y轴坐标
  317. * return 返回拖动到的目标索引
  318. */
  319. findTargetPostionIndex(curObj) {
  320. var resultIndex = this.curTouchPostionIndex;
  321. for (var i = 0, len = this.positionList.length; i < len; i++) {
  322. var item = this.positionList[i];
  323. if (
  324. curObj.curCenterX >= item.x &&
  325. curObj.curCenterX <= item.x + this.columnWidth
  326. ) {
  327. if (
  328. curObj.curCenterY >= item.y &&
  329. curObj.curCenterY <= item.y + this.rpxTopx(this.rowHeight)
  330. ) {
  331. resultIndex = i;
  332. break;
  333. }
  334. }
  335. }
  336. return resultIndex;
  337. },
  338. /* prx转换成px,返回值还是不带px单位的Number */
  339. rpxTopx(v) {
  340. return (this.windowWidth * v) / 750;
  341. },
  342. },
  343. };
  344. </script>
  345. <style scoped>
  346. .dragSort-view {
  347. background-color: #ffffff;
  348. text-align: center;
  349. border-radius: 4rpx;
  350. }
  351. .dragSort-view.is-touched {
  352. opacity: 0.9;
  353. }
  354. </style>

2.使用 index.vue

  1. <template>
  2. <view>
  3. <sort
  4. :list="dataArray"
  5. label="name"
  6. :columnNum="2"
  7. :columnSpace="30"
  8. :rowHeight="72"
  9. :rowSpace="30"
  10. @newDishList="newDishList"
  11. ref="dragSortNew"
  12. />
  13. </view>
  14. </template>
  15. <script>
  16. import sort from "./components/sort.vue";
  17. export default {
  18. components: {
  19. sort,
  20. },
  21. data() {
  22. return {
  23. dataArray: [{ name: "1" }, { name: "2" }, { name: "3" }],
  24. };
  25. },
  26. methods: {
  27. newDishList(e) {
  28. console.log(e, "修改后的新排序");
  29. },
  30. },
  31. };
  32. </script>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/434966
推荐阅读
相关标签
  

闽ICP备14008679号