OfficeSystem/node_modules/@climblee/uv-ui/components/uv-waterfall/uv-waterfall.vue
WindowBird 7ab9e16c35 init
2025-10-30 16:42:12 +08:00

265 lines
8.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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 '../../libs/mixin/mpMixin.js'
import mixin from '../../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 '../../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>