<template>
|
|
<view class="uv-waterfall">
|
|
<!-- #ifndef APP-NVUE -->
|
|
<view class="uv-waterfall__gap_left" :style="[gapLeftStyle]"></view>
|
|
<template v-if="columnNum>=1">
|
|
<view id="uv-waterfall-1" class="uv-waterfall__column">
|
|
<slot name="list1"></slot>
|
|
</view>
|
|
</template>
|
|
<template v-if="columnNum>=2">
|
|
<view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
|
|
<view id="uv-waterfall-2" class="uv-waterfall__column">
|
|
<slot name="list2"></slot>
|
|
</view>
|
|
</template>
|
|
<template v-if="columnNum>=3">
|
|
<view class="uv-waterfall__gap_center" :style="[gapCenterStyle]"></view>
|
|
<view id="uv-waterfall-3" class="uv-waterfall__column">
|
|
<slot name="list3"></slot>
|
|
</view>
|
|
</template>
|
|
<template v-if="columnNum>=4">
|
|
<view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
|
|
</view>
|
|
<view id="uv-waterfall-4" class="uv-waterfall__column">
|
|
<slot name="list4"></slot>
|
|
</view>
|
|
</template>
|
|
<template v-if="columnNum>=5">
|
|
<view class="uv-waterfall__gap_center" :style="[gapCenterStyle]">
|
|
</view>
|
|
<view id="uv-waterfall-5" class="uv-waterfall__column">
|
|
<slot name="list5"></slot>
|
|
</view>
|
|
</template>
|
|
<view class="uv-waterfall__gap_right" :style="[gapRightStyle]">
|
|
</view>
|
|
<!-- #endif -->
|
|
<!-- #ifdef APP-NVUE -->
|
|
<view class="waterfall-warapper">
|
|
<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]"
|
|
@loadmore="scrolltolower">
|
|
<slot></slot>
|
|
</waterfall>
|
|
</view>
|
|
<!-- #endif -->
|
|
</view>
|
|
</template>
|
|
<script>
|
|
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
|
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
|
import props from './props.js';
|
|
/**
|
|
* 瀑布流
|
|
* @description 该组件兼容所有端,nvue参考https://uniapp.dcloud.net.cn/component/waterfall.html
|
|
* @tutorial https://www.uvui.cn/components/list.html
|
|
* @property {Array} value/modelValue 瀑布流数组数据,非nvue生效 (默认 [] )
|
|
* @property {String} idKey 数据的id值,根据id值对数据执行删除操作,如数据为:{id: 1, name: 'uv-ui'},那么该值设置为id,非nvue有效 (默认 '' )
|
|
* @property {String | Number} addTime 每次插入数据的事件间隔,间隔越长能保证两列高度相近,但是用户体验不好,单位ms,非nvue生效(默认 200 )
|
|
* @property {String | Number} columnCount 瀑布流的列数(默认 2 )
|
|
* @property {String | Number} columnGap 列与列的间隙(默认 0 )
|
|
* @property {String | Number} leftGap 左边和列表的间隙(默认 0 )
|
|
* @property {String | Number} rightGap 右边和列表的间隙(默认 0 )
|
|
* @property {Boolean} showScrollbar 控制是否出现滚动条,仅nvue有效 (默认 false )
|
|
* @property {String | Number} columnWidth 描述瀑布流每一列的列宽,nvue生效 (默认 auto)
|
|
* @property {String | Number} width 瀑布流的宽度,nvue生效 (默认 屏幕宽 )
|
|
* @property {String | Number} height 瀑布流的高度,nvue生效 (默认 屏幕高 )
|
|
* @property {Object} customStyle 定义需要用到的外部样式
|
|
*
|
|
* @example <uv-waterfall v-model="list"></uv-waterfall>
|
|
*/
|
|
export default {
|
|
name: 'uv-waterfall',
|
|
mixins: [mpMixin, mixin, props],
|
|
data() {
|
|
return {
|
|
list1: [],
|
|
list2: [],
|
|
list3: [],
|
|
list4: [],
|
|
list5: [],
|
|
// 临时列表
|
|
tempList: []
|
|
}
|
|
},
|
|
computed: {
|
|
// 破坏value变量引用,否则数据会保持不变
|
|
copyValue() {
|
|
// #ifdef VUE2
|
|
return this.$uv.deepClone(this.value)
|
|
// #endif
|
|
// #ifdef VUE3
|
|
return this.$uv.deepClone(this.modelValue)
|
|
// #endif
|
|
},
|
|
columnNum() {
|
|
return this.columnCount <= 0 ? 0 : this.columnCount >= 5 ? 5 : this.columnCount;
|
|
},
|
|
gapLeftStyle() {
|
|
const style = {}
|
|
style.width = this.$uv.addUnit(this.leftGap)
|
|
return style;
|
|
},
|
|
gapRightStyle() {
|
|
const style = {}
|
|
style.width = this.$uv.addUnit(this.rightGap)
|
|
return style;
|
|
},
|
|
gapCenterStyle() {
|
|
const style = {}
|
|
style.width = this.$uv.addUnit(this.columnGap)
|
|
return style;
|
|
},
|
|
nvueWaterfallStyle() {
|
|
const style = {};
|
|
if (this.width != 0) style.width = this.$uv.addUnit(this.width)
|
|
if (this.height != 0) style.height = this.$uv.addUnit(this.height)
|
|
// 如果没有定义列表高度,则默认使用屏幕高度
|
|
if (!style.width) style.width = this.$uv.addUnit(this.$uv.sys().windowWidth, 'px')
|
|
if (!style.height) style.height = this.$uv.addUnit(this.$uv.sys().windowHeight, 'px')
|
|
return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
|
|
}
|
|
},
|
|
watch: {
|
|
copyValue(nVal, oVal) {
|
|
// #ifndef APP-NVUE
|
|
if (nVal.length != 0) {
|
|
// 取出数组发生变化的部分
|
|
let startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0
|
|
// 拼接原有数据
|
|
this.tempList = this.tempList.concat(this.$uv.deepClone(nVal.slice(startIndex)))
|
|
this.splitData()
|
|
}
|
|
// #endif
|
|
}
|
|
},
|
|
mounted() {
|
|
// #ifndef APP-NVUE
|
|
this.tempList = this.$uv.deepClone(this.copyValue)
|
|
this.splitData()
|
|
// #endif
|
|
},
|
|
methods: {
|
|
// 滚动到底部触发事件
|
|
scrolltolower(e) {
|
|
this.$uv.sleep(30).then(() => {
|
|
this.$emit('scrolltolower')
|
|
})
|
|
},
|
|
// 拆分数据
|
|
async splitData() {
|
|
let rectArr = [];
|
|
let emitList = {};
|
|
if (!this.tempList.length) return
|
|
for (let i = 1; i <= this.columnNum; i++) {
|
|
const rect = await this.$uvGetRect(`#uv-waterfall-${i}`);
|
|
rectArr.push({ ...rect, name: i });
|
|
}
|
|
let item = this.tempList[0]
|
|
// 因为经过上面两个await节点查询和定时器,数组有可能会变成空[],导致item的值为undefined
|
|
// 解决多次快速滚动会导致数据乱的问题
|
|
if (!item) return
|
|
const minCol = this.getMin(rectArr);
|
|
// 列宽可能使用的到
|
|
item.width = minCol.width;
|
|
this[`list${minCol.name}`].push(item);
|
|
emitList.name = `list${minCol.name}`;
|
|
emitList.value = item;
|
|
this.$emit('changeList', emitList);
|
|
// 移除临时数组中已处理的数据
|
|
this.tempList.splice(0, 1)
|
|
// 如果还有数据则继续执行
|
|
if (this.tempList.length) {
|
|
let _timeout = this.addTime;
|
|
// 部分平台在延时较短的情况会出现BUG
|
|
// #ifdef MP-BAIDU
|
|
_timeout = _timeout < 200 ? 200 : _timeout;
|
|
// #endif
|
|
await this.$uv.sleep(_timeout);
|
|
this.splitData()
|
|
} else {
|
|
this.$emit('finish')
|
|
}
|
|
},
|
|
getMin(arr) {
|
|
let result = null;
|
|
const filter = arr.filter(item => item.height == 0);
|
|
if (!filter.length) {
|
|
const min = Math.min.apply(Math, arr.map(item => {
|
|
return item.height;
|
|
}))
|
|
const [item] = arr.filter(item => item.height == min);
|
|
result = item;
|
|
} else {
|
|
let newArr = [];
|
|
arr.map((item, index) => {
|
|
newArr.push({ len: this[`list${index+1}`].length, item: item });
|
|
});
|
|
const minLen = Math.min.apply(Math, newArr.map(item => {
|
|
return item.len;
|
|
}))
|
|
try {
|
|
const { item } = newArr.find(item => item.len == minLen && item.item.height == 0);
|
|
result = item;
|
|
} catch (e) {
|
|
const { item } = newArr.find(item => item.item.height == 0);
|
|
result = item;
|
|
}
|
|
}
|
|
return result;
|
|
},
|
|
// 清空数据列表
|
|
async clear() {
|
|
// 清除数据
|
|
for (let i = 0; i < this.columnCount; i++) {
|
|
this[`list${i+1}`] = [];
|
|
}
|
|
// #ifdef VUE2
|
|
this.$emit('input', [])
|
|
// #endif
|
|
// #ifdef VUE3
|
|
this.$emit('update:modelValue', [])
|
|
// #endif
|
|
this.tempList = []
|
|
await this.$uv.sleep(300);
|
|
this.$emit('clear');
|
|
},
|
|
// 清除指定的某一条数据,根据id来实现
|
|
remove(id) {
|
|
let index = -1
|
|
// 删除组件数据
|
|
for (let i = 1; i <= this.columnCount; i++) {
|
|
index = this[`list${i}`].findIndex(item => item[this.idKey] == id)
|
|
if (index != -1) {
|
|
this[`list${i}`].splice(index, 1)
|
|
}
|
|
}
|
|
// 同时删除父组件对应的数据
|
|
// #ifdef VUE2
|
|
index = this.value.findIndex(item => item[this.idKey] == id)
|
|
if (index != -1) this.$emit('input', this.value.splice(index, 1))
|
|
// #endif
|
|
// #ifdef VUE3
|
|
index = this.modelValue.findIndex(item => item[this.idKey] == id)
|
|
if (index != -1) this.$emit('update:modelValue', this.modelValue.splice(index, 1))
|
|
// #endif
|
|
this.$emit('remove', id);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
|
.uv-waterfall {
|
|
@include flex(row);
|
|
align-items: flex-start;
|
|
&__column {
|
|
@include flex(column);
|
|
flex: 1;
|
|
// #ifndef APP-NVUE
|
|
height: auto;
|
|
// #endif
|
|
}
|
|
}
|
|
</style>
|