u-swipe-action-item.vue 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. <template>
  2. <view class="u-swipe-action-item" ref="u-swipe-action-item">
  3. <view class="u-swipe-action-item__right">
  4. <slot name="button">
  5. <view v-for="(item,index) in options" :key="index" class="u-swipe-action-item__right__button"
  6. :ref="`u-swipe-action-item__right__button-${index}`" :style="[{
  7. alignItems: item.style && item.style.round ? 'center' : 'stretch'
  8. }]" @tap="buttonClickHandler(item, index)">
  9. <view class="u-swipe-action-item__right__button__wrapper" :style="[{
  10. backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
  11. borderRadius: item.style && item.style.round ? item.style.round : '0',
  12. padding: item.style && item.style.round ? '0' : '0 15px',
  13. }, item.style]">
  14. <u-icon v-if="item.icon" :name="item.icon"
  15. :color="item.style && item.style.color ? item.style.color : '#ffffff'"
  16. :size="item.iconSize ? $u.addUnit(item.iconSize) : item.style && item.style.fontSize ? $u.getPx(item.style.fontSize) * 1.2 : 17"
  17. :customStyle="{
  18. marginRight: item.text ? '2px' : 0
  19. }"></u-icon>
  20. <text v-if="item.text" class="u-swipe-action-item__right__button__wrapper__text u-line-1"
  21. :style="[{
  22. color: item.style && item.style.color ? item.style.color : '#ffffff',
  23. fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',
  24. lineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',
  25. }]">{{ item.text }}</text>
  26. </view>
  27. </view>
  28. </slot>
  29. </view>
  30. <!-- #ifdef APP-VUE || MP-WEIXIN || MP-ALIPAY || H5 || MP-QQ -->
  31. <view class="u-swipe-action-item__content" @touchstart="wxs.touchstart" @touchmove="wxs.touchmove"
  32. @touchend="wxs.touchend" :status="status" :change:status="wxs.statusChange" :size="size"
  33. :change:size="wxs.sizeChange">
  34. <slot />
  35. </view>
  36. <!-- #endif -->
  37. <!-- #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK -->
  38. <view class="u-swipe-action-item__content" @touchstart="touchStartHandler" @touchmove="touchMoveHandler"
  39. @touchend="touchEndHandler">
  40. <slot />
  41. </view>
  42. <!-- #endif -->
  43. <!-- #ifdef APP-NVUE -->
  44. <view class="u-swipe-action-item__content" ref="u-swipe-action-item__content" @panstart="onTouchstart"
  45. @tap="clickHandler">
  46. <slot></slot>
  47. </view>
  48. <!-- #endif -->
  49. </view>
  50. </template>
  51. <!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
  52. <script src="./index.wxs" module="wxs" lang="wxs"></script>
  53. <!-- #endif -->
  54. <!-- #ifdef MP-ALIPAY -->
  55. <script src="./index.sjs" module="wxs" lang="sjs"></script>
  56. <!-- #endif -->
  57. <script>
  58. import props from './props.js';
  59. import mixin from '../../libs/mixin/mixin'
  60. import mpMixin from '../../libs/mixin/mpMixin';
  61. // #ifdef APP-NVUE
  62. import nvue from './nvue.js';
  63. // #endif
  64. /**
  65. * SwipeActionItem 滑动单元格子组件
  66. * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
  67. * @tutorial https://uview.d3u.cn/components/swipeAction.html
  68. * @property {Boolean} show 控制打开或者关闭(默认 false )
  69. * @property {String | Number} index 标识符,如果是v-for,可用index索引
  70. * @property {Boolean} disabled 是否禁用(默认 false )
  71. * @property {Boolean} autoClose 是否自动关闭其他swipe按钮组(默认 true )
  72. * @property {Number} threshold 滑动距离阈值,只有大于此值,才被认为是要打开菜单(默认 30 )
  73. * @property {Array} options 右侧按钮内容
  74. * @property {String | Number} duration 动画过渡时间,单位ms(默认 350 )
  75. * @event {Function(index)} open 组件打开时触发
  76. * @event {Function(index)} close 组件关闭时触发
  77. * @example <u-swipe-action><u-swipe-action-item :options="options1" ></u-swipe-action-item></u-swipe-action>
  78. */
  79. export default {
  80. name: 'u-swipe-action-item',
  81. mixins: [
  82. mpMixin,
  83. mixin,
  84. props,
  85. // #ifdef APP-NVUE
  86. nvue,
  87. // #endif
  88. ],
  89. data() {
  90. return {
  91. // 按钮的尺寸信息
  92. size: {},
  93. // 父组件u-swipe-action的参数
  94. parentData: {
  95. autoClose: true,
  96. },
  97. // 当前状态,open-打开,close-关闭
  98. status: 'close',
  99. // #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK
  100. // 其他小程序平台的状态管理
  101. startX: 0,
  102. startY: 0,
  103. moving: false,
  104. currentX: 0,
  105. buttonsWidth: 0,
  106. // #endif
  107. }
  108. },
  109. watch: {
  110. // 由于wxs/sjs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
  111. wxsInit(newValue, oldValue) {
  112. this.$nextTick(() => {
  113. this.queryRect()
  114. })
  115. }
  116. },
  117. computed: {
  118. wxsInit() {
  119. return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
  120. }
  121. },
  122. mounted() {
  123. this.init()
  124. },
  125. // #ifdef VUE3
  126. emits: ["click"],
  127. // #endif
  128. methods: {
  129. init() {
  130. // 初始化父组件数据
  131. this.updateParentData()
  132. // #ifndef APP-NVUE
  133. uni.$u.sleep().then(() => {
  134. this.queryRect()
  135. })
  136. // #endif
  137. },
  138. updateParentData() {
  139. // 此方法在mixin中
  140. this.getParentData('u-swipe-action')
  141. },
  142. // #ifndef APP-NVUE
  143. // 查询节点
  144. queryRect() {
  145. // #ifdef MP-ALIPAY
  146. // 支付宝小程序使用特殊的查询方式
  147. uni.createSelectorQuery().in(this).selectAll('.u-swipe-action-item__right__button').boundingClientRect().exec((res) => {
  148. const buttons = res[0] || [];
  149. this.size = {
  150. buttons,
  151. show: this.show,
  152. disabled: this.disabled,
  153. threshold: this.threshold,
  154. duration: this.duration
  155. }
  156. })
  157. // #endif
  158. // #ifndef MP-ALIPAY
  159. this.$uGetRect('.u-swipe-action-item__right__button', true).then(buttons => {
  160. this.size = {
  161. buttons,
  162. show: this.show,
  163. disabled: this.disabled,
  164. threshold: this.threshold,
  165. duration: this.duration
  166. }
  167. })
  168. // #endif
  169. },
  170. // #endif
  171. // #ifdef APP-VUE || MP-WEIXIN || MP-ALIPAY || H5 || MP-QQ
  172. closeHandler() {
  173. this.status = 'close'
  174. },
  175. setState(status) {
  176. this.status = status
  177. },
  178. closeOther() {
  179. // 尝试关闭其他打开的单元格
  180. this.parent && this.parent.closeOther(this)
  181. },
  182. // #endif
  183. // #ifdef MP-BAIDU || MP-TOUTIAO || MP-LARK
  184. // 其他小程序平台的原生JavaScript实现
  185. touchStartHandler(e) {
  186. if (this.disabled) return;
  187. const touch = e.touches[0];
  188. this.startX = touch.pageX;
  189. this.startY = touch.pageY;
  190. this.moving = true;
  191. this.closeOther();
  192. },
  193. touchMoveHandler(e) {
  194. if (!this.moving || this.disabled) return;
  195. const touch = e.touches[0];
  196. const moveX = touch.pageX - this.startX;
  197. const moveY = touch.pageY - this.startY;
  198. // 判断是否为水平滑动
  199. if (Math.abs(moveX) < Math.abs(moveY)) return;
  200. // 阻止默认滚动
  201. e.preventDefault && e.preventDefault();
  202. this.currentX = moveX;
  203. this.updateTransform(moveX);
  204. },
  205. touchEndHandler(e) {
  206. if (!this.moving || this.disabled) return;
  207. this.moving = false;
  208. const moveX = this.currentX;
  209. if (Math.abs(moveX) < this.threshold) {
  210. this.close();
  211. } else {
  212. if (moveX < 0) {
  213. this.open();
  214. } else {
  215. this.close();
  216. }
  217. }
  218. },
  219. updateTransform(translateX) {
  220. // 限制滑动范围
  221. if (translateX > 0) translateX = 0;
  222. if (Math.abs(translateX) > this.buttonsWidth) translateX = -this.buttonsWidth;
  223. // 更新样式
  224. const content = this.$refs['u-swipe-action-item'];
  225. if (content) {
  226. content.style.transform = `translateX(${translateX}px)`;
  227. }
  228. },
  229. open() {
  230. this.status = 'open';
  231. this.updateTransform(-this.buttonsWidth);
  232. this.$emit('open', this.index);
  233. },
  234. close() {
  235. this.status = 'close';
  236. this.updateTransform(0);
  237. this.$emit('close', this.index);
  238. },
  239. // #endif
  240. // 按钮被点击
  241. buttonClickHandler(item, index) {
  242. let duration = 0;
  243. if(this.closeOnClick) {
  244. duration = this.duration
  245. this.closeHandler()
  246. }
  247. setTimeout(() => {
  248. this.$emit('click', {
  249. index,
  250. name: this.name
  251. })
  252. }, duration)
  253. }
  254. },
  255. }
  256. </script>
  257. <style lang="scss" scoped>
  258. @import "../../libs/css/components.scss";
  259. .u-swipe-action-item {
  260. position: relative;
  261. overflow: hidden;
  262. /* #ifndef APP-NVUE || MP-WEIXIN */
  263. touch-action: pan-y;
  264. /* #endif */
  265. &__content {
  266. transform: translateX(0px);
  267. background-color: #FFFFFF;
  268. z-index: 10;
  269. }
  270. &__right {
  271. position: absolute;
  272. top: 0;
  273. bottom: 0;
  274. right: 0;
  275. @include flex;
  276. &__button {
  277. @include flex;
  278. justify-content: center;
  279. overflow: hidden;
  280. align-items: center;
  281. &__wrapper {
  282. @include flex;
  283. align-items: center;
  284. justify-content: center;
  285. padding: 0 15px;
  286. &__text {
  287. @include flex;
  288. align-items: center;
  289. color: #FFFFFF;
  290. font-size: 15px;
  291. text-align: center;
  292. justify-content: center;
  293. }
  294. }
  295. }
  296. }
  297. }
  298. </style>