珠宝小程序前端代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

264 lines
8.7 KiB

5 months ago
  1. <template>
  2. <view class="uv-waterfall">
  3. <!-- #ifndef APP-NVUE -->
  4. <view class="uv-waterfall__gap_left" :style="[gapLeftStyle]"></view>
  5. <template v-if="columnNum>=1">
  6. <view id="uv-waterfall-1" class="uv-waterfall__column">
  7. <slot name="list1"></slot>
  8. </view>
  9. </template>
  10. <template v-if="columnNum>=2">
  11. <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
  12. <view id="uv-waterfall-2" class="uv-waterfall__column">
  13. <slot name="list2"></slot>
  14. </view>
  15. </template>
  16. <template v-if="columnNum>=3">
  17. <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
  18. <view id="uv-waterfall-3" class="uv-waterfall__column">
  19. <slot name="list3"></slot>
  20. </view>
  21. </template>
  22. <template v-if="columnNum>=4">
  23. <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
  24. </view>
  25. <view id="uv-waterfall-4" class="uv-waterfall__column">
  26. <slot name="list4"></slot>
  27. </view>
  28. </template>
  29. <template v-if="columnNum>=5">
  30. <view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
  31. </view>
  32. <view id="uv-waterfall-5" class="uv-waterfall__column">
  33. <slot name="list5"></slot>
  34. </view>
  35. </template>
  36. <view class="uv-waterfall__gap_right" :style="[gapRightStyle]">
  37. </view>
  38. <!-- #endif -->
  39. <!-- #ifdef APP-NVUE -->
  40. <view class="waterfall-warapper">
  41. <waterfall :column-count="columnNum" :show-scrollbar="false" column-width="auto" :column-gap="columnGap" :left-gap="leftGap" :right-gap="rightGap" :always-scrollable-vertical="true" :style="[nvueWaterfallStyle]"
  42. @loadmore="scrolltolower">
  43. <slot></slot>
  44. </waterfall>
  45. </view>
  46. <!-- #endif -->
  47. </view>
  48. </template>
  49. <script>
  50. import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
  51. import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
  52. import props from './props.js';
  53. /**
  54. * 瀑布流
  55. * @description 该组件兼容所有端nvue参考https://uniapp.dcloud.net.cn/component/waterfall.html
  56. * @tutorial https://www.uvui.cn/components/list.html
  57. * @property {Array} value/modelValue 瀑布流数组数据非nvue生效 默认 []
  58. * @property {String} idKey 数据的id值根据id值对数据执行删除操作如数据为{id: 1, name: 'uv-ui'}那么该值设置为id非nvue有效 默认 ''
  59. * @property {String Number} addTime 每次插入数据的事件间隔间隔越长能保证两列高度相近但是用户体验不好单位ms非nvue生效默认 200
  60. * @property {String Number} columnCount 瀑布流的列数默认 2
  61. * @property {String Number} columnGap 列与列的间隙默认 0
  62. * @property {String Number} leftGap 左边和列表的间隙默认 0
  63. * @property {String Number} rightGap 右边和列表的间隙默认 0
  64. * @property {Boolean} showScrollbar 控制是否出现滚动条仅nvue有效 默认 false
  65. * @property {String Number} columnWidth 描述瀑布流每一列的列宽nvue生效 默认 auto
  66. * @property {String Number} width 瀑布流的宽度nvue生效 默认 屏幕宽
  67. * @property {String Number} height 瀑布流的高度nvue生效 默认 屏幕高
  68. * @property {Object} customStyle 定义需要用到的外部样式
  69. *
  70. * @example <uv-waterfall v-model="list"></uv-waterfall>
  71. */
  72. export default {
  73. name: 'uv-waterfall',
  74. mixins: [mpMixin, mixin, props],
  75. data() {
  76. return {
  77. list1: [],
  78. list2: [],
  79. list3: [],
  80. list4: [],
  81. list5: [],
  82. // 临时列表
  83. tempList: []
  84. }
  85. },
  86. computed: {
  87. // 破坏value变量引用,否则数据会保持不变
  88. copyValue() {
  89. // #ifdef VUE2
  90. return this.$uv.deepClone(this.value)
  91. // #endif
  92. // #ifdef VUE3
  93. return this.$uv.deepClone(this.modelValue)
  94. // #endif
  95. },
  96. columnNum() {
  97. return this.columnCount <= 0 ? 0 : this.columnCount >= 5 ? 5 : this.columnCount;
  98. },
  99. gapLeftStyle() {
  100. const style = {}
  101. style.width = this.$uv.addUnit(this.leftGap)
  102. return style;
  103. },
  104. gapRightStyle() {
  105. const style = {}
  106. style.width = this.$uv.addUnit(this.rightGap)
  107. return style;
  108. },
  109. gapCenterStyle() {
  110. const style = {}
  111. style.width = this.$uv.addUnit(this.columnGap)
  112. return style;
  113. },
  114. nvueWaterfallStyle() {
  115. const style = {};
  116. if (this.width != 0) style.width = this.$uv.addUnit(this.width)
  117. if (this.height != 0) style.height = this.$uv.addUnit(this.height)
  118. // 如果没有定义列表高度,则默认使用屏幕高度
  119. if (!style.width) style.width = this.$uv.addUnit(this.$uv.sys().windowWidth, 'px')
  120. if (!style.height) style.height = this.$uv.addUnit(this.$uv.sys().windowHeight, 'px')
  121. return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
  122. }
  123. },
  124. watch: {
  125. copyValue(nVal, oVal) {
  126. // #ifndef APP-NVUE
  127. if (nVal.length != 0) {
  128. // 取出数组发生变化的部分
  129. let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0
  130. // 拼接原有数据
  131. this.tempList = this.tempList.concat(this.$uv.deepClone(nVal.slice(startIndex)))
  132. this.splitData()
  133. }
  134. // #endif
  135. }
  136. },
  137. mounted() {
  138. // #ifndef APP-NVUE
  139. this.tempList = this.$uv.deepClone(this.copyValue)
  140. this.splitData()
  141. // #endif
  142. },
  143. methods: {
  144. // 滚动到底部触发事件
  145. scrolltolower(e) {
  146. this.$uv.sleep(30).then(() => {
  147. this.$emit('scrolltolower')
  148. })
  149. },
  150. // 拆分数据
  151. async splitData() {
  152. let rectArr = [];
  153. let emitList = {};
  154. if (!this.tempList.length) return
  155. for (let i = 1; i <= this.columnNum; i++) {
  156. const rect = await this.$uvGetRect(`#uv-waterfall-${i}`);
  157. rectArr.push({ ...rect, name: i });
  158. }
  159. let item = this.tempList[0]
  160. // 因为经过上面两个await节点查询和定时器,数组有可能会变成空[],导致item的值为undefined
  161. // 解决多次快速滚动会导致数据乱的问题
  162. if (!item) return
  163. const minCol = this.getMin(rectArr);
  164. // 列宽可能使用的到
  165. item.width = minCol.width;
  166. this[`list${minCol.name}`].push(item);
  167. emitList.name = `list${minCol.name}`;
  168. emitList.value = item;
  169. this.$emit('changeList', emitList);
  170. // 移除临时数组中已处理的数据
  171. this.tempList.splice(0, 1)
  172. // 如果还有数据则继续执行
  173. if (this.tempList.length) {
  174. let _timeout = this.addTime;
  175. // 部分平台在延时较短的情况会出现BUG
  176. // #ifdef MP-BAIDU
  177. _timeout = _timeout < 200 ? 200 : _timeout;
  178. // #endif
  179. await this.$uv.sleep(_timeout);
  180. this.splitData()
  181. } else {
  182. this.$emit('finish')
  183. }
  184. },
  185. getMin(arr) {
  186. let result = null;
  187. const filter = arr.filter(item => item.height == 0);
  188. if (!filter.length) {
  189. const min = Math.min.apply(Math, arr.map(item => {
  190. return item.height;
  191. }))
  192. const [item] = arr.filter(item => item.height == min);
  193. result = item;
  194. } else {
  195. let newArr = [];
  196. arr.map((item, index) => {
  197. newArr.push({ len: this[`list${index+1}`].length, item: item });
  198. });
  199. const minLen = Math.min.apply(Math, newArr.map(item => {
  200. return item.len;
  201. }))
  202. try {
  203. const { item } = newArr.find(item => item.len == minLen && item.item.height == 0);
  204. result = item;
  205. } catch (e) {
  206. const { item } = newArr.find(item => item.item.height == 0);
  207. result = item;
  208. }
  209. }
  210. return result;
  211. },
  212. // 清空数据列表
  213. async clear() {
  214. // 清除数据
  215. for (let i = 0; i < this.columnCount; i++) {
  216. this[`list${i+1}`] = [];
  217. }
  218. // #ifdef VUE2
  219. this.$emit('input', [])
  220. // #endif
  221. // #ifdef VUE3
  222. this.$emit('update:modelValue', [])
  223. // #endif
  224. this.tempList = []
  225. await this.$uv.sleep(300);
  226. this.$emit('clear');
  227. },
  228. // 清除指定的某一条数据,根据id来实现
  229. remove(id) {
  230. let index = -1
  231. // 删除组件数据
  232. for (let i = 1; i <= this.columnCount; i++) {
  233. index = this[`list${i}`].findIndex(item => item[this.idKey] == id)
  234. if (index != -1) {
  235. this[`list${i}`].splice(index, 1)
  236. }
  237. }
  238. // 同时删除父组件对应的数据
  239. // #ifdef VUE2
  240. index = this.value.findIndex(item => item[this.idKey] == id)
  241. if (index != -1) this.$emit('input', this.value.splice(index, 1))
  242. // #endif
  243. // #ifdef VUE3
  244. index = this.modelValue.findIndex(item => item[this.idKey] == id)
  245. if (index != -1) this.$emit('update:modelValue', this.modelValue.splice(index, 1))
  246. // #endif
  247. this.$emit('remove', id);
  248. }
  249. }
  250. }
  251. </script>
  252. <style lang="scss" scoped>
  253. @import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
  254. .uv-waterfall {
  255. @include flex(row);
  256. align-items: flex-start;
  257. &__column {
  258. @include flex(column);
  259. flex: 1;
  260. // #ifndef APP-NVUE
  261. height: auto;
  262. // #endif
  263. }
  264. }
  265. </style>