qb 1 year ago
parent
commit
7d83d68a2b
  1. 14
      uni_modules/jp-virtual-list/changelog.md
  2. 855
      uni_modules/jp-virtual-list/components/jp-virtual-list/jp-virtual-list.vue
  3. 76
      uni_modules/jp-virtual-list/components/jp-virtual-list/loading.vue
  4. BIN
      uni_modules/jp-virtual-list/components/jp-virtual-list/top.png
  5. 78
      uni_modules/jp-virtual-list/package.json
  6. 94
      uni_modules/jp-virtual-list/pages/index/index.vue
  7. 142
      uni_modules/jp-virtual-list/readme.md

14
uni_modules/jp-virtual-list/changelog.md

@ -0,0 +1,14 @@
## 2.0.1(2023-10-25)
修复无法点击元素的bug
## 2.0.0(2023-10-24)
实现兼容微信小程序,app,H5。实现滚动到指定位置,实现指定滚动方向
## 2.0(2023-10-24)
实现滚动到指定元素,实现滚动方向
## 1.2.1(2023-08-23)
更新说明
## 1.2(2023-08-18)
更新说明
## 1.1(2023-08-18)
支持下拉刷新,回到顶部
## 1.0(2023-08-17)
上线

855
uni_modules/jp-virtual-list/components/jp-virtual-list/jp-virtual-list.vue

@ -0,0 +1,855 @@
<template>
<view ref="listtop" id="listtop">
<scroll-view :scroll-top="scrollTop" :scroll-y="scrollY" class="scroll-view scroll-Y" @scrolltoupper="upper"
@scrolltolower="lower" @scroll="scroll" :scroll-into-view="scrollView" :lower-threshold="30"
id="scroll-view">
<view class="scrolls">
<view v-if="refresherm && JzscrollTop>10">
<view v-if="triggeredType==0" :class="direction=='bottom'?'refreshermB':'refresherm'"
:style="'height:' + JzscrollTop + 'px'">{{direction=='bottom'?'下拉刷新':'上拉刷新'}}</view>
<view v-if="triggeredType==1" :class="direction=='bottom'?'refreshermB':'refresherm'"
:style="'height:' + JzscrollTop + 'px'">松开刷新</view>
<view v-if="triggeredType==2" :class="direction=='bottom'?'refreshermB':'refresherm'"
:style="'height:' + JzscrollTop + 'px'">
<loading></loading>
</view>
<view v-if="triggeredType==3" :class="direction=='bottom'?'refreshermB':'refresherm'"
:style="'height:' + JzscrollTop + 'px'">刷新成功</view>
</view>
<view ref="listx" id="listx" @touchstart="touchstart3" @touchmove="touchmove3" @touchend="touchend3">
<view class="ball" v-if="direction=='bottom'" :style="'height:' + heights + 'px'"></view>
<view v-if="list2 && list2.length > 0" ref="listItem" id="listItem">
<slot :list="list2"></slot>
</view>
<view class="ball" v-if="direction=='top'" :style="'height:' + heights + 'px'"></view>
</view>
</view>
</scroll-view>
<div class="_backtop" v-if="isBackTop && direction=='bottom'" @click.stop="toTop">
<view v-if="old.scrollTop > 100">
<transition name="fade" appear>
<div class="top">
<image src="./top.png"></image>
</div>
</transition>
</view>
</div>
</view>
</template>
<!--
现有问题如下:
1因为小程序局限问题移动到端指定元素无法使用组件的形式且小程序需要定义滚动组件的高度所以建议小程序复制代码后操作
2当用户疯狂滑动页面可能会导致页面白屏问题
-->
<script>
import loading from './loading.vue'
let observer = null;
let scrollTop1 = null;
let scrollTop2 = null;
export default {
components: {
loading
},
data() {
return {
scrollY: true,
scrollClientY: 0,
JzscrollTop: 0,
refresherm: false,
triggeredType: 0,
scrollTop: 99999,
old: {
scrollTop: 0
},
list2: [],
clientHeight: 0,
heights: 0,
heightt: 0,
lowers: true,
copyList: [],
listPage: [],
page: 0,
scrollView: '',
noLower: true,
virtualHeight: 0
};
},
props: {
direction: {
// 20
type: String,
default: 'bottom'
},
refresher: {
//
type: Boolean,
default: true
},
pageSizes: {
// 20
type: Number,
default: 20
},
code: {
// 20
type: String,
default: 'id'
},
data: {
//
type: Array,
default () {
return [];
}
},
isBackTop: {
//
type: Boolean,
default: false
},
},
onUnload() {
if (observer) {
observer.disconnect()
}
},
created() {
if (this.direction == 'top') {
this.list = JSON.parse(JSON.stringify(this.data));
this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
this.getList();
} else {
this.scrollTop = 0
this.list = JSON.parse(JSON.stringify(this.data));
this.listPage = this.splitArrayIntoGroups2(this.list, this.pageSizes);
this.list2 = this.listPage[0]
}
},
mounted() {
observer = uni.createIntersectionObserver(this);
observer.relativeTo('.scroll-view', {
bottom: 100
}).observe('.ball', (res) => {
if (res.intersectionRatio > 0) {
let that = this
if (this.page > 0 && this.heights > 0) {
if (this.direction == 'top') {
that.scrollY = false
this.page--;
this.list2.splice(0, this.pageSizes);
this.list2 = [...this.list2, ...this.listPage[this.page - 1]];
this.$nextTick(() => {
this.heights = this.heightList[this.page - 1];
that.scrollY = true
})
} else {
that.scrollY = false
this.page--;
this.list2.splice(this.list2.length - this.pageSizes, that.pageSizes);
this.list2 = [...this.listPage[this.page - 1], ...this.list2]
this.$nextTick(() => {
this.heights = this.heightList[this.page - 1];
that.scrollY = true
})
}
}
}
})
// #ifdef MP-WEIXIN || APP
const query = uni.createSelectorQuery().in(this);
query.select('#listtop').boundingClientRect(data => {
this.clientHeight = data.height
}).exec();
// #endif
// #ifdef H5
this.clientHeight = this.$refs.listtop.$el.clientHeight
// #endif
},
watch: {
data() {
//
if (this.direction == 'top') {
this.page = 0;
this.heights = 0;
this.heightList = [0];
this.list = JSON.parse(JSON.stringify(this.data));
this.listPage = this.splitArrayIntoGroups(this.list, this.pageSizes);
this.getList();
} else {
this.page = 0;
this.heights = 0;
this.heightList = [0];
this.list = JSON.parse(JSON.stringify(this.data));
this.listPage = this.splitArrayIntoGroups2(this.list, this.pageSizes);
this.list2 = this.listPage[0]
}
}
},
methods: {
toTop() {
if (this.direction == 'top') {
} else {
let that = this
let scrollTops = this.old.scrollTop
const duration = 1000 // duration 1000ms
const easing = (t) => t * (2 - t) //
let start = scrollTops //
let end = 0 //
let change = end - start //
let currentTime = 0 //
//
const animateScroll = function() {
currentTime += 16 // 16ms
let val = easing(currentTime / duration) * change + start
that.scrollTop = val
if (currentTime < duration) {
setTimeout(animateScroll, 16)
}
}
animateScroll()
}
},
addPage(list) {
if (this.direction == 'top') {
let id = list[0][this.code]
let li = list.reverse()
this.listPage.push(li)
this.upper()
this.scrollView = null
let that = this
this.$nextTick(function() {
this.scrollView = id
});
} else {
let id = list[0][this.code]
let li = list
this.listPage.push(li)
this.lower()
this.scrollView = null
let that = this
this.$nextTick(function() {
this.scrollView = id
});
}
},
async toAssign(val, secl) {
this.noLower = false
let that = this
const query2 = uni.createSelectorQuery().in(this);
let obj = null
for (var i = 0; i < this.listPage.length; i++) {
let list = this.listPage[i]
for (var j = 0; j < list.length; j++) {
let item = list[j]
if (item[this.code] == val) {
obj = {
page: i + 1,
index: j,
id: val
}
break
}
}
if (obj) {
break
}
}
// dom
if (this.list2.some(el => {
return el[this.code] == val
})) {
this.scrollView = null
this.$nextTick(function() {
this.scrollView = val
});
} else {
// page
//
if (this.page < obj.page) {
let c = (obj.page + this.page) >= this.listPage.length ? (obj.page - this.page - 1) : (obj
.page - this.page)
if (this.heightList[this.page + c - 1]) {
if (this.direction == 'top') {
this.list2 = [...this.listPage[this.page + c], ...this.listPage[this.page + c - 1]]
} else {
this.list2 = [...this.listPage[this.page + c - 1], ...this.listPage[this.page + c]]
}
this.heights = this.heightList[this.page + c - 1];
this.scrollView = ''
this.page = this.page + c
this.$nextTick(function() {
this.scrollView = val
// #ifdef MP-WEIXIN
const query = uni.createSelectorQuery().in(secl);
let id = '#' + val
query.select(id).boundingClientRect(data => {
if (data.top) {
that.scrollTop = data.top
}
}).exec();
// #endif
});
} else {
let pasc = that.page
for (let i = 0; i < c; i++) {
pasc++
let a = await this.toUpper(pasc)
}
this.scrollView = ''
this.$nextTick(function() {
this.scrollView = val
setTimeout(() => {
that.noLower = true
}, 1000)
// #ifdef MP-WEIXIN
const query = uni.createSelectorQuery().in(secl);
let id = '#' + val
query.select(id).boundingClientRect(data => {
if (data.top) {
that.scrollTop = data.top
}
}).exec();
// #endif
});
}
} else {
let c = this.page - obj.page
for (let i = 0; i < c; i++) {
let a = await this.tolower()
}
this.scrollView = ''
this.$nextTick(function() {
this.scrollView = val
setTimeout(() => {
that.noLower = true
}, 1000)
// #ifdef MP-WEIXIN
const query = uni.createSelectorQuery().in(secl);
let id = '#' + val
query.select(id).boundingClientRect(data => {
if (data.top) {
that.scrollTop = data.top
}
}).exec();
// #endif
});
}
}
},
tolower: function() { //
return new Promise(resolve => {
let that = this
if (this.direction == 'top') {
this.page--;
this.list2.splice(0, this.pageSizes);
this.list2 = [...this.list2, ...this.listPage[this.page - 1]];
this.$nextTick(() => {
this.heights = this.heightList[this.page - 1];
resolve()
})
} else {
this.page--;
this.list2.splice(this.list2.length - this.pageSizes, this.pageSizes);
this.list2 = [...this.listPage[this.page - 1], ...this.list2]
this.$nextTick(() => {
this.heights = this.heightList[this.page - 1];
resolve()
})
}
})
},
toUpper: function(pasc) { //
return new Promise(resolve => {
let that = this
if (this.direction == 'top') {
if (this.listPage.length - 1 > this.page) {
this.scrollTop = this.old.scrollTop
this.$nextTick(function() {
this.scrollTop = 100
});
// #ifdef MP-WEIXIN || APP
const query = uni.createSelectorQuery().in(this);
query.select('#listx').boundingClientRect(data => {
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(data.height);
}
this.list2 = [...this.listPage[this.page], ...this.list2]
this.$nextTick(() => {
if (this.list2.length > this.pageSizes * 2) {
this.list2.splice(this.list2.length - this.pageSizes,
that.pageSizes);
this.heights = this.heightList[this.page - 1];
}
setTimeout(() => {
resolve(1)
}, 80)
})
}).exec();
// #endif
// #ifdef H5
let container = this.$refs.listx.$el
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(container.clientHeight);
}
this.list2 = [...this.listPage[this.page], ...this.list2]
this.$nextTick(() => {
if (that.list2.length > that.pageSizes * 2) {
that.list2.splice(that.list2.length - that.pageSizes, that
.pageSizes);
that.heights = that.heightList[that.page - 1];
}
setTimeout(() => {
resolve(1)
}, 80)
})
// #endif
} else {
resolve(1)
}
} else {
if (this.listPage.length - 1 > this.page) {
this.scrollTop = this.old.scrollTop
// #ifdef MP-WEIXIN || APP
this.$nextTick(function() {
that.page = pasc
const query = uni.createSelectorQuery().in(this);
query.select('#listx').boundingClientRect(data => {
if (!that.heightList[that.page]) {
that.heightList.push(data.height);
}
that.list2 = [...that.list2, ...that.listPage[that.page]]
that.$nextTick(() => {
if (that.list2.length > (that.listPage[that
.page].length + that.listPage[that
.page - 1].length)) {
that.list2.splice(0, that.pageSizes);
that.heights = that.heightList[that.page -
1];
}
setTimeout(() => {
resolve(1)
}, 80)
})
}).exec();
});
// #endif
// #ifdef H5
let container = this.$refs.listx.$el
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(container.clientHeight);
}
this.list2 = [...this.list2, ...this.listPage[this.page]]
that.scrollTop = 0
this.$nextTick(() => {
//
if (that.list2.length > (that.listPage[this.page].length + that
.listPage[this.page - 1].length)) {
that.list2.splice(0, this.pageSizes);
that.heights = that.heightList[that.page - 1];
that.scrollTop = 999999
}
setTimeout(() => {
resolve(1)
}, 80)
})
// #endif
} else {
resolve(1)
}
}
})
},
//
refreshers() {
if (this.direction == 'top') {
this.triggeredType = 3
let that = this
setTimeout(() => {
that.refresherm = false
that.JzscrollTop = 0
that.goTop()
that.lowers = true
that.scrollY = true
}, 500)
} else {
this.triggeredType = 3
let that = this
setTimeout(() => {
that.refresherm = false
that.JzscrollTop = 0
that.lowers = true
that.scrollY = true
}, 500)
}
},
touchend3(e) { //
if (this.direction == 'top') {
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){
this.touchend(e)
}
}else{
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){
this.touchend2(e)
}
}
},
touchstart3(e) { //使
if (this.direction == 'top') {
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){
this.touchstart(e)
}
}else{
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){
this.touchstart2(e)
}
}
},
touchmove3(e) { //preventDefault()
if (this.direction == 'top') {
if(this.heights == 0 && (this.scrollTop == 99999 || this.lowers)){
this.touchmove(e)
}
}else{
if(this.heights == 0 && (this.old.scrollTop == 0 || this.lowers)){
this.touchmove2(e)
}
}
},
//
touchend(e) { //
if (this.heights == 0 && this.refresherm) {
if (this.JzscrollTop >= 80) {
this.triggeredType = 2
this.JzscrollTop = 80
this.$emit('refresher')
} else {
this.triggeredType = 0
this.JzscrollTop = 0
this.scrollY = true
}
} else {
this.scrollY = true
this.JzscrollTop = 0
}
},
touchstart(e) { //使
console.log(this.refresher)
if (this.refresher && this.heights == 0 && (this.scrollTop == 99999 || this.lowers)) {
this.refresherm = true
let pas = e.changedTouches[0]
this.scrollClientY = pas.pageY
this.JzscrollTop = 0
}
},
touchmove(e) { //preventDefault()
if (this.heights == 0 && this.refresherm && this.triggeredType != 2) {
let pas = e.changedTouches[0]
if (this.scrollClientY > pas.pageY) {
this.scrollY = false
//
let y = (this.scrollClientY - pas.pageY)
this.JzscrollTop = y > 150 ? 150 : y
if (this.JzscrollTop >= 100) {
this.triggeredType = 1
} else {
this.triggeredType = 0
}
this.$forceUpdate()
}
}
},
//
touchend2(e) { //
if (this.heights == 0 && this.refresherm) {
if (this.JzscrollTop >= 80) {
this.triggeredType = 2
this.JzscrollTop = 80
this.$emit('refresher')
} else {
this.triggeredType = 0
this.JzscrollTop = 0
this.scrollY = true
}
} else {
this.scrollY = true
this.JzscrollTop = 0
}
},
touchstart2(e) { //使
if (this.refresher && this.heights == 0 && (this.scrollTop == 0 || this.lowers)) {
this.refresherm = true
let pas = e.changedTouches[0]
this.scrollClientY = pas.pageY
this.JzscrollTop = 0
}
},
touchmove2(e) { //preventDefault()
if (this.heights == 0 && this.refresherm && this.triggeredType != 2) {
let pas = e.changedTouches[0]
if (this.scrollClientY < pas.pageY) {
this.scrollY = false
//
let y = (pas.pageY - this.scrollClientY)
this.JzscrollTop = y > 150 ? 150 : y
if (this.JzscrollTop >= 100) {
this.triggeredType = 1
} else {
this.triggeredType = 0
}
this.$forceUpdate()
}
}
},
//
upper: function(e) { //
if (!this.noLower) {
return
}
if (this.direction == 'top') {
if (this.listPage.length - 1 > this.page) {
console.log('到达顶部,开始加载数据')
let that = this
// #ifdef MP-WEIXIN || APP
const query = uni.createSelectorQuery().in(this);
query.select('#listx').boundingClientRect(data => {
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(data.height);
}
this.list2 = [...this.listPage[this.page], ...this.list2]
// #ifndef APP
this.$nextTick(() => {
if (this.list2.length > this.pageSizes * 2) {
this.list2.splice(this.list2.length - this.pageSizes, that
.pageSizes);
this.heights = this.heightList[this.page - 1];
}
})
// #endif
// #ifndef MP-WEIXIN
setTimeout(() => {
if (this.list2.length > this.pageSizes * 2) {
this.list2.splice(this.list2.length - this.pageSizes, that
.pageSizes);
this.heights = this.heightList[this.page - 1];
}
}, 80)
// #endif
}).exec();
// #endif
// #ifdef H5
let container = this.$refs.listx.$el
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(container.clientHeight);
}
console.log(this.heightList, this.listPage)
this.list2 = [...this.listPage[this.page], ...this.list2]
this.$nextTick(() => {
//
if (that.list2.length > (that.listPage[this.page].length + that.listPage[this
.page - 1].length)) {
that.list2.splice(that.list2.length - that.pageSizes, that.pageSizes);
that.heights = that.heightList[that.page - 1];
}
})
// #endif
} else {
this.$emit('scrolltoupper', this.page)
}
} else {
this.lowers = true
}
},
lower: function(e) { //
if (!this.noLower) {
return
}
if (this.direction == 'top') {
this.lowers = true
} else {
if (this.noLower && this.listPage.length - 1 > this.page) {
console.log('到达底部,开始加载数据')
let that = this
// #ifdef MP-WEIXIN || APP
const query = uni.createSelectorQuery().in(this);
query.select('#listx').boundingClientRect(data => {
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(data.height);
}
this.list2 = [...this.list2, ...this.listPage[this.page]]
// #ifndef APP
this.$nextTick(() => {
if (this.list2.length > this.pageSizes * 2) {
this.list2.splice(0, this.pageSizes);
this.heights = this.heightList[this.page - 1];
}
})
// #endif
// #ifndef MP-WEIXIN
setTimeout(() => {
if (this.list2.length > this.pageSizes * 2) {
this.list2.splice(0, this.pageSizes);
this.heights = this.heightList[this.page - 1];
}
}, 80)
// #endif
}).exec();
// #endif
// #ifdef H5
let container = this.$refs.listx.$el
this.page++
if (!this.heightList[this.page]) {
this.heightList.push(container.clientHeight);
}
this.list2 = [...this.list2, ...this.listPage[this.page]]
this.$nextTick(() => {
//
if (that.list2.length > (that.listPage[this.page].length + that.listPage[this
.page - 1].length)) {
that.list2.splice(0, this.pageSizes);
that.heights = that.heightList[that.page - 1];
}
})
// #endif
} else {
this.$emit('scrolltoupper', this.page)
}
}
},
scroll: function(e) { //
let that = this
if (e.detail.scrollHeight > (e.detail.scrollTop + this.clientHeight + 200)) {
this.lowers = false
}
this.old.scrollTop = e.detail.scrollTop
},
goTop: function(e) {
// view
this.scrollTop = this.old.scrollTop
this.$nextTick(function() {
this.scrollTop = 999999
});
},
// *********** ***************
splitArrayIntoGroups(array, groupSize) {
//
const groups = [];
for (let i = 0; i < array.length; i += groupSize) {
groups.push(array.slice(i, i + groupSize).reverse());
}
return groups;
},
splitArrayIntoGroups2(array, groupSize) {
const groups = [];
for (let i = 0; i < array.length; i += groupSize) {
groups.push(array.slice(i, i + groupSize));
}
return groups;
},
getList() {
this.list2 = this.listPage[0]
this.$nextTick(function() {
this.goTop()
});
}
}
};
</script>
<style lang="scss" scoped>
._backtop {
position: absolute;
z-index: 1;
right: 30rpx;
bottom: 50px;
.top {
width: 60px;
height: 30px;
box-shadow: 0.3rem 0.3rem 0.6rem rgba(0, 0, 0, .3), -0.2rem -0.2rem 0.5rem rgba(255, 255, 255, .3);
background: #fff;
border-radius: 50%;
padding: 15px 0;
text-align: center;
image {
width: 30px;
height: 30px;
}
}
/* 过渡类名 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.7s;
}
.fade-enter,
.fade-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
}
.list_item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f5f5f5;
}
.scrolls {
position: relative;
}
.refreshermB {
text-align: center;
line-height: 60px;
height: 0px;
color: #999;
background-color: #fff;
}
.refresherm {
text-align: center;
position: absolute;
bottom: 0;
left: 0;
right: 0;
line-height: 60px;
height: 0px;
color: #999;
z-index: 99;
background-color: #fff;
}
.scroll-Y {
/* #ifndef MP-WEIXIN */
height: 100%;
/* #endif */
/* #ifdef MP-WEIXIN */
height: calc(100vh - 80px);
/* #endif */
position: relative;
}
</style>

76
uni_modules/jp-virtual-list/components/jp-virtual-list/loading.vue

@ -0,0 +1,76 @@
<template>
<div class="template">
<view class="cartoon" >
<span class="char"></span>
<span class="char"></span>
<span class="char"></span>
<span class="char"></span>
<span class="char"></span>
<span class="char">.</span>
<span class="char">.</span>
<span class="char">.</span>
</view>
</div>
</template>
<style lang="scss" scoped>
.cartoon{
.char {
display: inline-block;
transform: translateY(8px);
width: 20px;
height: 20px;
border-radius: 3px;
animation: bounce 0.5s infinite alternate;
}
.char:nth-child(2) {
animation-delay: 0.1s;
}
.char:nth-child(3) {
animation-delay: 0.2s;
}
.char:nth-child(4) {
animation-delay: 0.3s;
}
.char:nth-child(5) {
animation-delay: 0.4s;
}
.char:nth-child(6) {
animation-delay: 0.5s;
}
.char:nth-child(7) {
animation-delay: 0.6s;
}
.char:nth-child(8) {
animation-delay: 0.7s;
}
@keyframes bounce {
0% {
transform: translateY(8px);
border-radius: 5px;
}
100% {
transform: translateY(-8px);
border-radius: 3px;
}
}
}
.template {
padding: 20px;
text-align: center;
.loader {
margin: 25px;
}
}
</style>

BIN
uni_modules/jp-virtual-list/components/jp-virtual-list/top.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 B

78
uni_modules/jp-virtual-list/package.json

@ -0,0 +1,78 @@
{
"id": "jp-virtual-list",
"displayName": "虚拟列表 长列表优化 大数据列表高度不固定时展示,支持下拉刷新和回到顶部",
"version": "2.0.1",
"description": "当需要渲染大量的列表数据时,如果一次性将所有的数据渲染到DOM会导致页面崩溃。为了解决这个问题,可以使用虚拟滚动技术,只渲染可见区域内的数据,将未展示的数据暂不渲染",
"keywords": [
"虚拟滚动列表",
"百万级数据展示",
"只渲染可见区域内的数据",
"虚拟列表",
"长列表"
],
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"type": "component-vue"
},
"uni_modules": {
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}

94
uni_modules/jp-virtual-list/pages/index/index.vue

@ -0,0 +1,94 @@
<template>
<view class="h100">
<view style="line-height: 40px;display: flex;justify-content: space-between;">
<view @click="toAssign">滚动到Item999</view>
<view @click="toAssign1">滚动到Item100</view>
<view @click="toAssign2">滚动到Item0</view>
</view>
<jp-virtual-list code="id" class="h100" :refresher="true" isBackTop @scrolltoupper="scrolltoupper" @refresher="refresher" :data="listx" ref="search">
<template v-slot="{ list }">
<view v-for="item in list" :key="item.id" :id="item.id" :ref="item.id">
<view class="list_item" :style="'height:' + item.height +'rpx'">
{{item.id}}随机高度{{item.height}}
</view>
</view>
</template>
</jp-virtual-list>
</view>
</template>
<script>
export default {
data() {
return {
listx: [],
key: ''
}
},
mounted() {
const data = []
for (let i = 0; i < 10000; i++) {
data.push({
name: `Item ${i}`,
index: i,
id: `Item${i}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
this.listx = data
},
methods: {
toAssign(){
this.$refs.search.toAssign('Item999',this)
},
toAssign1(){
this.$refs.search.toAssign('Item100',this)
},
toAssign2(){
this.$refs.search.toAssign('Item0',this)
},
scrolltoupper(){
const data = []
for (let i = 0; i < 30; i++) {
data.push({
name: `Itemc ${i}`,
index: i,
id: `Itemc${i}${(Math.floor(Math.random() * 100)+100)}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
setTimeout(()=>{
//
this.$refs.search.addPage(data)
},1000)
},
refresher(){
const data = []
for (let i = 0; i < 10000; i++) {
data.push({
name: `Item ${i}`,
index: i,
id: `Item${i}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
setTimeout(()=>{
this.listx = data
//
this.$refs.search.refreshers()
},2000)
},
}
}
</script>
<style lang="scss">
.h100{
height: calc(100vh - 80px);
}
.list_item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f5f5f5;
}
</style>

142
uni_modules/jp-virtual-list/readme.md

@ -0,0 +1,142 @@
## 虚拟列表-解决大数据列表操作问题
> **组件名:jp-virtual-list**
### 安装方式
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
##有项目需要开发的请联系 QQ:371524845
###开发不易,如果帮助到你的,请支持 有问题请留言,作者会积极更新
### 基本用法
```html
<template>
<view class="h100">
<view style="line-height: 40px;display: flex;justify-content: space-between;">
<view @click="toAssign">滚动到:Item999</view>
<view @click="toAssign1">滚动到:Item100</view>
<view @click="toAssign2">滚动到:Item0</view>
</view>
<jp-virtual-list code="id" class="h100" :refresher="true" isBackTop @scrolltoupper="scrolltoupper" @refresher="refresher" :data="listx" ref="search">
<template v-slot="{ list }">
<view v-for="item in list" :key="item.id" :id="item.id" :ref="item.id">
<view class="list_item" :style="'height:' + item.height +'rpx'">
{{item.id}}随机高度:{{item.height}}
</view>
</view>
</template>
</jp-virtual-list>
</view>
</template>
<script>
export default {
data() {
return {
listx: [],
key: ''
}
},
mounted() {
const data = []
for (let i = 0; i < 10000; i++) {
data.push({
name: `Item ${i}`,
index: i,
id: `Item${i}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
this.listx = data
},
methods: {
toAssign(){
this.$refs.search.toAssign('Item999',this)
},
toAssign1(){
this.$refs.search.toAssign('Item100',this)
},
toAssign2(){
this.$refs.search.toAssign('Item0',this)
},
scrolltoupper(){
const data = []
for (let i = 0; i < 30; i++) {
data.push({
name: `Itemc ${i}`,
index: i,
id: `Itemc${i}${(Math.floor(Math.random() * 100)+100)}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
setTimeout(()=>{
// 告诉组件我需要添加该数据
this.$refs.search.addPage(data)
},1000)
},
refresher(){
const data = []
for (let i = 0; i < 10000; i++) {
data.push({
name: `Item ${i}`,
index: i,
id: `Item${i}`,
height: (Math.floor(Math.random() * 100)+100)
})
}
setTimeout(()=>{
this.listx = data
// 告诉组件刷新成功了
this.$refs.search.refreshers()
},2000)
},
}
}
</script>
<style lang="scss">
.h100{
height: calc(100vh - 80px);
}
.list_item {
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #f5f5f5;
}
</style>
```
#####
## API
### Props
| 属性名 | 类型 | 默认值 | 说明 |
| | | | |
| direction| String | 'bottom' | 数据加载方向(bottom/top) |
| lazy | Boolean|false | 【2.0以上版本已弃用】 是否懒加载,两种模式一种懒加载(数据量在5000以下),虚拟dom(大数据模式) |
| pageSizes| Number | 20 | 加载分页数 |
| data | Array | [] | 数据列表 |
| refresher| Boolean |false| 是否拥有下拉刷新 |
| code| String |'id'| 到达指定元素(需要设置元素的id和ref为当前值,参考基本用法) |
| isBackTop | Boolean | false | 是否拥有回到顶部(当前只用 direction=='bottom' 时启用) |
##### * 注意jp-virtual-list需要一个高度
### 回调
| 事件名 | 类型 | 回调参数 | 说明 |
| | | | |
| @operate | function |无 | 操作数据成功之后的回调 |
| @refresher | function |无 | 下拉刷新的回调 |
### 事件
| 事件名 | 说明 |
| | |
| refreshers | 下拉刷新成功之后调用,用来关闭刷新动画 |
| toAssign| 到达指定元素(需要设置到达元素的id和ref值为指定的数据,需要和code统一) |
| addPage| 向数组最后添加数据 |
Loading…
Cancel
Save