记录一次H5滚动翻页插件mescrollJs的简易封装

2,358 阅读2分钟

前言

    最近由于工作需要,在一个H5页面中需要使用多种滚动加载列表。参照其他童鞋的写法后发现冗余实在是多,简直把CV功力练到了极致!!!为了用起来舒心,对mescroll.js库进行了vue版本的简易封装,希望能对各位有所帮助!

正文

    mescroll.js是一个基于vue的上拉下拉插件,主要应用于滚动加载列表,github有3.7k星, 地址github.com/mescroll/me…,需要的可以取自行查看。

1 后端部分

    在基于nodejs的mvc架构探索一文中,对express进行了简易mvc架构封装,本文的后端部分是基于此开展的。

1.1 添加json处理

app.use(Express.json({
    type: ['application/json', 'application/x-www-form-urlencoded']
}));

提醒:body-parser功能2019年已被express设置为内置功能,详见express官方文档,因此不用再安装body-parser包,直接使用Express.json()即可,详见express声明文件: declare namespace e { var json: typeof bodyParser.json; ...... }

1.2 添加跨域处理

app.use((req, res, next) => {
    console.log(`request ${req.url} is targeted!`.green.bold);
    // 跨域处理
    res.setHeader("Access-Control-Allow-Origin", "*");
    res.setHeader("Access-Control-Allow-Headers", "*");
    res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    next();
})

1.3 分页接口模拟

// controller/test.mjs
const userTemplate = {
    name: '张三',
    hobby: '钓鱼',
    sex: '男'
}
const users = Array.from({length: 1000}).map((_, index) => ({
    name: userTemplate.name + (index + 1),
    hobby: userTemplate.hobby + (index + 1),
    sex: index%2 === 0 ? '男': '女'
}));
export default {
    get: {
        async userList(req, res) {
            res.send({data: [], code: 200});
        },
    },
    post: {
        async userList(req, res) {
            const {pageNumber, pageSize} = req.body;
            const start = pageNumber * pageSize;
            const end  = start + pageSize;
            res.send({code: 200, data: users.slice(start, end), total: users.length});
        },
    },
    put: {},
    delete: {}
}

2 前端部分

    对mescroll.js进行了组件化,封装了数据获取、以及上拉下拉相关逻辑,实际调用引入组件,并编写列表项样式即可,利用了vue slot与slot-scope相关特性实现。

2.1 入口页面

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./app.css">
</head>
<body>
    <div id="app"></div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/mescroll.js@1.4.1/mescroll.min.css">
	<script src="https://cdn.jsdelivr.net/npm/mescroll.js@1.4.1/mescroll.min.js" charset="utf-8"></script>
    <script src="app.js"></script>
</body>
</html>

2.2 组件

//自定义scorll组件
const MeScrollComponent = {
    name: 'MeScroll',
    template: `
        <div>
            <slot :dataList="dataList"></slot>
        </div>
    `,
    props: {
      id: {
        type: String,
        required: true,
      },
      fetchDataService: {
        type: Object,
        required: true,
      },
      fetchDataApi: {
        type: String,
        required: true,
      },
      query: {
        type: Object | null,
        default() {
          return {};
        }
      },
      pageSize: {
        type: Number,
        default: 20,
      },
    },
    data() {
      return {
        meScroll: null,
        pageNumber: 1,
        dataList: [],
      }
    },
    async mounted() {
      console.log('scroll mounted');
      this.initScroll();
      await this.getDataList();
    },
    methods: {
      async getDataList() {
        try {
          const query = {
            pageNumber: this.pageNumber,
            pageSize: this.pageSize,
            ...this.query
          }
          const res = await this.fetchDataService[this.fetchDataApi](query);
          if (res) {
            //拆分时间
            let resData = res.data;
            if (this.pageNumber === 0) {
              if (resData === null || resData.length === 0) {
                this.meScroll.endSuccess(this.pageSize, false);
                this.meScroll.showNoMore();
              } else {
                this.dataList = resData;
              }
            } else {
              if (resData !== null) {
                this.dataList = this.dataList.concat(resData);
              }
            }
            //判断是否有下一页
            if (this.pageSize * (this.pageNumber + 1) < res.total) {
              this.meScroll.endSuccess(this.pageSize, true);
            } else {
              this.meScroll.endSuccess(this.pageSize, false);
              this.meScroll.showNoMore();
            }
          } else {
            //请求错误,清空数组,处理刷新控件
            this.dataList = [];
            this.meScroll.endSuccess(this.pageSize, false);
            this.meScroll.showNoMore();
          }
        } catch (error) {
          console.error(error);
        }
  
      },
      //加载更多
      loadMore() {
        this.pageNumber++;
        this.getDataList().then();
      },
      //上拉刷新
      refresh() {
        this.pageNumber = 0;
        this.getDataList().then();
      },
      initScroll() {
        let _this = this;
        this.meScroll = new MeScroll(_this.id, {
          down: {
            callback: _this.refresh,
            auto: false,
            offset: 60,
          },
          up: {
            callback: _this.loadMore,
            auto: false,
            noMoreSize: 3,
            isBounce: false,
            empty: {
              //列表第一页无任何数据时,显示的空提示布局; 需配置warpId才显示
              warpId: 'stickyfill', //父布局的id (1.3.5版本支持传入dom元素)
              tip: '暂无相关设备~', //提示
            },
            offset: 60,
            htmlNodata: '<p class="upwarp-nodata">-- 没有更多数据了 --</p>',
          },
        });
      },
    },
    watch: {
       query:{
           handler(val, oldVal) {
               console.log('query changed', oldVal, '->',val);
               if(!oldVal || !Object.keys(oldVal).length) return; // 防止mounted初始化查询条件触发两次查询!!!
               else if(JSON.stringify(val) === JSON.stringify(oldVal)) return; // 搜索条件没变化不更新
               else {
                this.meScroll.clearDataList();
                this.dataList = [];
                this.refresh();
              }
           }
       },
       id: {
          handler(val, oldVal) {
              console.log('id changed', oldVal, '->',val);
              if (val && oldVal && oldVal !== val && this.meScroll) { // 同一组件使用了多个mescroll组件,id切换触发,适用于多个mescroll组件滚动列差异较大的情况
                this.meScroll.clearDataList();
                this.dataList = [];
                this.refresh();
              }   
          }
       } 
    }
}

//数据请求
const userService = {
    list(data) {
        return fetch('http://localhost:3000/test/userList', {
            method: 'POST',
            body: JSON.stringify(data),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(res => res.json());
    }
}
//入口js
new Vue({
    el: '#app',
    components: {
        MeScroll: MeScrollComponent,
    },
    data() {
        return {
            userService,
        }
    },
    template: `
       <div id="app">
          <div class="scroll-item-header">
            <span>姓名</span>
            <span>性别</span>
            <span>爱好</span>
          </div>
          <div id="mescroll" class="mescroll">
              <me-scroll id="mescroll" :fetchDataService="userService" fetchDataApi="list">
                    <template #default="{dataList}">
                        <div v-for="(item, index) in dataList" :key="index" class="scroll-item">
                            <span>{{ item.name }}</span>
                            <span>{{ item.sex }}</span>
                            <span>{{ item.hobby }}</span>
                        </div>
                    </template>
              </me-scroll>
          </div>
       </div>
    `,
})

3 国际惯例

    欢迎前往原文浏览,并留下您宝贵的意见!