cl-waterfall.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. <template>
  2. <view
  3. class="cl-waterfall"
  4. :style="{
  5. columnCount,
  6. columnGap,
  7. }"
  8. >
  9. <!-- 纵向 -->
  10. <template v-if="direction === 'vertical'">
  11. <view class="cl-waterfall__item" v-for="(item, index) in list" :key="index">
  12. <slot :item="item" :index="index"></slot>
  13. </view>
  14. </template>
  15. <!-- 横向 -->
  16. <template v-else-if="direction === 'horizontal'">
  17. <slot></slot>
  18. </template>
  19. </view>
  20. </template>
  21. <script>
  22. import { isEmpty } from "../../utils";
  23. /**
  24. * waterfall 瀑布流
  25. * @description 瀑布流布局,自定义内容
  26. * @tutorial https://docs.cool-js.com/uni/components/layout/waterfall.html
  27. * @property {Array} value 数据列表
  28. * @property {Number} column 列的数量,默认2
  29. * @property {Number} gutter 列间隔,默认20
  30. * @property {String} direction 布局方向 horizontal | vertical,默认horizontal
  31. * @property {String} nodeKey 匹配值,默认 id
  32. * @example 见教程
  33. */
  34. export default {
  35. name: "cl-waterfall",
  36. componentName: "ClWaterfall",
  37. props: {
  38. value: Array,
  39. column: {
  40. type: Number,
  41. default: 2,
  42. },
  43. gutter: {
  44. type: Number,
  45. default: 20,
  46. },
  47. direction: {
  48. type: String,
  49. default: "horizontal", // horizontal, vertical
  50. },
  51. nodeKey: {
  52. type: String,
  53. default: "id",
  54. },
  55. },
  56. data() {
  57. return {
  58. list: [],
  59. };
  60. },
  61. watch: {
  62. list: {
  63. handler(val) {
  64. this.$emit("input", val);
  65. },
  66. },
  67. },
  68. computed: {
  69. columnCount() {
  70. return this.direction === "vertical" ? this.column : "none";
  71. },
  72. columnGap() {
  73. return this.direction === "vertical" ? `${this.gutter}rpx` : "none";
  74. },
  75. },
  76. mounted() {
  77. this.refresh(this.value);
  78. },
  79. methods: {
  80. // 刷新列表
  81. refresh(list) {
  82. // 清空列表,清空 cl-waterfall-column 的组件遍历
  83. this.clear();
  84. this.$nextTick(() => {
  85. switch (this.direction) {
  86. case "horizontal":
  87. this.list = new Array(this.column).fill(1).map(() => []);
  88. // 等待 cl-waterfall-column 渲染完成后追加数据
  89. this.$nextTick(() => {
  90. this.append(list);
  91. });
  92. break;
  93. case "vertical":
  94. this.list = list;
  95. break;
  96. }
  97. });
  98. },
  99. // 计算高度,一个个往列表追加
  100. async append(list) {
  101. for (let i in list) {
  102. const next = async () => {
  103. const rects = await this.getRect();
  104. // 获取 cl-waterfall-column 的高度,比较后在最小的列中塞入
  105. return Promise.all(rects).then((res) => {
  106. let colsHeight = res.map((e) => e.height);
  107. let minH = Math.min(...colsHeight);
  108. let index = colsHeight.indexOf(minH);
  109. if (index < 0) {
  110. index = 0;
  111. }
  112. this.list[index].push(list[i]);
  113. return true;
  114. });
  115. };
  116. await next();
  117. }
  118. },
  119. // 更新单条数据,根据nodeKey来匹配
  120. update(id, data) {
  121. const next = (e) => {
  122. let d = e[this.nodeKey] === id;
  123. if (d) {
  124. Object.assign(e, data);
  125. }
  126. return Boolean(d);
  127. };
  128. switch (this.direction) {
  129. case "horizontal":
  130. this.list.find((col) => {
  131. return col.find(next);
  132. });
  133. break;
  134. case "vertical":
  135. this.list.find(next);
  136. break;
  137. }
  138. },
  139. // 清空列表
  140. clear() {
  141. this.list = [];
  142. },
  143. // 获取列
  144. getRect() {
  145. // #ifdef MP || H5
  146. return new Promise((resolve) => {
  147. let timer = null;
  148. const fn = () => {
  149. // #ifdef H5
  150. let children = this.$children[0].$children;
  151. // #endif
  152. // #ifdef MP || APP
  153. let children = this.$children;
  154. // #endif
  155. if (isEmpty(children)) {
  156. timer = setTimeout(() => {
  157. fn();
  158. }, 50);
  159. } else {
  160. clearTimeout(timer);
  161. resolve(children.map((e) => e.getRect()));
  162. }
  163. };
  164. fn();
  165. });
  166. // #endif
  167. // #ifdef APP
  168. return new Promise((resolve) => {
  169. uni.createSelectorQuery()
  170. .selectAll(`.cl-waterfall-column`)
  171. .boundingClientRect(resolve)
  172. .exec();
  173. });
  174. // #endif
  175. },
  176. },
  177. };
  178. </script>