nvue.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  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可能不为一个对象
  72. e?.stopPropagation && e.stopPropagation()
  73. e?.preventDefault && e.preventDefault()
  74. this.moving = true
  75. // 获取元素ref
  76. const content = this.getContentRef()
  77. let expression = `min(max(${-this.buttonsWidth}, x), 0)`
  78. // 尝试关闭其他打开的单元格
  79. this.parent && this.parent.closeOther(this)
  80. // 阿里为了KPI而开源的BindingX
  81. this.panEvent = bindingX.bind({
  82. anchor: content,
  83. eventType: 'pan',
  84. props: [{
  85. element: content,
  86. // 绑定width属性,设置其宽度值
  87. property: 'transform.translateX',
  88. expression
  89. }]
  90. }, (res) => {
  91. this.moving = false
  92. if (res.state === 'end' || res.state === 'exit') {
  93. const deltaX = res.deltaX
  94. if(deltaX <= -this.buttonsWidth || deltaX >= 0) {
  95. // 如果触摸滑动的过程中,大于单元格的总宽度,或者大于0,意味着已经动过滑动达到了打开或者关闭的状态
  96. // 这里直接进行状态的标记
  97. this.$nextTick(() => {
  98. this.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'
  99. })
  100. } else if(Math.abs(deltaX) > uni.$u.getPx(this.threshold)) {
  101. // 在移动大于阈值、并且小于总按钮宽度时,进行自动打开或者关闭
  102. // 移动距离大于0时,意味着需要关闭状态
  103. if(Math.abs(deltaX) < this.buttonsWidth) {
  104. this.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')
  105. }
  106. } else {
  107. // 在小于阈值时,进行关闭操作(如果在打开状态下,将不会执行bindingX)
  108. this.moveCellByAnimation('close')
  109. }
  110. }
  111. })
  112. },
  113. // 释放bindingX
  114. unbindBindingX() {
  115. // 释放上一次的资源
  116. if (this?.panEvent?.token != 0) {
  117. bindingX.unbind({
  118. token: this.panEvent?.token,
  119. // pan为手势事件
  120. eventType: 'pan'
  121. })
  122. }
  123. },
  124. // 查询按钮节点信息
  125. queryRect() {
  126. // 历遍所有按钮数组,通过getRectByDom返回一个promise
  127. const promiseAll = this.options.map((item, index) => {
  128. return this.getRectByDom(this.$refs[`u-swipe-action-item__right__button-${index}`][0])
  129. })
  130. // 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
  131. Promise.all(promiseAll).then(sizes => {
  132. this.buttons = sizes
  133. // 计算所有按钮总宽度
  134. this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
  135. })
  136. },
  137. // 通过nvue的dom模块,查询节点信息
  138. getRectByDom(ref) {
  139. return new Promise(resolve => {
  140. dom.getComponentRect(ref, res => {
  141. resolve(res.size)
  142. })
  143. })
  144. },
  145. // 移动单元格到左边或者右边尽头
  146. moveCellByAnimation(status = 'open') {
  147. if(this.moving) return
  148. // 标识当前状态
  149. this.moveing = true
  150. const content = this.getContentRef()
  151. const x = status === 'open' ? -this.buttonsWidth : 0
  152. animation.transition(content, {
  153. styles: {
  154. transform: `translateX(${x}px)`,
  155. },
  156. duration: uni.$u.getDuration(this.duration, false),
  157. timingFunction: 'ease-in-out'
  158. }, () => {
  159. this.moving = false
  160. this.status = status
  161. this.unbindBindingX()
  162. })
  163. },
  164. // 获取元素ref
  165. getContentRef() {
  166. return this.$refs['u-swipe-action-item__content'].ref
  167. },
  168. beforeDestroy() {
  169. this.unbindBindingX()
  170. }
  171. }
  172. }