nvue.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. // nvue操作dom的库,用于获取dom的尺寸信息
  2. const dom = uni.requireNativePlugin('dom');
  3. const bindingX = uni.requireNativePlugin('bindingx');
  4. const animation = uni.requireNativePlugin('animation');
  5. export default {
  6. data() {
  7. return {
  8. // 所有按钮的总宽度
  9. buttonsWidth: 0,
  10. // 是否正在移动中
  11. moving: false
  12. }
  13. },
  14. computed: {
  15. // 获取过渡时间
  16. getDuratin() {
  17. let duration = String(this.duration)
  18. // 如果ms为单位,返回ms的数值部分
  19. if (duration.indexOf('ms') >= 0) return parseInt(duration)
  20. // 如果s为单位,为了得到ms的数值,需要乘以1000
  21. if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
  22. // 如果值传了数值,且小于30,认为是s单位
  23. duration = Number(duration)
  24. return duration < 30 ? duration * 1000 : duration
  25. }
  26. },
  27. watch: {
  28. show(n) {
  29. if(n) {
  30. this.moveCellByAnimation('open')
  31. } else {
  32. this.moveCellByAnimation('close')
  33. }
  34. }
  35. },
  36. mounted() {
  37. this.initialize()
  38. },
  39. methods: {
  40. initialize() {
  41. this.queryRect()
  42. },
  43. // 关闭单元格,用于打开一个,自动关闭其他单元格的场景
  44. closeHandler() {
  45. if(this.status === 'open') {
  46. // 如果在打开状态下,进行点击的话,直接关闭单元格
  47. return this.moveCellByAnimation('close') && this.unbindBindingX()
  48. }
  49. },
  50. // 点击单元格
  51. clickHandler() {
  52. // 如果在移动中被点击,进行忽略
  53. if(this.moving) return
  54. // 尝试关闭其他打开的单元格
  55. this.parent && this.parent.closeOther(this)
  56. if(this.status === 'open') {
  57. // 如果在打开状态下,进行点击的话,直接关闭单元格
  58. return this.moveCellByAnimation('close') && this.unbindBindingX()
  59. }
  60. },
  61. // 滑动单元格
  62. onTouchstart(e) {
  63. // 如果当前正在移动中,或者disabled状态,则返回
  64. if(this.moving || this.disabled) {
  65. return this.unbindBindingX()
  66. }
  67. if(this.status === 'open') {
  68. // 如果在打开状态下,进行点击的话,直接关闭单元格
  69. return this.moveCellByAnimation('close') && this.unbindBindingX()
  70. }
  71. e.stopPropagation && e.stopPropagation()
  72. e.preventDefault && e.preventDefault()
  73. this.moving = true
  74. // 获取元素ref
  75. const content = this.getContentRef()
  76. let expression = `min(max(${-this.buttonsWidth}, x), 0)`
  77. // 尝试关闭其他打开的单元格
  78. this.parent && this.parent.closeOther(this)
  79. // 阿里为了KPI而开源的BindingX
  80. this.panEvent = bindingX.bind({
  81. anchor: content,
  82. eventType: 'pan',
  83. props: [{
  84. element: content,
  85. // 绑定width属性,设置其宽度值
  86. property: 'transform.translateX',
  87. expression
  88. }]
  89. }, (res) => {
  90. this.moving = false
  91. if (res.state === 'end' || res.state === 'exit') {
  92. const deltaX = res.deltaX
  93. if(deltaX <= -this.buttonsWidth || deltaX >= 0) {
  94. // 如果触摸滑动的过程中,大于单元格的总宽度,或者大于0,意味着已经动过滑动达到了打开或者关闭的状态
  95. // 这里直接进行状态的标记
  96. this.$nextTick(() => {
  97. this.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'
  98. })
  99. } else if(Math.abs(deltaX) > uni.$u.getPx(this.threshold)) {
  100. // 在移动大于阈值、并且小于总按钮宽度时,进行自动打开或者关闭
  101. // 移动距离大于0时,意味着需要关闭状态
  102. if(Math.abs(deltaX) < this.buttonsWidth) {
  103. this.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')
  104. }
  105. } else {
  106. // 在小于阈值时,进行关闭操作(如果在打开状态下,将不会执行bindingX)
  107. this.moveCellByAnimation('close')
  108. }
  109. }
  110. })
  111. },
  112. // 释放bindingX
  113. unbindBindingX() {
  114. // 释放上一次的资源
  115. if (this?.panEvent?.token != 0) {
  116. bindingX.unbind({
  117. token: this.panEvent?.token,
  118. // pan为手势事件
  119. eventType: 'pan'
  120. })
  121. }
  122. },
  123. // 查询按钮节点信息
  124. queryRect() {
  125. // 历遍所有按钮数组,通过getRectByDom返回一个promise
  126. const promiseAll = this.options.map((item, index) => {
  127. return this.getRectByDom(this.$refs[`u-swipe-action-item__right__button-${index}`][0])
  128. })
  129. // 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
  130. Promise.all(promiseAll).then(sizes => {
  131. this.buttons = sizes
  132. // 计算所有按钮总宽度
  133. this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
  134. })
  135. },
  136. // 通过nvue的dom模块,查询节点信息
  137. getRectByDom(ref) {
  138. return new Promise(resolve => {
  139. dom.getComponentRect(ref, res => {
  140. resolve(res.size)
  141. })
  142. })
  143. },
  144. // 移动单元格到左边或者右边尽头
  145. moveCellByAnimation(status = 'open') {
  146. if(this.moving) return
  147. // 标识当前状态
  148. this.moveing = true
  149. const content = this.getContentRef()
  150. const x = status === 'open' ? -this.buttonsWidth : 0
  151. animation.transition(content, {
  152. styles: {
  153. transform: `translateX(${x}px)`,
  154. },
  155. duration: uni.$u.getDuration(this.duration, false),
  156. timingFunction: 'ease-in-out'
  157. }, () => {
  158. this.moving = false
  159. this.status = status
  160. this.unbindBindingX()
  161. })
  162. },
  163. // 获取元素ref
  164. getContentRef() {
  165. return this.$refs['u-swipe-action-item__content'].ref
  166. },
  167. beforeDestroy() {
  168. this.unbindBindingX()
  169. }
  170. }
  171. }