小程序初始提交
This commit is contained in:
@@ -0,0 +1,759 @@
|
||||
<template>
|
||||
<view class="cl-list-view" :class="[pt.className]">
|
||||
<!-- 滚动容器 -->
|
||||
<scroll-view
|
||||
class="cl-list-view__scroller"
|
||||
:class="[pt.scroller?.className]"
|
||||
:scroll-top="targetScrollTop"
|
||||
:scroll-into-view="scrollIntoView"
|
||||
:scroll-with-animation="scrollWithAnimation"
|
||||
:show-scrollbar="showScrollbar"
|
||||
:refresher-triggered="refreshTriggered"
|
||||
:refresher-enabled="refresherEnabled"
|
||||
:refresher-threshold="refresherThreshold"
|
||||
:refresher-background="refresherBackground"
|
||||
refresher-default-style="none"
|
||||
direction="vertical"
|
||||
@scrolltoupper="onScrollToUpper"
|
||||
@scrolltolower="onScrollToLower"
|
||||
@scroll="onScroll"
|
||||
@scrollend="onScrollEnd"
|
||||
@refresherpulling="onRefresherPulling"
|
||||
@refresherrefresh="onRefresherRefresh"
|
||||
@refresherrestore="onRefresherRestore"
|
||||
@refresherabort="onRefresherAbort"
|
||||
>
|
||||
<!-- 下拉刷新 -->
|
||||
<!-- #ifndef APP-HARMONY -->
|
||||
<view
|
||||
slot="refresher"
|
||||
class="cl-list-view__refresher"
|
||||
:class="[
|
||||
{
|
||||
'is-pulling': refresherStatus === 'pulling',
|
||||
'is-refreshing': refresherStatus === 'refreshing'
|
||||
},
|
||||
pt.refresher?.className
|
||||
]"
|
||||
:style="{
|
||||
height: refresherThreshold + 'px'
|
||||
}"
|
||||
>
|
||||
<slot name="refresher" :status="refresherStatus" :text="refresherText">
|
||||
<cl-loading
|
||||
v-if="refresherStatus === 'refreshing'"
|
||||
:size="28"
|
||||
:pt="{
|
||||
className: 'mr-2'
|
||||
}"
|
||||
></cl-loading>
|
||||
<cl-text> {{ refresherText }} </cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 列表 -->
|
||||
<view
|
||||
class="cl-list-view__virtual-list"
|
||||
:class="[pt.list?.className]"
|
||||
:style="listStyle"
|
||||
>
|
||||
<!-- 顶部占位 -->
|
||||
<view class="cl-list-view__spacer-top" :style="spacerTopStyle">
|
||||
<slot name="top"></slot>
|
||||
</view>
|
||||
|
||||
<!-- 列表项 -->
|
||||
<view
|
||||
v-for="(item, index) in visibleItems"
|
||||
:key="item.key"
|
||||
class="cl-list-view__virtual-item"
|
||||
>
|
||||
<view
|
||||
class="cl-list-view__header"
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark
|
||||
}
|
||||
]"
|
||||
:style="{
|
||||
height: headerHeight + 'px'
|
||||
}"
|
||||
v-if="item.type == 'header'"
|
||||
>
|
||||
<slot name="header" :index="item.data.index!">
|
||||
<cl-text> {{ item.data.label }} </cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<view
|
||||
v-else
|
||||
class="cl-list-view__item"
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark
|
||||
},
|
||||
pt.item?.className
|
||||
]"
|
||||
:hover-class="pt.itemHover?.className"
|
||||
:style="{
|
||||
height: virtual ? itemHeight + 'px' : 'auto'
|
||||
}"
|
||||
@tap="onItemTap(item)"
|
||||
>
|
||||
<slot
|
||||
name="item"
|
||||
:item="item"
|
||||
:data="item.data"
|
||||
:value="item.data.value"
|
||||
:index="index"
|
||||
>
|
||||
<view class="cl-list-view__item-inner">
|
||||
<cl-text> {{ item.data.label }} </cl-text>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部占位 -->
|
||||
<view class="cl-list-view__spacer-bottom" :style="spacerBottomStyle">
|
||||
<slot name="bottom"></slot>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<cl-empty v-if="noData" :fixed="false"></cl-empty>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 右侧索引栏 -->
|
||||
<cl-index-bar
|
||||
v-if="hasIndex"
|
||||
v-model="activeIndex"
|
||||
:list="indexList"
|
||||
:pt="{
|
||||
className: parseClass([pt.indexBar?.className])
|
||||
}"
|
||||
@change="onIndexChange"
|
||||
>
|
||||
</cl-index-bar>
|
||||
|
||||
<!-- 索引提示 -->
|
||||
<view
|
||||
class="cl-list-view__index"
|
||||
:class="[
|
||||
{
|
||||
'is-dark': isDark
|
||||
}
|
||||
]"
|
||||
:style="{ height: headerHeight + 'px' }"
|
||||
v-if="hasIndex"
|
||||
>
|
||||
<slot name="index" :index="indexList[activeIndex]">
|
||||
<cl-text> {{ indexList[activeIndex] }} </cl-text>
|
||||
</slot>
|
||||
</view>
|
||||
|
||||
<!-- 回到顶部 -->
|
||||
<cl-back-top :top="scrollTop" v-if="showBackTop" @back-top="scrollToTop"></cl-back-top>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, getCurrentInstance, nextTick, onMounted, ref, watch, type PropType } from "vue";
|
||||
import type {
|
||||
ClListViewItem,
|
||||
ClListViewGroup,
|
||||
ClListViewVirtualItem,
|
||||
PassThroughProps,
|
||||
ClListViewRefresherStatus
|
||||
} from "../../types";
|
||||
import { isApp, isDark, isEmpty, parseClass, parsePt } from "@/cool";
|
||||
import { t } from "@/locale";
|
||||
|
||||
defineOptions({
|
||||
name: "cl-list-view"
|
||||
});
|
||||
|
||||
defineSlots<{
|
||||
// 顶部插槽
|
||||
top(): any;
|
||||
// 分组头部插槽
|
||||
header(props: { index: string }): any;
|
||||
// 列表项插槽
|
||||
item(props: {
|
||||
data: ClListViewItem;
|
||||
item: ClListViewVirtualItem;
|
||||
value: any | null;
|
||||
index: number;
|
||||
}): any;
|
||||
// 底部插槽
|
||||
bottom(): any;
|
||||
// 索引插槽
|
||||
index(props: { index: string }): any;
|
||||
// 下拉刷新插槽
|
||||
refresher(props: { status: ClListViewRefresherStatus; text: string }): any;
|
||||
}>();
|
||||
|
||||
const props = defineProps({
|
||||
// 透传样式配置
|
||||
pt: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
// 列表数据源
|
||||
data: {
|
||||
type: Array as PropType<ClListViewItem[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 列表项高度
|
||||
itemHeight: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 分组头部高度
|
||||
headerHeight: {
|
||||
type: Number,
|
||||
default: 32
|
||||
},
|
||||
// 列表顶部预留空间高度
|
||||
topHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 列表底部预留空间高度
|
||||
bottomHeight: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
// 缓冲区大小,即可视区域外预渲染的项目数量
|
||||
bufferSize: {
|
||||
type: Number,
|
||||
default: isApp() ? 5 : 15
|
||||
},
|
||||
// 是否启用虚拟列表渲染,当数据量大时建议开启以提升性能
|
||||
virtual: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滚动到指定位置
|
||||
scrollIntoView: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
// 是否启用滚动动画
|
||||
scrollWithAnimation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示滚动条
|
||||
showScrollbar: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否启用下拉刷新
|
||||
refresherEnabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 下拉刷新触发距离,相当于下拉内容高度
|
||||
refresherThreshold: {
|
||||
type: Number,
|
||||
default: 50
|
||||
},
|
||||
// 下拉刷新区域背景色
|
||||
refresherBackground: {
|
||||
type: String,
|
||||
default: "transparent"
|
||||
},
|
||||
// 下拉刷新默认文案
|
||||
refresherDefaultText: {
|
||||
type: String,
|
||||
default: () => t("下拉刷新")
|
||||
},
|
||||
// 释放刷新文案
|
||||
refresherPullingText: {
|
||||
type: String,
|
||||
default: () => t("释放立即刷新")
|
||||
},
|
||||
// 正在刷新文案
|
||||
refresherRefreshingText: {
|
||||
type: String,
|
||||
default: () => t("加载中")
|
||||
},
|
||||
// 是否显示回到顶部按钮
|
||||
showBackTop: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
"item-tap",
|
||||
"refresher-pulling",
|
||||
"refresher-refresh",
|
||||
"refresher-restore",
|
||||
"refresher-abort",
|
||||
"scrolltoupper",
|
||||
"scrolltolower",
|
||||
"scroll",
|
||||
"scrollend",
|
||||
"pull",
|
||||
"top",
|
||||
"bottom"
|
||||
]);
|
||||
|
||||
// 获取当前组件实例,用于后续DOM操作
|
||||
const { proxy } = getCurrentInstance()!;
|
||||
|
||||
// 透传样式配置类型
|
||||
type PassThrough = {
|
||||
className?: string;
|
||||
item?: PassThroughProps;
|
||||
itemHover?: PassThroughProps;
|
||||
list?: PassThroughProps;
|
||||
indexBar?: PassThroughProps;
|
||||
scroller?: PassThroughProps;
|
||||
refresher?: PassThroughProps;
|
||||
};
|
||||
|
||||
// 解析透传样式配置
|
||||
const pt = computed(() => parsePt<PassThrough>(props.pt));
|
||||
|
||||
// 当前激活的索引位置,用于控制索引栏的高亮状态
|
||||
const activeIndex = ref(0);
|
||||
|
||||
// 是否没有数据
|
||||
const noData = computed(() => {
|
||||
return isEmpty(props.data);
|
||||
});
|
||||
|
||||
// 是否包含索引
|
||||
const hasIndex = computed(() => {
|
||||
return props.data.every((e) => e.index != null) && !noData.value;
|
||||
});
|
||||
|
||||
// 计算属性:将原始数据按索引分组
|
||||
const data = computed<ClListViewGroup[]>(() => {
|
||||
// 初始化分组数组
|
||||
const group: ClListViewGroup[] = [];
|
||||
|
||||
// 遍历原始数据,按index字段进行分组
|
||||
props.data.forEach((item) => {
|
||||
// 查找是否已存在相同index的分组
|
||||
const index = group.findIndex((group) => group.index == item.index);
|
||||
|
||||
if (index != -1) {
|
||||
// 如果分组已存在,将当前项添加到该分组的列表中
|
||||
group[index].children.push(item);
|
||||
} else {
|
||||
// 如果分组不存在,创建新的分组
|
||||
group.push({
|
||||
index: item.index ?? "",
|
||||
children: [item]
|
||||
} as ClListViewGroup);
|
||||
}
|
||||
});
|
||||
|
||||
return group;
|
||||
});
|
||||
|
||||
// 计算属性:提取所有分组的索引列表,用于索引栏显示
|
||||
const indexList = computed<string[]>(() => {
|
||||
return data.value.map((item) => item.index);
|
||||
});
|
||||
|
||||
// 计算属性:将分组数据扁平化为虚拟列表项数组
|
||||
const virtualItems = computed<ClListViewVirtualItem[]>(() => {
|
||||
// 初始化虚拟列表数组
|
||||
const items: ClListViewVirtualItem[] = [];
|
||||
|
||||
// 初始化顶部位置,考虑预留空间
|
||||
let top = props.topHeight;
|
||||
// 初始化索引计数器
|
||||
let index = 0;
|
||||
|
||||
// 遍历每个分组,生成虚拟列表项
|
||||
data.value.forEach((group, groupIndex) => {
|
||||
if (group.index != "") {
|
||||
// 添加分组头部项
|
||||
items.push({
|
||||
key: `header-${groupIndex}`,
|
||||
type: "header",
|
||||
index: index++,
|
||||
top,
|
||||
height: props.headerHeight,
|
||||
data: {
|
||||
label: group.index!,
|
||||
index: group.index
|
||||
}
|
||||
});
|
||||
// 更新top位置
|
||||
top += props.headerHeight;
|
||||
}
|
||||
|
||||
// 添加分组内的所有列表项
|
||||
group.children.forEach((item, itemIndex) => {
|
||||
items.push({
|
||||
key: `item-${groupIndex}-${itemIndex}`,
|
||||
type: "item",
|
||||
index: index++,
|
||||
top,
|
||||
height: props.itemHeight,
|
||||
data: item
|
||||
});
|
||||
// 更新top位置
|
||||
top += props.itemHeight;
|
||||
});
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
// 计算属性:计算整个列表的总高度
|
||||
const listHeight = computed<number>(() => {
|
||||
return (
|
||||
// 所有项目高度之和
|
||||
virtualItems.value.reduce((total, item) => total + item.height, 0) +
|
||||
// 加上顶部预留空间高度
|
||||
props.topHeight +
|
||||
// 加上底部预留空间高度
|
||||
props.bottomHeight
|
||||
);
|
||||
});
|
||||
|
||||
// 当前滚动位置
|
||||
const scrollTop = ref(0);
|
||||
|
||||
// 目标滚动位置,用于控制滚动到指定位置
|
||||
const targetScrollTop = ref(0);
|
||||
|
||||
// 滚动容器的高度
|
||||
const scrollerHeight = ref(0);
|
||||
|
||||
// 计算属性:获取当前可见区域的列表项
|
||||
const visibleItems = computed<ClListViewVirtualItem[]>(() => {
|
||||
// 如果虚拟列表为空,返回空数组
|
||||
if (isEmpty(virtualItems.value)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 如果未启用虚拟列表,直接返回所有项目
|
||||
if (!props.virtual) {
|
||||
return virtualItems.value;
|
||||
}
|
||||
|
||||
// 计算缓冲区高度
|
||||
const bufferHeight = props.bufferSize * props.itemHeight;
|
||||
// 计算可视区域的顶部位置(包含缓冲区)
|
||||
const viewportTop = scrollTop.value - bufferHeight;
|
||||
// 计算可视区域的底部位置(包含缓冲区)
|
||||
const viewportBottom = scrollTop.value + scrollerHeight.value + bufferHeight;
|
||||
|
||||
// 初始化可见项目数组
|
||||
const visible: ClListViewVirtualItem[] = [];
|
||||
|
||||
// 使用二分查找优化查找起始位置
|
||||
let startIndex = 0;
|
||||
let endIndex = virtualItems.value.length - 1;
|
||||
|
||||
// 二分查找第一个可见项目的索引
|
||||
while (startIndex < endIndex) {
|
||||
const mid = Math.floor((startIndex + endIndex) / 2);
|
||||
const item = virtualItems.value[mid];
|
||||
|
||||
if (item.top + item.height <= viewportTop) {
|
||||
startIndex = mid + 1;
|
||||
} else {
|
||||
endIndex = mid;
|
||||
}
|
||||
}
|
||||
|
||||
// 从找到的起始位置开始,收集所有可见项目
|
||||
for (let i = startIndex; i < virtualItems.value.length; i++) {
|
||||
const item = virtualItems.value[i];
|
||||
|
||||
// 如果项目完全超出视口下方,停止收集
|
||||
if (item.top >= viewportBottom) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 如果项目与视口有交集,添加到可见列表
|
||||
if (item.top + item.height > viewportTop) {
|
||||
visible.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
});
|
||||
|
||||
// 计算属性:计算上方占位容器的高度
|
||||
const spacerTopHeight = computed<number>(() => {
|
||||
// 如果没有可见项目,返回0
|
||||
if (isEmpty(visibleItems.value)) {
|
||||
return 0;
|
||||
}
|
||||
// 返回第一个可见项目的顶部位置
|
||||
return visibleItems.value[0].top;
|
||||
});
|
||||
|
||||
// 计算属性:计算下方占位容器的高度
|
||||
const spacerBottomHeight = computed<number>(() => {
|
||||
// 如果没有可见项目,返回0
|
||||
if (isEmpty(visibleItems.value)) {
|
||||
return 0;
|
||||
}
|
||||
// 获取最后一个可见项目
|
||||
const lastItem = visibleItems.value[visibleItems.value.length - 1];
|
||||
// 计算下方占位高度
|
||||
return listHeight.value - (lastItem.top + lastItem.height);
|
||||
});
|
||||
|
||||
// 列表样式
|
||||
const listStyle = computed(() => {
|
||||
return {
|
||||
height: props.virtual ? `${listHeight.value}px` : "auto"
|
||||
};
|
||||
});
|
||||
|
||||
// 上方占位容器样式
|
||||
const spacerTopStyle = computed(() => {
|
||||
return {
|
||||
height: props.virtual ? `${spacerTopHeight.value}px` : "auto"
|
||||
};
|
||||
});
|
||||
|
||||
// 下方占位容器样式
|
||||
const spacerBottomStyle = computed(() => {
|
||||
return {
|
||||
height: props.virtual ? `${spacerBottomHeight.value}px` : "auto"
|
||||
};
|
||||
});
|
||||
|
||||
// 存储每个分组头部距离顶部的位置数组
|
||||
const tops = ref<number[]>([]);
|
||||
|
||||
// 计算并更新所有分组头部的位置
|
||||
function getTops() {
|
||||
// 初始化一个空数组
|
||||
const arr = [] as number[];
|
||||
|
||||
// 初始化顶部位置
|
||||
let top = 0;
|
||||
|
||||
// 计算每个分组的顶部位置
|
||||
data.value.forEach((group) => {
|
||||
// 将当前分组头部的位置添加到数组中
|
||||
arr.push(top);
|
||||
|
||||
// 累加当前分组的总高度(头部高度+所有项目高度)
|
||||
top += props.headerHeight + group.children.length * props.itemHeight;
|
||||
});
|
||||
|
||||
tops.value = arr;
|
||||
}
|
||||
|
||||
// 下拉刷新触发标志
|
||||
const refreshTriggered = ref(false);
|
||||
|
||||
// 下拉刷新相关状态
|
||||
const refresherStatus = ref<"default" | "pulling" | "refreshing">("default");
|
||||
|
||||
// 下拉刷新文案
|
||||
const refresherText = computed(() => {
|
||||
switch (refresherStatus.value) {
|
||||
case "pulling":
|
||||
return props.refresherPullingText;
|
||||
case "refreshing":
|
||||
return props.refresherRefreshingText;
|
||||
default:
|
||||
return props.refresherDefaultText;
|
||||
}
|
||||
});
|
||||
|
||||
// 停止下拉刷新
|
||||
function stopRefresh() {
|
||||
refreshTriggered.value = false;
|
||||
refresherStatus.value = "default";
|
||||
}
|
||||
|
||||
// 滚动到顶部事件处理函数
|
||||
function onScrollToUpper(e: UniScrollToUpperEvent) {
|
||||
emit("scrolltoupper", e);
|
||||
emit("top");
|
||||
}
|
||||
|
||||
// 滚动到底部事件处理函数
|
||||
function onScrollToLower(e: UniScrollToLowerEvent) {
|
||||
emit("scrolltolower", e);
|
||||
emit("bottom");
|
||||
}
|
||||
|
||||
// 滚动锁定标志,用于防止滚动时触发不必要的计算
|
||||
let scrollLock = false;
|
||||
|
||||
// 滚动事件处理函数
|
||||
function onScroll(e: UniScrollEvent) {
|
||||
// 更新当前滚动位置
|
||||
scrollTop.value = Math.floor(e.detail.scrollTop);
|
||||
|
||||
// 如果滚动被锁定,直接返回
|
||||
if (scrollLock) return;
|
||||
|
||||
// 根据滚动位置自动更新激活的索引
|
||||
tops.value.forEach((top, index) => {
|
||||
if (scrollTop.value >= top) {
|
||||
activeIndex.value = index;
|
||||
}
|
||||
});
|
||||
|
||||
emit("scroll", e);
|
||||
}
|
||||
|
||||
// 滚动结束事件处理函数
|
||||
function onScrollEnd(e: UniScrollEvent) {
|
||||
emit("scrollend", e);
|
||||
}
|
||||
|
||||
// 行点击事件处理函数
|
||||
function onItemTap(item: ClListViewVirtualItem) {
|
||||
emit("item-tap", item.data);
|
||||
}
|
||||
|
||||
// 索引栏点击事件处理函数
|
||||
function onIndexChange(index: number) {
|
||||
// 锁定滚动,防止触发不必要的计算
|
||||
scrollLock = true;
|
||||
|
||||
// 设置目标滚动位置为对应分组头部的位置
|
||||
targetScrollTop.value = tops.value[index];
|
||||
|
||||
// 300ms后解除滚动锁定
|
||||
setTimeout(() => {
|
||||
scrollLock = false;
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// 下拉刷新事件处理函数
|
||||
function onRefresherPulling(e: UniRefresherEvent) {
|
||||
if (e.detail.dy > props.refresherThreshold) {
|
||||
refresherStatus.value = "pulling";
|
||||
}
|
||||
emit("refresher-pulling", e);
|
||||
}
|
||||
|
||||
// 下拉刷新事件处理函数
|
||||
function onRefresherRefresh(e: UniRefresherEvent) {
|
||||
refresherStatus.value = "refreshing";
|
||||
refreshTriggered.value = true;
|
||||
emit("refresher-refresh", e);
|
||||
emit("pull", e);
|
||||
}
|
||||
|
||||
// 恢复下拉刷新
|
||||
function onRefresherRestore(e: UniRefresherEvent) {
|
||||
refresherStatus.value = "default";
|
||||
emit("refresher-restore", e);
|
||||
}
|
||||
|
||||
// 停止下拉刷新
|
||||
function onRefresherAbort(e: UniRefresherEvent) {
|
||||
refresherStatus.value = "default";
|
||||
emit("refresher-abort", e);
|
||||
}
|
||||
|
||||
// 滚动到顶部
|
||||
function scrollToTop() {
|
||||
targetScrollTop.value = 0.01;
|
||||
|
||||
nextTick(() => {
|
||||
targetScrollTop.value = 0;
|
||||
});
|
||||
}
|
||||
|
||||
// 获取滚动容器的高度
|
||||
function getScrollerHeight() {
|
||||
setTimeout(() => {
|
||||
uni.createSelectorQuery()
|
||||
.in(proxy)
|
||||
.select(".cl-list-view__scroller")
|
||||
.boundingClientRect()
|
||||
.exec((res) => {
|
||||
if (isEmpty(res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置容器高度
|
||||
scrollerHeight.value = (res[0] as NodeInfo).height ?? 0;
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// 组件挂载后的初始化逻辑
|
||||
onMounted(() => {
|
||||
// 获取容器高度
|
||||
getScrollerHeight();
|
||||
|
||||
// 监听数据变化,重新计算位置信息
|
||||
watch(
|
||||
computed(() => props.data),
|
||||
() => {
|
||||
getTops();
|
||||
},
|
||||
{
|
||||
// 立即执行一次
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
data,
|
||||
stopRefresh
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cl-list-view {
|
||||
@apply h-full w-full relative;
|
||||
|
||||
&__scroller {
|
||||
@apply h-full w-full;
|
||||
}
|
||||
|
||||
&__virtual-list {
|
||||
@apply relative w-full;
|
||||
}
|
||||
|
||||
&__spacer-top,
|
||||
&__spacer-bottom {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
&__index {
|
||||
@apply flex flex-row items-center bg-white;
|
||||
@apply absolute top-0 left-0 w-full px-[20rpx] z-20;
|
||||
|
||||
&.is-dark {
|
||||
@apply bg-surface-600 border-none;
|
||||
}
|
||||
}
|
||||
|
||||
&__virtual-item {
|
||||
@apply w-full;
|
||||
}
|
||||
|
||||
&__header {
|
||||
@apply flex flex-row items-center relative px-[20rpx] z-10;
|
||||
}
|
||||
|
||||
&__item {
|
||||
&-inner {
|
||||
@apply flex flex-row items-center px-[20rpx] h-full;
|
||||
}
|
||||
}
|
||||
|
||||
&__refresher {
|
||||
@apply flex flex-row items-center justify-center w-full h-full;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,33 @@
|
||||
import type { ClListViewItem, ClListViewGroup, ClListViewVirtualItem, PassThroughProps, ClListViewRefresherStatus } from "../../types";
|
||||
|
||||
export type ClListViewPassThrough = {
|
||||
className?: string;
|
||||
item?: PassThroughProps;
|
||||
itemHover?: PassThroughProps;
|
||||
list?: PassThroughProps;
|
||||
indexBar?: PassThroughProps;
|
||||
scroller?: PassThroughProps;
|
||||
refresher?: PassThroughProps;
|
||||
};
|
||||
|
||||
export type ClListViewProps = {
|
||||
className?: string;
|
||||
pt?: ClListViewPassThrough;
|
||||
data?: ClListViewItem[];
|
||||
itemHeight?: number;
|
||||
headerHeight?: number;
|
||||
topHeight?: number;
|
||||
bottomHeight?: number;
|
||||
bufferSize?: number;
|
||||
virtual?: boolean;
|
||||
scrollIntoView?: string;
|
||||
scrollWithAnimation?: boolean;
|
||||
showScrollbar?: boolean;
|
||||
refresherEnabled?: boolean;
|
||||
refresherThreshold?: number;
|
||||
refresherBackground?: string;
|
||||
refresherDefaultText?: string;
|
||||
refresherPullingText?: string;
|
||||
refresherRefreshingText?: string;
|
||||
showBackTop?: boolean;
|
||||
};
|
||||
Reference in New Issue
Block a user