custom-waterfalls-flow.vue 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. <template>
  2. <view class="waterfalls-flow">
  3. <view v-for="(item,index) in data.column" :key="index" class="waterfalls-flow-column"
  4. :id="`waterfalls_flow_column_${index+1}`" :msg="msg" :style="{'width':w,'margin-left':index==0?0:m}">
  5. <view :class="['column-value',{'column-value-show':item2.o}]" v-for="(item2,index2) in columnValue(index)"
  6. :key="index2" :style="[s1]" @click.stop="wapperClick(item2)">
  7. <view class="inner" v-if="data.seat==1">
  8. <!-- #ifdef MP-WEIXIN -->
  9. <!-- #ifdef VUE2 -->
  10. <slot name="slot{{item2.index}}"></slot>
  11. <!-- #endif -->
  12. <!-- #ifdef VUE3 -->
  13. <slot :name="`slot${item2.index}`"></slot>
  14. <!-- #endif -->
  15. <!-- #endif -->
  16. <!-- #ifndef MP-WEIXIN -->
  17. <slot v-bind="item2"></slot>
  18. <!-- #endif -->
  19. </view>
  20. <image
  21. :class="['img',{'img-hide':item2[hideImageKey]==true||item2[hideImageKey]==1},{'img-error':!item2[data.imageKey]}]"
  22. :src="item2[data.imageKey]" mode="widthFix" @load="imgLoad(item2,index+1)"
  23. @error="imgError(item2,index+1)" @click.stop="imageClick(item2)"></image>
  24. <view class="inner" v-if="data.seat==2">
  25. <!-- #ifdef MP-WEIXIN -->
  26. <!-- #ifdef VUE2 -->
  27. <slot name="slot{{item2.index}}"></slot>
  28. <!-- #endif -->
  29. <!-- #ifdef VUE3 -->
  30. <slot :name="`slot${item2.index}`"></slot>
  31. <!-- #endif -->
  32. <!-- #endif -->
  33. <!-- #ifndef MP-WEIXIN -->
  34. <slot v-bind="item2"></slot>
  35. <!-- #endif -->
  36. </view>
  37. </view>
  38. </view>
  39. </view>
  40. </template>
  41. <script>
  42. export default {
  43. props: {
  44. value: Array,
  45. column: { // 列的数量
  46. type: [String, Number],
  47. default: 2
  48. },
  49. maxColumn: { // 最大列数
  50. type: [String, Number],
  51. default: 5
  52. },
  53. columnSpace: { // 列之间的间距 百分比
  54. type: [String, Number],
  55. default: 2
  56. },
  57. imageKey: { // 图片key
  58. type: [String],
  59. default: 'image'
  60. },
  61. hideImageKey: { // 隐藏图片key
  62. type: [String],
  63. default: 'hide'
  64. },
  65. seat: { // 文本的位置,1图片之上 2图片之下
  66. type: [String, Number],
  67. default: 2
  68. },
  69. listStyle: { // 单个展示项的样式:eg:{'background':'red'}
  70. type: Object
  71. }
  72. },
  73. data() {
  74. return {
  75. data: {
  76. list: this.value ? this.value : [],
  77. column: this.column < 2 ? 2 : this.column,
  78. columnSpace: this.columnSpace <= 5 ? this.columnSpace : 5,
  79. imageKey: this.imageKey,
  80. seat: this.seat
  81. },
  82. msg: 0,
  83. listInitStyle: {
  84. 'border-radius': '12rpx',
  85. 'margin-bottom': '20rpx',
  86. 'background-color': '#fff'
  87. },
  88. adds: [], //预置数据
  89. isLoaded: true,
  90. curIndex: 0,
  91. isRefresh: true,
  92. flag: false,
  93. refreshDatas: []
  94. }
  95. },
  96. computed: {
  97. // 计算列宽
  98. w() {
  99. const column_rate = `${100 / this.data.column - (+this.data.columnSpace)}%`;
  100. return column_rate;
  101. },
  102. // 计算margin
  103. m() {
  104. const column_margin =
  105. `${(100-(100 / this.data.column - (+this.data.columnSpace)).toFixed(5)*this.data.column)/(this.data.column-1)}%`;
  106. return column_margin;
  107. },
  108. // list样式
  109. s1() {
  110. return {
  111. ...this.listInitStyle,
  112. ...this.listStyle
  113. };
  114. }
  115. },
  116. created() {
  117. // 初始化
  118. this.refresh();
  119. },
  120. methods: {
  121. // 预加载图片
  122. loadImages(idx = 0) {
  123. let count = 0;
  124. const newList = this.data.list.filter((item, index) => index >= idx);
  125. for (let i = 0; i < newList.length; i++) {
  126. // #ifndef APP-PLUS
  127. uni.getImageInfo({
  128. src: `${newList[i][this.imageKey]}.jpg`,
  129. complete: res => {
  130. count++;
  131. if (count == newList.length) this.initValue(idx);
  132. }
  133. })
  134. // #endif
  135. // #ifdef APP-PLUS
  136. plus.io.getImageInfo({
  137. src: `${newList[i][this.imageKey]}.jpg`,
  138. complete: res => {
  139. count++;
  140. if (count == newList.length) this.initValue(idx);
  141. }
  142. })
  143. // #endif
  144. }
  145. },
  146. // 刷新
  147. refresh() {
  148. if (!this.isLoaded) {
  149. this.refreshDatas = this.value;
  150. return false;
  151. };
  152. setTimeout(() => {
  153. this.refreshDatas = [];
  154. this.isRefresh = true;
  155. this.adds = [];
  156. this.data.list = this.value ? this.value : [];
  157. this.data.column = this.column < 2 ? 2 : this.column >= this.maxColumn ? this.maxColumn : this
  158. .column;
  159. this.data.columnSpace = this.columnSpace <= 5 ? this.columnSpace : 5;
  160. this.data.imageKey = this.imageKey;
  161. this.data.seat = this.seat;
  162. this.curIndex = 0;
  163. // 每列的数据初始化
  164. for (let i = 1; i <= this.data.column; i++) {
  165. this.data[`column_${i}_values`] = [];
  166. this.msg++;
  167. }
  168. this.$nextTick(() => {
  169. this.initValue(this.curIndex, 'refresh==>');
  170. })
  171. }, 1)
  172. },
  173. columnValue(index) {
  174. return this.data[`column_${index+1}_values`];
  175. },
  176. change(newValue) {
  177. for (let i = 0; i < this.data.list.length; i++) {
  178. const cv = this.data[`column_${this.data.list[i].column}_values`];
  179. for (let j = 0; j < cv.length; j++) {
  180. if (newValue[i] && i === cv[j].index) {
  181. this.data[`column_${this.data.list[i].column}_values`][j] = Object.assign(cv[j], newValue[i]);
  182. this.msg++;
  183. break;
  184. }
  185. }
  186. }
  187. },
  188. getMin(a, s) {
  189. let m = a[0][s];
  190. let mo = a[0];
  191. for (var i = a.length - 1; i >= 0; i--) {
  192. if (a[i][s] < m) {
  193. m = a[i][s];
  194. }
  195. }
  196. mo = a.filter(i => i[s] == m);
  197. return mo[0];
  198. },
  199. // 计算每列的高度
  200. getMinColumnHeight() {
  201. return new Promise(resolve => {
  202. const heightArr = [];
  203. for (let i = 1; i <= this.data.column; i++) {
  204. const query = uni.createSelectorQuery().in(this);
  205. query.select(`#waterfalls_flow_column_${i}`).boundingClientRect(data => {
  206. heightArr.push({
  207. column: i,
  208. height: data.height
  209. });
  210. }).exec(() => {
  211. if (this.data.column <= heightArr.length) {
  212. resolve(this.getMin(heightArr, 'height'));
  213. }
  214. });
  215. }
  216. })
  217. },
  218. async initValue(i, from) {
  219. this.isLoaded = false;
  220. if (i >= this.data.list.length || this.refreshDatas.length) {
  221. this.msg++;
  222. this.loaded();
  223. return false;
  224. }
  225. const minHeightRes = await this.getMinColumnHeight();
  226. const c = this.data[`column_${minHeightRes.column}_values`];
  227. this.data.list[i].column = minHeightRes.column;
  228. c.push({
  229. ...this.data.list[i],
  230. cIndex: c.length,
  231. index: i,
  232. o: 0
  233. });
  234. this.msg++;
  235. },
  236. // 图片加载完成
  237. imgLoad(item, c) {
  238. const i = item.index;
  239. item.o = 1;
  240. this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
  241. this.initValue(i + 1);
  242. },
  243. // 图片加载失败
  244. imgError(item, c) {
  245. const i = item.index;
  246. item.o = 1;
  247. item[this.data.imageKey] = null;
  248. this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
  249. this.initValue(i + 1);
  250. },
  251. // 渲染结束
  252. loaded() {
  253. if (this.refreshDatas.length) {
  254. this.isLoaded = true;
  255. this.refresh();
  256. return false;
  257. }
  258. this.curIndex = this.data.list.length;
  259. if (this.adds.length) {
  260. this.data.list = this.adds[0];
  261. this.adds.splice(0, 1);
  262. this.initValue(this.curIndex);
  263. } else {
  264. if (this.data.list.length) this.$emit('loaded');
  265. this.isLoaded = true;
  266. this.isRefresh = false;
  267. }
  268. },
  269. // 单项点击事件
  270. wapperClick(item) {
  271. this.$emit('wapperClick', item);
  272. },
  273. // 图片点击事件
  274. imageClick(item) {
  275. this.$emit('imageClick', item);
  276. }
  277. },
  278. watch: {
  279. value: {
  280. deep: true,
  281. handler(newValue, oldValue) {
  282. setTimeout(() => {
  283. this.$nextTick(() => {
  284. if (this.isRefresh) return false;
  285. if (this.isLoaded) {
  286. // if (newValue.length <= this.curIndex) return this.refresh();
  287. if (newValue.length <= this.curIndex) return this.change(newValue);
  288. this.data.list = newValue;
  289. this.$nextTick(() => {
  290. this.initValue(this.curIndex, 'watch==>');
  291. })
  292. } else {
  293. this.adds.push(newValue);
  294. }
  295. })
  296. }, 10)
  297. }
  298. },
  299. column(newValue) {
  300. this.refresh();
  301. }
  302. }
  303. }
  304. </script>
  305. <style lang="scss" scoped>
  306. .waterfalls-flow {
  307. overflow: hidden;
  308. &-column {
  309. float: left;
  310. }
  311. }
  312. .column-value {
  313. width: 100%;
  314. font-size: 0;
  315. overflow: hidden;
  316. transition: opacity .4s;
  317. opacity: 0;
  318. position: relative;
  319. &-show {
  320. opacity: 1;
  321. }
  322. .inner {
  323. font-size: 30rpx;
  324. }
  325. .img {
  326. width: 100%;
  327. &-hide {
  328. display: none;
  329. }
  330. &-error {
  331. background: #f2f2f2 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAiAQMAAAAatXkPAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAAIZJREFUCNdlzjEKwkAUBNAfEGyCuYBkLyLuxRYW2SKlV1JSeA2tUiZg4YrLjv9PGsHqNTPMSAQuyAJgRDHSyvBPwtZoSJXakeJI9iuRLGDygdl6V0yKDtyMAeMPZySj8yfD+UapvRPj2JOwkyAooSV5IwdDjPdCPspe8LyTl9IKJvDETKKRv6vnlUasgg0fAAAAAElFTkSuQmCC) no-repeat center center;
  332. }
  333. }
  334. }
  335. </style>