This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,349 @@
<script lang="ts" setup>
import { PropType, computed, ref, provide, nextTick, onMounted, getCurrentInstance } from "vue"
import { getUid, getUnit } from "../../core/util/xCoreUtil.uts"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { checkIsCssUnit } from "../../core/util/xCoreUtil.uts"
import { xWaterfallDataItemType } from "../../interface.uts"
import { xConfig, xProvitae } from "../../config/xConfig.uts"
type callType = () => Promise<boolean>
/**
* @name 瀑布流 xWaterfall
* @description 可以通过columnu来控制排版列数,默认是2列排版。可以无限加载不卡,全异步流加载.使用时请参考demo布局,已精心为你规划如何提高性能.
* @page /pages/index/waterfall
* @category 展示组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xWaterfall"})
const emit = defineEmits([
'bottom',
'top',
])
const proxy = getCurrentInstance()?.proxy
const xWaterFallWrap = ref<UniElement|null>(null)
let resizeObserver = null as UniResizeObserver | null
const props = defineProps({
/**
* 瀑布流的列数
* 不可动态动态更改。
*/
column: {
type: Number,
default: 2
},
/**
* 用来刷新数据的,你可以通过你的外面list.length来传递进来
* 比如为0时,自动清空瀑布流数据。
*/
listCount: {
type: Number,
default: 0
},
/**
* 间距,单位不能为%比,可以是数字字符,带rpx,px等单位
* 默认的数字符单位是全局的unit单位。
*/
gutter: {
type: String,
default: '8'
},
/**
* 是否响应尺寸监测。设置为false可以关闭
* 关闭后能增加性能。
*/
isResize:{
type:Boolean,
default:false
},
disabledScroll:{
type:Boolean,
default:false
}
})
const firstRender = ref(false)
const domList = ref<xWaterfallDataItemType[]>([])
const parentWidth = ref(0)
// 视图的顶点.
const parentTop = ref(0)
const parentPositonTop = ref(0)
// 可视范围高
const parentShowViewHeight = ref(uni.getWindowInfo().windowHeight)
const tidkey = ref(true)
const isResize = computed(():boolean=>props.isResize)
const _disabledScroll = computed(():boolean=>props.disabledScroll)
let timeid = 0
const cols = ref<number[]>([])
const totalHeight = computed(() : number => {
let sort = cols.value.slice(0).sort((a : number, b : number) : number => b - a);
if (sort.length == 0) return 0;
return sort[0]
})
let tid = 0
const currentId = ref("")
/**
* 添加元素。
* @hidden
*/
const pushChild = (item : xWaterfallDataItemType) : Map<string, number> => {
let index = domList.value.findIndex((el : xWaterfallDataItemType) : boolean => el.id == item.id);
let col = -1;
let minHeight = 0;
if (item.col == -1 && item.height > 0) {
let minAr = cols.value.slice(0).sort((a : number, b : number) : number => a - b);
let minnumber = minAr[0]
let minindex = cols.value.findIndex((el : number) : boolean => el == minnumber);
col = minindex;
minHeight = minnumber;
cols.value[minindex] = cols.value[minindex] + item.height
item.col = col;
}
if (index == -1) {
domList.value.push(item)
} else {
domList.value[index] = item;
}
if (currentId.value == "" && firstRender.value == false) {
firstRender.value = true;
nextTick(() => {
currentId.value = item.id;
})
}
let maps = new Map<string, number>();
maps.set('col', col)
maps.set('top', minHeight)
return maps;
}
/**
* @hidden
*/
const refreshCallTop = () => {
let cid = currentId.value;
if (cid == "" && domList.value.length > 0) {
cid = domList.value[0].id
}
if (cid == "") return;
clearTimeout(tid)
function updateAsync(){
tid = setTimeout(function () {
let index = domList.value.findIndex((el : xWaterfallDataItemType) : boolean => el.id == cid);
if (index == -1) return;
index = index + 1;
if (index >= domList.value.length) return;
currentId.value = domList.value[index].id;
}, 20);
}
// #ifdef APP-ANDROID
class IntentRunable extends Runnable {
override run() {
updateAsync()
}
}
UTSAndroid.getUniActivity()!.runOnUiThread(new IntentRunable())
// #endif
// #ifndef APP-ANDROID
updateAsync()
// #endif
}
const getColunm = () : number[] => {
// #ifdef APP-ANDROID
return Array.fromNative(new IntArray((props.column).toInt()))
// #endif
return new Array(props.column).fill(0)
}
/**
* 移除元素。
* @hidden
*/
const removeChild = (id : string, height : number, mycol : number) => {
let index = domList.value.findIndex((el : xWaterfallDataItemType) : boolean => el.id == id);
if (index > -1 && mycol > -1) {
domList.value.splice(index, 1)
cols.value[mycol] = cols.value[mycol] - height
}
}
provide("xWaterFallColumn", computed(() : number => props.column))
provide("xWaterGutter", computed(() : number => {
let p = checkIsCssUnit(props.gutter, xConfig.unit)
let unit = getUnit(p)
let n = parseInt(p)
if (unit == 'rpx') {
n = uni.rpx2px(n)
}
return n;
}))
provide("xWaterWidthParent", computed(() : number => parentWidth.value))
provide("xWaterTopParent", computed(() : number => parentTop.value))
provide("xWaterPositionTopParent", computed(() : number => parentPositonTop.value))
provide("xWaterFallCurrentId", computed(() : string => currentId.value))
provide("xWaterParentShowViewHeight", computed(() : number => parentShowViewHeight.value))
watch(() : number => props.listCount, () => {
if (props.listCount == 0) {
cols.value = getColunm()
domList.value = [] as xWaterfallDataItemType[];
currentId.value = ''
firstRender.value = false
} else {
nextTick(() => {
refreshCallTop()
})
}
})
const resize = ()=>{
let ele = xWaterFallWrap.value
if (ele == null) return;
// #ifdef APP||WEB
resizeObserver = new UniResizeObserver((entries : Array<UniResizeObserverEntry>) => {
entries.forEach(entry => {
if(!isResize.value) return;
if (entry.target == ele) {
let bound = ele.getBoundingClientRect();
if(bound.width==parentWidth.value) return;
clearTimeout(timeid)
timeid = setTimeout(function() {
tidkey.value = false
domList.value = [] as xWaterfallDataItemType[];
cols.value = getColunm()
parentWidth.value = bound.width!;
currentId.value = ''
firstRender.value = false
timeid = setTimeout(function() {
tidkey.value = true
}, 50);
}, 250);
}
})
})
resizeObserver?.observe(ele!)
// #endif
// #ifdef MP-WEIXIN
ele.getBoundingClientRectAsync().then(bound=>{
if(bound.width==parentWidth.value) return;
tidkey.value = false
domList.value = [] as xWaterfallDataItemType[];
cols.value = getColunm()
parentWidth.value = bound.width!;
currentId.value = ''
firstRender.value = false
timeid = setTimeout(function() {
tidkey.value = true
}, 50);
})
// #endif
}
const getParentNodeinfo = () => {
uni.createSelectorQuery()
.in(proxy)
.select(".xWaterFallWrap")
.boundingClientRect()
.exec((res) => {
let node = res[0] as NodeInfo;
parentWidth.value = node.width!;
// parentShowViewHeight.value = node.bottom!
parentPositonTop.value = node.top!;
resize();
})
}
let tidsd = 12
onMounted(() => {
cols.value = getColunm()
// #ifndef APP-HARMONY
getParentNodeinfo()
// #endif
// #ifdef APP-HARMONY
tidsd = setTimeout(function() {
parentShowViewHeight.value = uni.getSystemInfoSync().windowHeight
getParentNodeinfo()
}, 150);
// #endif
})
onBeforeUnmount(() => {
// uni.$off("onResize",resize)
resizeObserver?.disconnect()
clearTimeout(timeid)
clearTimeout(tidsd)
})
onPageScroll((opts:OnPageScrollOptions)=>{
let ele = xWaterFallWrap.value
if (ele == null) return;
// let bounds = ele.getBoundingClientRect()
// parentPositonTop.value = bounds.top;
// parentTop.value = opts.scrollTop;
})
function onScroll(evt:UniScrollEvent){
let ele = xWaterFallWrap.value
if (ele == null) return;
parentTop.value = evt.detail.scrollTop;
}
function topCallFun(){
emit('top')
}
function bootomCallFun(){
emit('bottom')
}
defineExpose({
pushChild,
nextRender: refreshCallTop,
removeChild
})
</script>
<template>
<view ref="xWaterFallWrap" style="flex:1" >
<scroll-view
:show-scrollbar="false"
@scrolltolower="bootomCallFun"
@scrolltoupper="topCallFun"
:direction="_disabledScroll?'none':'vertical'"
@scroll="onScroll"
class="xWaterFallWrapScoll">
<view v-if="tidkey" class="xWaterFallWrap" :style="{height:totalHeight+'px'}">
<!--
@slot 只能放置直接子节点x-waterfall-item
-->
<slot></slot>
</view>
</scroll-view>
</view>
</template>
<style scoped>
.xWaterFallWrapScoll{
height:100%;
}
.xWaterFallWrap {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: flex-start;
}
</style>