uniapp scroll-view解析
1. uniapp scroll-view解析
<scroll-viewscroll-ystyle="width: 100%; height: 100%"refresher-enabled:refresher-triggered="triggered"@scrolltolower="onreachBottom"@refresherrefresh="refresherrefresh()"@refresherrestore="refresherrestore()"@refresherabort="refresherabort()"></scroll-view>
1.1. refresher-enabled
refresher-enabled是scroll-view组件的一个属性,用于开启自定义下拉刷新功能,默认值为false。
在uni-app中,如果需要自定义下拉刷新样式,可以使用scroll-view组件,并通过设置refresher-enabled属性为true来启用该功能。同时,还需要绑定refresher-triggered属性来设置当前下拉刷新状态,以及在data中定义该属性并默认设为false。此外,还需要在scroll-view上绑定refresherrefresh事件,用于处理下拉刷新的逻辑。
1.2. refresher-triggered
refresher-triggered属性用于控制下拉刷新的状态。
在使用scroll-view组件实现下拉刷新功能时,需要在scroll-view上绑定refresher-triggered属性,并在data中定义该属性,默认设置为false。当用户下拉触发刷新时,将refresher-triggered属性设置为true,表示下拉刷新已被触发。在数据刷新完成后,将refresher-triggered属性重新设置为false,以关闭下拉刷新状态。
1.3. @scrolltolower
@scrolltolower事件用于监听滚动到底部的事件。当用户滚动页面到底部时,这个事件会被触发,通常用于实现无限滚动或者加载更多数据的功能。
1.4. @scrolltolower
@refresherrefresh是自定义下拉刷新的事件。
当用户在scroll-view组件中下拉并释放时,如果自定义下拉刷新功能已启用(通过设置refresher-enabled为true),就会触发@refresherrefresh事件。开发者可以在该事件的处理函数中执行数据刷新操作,如发起网络请求获取最新数据。在处理完数据刷新后,通常需要将refresher-triggered的状态重置为false,以允许下一次的下拉刷新操作。
1.5. @refresherrestore
@refresherrestore事件用于在下拉刷新完成后恢复视图状态。
@refresherrestore事件在下拉刷新完成后触发,用于恢复视图状态。通常在下拉刷新完成后,需要将refresher-triggered属性设置为false,以隐藏刷新图标并恢复视图状态。
1.5.1. 代码示例
<template><scroll-view:refresher-enabled="true":refresher-triggered="isRefreshing"@refresherrefresh="onRefresh"@refresherrestore="onRestore"scroll-y><!-- 列表内容 --><view v-for="(item, index) in items" :key="index">{{ item }}</view></scroll-view>
</template><script>
export default {data() {return {isRefreshing: false,items: [] // 列表数据};},methods: {async onRefresh() {this.isRefreshing = true;try {// 模拟网络请求await this.fetchData();} catch (error) {console.error('刷新失败:', error);} finally {this.isRefreshing = false;}},onRestore() {console.log('下拉刷新已完成,视图已恢复');},async fetchData() {// 模拟网络请求return new Promise(resolve => {setTimeout(() => {// 更新列表数据this.items = ['新数据1', '新数据2', '新数据3'];resolve();}, 2000);});}}
};
</script>
<style>
/* 样式可以根据需要自定义 */
</style>
1.5.2. 代码解释
(1)模板部分:
scroll-view组件的refresher-enabled属性设置为true,启用下拉刷新功能。
refresher-triggered属性绑定到isRefreshing,用于控制下拉刷新状态。
@refresherrefresh事件绑定到onRefresh方法,处理下拉刷新逻辑。
@refresherrestore事件绑定到onRestore方法,处理下拉刷新完成后的恢复逻辑。
(2)脚本部分:
data函数返回组件的初始数据,包括isRefreshing和items。
onRefresh方法在下拉刷新时被调用,将isRefreshing设置为true,模拟网络请求后更新列表数据,最后将isRefreshing设置为false。
onRestore方法在下拉刷新完成后被调用,用于恢复视图状态。
fetchData方法模拟网络请求,更新列表数据。
通过以上步骤,你可以实现uni-app中scroll-view组件的下拉刷新功能,并在刷新完成后恢复视图状态。
1.6.@refresherabort
在 UniApp 中,@refresherabort 是 scroll-view 组件的一个事件,当用户在下拉刷新过程中取消刷新操作时,会触发 @refresherabort 事件。你可以通过定义 refresherabort 方法来处理这个事件。
1.7.1. listRefreshPage.vue
<template><view class="page-layout"><view class="header-layout"></view><view id="swiperBox" :style="{ height: scrollHeight + 'px' }"><scroll-viewscroll-ystyle="width: 100%; height: 100%"refresher-enabled:refresher-triggered="triggered"@scrolltolower="onreachBottom"@refresherrefresh="refresherrefresh()"@refresherrestore="refresherrestore()"@refresherabort="refresherabort()"><view class="list-layout"><view class="list-item-layout"v-for="(item, index) in contentList":key="index"><view class="list-item-title">{{ item.name }}</view><view class="list-item-body"><!--任务名称--><view class="list-item-row"><view class="list-item-label"> 任务名称:</view><view class="list-item-label">{{ item.taskName }}</view></view><!--走访时间--><view class="list-item-row"><view class="list-item-label"> 走访时间:</view><view class="list-item-label">{{ item.taskStartDate.slice(0, 10) }} 至{{ item.taskEndDate.slice(0, 10) }}</view></view></view></view><view class="list-load-more"><list-load-more :status="status"/></view></view></scroll-view></view></view>
</template><script>
import listRefeshData from '../../../data/listRefeshLoad.json'
import ListLoadMore from "./list-load-more.vue";export default {components: {ListLoadMore: ListLoadMore},data() {return {scrollHeight: 0,listRefeshData: listRefeshData,contentList: [],triggered: false,status: "loadmore",taskName: "走访任务名称",params: {current: 1,size: 10,name: null,status: "",},current: 0,};},onLoad(params) {this.params.current = 1;this.contentList = [];this.getList();},onReady() {let that = this;//获取屏幕信息uni.getSystemInfo({success(screenObj) {let {windowWidth, windowHeight, safeArea} = screenObj;const query = uni.createSelectorQuery().in(that);query.select("#swiperBox").boundingClientRect((data) => {that.scrollHeight = safeArea.bottom - data.top;}).exec();},});},methods: {getList(override, search) {let that = this;if (override) {that.params.current = 1;}let {records, current, pages, code} = this.listRefeshData.data;const {size} = that.params;if (that.contentList.length > size) {that.status = "nomore";return} else {that.status = "loadmore";}if (override) {// 下拉刷新that.contentList = records;that.page = pages;} else {// 触底刷新进这个that.contentList = that.contentList.concat(records);}that.params.current++;if (search) {that.showSearch = false;}uni.stopPullDownRefresh();},/*** 界面下拉触发,triggered可能不是true,要设为true* @param index*/refresherrefresh(index) {let that = this;if (that.triggered) {return;}that.triggered = true;//界面下拉触发,triggered可能不是true,要设为trueif (!that.triggered) {that.triggered = true;}setTimeout(() => {that.triggered = false; //触发onRestore,并关闭刷新图标that.triggered = false;this.getList(true);}, 100);},/*** 刷新停止*/refresherrestore(index) {let that = this;that.triggered = false;},/*** 刷新停止*/refresherabort(index) {let that = this;that.triggered = false;},/*** scroll-view到底部加载更多*/onreachBottom() {let current = this.current;this.status = "loading";setTimeout(() => {this.getList();}, 100);},},
};
</script>
<style scoped lang="scss">
.page-layout {background: #efefef;
}.header-layout {height: 80px;
}.list-layout {padding: 10px 15px;
}.list-item-layout {background: #ffffff;border-radius: 20px;margin-top: 20px;overflow: hidden;//&:not(:last-of-type) {// border-bottom: 2px solid #e4e7ed;//}
}.list-item-title {display: flex;flex-direction: column;align-items: start;padding: 8px 15px;background: linear-gradient(90deg,#d8ebff 0%,rgba(216, 235, 255, 0) 100%);
}.list-item-body {padding: 30px 25px;
}.list-item-row {display: flex;flex-direction: row;align-items: center;padding: 10px 5px;
}.list-item-label {color: #333;font-size: 16px;
}.list-load-more {padding: 30px 20px;
}
</style>
1.7.2. list-load-loading.vue
<template><view class="page-layout"><view class="header-layout"></view><view id="swiperBox" :style="{ height: scrollHeight + 'px' }"><scroll-viewscroll-ystyle="width: 100%; height: 100%"refresher-enabled:refresher-triggered="triggered"@scrolltolower="onreachBottom"@refresherrefresh="refresherrefresh()"@refresherrestore="refresherrestore()"@refresherabort="refresherabort()"><view class="list-layout"><view class="list-item-layout"v-for="(item, index) in contentList":key="index"><view class="list-item-title">{{ item.name }}</view><view class="list-item-body"><!--任务名称--><view class="list-item-row"><view class="list-item-label"> 任务名称:</view><view class="list-item-label">{{ item.taskName }}</view></view><!--走访时间--><view class="list-item-row"><view class="list-item-label"> 走访时间:</view><view class="list-item-label">{{ item.taskStartDate.slice(0, 10) }} 至{{ item.taskEndDate.slice(0, 10) }}</view></view></view></view><view class="list-load-more"><list-load-more :status="status"/></view></view></scroll-view></view></view>
</template><script>
import listRefeshData from '../../../data/listRefeshLoad.json'
import ListLoadMore from "./list-load-more.vue";export default {components: {ListLoadMore: ListLoadMore},data() {return {scrollHeight: 0,listRefeshData: listRefeshData,contentList: [],triggered: false,status: "loadmore",taskName: "走访任务名称",params: {current: 1,size: 10,name: null,status: "",},current: 0,};},onLoad(params) {this.params.current = 1;this.contentList = [];this.getList();},onReady() {let that = this;//获取屏幕信息uni.getSystemInfo({success(screenObj) {let {windowWidth, windowHeight, safeArea} = screenObj;const query = uni.createSelectorQuery().in(that);query.select("#swiperBox").boundingClientRect((data) => {that.scrollHeight = safeArea.bottom - data.top;}).exec();},});},methods: {getList(override, search) {let that = this;if (override) {that.params.current = 1;}let {records, current, pages, code} = this.listRefeshData.data;const {size} = that.params;if (that.contentList.length > size) {that.status = "nomore";return} else {that.status = "loadmore";}if (override) {// 下拉刷新that.contentList = records;that.page = pages;} else {// 触底刷新进这个that.contentList = that.contentList.concat(records);}that.params.current++;if (search) {that.showSearch = false;}uni.stopPullDownRefresh();},/*** 界面下拉触发,triggered可能不是true,要设为true* @param index*/refresherrefresh(index) {let that = this;if (that.triggered) {return;}that.triggered = true;//界面下拉触发,triggered可能不是true,要设为trueif (!that.triggered) {that.triggered = true;}setTimeout(() => {that.triggered = false; //触发onRestore,并关闭刷新图标that.triggered = false;this.getList(true);}, 100);},/*** 刷新停止*/refresherrestore(index) {let that = this;that.triggered = false;},/*** 刷新停止*/refresherabort(index) {let that = this;that.triggered = false;},/*** scroll-view到底部加载更多*/onreachBottom() {let current = this.current;this.status = "loading";setTimeout(() => {this.getList();}, 100);},},
};
</script>
<style scoped lang="scss">
.page-layout {background: #efefef;
}.header-layout {height: 80px;
}.list-layout {padding: 10px 15px;
}.list-item-layout {background: #ffffff;border-radius: 20px;margin-top: 20px;overflow: hidden;//&:not(:last-of-type) {// border-bottom: 2px solid #e4e7ed;//}
}.list-item-title {display: flex;flex-direction: column;align-items: start;padding: 8px 15px;background: linear-gradient(90deg,#d8ebff 0%,rgba(216, 235, 255, 0) 100%);
}.list-item-body {padding: 30px 25px;
}.list-item-row {display: flex;flex-direction: row;align-items: center;padding: 10px 5px;
}.list-item-label {color: #333;font-size: 16px;
}.list-load-more {padding: 30px 20px;
}
</style>
1.7.3. list-load-more.vue
<template><view class="list-load-more-layout" :style="{backgroundColor: bgColor,height: addUnit(height) }"><view :class="status == 'loadmore' || status == 'nomore'? 'list-load-more-more' : ''" class="list-load-more-inner"><view class="list-load-more-icon-layout"><list-load-loading class="list-load-more-icon-icon" :color="iconColor":mode="iconType == 'circle' ? 'circle' : 'flower'":show="status == 'loading' && icon"></list-load-loading></view><!-- 如果没有更多的状态下,显示内容为dot(粗点),加载特定样式 --><view class="list-load-more-txt" @tap="loadMore">{{ showText }}</view></view></view>
</template><script>
import ListLoadLoading from "./list-load-loading.vue";/*** loadmore 加载更多* @description 此组件一般用于标识页面底部加载数据时的状态。* @tutorial https://www.uviewui.com/components/loadMore.html* @property {String} status 组件状态(默认loadmore)* @property {String} bg-color 组件背景颜色,在页面是非白色时会用到(默认#ffffff)* @property {Boolean} icon 加载中时是否显示图标(默认true)* @property {String} icon-type 加载中时的图标类型(默认circle)* @property {String} icon-color icon-type为circle时有效,* 加载中的动画图标的颜色(默认#b7b7b7)* @property {Boolean} is-dot status为nomore时,内容显示为一个"●"(默认false)* @property {String} color 字体颜色(默认#606266)* @property {Object} load-text 自定义显示的文字,见上方说明示例* @event {Function} loadmore status为loadmore时,点击组件会发出此事件* @example <u-loadmore :status="status" icon-type="iconType"* load-text="loadText" />*/
export default {name: "u-loadmore",components: {ListLoadLoading: ListLoadLoading},props: {// 组件背景色bgColor: {type: String,default: 'transparent'},// 是否显示加载中的图标icon: {type: Boolean,default: true},// 字体大小fontSize: {type: String,default: '16'},// 字体颜色color: {type: String,default: '#606266'},// 组件状态,loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态status: {type: String,default: 'loadmore'},// 加载中状态的图标,flower-花朵状图标,circle-圆圈状图标iconType: {type: String,default: 'circle'},// 显示的文字loadText: {type: Object,default() {return {loadmore: '加载更多',loading: '正在加载...',nomore: '没有更多了'}}},// 在“没有更多”状态下,是否显示粗点isDot: {type: Boolean,default: false},// 加载中显示圆圈动画时,动画的颜色iconColor: {type: String,default: '#b7b7b7'},// 高度,单位pxheight: {type: [String, Number],default: 'auto'}},data() {return {// 粗点dotText: "●"}},computed: {// 加载中圆圈动画的样式cricleStyle() {return {borderColor: `#e5e5e5 #e5e5e5 #e5e5e5 ${this.circleColor}`}},// 显示的提示文字showText() {let text = '';if (this.status == 'loadmore') text = this.loadText.loadmore;else if (this.status == 'loading') text = this.loadText.loading;else if (this.status == 'nomore' && this.isDot) text = this.dotText;else text = this.loadText.nomore;return text;}},methods: {addUnit(num) {if (!num)return 0;if (typeof num === "number")return num + "px";return num;},loadMore() {// 只有在“加载更多”的状态下才发送点击事件,// 内容不满一屏时无法触发底部上拉事件,所以需要点击来触发if (this.status == 'loadmore') this.$emit('loadmore');}}
}
</script><style scoped lang="scss">
.list-load-more-layout {justify-content: center;align-items: center;
}
.list-load-more-inner {display: flex;flex-direction: row;justify-content: center;align-items: center;padding: 0 12px;
}.list-load-more-more {position: relative;justify-content: center;
}.list-load-more-icon-layout {margin-right: 8px;
}.list-load-more-icon-icon {align-items: center;justify-content: center;
}.list-load-more-txt {color: black;font-size: 16px;
}</style>
1.7.4. listRefeshLoad.json
{"code": 200,"success": true,"data": {"records": [{"id": "33496","name": "张三","taskStartDate": "2025-07-28","taskEndDate": "2025-08-03","taskName": "12314314"},{"id": "32921","name": "曹十八","taskStartDate": "2025-07-28","taskEndDate": "2025-08-03","taskName": "32154352"},{"id": "329231","name": "刘一","taskStartDate": "2025-07-28","taskEndDate": "2025-08-03","taskName": "23515346"}],"total": 3,"size": 10,"current": 1,"pages": 1},"msg": "操作成功"
}
1.7.5. listRefreshPage.vue
// An highlighted block
var foo = 'bar';