u-waterfall.vue 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <template>
  2. <view class="u-waterfall">
  3. <view
  4. v-for="(columnList, columnIndex) in columns"
  5. :key="columnIndex"
  6. :id="`u-column-${columnIndex}`"
  7. class="u-column"
  8. >
  9. <slot :columnList="columnList" :columnIndex="columnIndex"></slot>
  10. </view>
  11. </view>
  12. </template>
  13. <script>
  14. import props from './props.js';
  15. import mixin from '../../libs/mixin/mixin'
  16. import mpMixin from '../../libs/mixin/mpMixin';
  17. /**
  18. * waterfall 瀑布流
  19. * @description 这是一个瀑布流形式的组件,内容分为多列,结合uView的懒加载组件效果更佳。
  20. * 或者没有利用vue作用域插槽的做法,uView的瀑布流实现了真正的 组件化,
  21. * 搭配LazyLoad 懒加载和loadMore 加载更多组件,让您开箱即用,眼前一亮。
  22. * @tutorial https://uview.d3u.cn/components/waterfall.html
  23. * @property {Array} value/modelValue 用于渲染的数据
  24. * @property {String Number} add-time 单条数据添加到队列的时间间隔,单位ms,见上方注意事项说明(默认200)
  25. * @property {String Number} column 瀑布流列数(默认2)
  26. * @example <u-waterfall v-model="list" :column="2"></u-waterfall>
  27. */
  28. export default {
  29. name: "u-waterfall",
  30. mixins: [mpMixin, mixin, props],
  31. data() {
  32. return {
  33. columns: [], // 动态列数组
  34. tempList: [],
  35. children: []
  36. }
  37. },
  38. watch: {
  39. copyFlowList(nVal, oVal) {
  40. // 取差值,即这一次数组变化新增的部分
  41. let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0;
  42. // 拼接上原有数据
  43. this.tempList = this.tempList.concat(this.cloneData(nVal.slice(startIndex)));
  44. this.splitData();
  45. }
  46. },
  47. mounted() {
  48. this.initColumns();
  49. this.tempList = this.cloneData(this.copyFlowList);
  50. this.$nextTick(() => {
  51. uni.$u.sleep(10).then(() => {
  52. this.splitData();
  53. })
  54. })
  55. },
  56. computed: {
  57. // 破坏flowList变量的引用,否则watch的结果新旧值是一样的
  58. copyFlowList() {
  59. return this.cloneData(this.flowList);
  60. },
  61. flowList() {
  62. // #ifdef VUE2
  63. return this.value;
  64. // #endif
  65. // #ifdef VUE3
  66. return this.modelValue;
  67. // #endif
  68. },
  69. },
  70. // #ifdef VUE3
  71. emits: ['update:modelValue'],
  72. // #endif
  73. methods: {
  74. // 初始化列数组
  75. initColumns() {
  76. const columnCount = parseInt(this.column);
  77. this.columns = [];
  78. for (let i = 0; i < columnCount; i++) {
  79. this.columns.push([]);
  80. }
  81. },
  82. async splitData() {
  83. if (!this.tempList.length) {
  84. return;
  85. }
  86. if(!this.columns.length) {
  87. return;
  88. }
  89. // 获取所有列的高度
  90. const columnHeights = [];
  91. for (let i = 0; i < this.columns.length; i++) {
  92. try {
  93. const rect = await this.$uGetRect(`#u-column-${i}`);
  94. columnHeights.push(rect ? rect.height : 0);
  95. } catch (error) {
  96. columnHeights.push(0);
  97. }
  98. }
  99. // 找到最短的列
  100. let minHeight = Math.min(...columnHeights);
  101. let minIndex = columnHeights.indexOf(minHeight);
  102. // 如果所有列高度相同,使用轮询分配
  103. if (columnHeights.every(height => height === minHeight)) {
  104. minIndex = this.getShortestColumnIndex();
  105. }
  106. let item = this.tempList[0];
  107. // 解决多次快速上拉后,可能数据会乱的问题
  108. if (!item){
  109. return;
  110. }
  111. // 将数据添加到最短的列
  112. this.columns[minIndex].push(item);
  113. // 移除临时列表的第一项
  114. this.tempList.splice(0, 1);
  115. // 如果临时数组还有数据,继续循环
  116. if (this.tempList.length) {
  117. setTimeout(() => {
  118. this.splitData();
  119. }, this.addTime)
  120. }
  121. },
  122. // 获取元素最少的列索引(用于高度相同时的轮询分配)
  123. getShortestColumnIndex() {
  124. let minLength = Math.min(...this.columns.map(col => col.length));
  125. return this.columns.findIndex(col => col.length === minLength);
  126. },
  127. // 复制而不是引用对象和数组
  128. cloneData(data) {
  129. return JSON.parse(JSON.stringify(data));
  130. },
  131. // 清空数据列表
  132. clear() {
  133. for (let i = 0; i < this.columns.length; i++) {
  134. this.columns[i] = [];
  135. }
  136. // 同时清除父组件列表中的数据
  137. // #ifdef VUE2
  138. this.$emit('input', []);
  139. // #endif
  140. // #ifdef VUE3
  141. this.$emit('update:modelValue', []);
  142. // #endif
  143. this.tempList = [];
  144. },
  145. // 清除某一条指定的数据,根据id实现
  146. remove(id) {
  147. // 如果findIndex找不到合适的条件,就会返回-1
  148. let index = -1;
  149. let columnIndex = -1;
  150. // 在所有列中查找要删除的数据
  151. for (let i = 0; i < this.columns.length; i++) {
  152. index = this.columns[i].findIndex(val => val[this.idKey] == id);
  153. if (index !== -1) {
  154. columnIndex = i;
  155. break;
  156. }
  157. }
  158. // 如果找到了,删除对应的数据
  159. if (index !== -1 && columnIndex !== -1) {
  160. this.columns[columnIndex].splice(index, 1);
  161. }
  162. // 同时清除父组件的数据中的对应id的条目
  163. index = this.flowList.findIndex(val => val[this.idKey] == id);
  164. if (index !== -1) {
  165. // #ifdef VUE2
  166. this.$emit('input', this.flowList.splice(index, 1));
  167. // #endif
  168. // #ifdef VUE3
  169. this.$emit('update:modelValue', this.flowList.splice(index, 1));
  170. // #endif
  171. }
  172. },
  173. // 修改某条数据的某个属性
  174. modify(id, key, value) {
  175. // 如果findIndex找不到合适的条件,就会返回-1
  176. let index = -1;
  177. let columnIndex = -1;
  178. // 在所有列中查找要修改的数据
  179. for (let i = 0; i < this.columns.length; i++) {
  180. index = this.columns[i].findIndex(val => val[this.idKey] == id);
  181. if (index !== -1) {
  182. columnIndex = i;
  183. break;
  184. }
  185. }
  186. // 如果找到了,修改对应的数据
  187. if (index !== -1 && columnIndex !== -1) {
  188. this.columns[columnIndex][index][key] = value;
  189. }
  190. // 修改父组件的数据中的对应id的条目
  191. index = this.flowList.findIndex(val => val[this.idKey] == id);
  192. if (index !== -1) {
  193. // 首先复制一份value的数据
  194. let data = this.cloneData(this.flowList);
  195. // 修改对应索引的key属性的值为value
  196. data[index][key] = value;
  197. // 修改父组件通过v-model绑定的变量的值
  198. // #ifdef VUE2
  199. this.$emit('input', data);
  200. // #endif
  201. // #ifdef VUE3
  202. this.$emit('update:modelValue', data);
  203. // #endif
  204. }
  205. }
  206. }
  207. }
  208. </script>
  209. <style lang="scss" scoped>
  210. @import '../../libs/css/components.scss';
  211. .u-waterfall {
  212. @include flex(row);
  213. align-items: flex-start;
  214. }
  215. .u-column {
  216. @include flex(column);
  217. flex: 1;
  218. height: auto;
  219. }
  220. .u-image {
  221. width: 100%;
  222. }
  223. </style>