1. uniapp scroll-view解析
<scroll-view
scroll-y
style="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-view
scroll-y
style="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,要设为true
if (!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-view
scroll-y
style="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,要设为true
if (!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'
},
// 高度,单位px
height: {
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';