效果
node后端代码
对网易音乐接口请求到的数据进行进一步处理
const express = require('express')
const axios = require('axios')
const cors = require('cors')
const app = express()
const port = 8001
app.use(cors())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.get('/getSearchRes', (req, res) => {
const params = req.query
axios.get('http://47.103.29.206:3000/search', { params }).then((response) => {
const data = response.data.result
if (data.songCount === 0) {
res.json({
code: 200,
songs: [],
hasMore: false,
songCount: 0,
})
} else {
const songs = response.data.result.songs
const handleSongs =songs? songs.map((item) => {
return {
id: item.id,
songName: item.name,
singer: item.artists.map((list) => {
return list.name
}).join('/')
}
}):[]
res.json({
code: 200,
songs: handleSongs,
hasMore: data.hasMore,
songCount: data.songCount,
})
}
})
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}/getSearchRes`)
})
必选参数 : keywords : 关键词
可选参数 : limit : 返回数量 , 默认为 30 offset : 偏移数量,用于分页 , 如 : 如 😦 页数 -1)*30, 其中 30 为 limit 的值 , 默认为 0
有数据时 没有数据时
前端代码
通常,分页查询后端接收前端的字段有时是offset有时是page 用offset的话,每次可以用songs.length当做offset的值 page的话就比较简单了,直接传页数就行
这里传入的offset,这里前端传参数的时候可以用page*limit来代替 ,到时候触底加载只需让page++就能请求到下一页数据 page初始值为0 我觉得这个接口有点问题,offset为100 limit为100 那请求到的数据应该是第100-200首歌,一共有224首,所以hasmore应该是true才对,这里为什么是false
但是有的情况又是正常的比如说 搜索呵呵呵呵呵哈 limit为30
基础代码
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Vue 测试实例 - 菜鸟教程(runoob.com)</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/3.0.5/vue.global.js"></script>
<script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
</head>
<body>
<div id="hello-vue" class="demo">
<input v-model="inputData" type="text" />
<ul class="songContainer">
<li v-for="(item, index) in songs" :key="index">
{{item.songName}}--{{item.singer}}
</li>
</ul>
<button @click="page++" v-if="hasMore">点我page+1</button>
<div v-if="!hasMore&&inputData">已经加载完了</div>
</div>
<script>
const HelloVueApp = {
data() {
return {
inputData: '',
songs: [],
hasMore: true,
songCount: 0,
limit: 100,
page: 0
}
},
computed: {
},
created() {
this.$watch('inputData', this.debounce(async (val) => {
this.initSearch()
this.inputData = val
const data = await this.searchFun()
this.songs = data.songs,
this.hasMore = data.hasMore
}))
},
watch: {
}
},
methods: {
debounce(fn, delay = 300) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
},
initSearch() {
this.songs = [],
this.hasMore = true,
this.songCount = 0,
this.offset = 0,
this.page = 0
},
async searchFun() {
const result = await axios.get('http://localhost:8001/getSearchRes',
{
params: {
keywords: this.inputData,
limit: this.limit,
offset: this.page * this.limit
}
})
console.log(result);
return result.data
}
},
}
Vue.createApp(HelloVueApp).mount('#hello-vue')
</script>
</body>
</html>
better-scroll pullup使用
安装 cnpm i better-scroll -S 官网 https://better-scroll.github.io/docs/zh-CN/plugins/pullup.html https://better-scroll.github.io/docs/zh-CN/plugins/observe-dom.html#%E5%AE%89%E8%A3%85
引入betterscroll并注册pullUpload 注意监听dom变化
初始化Betterscroll需要一个ref容器,这个容器在这里不能是ul节点 因为里面有很多子元素,外面再套个父盒子 ref名称叫rootRef
实例化BScroll 并传入配置参数
this.bs.on('pullingUp', this.pullingUpHandler) //监听pullingUp事件,进入回调函数
回调函数 每次一触底就显示一个loading的gif ,显示与否需要通过自定义一个标志变量控制–isPullUpLoad 当请求到数据以后,再将它变成false
请求下一页数据
loading与“已经加载完成显示”
扩展
如果有一些后端数据过滤掉了,比如说收费歌曲不展示,可能会导致请求到的数据是不满一屏的,这种情况就不满足better-scroll的滚动条件了,因为内层高度小于外层容器高度,不能滚动的话就不能触发监听触底的函数了,虽然说新版本的better-scroll已经解决了这个问题,但是仅仅依赖better-scroll自身的处理用户体验不是很好
比如说这个,它是不满一屏的,虽然上拉依然能够加载数据,但是用户不知道这种情况下还有更多数据,也就不会做上拉加载这种事情了。
看这个更直观的场景, songs为空,但是hasmore为true ,这种情况还是能拖动,并且有数据,而且每一屏的数据非常少,大量的数据都被过滤了 现在来模拟一下这个场景, 让每一页能整除2和3的过滤掉
如何处理呢 希望数据不满一屏的情况下,再去请求下一页数据,直到填充到满足一屏为止,
bs(BScroll实例对象)内的maxScrollY属性
https://blog.csdn.net/qq_40196738/article/details/90200439 better-scroll的maxScrollY属性 并不是列表的offsetHeight,而是整体list高度减去当前显示区域??,即scroll还能滑动的偏移。还有this.scroll.y表示当前滑动偏移。
当第一次发起请求后,并且当maxScrollY>=-1 ,说明是不可滚动的,这种情况就不断去执行searchFn请求并拼接数据 并且在searchFn中继续判断是否>=-1 ;还有个要注意的就是nexttick ,数据到dom变化需要时间,等dom渲染之后再进行-1的判断
自动请求了三次
总代码
node
const express = require('express')
const axios = require('axios')
const cors = require('cors')
const app = express()
const port = 8001
app.use(cors())
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.get('/getSearchRes', (req, res) => {
const params = req.query
axios.get('http://47.103.29.206:3000/search', { params }).then((response) => {
const data = response.data.result
if (data.songCount === 0) {
res.json({
code: 200,
songs: [],
hasMore: false,
songCount: 0,
})
} else {
const songs = response.data.result.songs
const handleSongs =songs? songs.map((item) => {
return {
id: item.id,
songName: item.name,
singer: item.artists.map((list) => {
return list.name
}).join('/')
}
}):[]
res.json({
code: 200,
songs: handleSongs.filter((item,index,arr)=>{
if(!(index%2==0||index%3==0)){
return item
}
}),
hasMore: data.hasMore,
songCount: data.songCount
})
}
})
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}/getSearchRes`)
})
<!-- -->
<template>
<div id="hello-vue" class="demo">
<input
v-model="inputData"
type="text"
style="display: block; margin: 0 auto"
/>
<div ref="rootRef" class="scrollCon" style="margin-top: 20px">
<ul class="songContainer">
<li v-for="(item, index) in songs" :key="index" style="font-size: 8px">
{{ item.songName }}--{{ item.singer }}
</li>
<div style="width: 100%; padding-bottom: 20px" v-show="isPullUpLoad">
<img
src="https://img-blog.csdnimg.cn/fc901b1ca5974221b33e0d4b60eb64d2.gif"
style="margin: 0 auto; display: block"
width="30"
height="30"
/>
</div>
<div v-if="!hasMore && inputData">--------已经加载完了--------</div>
</ul>
</div>
</div>
</template>
<script>
import axios from "axios";
import BScroll from "better-scroll";
import { PullUpLoad } from "better-scroll";
import { ObserveDom } from "better-scroll";
BScroll.use(PullUpLoad);
BScroll.use(ObserveDom);
export default {
data() {
return {
inputData: "ff",
songs: [],
hasMore: true,
songCount: 0,
// offset: 0,
limit: 30,
page: 0,
isPullUpLoad: false,
bs: null,
};
},
computed: {},
created() {
// 防抖
this.$watch(
"inputData",
this.debounce(async (val) => {
this.initSearch();
this.inputData = val;
const data = await this.searchFun();
this.songs = data.songs;
this.hasMore = data.hasMore;
await this.$nextTick()
this.makeItScrollable()
}),
// watch api第三个参数 将立即以表达式的当前值触发回调
{
immediate: true,
}
);
},
mounted() {
const bscroll = (this.bs = new BScroll(this.$refs.rootRef, {
pullUpLoad: true,
observeDOM: true,
click: true,
}));
bscroll.on("pullingUp", this.pullingUpHandler);
},
watch: {
hasMore(val) {
if (!val) {
console.log("到最底部了");
}
},
},
methods: {
// 防抖函数
debounce(fn, delay = 300) {
let timer = null;
return function () {
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, delay);
};
},
// 每次搜索page songs之类的信息需要清除
initSearch() {
(this.songs = []),
(this.hasMore = true),
(this.songCount = 0),
(this.offset = 0),
(this.page = 0);
},
// 向后端发送请求方法,返回请求到的数据
async searchFun() {
const result = await axios.get("http://localhost:8001/getSearchRes", {
params: {
keywords: this.inputData,
limit: this.limit,
offset: this.page * this.limit,
},
});
console.log(result);
return result.data;
},
// 触底回调
async pullingUpHandler() {
this.isPullUpLoad = true;
console.log(12345);
this.page++;
// 请求到数据后拼接到songs后面
const data = await this.searchFun();
this.songs = this.songs.concat(data.songs);
this.hasMore = data.hasMore;
this.bs.finishPullUp();
this.bs.refresh();
this.isPullUpLoad = false;
},
// 不满一屏优化
async makeItScrollable() {
console.log(this.bs.maxScrollY);
if (this.bs.maxScrollY >= -1) {
this.page++
const data = await this.searchFun();
this.songs = this.songs.concat(data.songs);
this.hasMore = data.hasMore;
}
},
},
};
</script>
<style >
.scrollCon {
height: 300px;
overflow: hidden;
padding-bottom: 30px;
}
</style>
还有一个地方需要修改的,就是如果input框里的值是空的,发送请求可以不用做请求直接return,就不用请求空数据了
其他链接 https://www.cnblogs.com/xzc666/p/9455861.html iscroll
外链生成
|