前言
最近由于工作需要,在一个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 国际惯例
欢迎前往原文浏览,并留下您宝贵的意见!