waterfall-list.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <template>
  2. <view>
  3. <view class="waterfall-box h-flex-x h-flex-2" v-if="showList">
  4. <view>
  5. <view v-for="(item,index) in leftList" :key="item._render_id"
  6. class="list-item"
  7. :class="{'show': showPage > item._current_page }"
  8. >
  9. <helang-waterfall-item
  10. :params="item"
  11. tag="left"
  12. :index="index"
  13. @height="onHeight"
  14. @click="onClick"
  15. ></helang-waterfall-item>
  16. </view>
  17. </view>
  18. <view>
  19. <view v-for="(item,index) in rightList" :key="item._render_id"
  20. class="list-item"
  21. :class="{'show': showPage > item._current_page }"
  22. >
  23. <helang-waterfall-item
  24. :params="item"
  25. @height="onHeight"
  26. @click="onClick"
  27. tag="right"
  28. :index="index"
  29. ></helang-waterfall-item>
  30. </view>
  31. </view>
  32. </view>
  33. <slot name="default"></slot>
  34. </view>
  35. </template>
  36. <script>
  37. import helangWaterfallItem from "./waterfall-item.vue"
  38. export default {
  39. name:"helangWaterfallList",
  40. options:{
  41. virtualHost: true
  42. },
  43. components: {
  44. "helang-waterfall-item": helangWaterfallItem
  45. },
  46. props:{
  47. // 组件状态
  48. status:{
  49. type: String,
  50. default:''
  51. },
  52. // 待渲染的数据
  53. list:{
  54. type: Array,
  55. default(){
  56. return [];
  57. }
  58. },
  59. // 重置列表,设置为 true 时,瀑布流会自动重新渲染列表
  60. reset:{
  61. type: Boolean,
  62. default:false
  63. },
  64. },
  65. watch:{
  66. "$props.status"(newValue,oldValue){
  67. // 状态变更为 加载成功 时,执行瀑布流数据渲染
  68. if(newValue == 'success'){
  69. this.startRender();
  70. }else if(!this.showList){
  71. this.resetData();
  72. }
  73. }
  74. },
  75. computed:{
  76. showList(){
  77. return !["fail","empty"].includes(this.$props.status);
  78. }
  79. },
  80. data() {
  81. return {
  82. // 左侧列表高度
  83. leftHeight: 0,
  84. // 右侧列表高度
  85. rightHeight: 0,
  86. // 左侧列表数据
  87. leftList: [],
  88. // 右侧列表数据
  89. rightList: [],
  90. // 待渲染列表
  91. awaitRenderList:[],
  92. // 当前展示页码数据
  93. showPage:1
  94. }
  95. },
  96. mounted() {
  97. if(this.$props.status == 'success'){
  98. this.startRender();
  99. }
  100. },
  101. methods: {
  102. // 监听高度变化
  103. onHeight(height, tag) {
  104. /**
  105. * 这个为实际渲染后 CSS 中 margin-buttom 的值,本示例默认为20rpx
  106. * 用于解决实际渲染后因为数据条数关系,高度差计算偏差的问题
  107. * */
  108. let marginBottom = uni.upx2px(10);
  109. // console.log(`左高:${this.leftHeight},右高:${this.rightHeight},当前高:${height},插入方向:${tag}`)
  110. if (tag == 'left') {
  111. this.leftHeight += (height + marginBottom);
  112. } else {
  113. this.rightHeight += (height + marginBottom);
  114. }
  115. this.renderList();
  116. },
  117. // 组件点击事件
  118. onClick(index, tag){
  119. // 对应的数据
  120. if(tag == 'left'){
  121. this.$emit("click",this.leftList[index],index,tag);
  122. }else{
  123. this.$emit("click",this.rightList[index],index,tag);
  124. }
  125. },
  126. // 渲染列表,这里实现瀑布流的左右分栏
  127. renderList() {
  128. // 待渲染长度为 0 时表示已渲染完成
  129. if(this.awaitRenderList.length < 1){
  130. this.showPage++;
  131. this.$emit("done");
  132. // 为防止 js 数值类型最大值溢出,当高度值大于 1亿时重置高度
  133. if(this.leftHeight > 100000000){
  134. if(this.leftHeight > this.rightHeight){
  135. this.leftHeight = 2;
  136. this.rightHeight = 1;
  137. }else{
  138. this.leftHeight = 1;
  139. this.rightHeight = 2;
  140. }
  141. }
  142. return;
  143. }
  144. let item = {
  145. ...this.awaitRenderList.splice(0,1)[0],
  146. // 当前数据添加当前页面标识
  147. _current_page:this.showPage,
  148. // 当前数据添加一个渲染id,解决 v-for 重复会出现不执行 load 的 BUG
  149. _render_id:new Date().getTime()
  150. };
  151. if(this.leftHeight > this.rightHeight){
  152. this.rightList.push(item);
  153. }else{
  154. this.leftList.push(item);
  155. }
  156. },
  157. // 重置数据
  158. resetData(){
  159. this.leftHeight = 0;
  160. this.rightHeight = 0;
  161. this.leftList = [];
  162. this.rightList = [];
  163. this.awaitRenderList = [];
  164. // 当前展示页码数据
  165. this.showPage = 1;
  166. },
  167. // 启动渲染
  168. startRender(){
  169. if(!this.showList){
  170. this.resetData();
  171. return;
  172. }
  173. if(!this.$props.list || this.$props.list.length < 1){
  174. console.log('河浪瀑布流插件提示:当前数据为空,不会触发列表渲染');
  175. return;
  176. }
  177. // 若本次渲染为 重置 则先恢复组件的默认参数
  178. if(this.$props.reset){
  179. this.resetData();
  180. }
  181. this.awaitRenderList = [...this.$props.list];
  182. this.renderList();
  183. }
  184. }
  185. }
  186. </script>
  187. <style lang="scss" scoped>
  188. .waterfall-box {
  189. padding: 20rpx 10rpx;
  190. box-sizing: border-box;
  191. >view {
  192. padding: 0 10rpx;
  193. }
  194. .list-item{
  195. margin-bottom: 0;
  196. // 设置透明,默认是可视的
  197. opacity: 0;
  198. box-shadow: 0px 4rpx 8rpx 0px rgba(0,0,0,0.04);
  199. // 默认超出隐藏,不影响加载中的文字显示效果
  200. overflow: hidden;
  201. height: 0;
  202. &.show{
  203. margin-bottom: 20rpx;
  204. opacity: 1;
  205. // overflow: auto;
  206. height: auto;
  207. }
  208. }
  209. }
  210. .h-flex-x {
  211. display: flex;
  212. flex-direction: row;
  213. flex-wrap: nowrap;
  214. justify-content: flex-start;
  215. align-items: flex-start;
  216. align-content: flex-start;
  217. &.h-flex-2 {
  218. >view {
  219. width: 50%;
  220. }
  221. }
  222. }
  223. </style>