cl-list-item.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. <template>
  2. <view
  3. :class="['cl-list-item', isAppend, isDisabled, isBorder]"
  4. :style="[customStyle]"
  5. @touchstart="onTouchStart"
  6. @touchmove="onTouchMove"
  7. @touchend="onTouchEnd"
  8. @tap.stop="onTap"
  9. >
  10. <view
  11. class="cl-list-item__swiper"
  12. :style="{
  13. transform: translateX,
  14. }"
  15. >
  16. <view class="cl-list-item__container">
  17. <view class="cl-list-item__icon" v-if="$slots.icon">
  18. <slot name="icon"></slot>
  19. </view>
  20. <text class="cl-list-item__label" v-if="label && label != 'true'">{{ label }}</text>
  21. <view :class="['cl-list-item__content', isJustify, isColor]">
  22. <slot></slot>
  23. </view>
  24. <view class="cl-list-item__append">
  25. <slot name="append"></slot>
  26. </view>
  27. </view>
  28. <template v-if="swipe != 'none'">
  29. <view :class="[`cl-list-item__menu-${swipe}`]">
  30. <slot name="menu"></slot>
  31. </view>
  32. </template>
  33. </view>
  34. </view>
  35. </template>
  36. <script>
  37. import Parent from "../../mixins/parent";
  38. /**
  39. * list-item 列表项
  40. * @description 列表项,自定义内容,支持滑动
  41. * @tutorial https://docs.cool-js.com/uni/components/layout/list.html
  42. * @property {String} label 标签内容
  43. * @property {Boolean} disabled 是否禁用
  44. * @property {Boolean} border 是否带有下边框,默认true
  45. * @property {Boolean} type 类型 primary | success | error | warning | info
  46. * @property {Boolean} justify 水平布局方式,默认end
  47. * @property {Boolean} swipe 是否滑动 none | left | right,默认none
  48. * @event {Function} tap 点击时触发
  49. * @example 见教程
  50. */
  51. export default {
  52. name: "cl-list-item",
  53. componentName: "ClListItem",
  54. props: {
  55. // 标签内容
  56. label: String,
  57. // 是否禁用
  58. disabled: Boolean,
  59. // 是否带有下边框
  60. border: {
  61. type: Boolean,
  62. default: null,
  63. },
  64. // 类型 primary | success | error | warning | info
  65. type: String,
  66. // 水平布局方式
  67. justify: {
  68. type: String,
  69. default: "end",
  70. },
  71. // 是否滑动 none | left | right
  72. swipe: {
  73. type: String,
  74. default: "none",
  75. validator: (val) => {
  76. return ["none", "left", "right"].indexOf(val) !== -1;
  77. },
  78. },
  79. // 自定义样式
  80. customStyle: Object,
  81. },
  82. mixins: [Parent],
  83. data() {
  84. return {
  85. touch: {
  86. start: 0,
  87. end: 0,
  88. x: 0,
  89. maxX: 0,
  90. direction: "left",
  91. lock: true,
  92. },
  93. menu: {
  94. width: 0,
  95. },
  96. Keys: ["disabled", "justify", "border"],
  97. ComponentName: "ClList",
  98. };
  99. },
  100. computed: {
  101. isColor() {
  102. return this.type ? `is-color-${this.type}` : "";
  103. },
  104. isBorder() {
  105. if (this.border === null) {
  106. if (this.parent.border === true) {
  107. return `is-border`;
  108. }
  109. }
  110. return this.border ? "is-border" : "";
  111. },
  112. isJustify() {
  113. return (this.parent.justify || this.justify) !== "start"
  114. ? `is-justify-${this.justify}`
  115. : "";
  116. },
  117. isAppend() {
  118. return this.$slots.append ? "cl-list-item--append" : "";
  119. },
  120. isDisabled() {
  121. return this.parent.disabled || this.disabled ? "cl-list-item--disabled" : "";
  122. },
  123. translateX() {
  124. return `translateX(${this.touch.x}px)`;
  125. },
  126. },
  127. watch: {
  128. swipe() {
  129. this.setMenu();
  130. },
  131. },
  132. mounted() {
  133. this.setMenu();
  134. },
  135. methods: {
  136. onTap(e) {
  137. this.$emit("click", e);
  138. this.$emit("tap", e);
  139. },
  140. onTouchStart(e) {
  141. if (this.swipe != "none") {
  142. this.touch.start = e.touches[0].pageX;
  143. this.touch.lock = false;
  144. }
  145. },
  146. onTouchMove(e) {
  147. const { start, end, lock, maxX } = this.touch;
  148. if (!lock) {
  149. // 滑动距离
  150. let offsetX = e.touches[0].pageX - start;
  151. // 移动方向
  152. this.touch.direction = offsetX > 0 ? "right" : "left";
  153. // 偏移距离
  154. let x = end + offsetX;
  155. if (this.swipe == "left") {
  156. if (x > maxX) {
  157. x = maxX;
  158. }
  159. if (x < 0) {
  160. x = 0;
  161. }
  162. }
  163. if (this.swipe == "right") {
  164. if (x < maxX) {
  165. x = maxX;
  166. }
  167. if (x > 0) {
  168. x = 0;
  169. }
  170. }
  171. this.touch.x = x;
  172. }
  173. },
  174. onTouchEnd() {
  175. const { direction, x, end, lock, maxX } = this.touch;
  176. if (!lock) {
  177. if (Math.abs(x - end) > 50) {
  178. if (direction === this.swipe) {
  179. this.touch.x = 0;
  180. } else {
  181. this.touch.x = maxX;
  182. }
  183. this.touch.end = this.touch.x;
  184. } else {
  185. this.touch.x = end === 0 ? 0 : maxX;
  186. }
  187. this.touch.lock = true;
  188. }
  189. },
  190. // 设置菜单宽度
  191. setMenu() {
  192. if (this.swipe != "none") {
  193. const query = uni.createSelectorQuery().in(this);
  194. query
  195. .select(`.cl-list-item__menu-${this.swipe}`)
  196. .boundingClientRect((data) => {
  197. if (data) {
  198. this.menu.width = data.width;
  199. this.touch.maxX = this.menu.width * (this.swipe === "right" ? -1 : 1);
  200. }
  201. })
  202. .exec();
  203. }
  204. },
  205. // 滑动后还原位置的方法
  206. restore(callback) {
  207. this.touch.start = 0;
  208. this.touch.end = 0;
  209. this.touch.lock = true;
  210. this.touch.x = 0;
  211. if (callback) {
  212. setTimeout(() => {
  213. callback();
  214. }, 300);
  215. }
  216. },
  217. },
  218. };
  219. </script>