uniapp scroll-view解析

发布于:2025-08-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

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';

网站公告

今日签到

点亮在社区的每一天
去签到