swiper-delete.vue 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. <template>
  2. <view class="swipe-delete-wrapper">
  3. <view
  4. class="swipe-delete-container"
  5. :style="{ transform: `translateX(${translateX}px)`, transition: transitionStyle }"
  6. @touchstart="handleTouchStart"
  7. @touchmove="handleTouchMove"
  8. @touchend="handleTouchEnd"
  9. >
  10. <!-- 主体内容插槽:完全交给外部控制样式 -->
  11. <view class="swipe-content">
  12. <slot></slot>
  13. </view>
  14. <!-- 删除按钮:仅保留基础样式,无额外间距/边框 -->
  15. <view class="swipe-delete-btn" @tap.stop="handleDelete">
  16. {{ btnText || '删除' }}
  17. </view>
  18. </view>
  19. </view>
  20. </template>
  21. <script>
  22. export default {
  23. name: 'SwipeDelete',
  24. props: {
  25. btnText: {
  26. type: String,
  27. default: '删除'
  28. },
  29. btnWidth: {
  30. type: [Number, String],
  31. default: 80
  32. },
  33. disabled: {
  34. type: Boolean,
  35. default: false
  36. }
  37. },
  38. data() {
  39. return {
  40. translateX: 0,
  41. startX: 0,
  42. startY: 0,
  43. isMoving: false,
  44. transitionStyle: 'transform 0.3s ease',
  45. maxSlide: 0,
  46. startTranslateX: 0
  47. };
  48. },
  49. created() {
  50. this.maxSlide = -Number(this.btnWidth);
  51. },
  52. methods: {
  53. handleTouchStart(e) {
  54. if (this.disabled) return;
  55. e.preventDefault(); // 阻止iOS默认触摸行为
  56. this.transitionStyle = 'none'; // 滑动时取消动画,更跟手
  57. this.startX = e.changedTouches[0].clientX;
  58. this.startY = e.changedTouches[0].clientY;
  59. this.startTranslateX = this.translateX; // 记录当前偏移量,避免展开后继续滑触发动画
  60. this.isMoving = true;
  61. },
  62. handleTouchMove(e) {
  63. if (!this.isMoving || this.disabled) return;
  64. const moveX = e.changedTouches[0].clientX - this.startX;
  65. const moveY = e.changedTouches[0].clientY - this.startY;
  66. // 过滤竖向滑动,避免上下滚动误触
  67. if (Math.abs(moveY) > Math.abs(moveX)) {
  68. this.isMoving = false;
  69. return;
  70. }
  71. // 基于当前偏移量计算新位置,严格限制滑动边界(0 ~ -btnWidth)
  72. let newTranslateX = this.startTranslateX + moveX;
  73. newTranslateX = Math.max(this.maxSlide, Math.min(0, newTranslateX));
  74. // 仅偏移量变化时更新,避免冗余动画
  75. if (newTranslateX !== this.translateX) {
  76. this.translateX = newTranslateX;
  77. }
  78. },
  79. handleTouchEnd() {
  80. if (this.disabled) return;
  81. this.isMoving = false;
  82. this.transitionStyle = 'transform 0.3s ease'; // 恢复动画,平滑复位/展开
  83. // 低阈值判断,避免回滑突然复位(滑动超过30%才展开)
  84. const slideRatio = Math.abs(this.translateX) / Math.abs(this.maxSlide);
  85. this.translateX = slideRatio > 0.3 ? this.maxSlide : 0;
  86. },
  87. handleDelete() {
  88. this.$emit('delete'); // 触发删除事件,交给外部处理逻辑
  89. this.translateX = 0; // 点击删除后自动收回
  90. },
  91. close() {
  92. // 供外部手动关闭(比如列表刷新时)
  93. this.transitionStyle = 'transform 0.3s ease';
  94. this.translateX = 0;
  95. }
  96. }
  97. };
  98. </script>
  99. <style scoped>
  100. /* 外层容器:仅保留溢出隐藏和定位,无边框/间距 */
  101. .swipe-delete-wrapper {
  102. width: 100%;
  103. overflow: hidden;
  104. /* 核心:隐藏右侧的删除按钮 */
  105. position: relative;
  106. }
  107. /* 滑动容器:宽度=100%+按钮宽,确保删除按钮在屏幕外 */
  108. .swipe-delete-container {
  109. display: flex;
  110. width: calc(100% + 80px);
  111. height: 100%;
  112. position: relative;
  113. }
  114. /* 主体内容:仅占满宽度,无额外样式,交给外部控制 */
  115. .swipe-content {
  116. width: 100%;
  117. height: 100%;
  118. box-sizing: border-box;
  119. /* 避免外部padding导致宽度溢出 */
  120. }
  121. /* 删除按钮:仅保留核心样式,无圆角/边框,适配外部容器 */
  122. .swipe-delete-btn {
  123. width: 80px;
  124. height: 100%;
  125. background-color: #ff4757;
  126. color: #fff;
  127. display: flex;
  128. align-items: center;
  129. justify-content: center;
  130. font-size: 14px;
  131. flex-shrink: 0;
  132. /* 防止宽度被压缩 */
  133. position: absolute;
  134. right: 0;
  135. top: 0;
  136. }
  137. </style>