首页
工具
心境语句
相册
建站轨迹
关于
Search
1
微信小程序:计算属性的两种体现方式及应用场景
1,594 阅读
2
Antd Upload 组件上传文件接收数据流并下载
1,059 阅读
3
C#插件火车头采集器动态切换代理IP,及自动切换UserAgent
542 阅读
4
[C#]使用dnSpy对目标程序(EXE或DLL)进行反编译修改并编译运行
537 阅读
5
ADODB.Connection 错误 800a0e7a 未找到提供程序。该程序可能未正确安装解决方法
499 阅读
react
typecho
ASP
Centos
MYSQL
PHP
Sql server
Javascript
nodejs
数据采集
.NET
git
编程算法
管理及流程
Vue
微信小程序
android
python
mongodb
登录
Search
标签搜索
kotlin
node-sass
nuxtjs
C#火车头插件
火车头采集器
火车头代理
C#反编译
程序逆向
dnSpy教程
Antd
InputNumber
NPM教程
NPM命令
rrweb教程
git慢
git镜像
vim命令
git命令
网页音乐插件
网页播放器
Elysian
累计撰写
74
篇文章
累计收到
0
条评论
首页
栏目
react
typecho
ASP
Centos
MYSQL
PHP
Sql server
Javascript
nodejs
数据采集
.NET
git
编程算法
管理及流程
Vue
微信小程序
android
python
mongodb
页面
工具
心境语句
相册
建站轨迹
关于
搜索到
1
篇与
vue组件
的结果
2021-12-28
vue继承vant实现支持下拉刷新上拉加载更多的瀑布流组件
组件背景需要实现瀑布流效果、使用的前端框架是基于vant的。瀑布流内容多种形式,除了图文还有标签形式。项目需要SSR。片寻网络,找到一个不错的瀑布流组件,基于它修改了部分内容(优化了数据获取方式,传递列表支持ssr,传递promise参数支持下拉刷新和上拉加载更多,修改内容部分,使用slot支持组件外部自定义内容格式)实现效果组件源码:<template> <div class="flow-box"> <div class="type-box" v-if="typeList && typeList.length"> <ul class="type-list"> <li @click="changeType(index)" v-for="(item, index) in typeList" :class="['type-item', typeIndex == index ? 'type-item-on' : '']" :key="index" > <div class="text">{{ item.name }}</div> <div class="line"></div> </li> </ul> </div> <van-pull-refresh v-model="isLoading" @refresh="onRefresh" style="min-height: 100vh" :success-duration="800" success-text="加载成功" loading-text="加载中..." > <van-list v-if="haveData == 2" v-model="loading" :finished="finished" finished-text="没有更多了" @load="onBottomLoad" :offset="150" :immediate-check="false" > <div class="data-list-box" id="data-list-box"> <div class="data-item" v-for="(item, index) in dataList" :style="{ width: boxWidth + 'px' }" :key="index" > <slot :item="item" :index="index"></slot> </div> </div> </van-list> </van-pull-refresh> </div> </template> <script> export default { props: ["dataSource", "promisefunc"], data() { return { typeList: [ //分类列表 // { name: "全部" }, // { name: "手机数码" }, // { name: "家用电器" }, // { name: "酒水饮料" }, // { name: "钟表珠宝" }, // { name: "美妆护肤" }, // { name: "运动户外" }, // { name: "汽车生活" }, ], typeIndex: 0, //分类索引 dataList: this.dataSource || [], //列表数据 haveData: 0, //是否有数据,1=无,2=有,0=页面还未初始化 pageIndex: 1, //页码 pageSize: 8, //每页加载数据数量 isLoading: false, //下拉刷新进行中,请求开始true, 请求完成false,用于下拉刷新组件van-pull-refresh loading: false, //上拉加载更多中,上拉触底时自动变成true, 请求完成设置为false, 用于列表组件van-list finished: false, //上拉加载是否加载完最后一页数,用于组件van-list itemCount: 0, //上一次加载完成后的瀑布流item个数 lastRowHeights: [0, 0], //最后一行标签的顶部间距+高度,2列 boxMargin: 15, //每个item之间的边距 boxWidth: 165, //每个item宽度 }; }, created() { if (process.server) { return; } this.$toast.loading({ message: "加载中...", duration: 0, }); //当前瀑布流设置为两列,计算瀑布流每个item和图片的宽度 //console.log("load", window.innerWidth); let screenWidth = window.innerWidth || 375; //屏幕宽度 this.boxWidth = (screenWidth - this.boxMargin * 3) / 2; //每个item的宽度 if (this.dataSource == null || !this.dataSource.length) { this.onRefresh(); //刷新数据 } else { this.pageIndex = 1; //重置第一页 this.finished = false; //上拉加载"所有数据已经完成"标识 重置为false this.dataList = []; this.loadImagesHeight(this.dataSource); } }, methods: { changeType(index) { //切换类型 if (this.typeIndex == index) return; this.$toast.loading({ message: "加载中...", duration: 0, }); this.typeIndex = index; this.onRefresh(); }, onRefresh() { //下拉刷新 // if (this.isLoading) return; //还在请求中,返回 this.pageIndex = 1; //重置第一页 this.finished = false; //上拉加载"所有数据已经完成"标识 重置为false //接口请求 this.getDataList(); }, onBottomLoad() { //上拉加载更多 if (this.finished) return; //说明所有数据已经加载完毕,返回 this.getDataList(); //下一页数据请求中 }, //数据请求 getDataList() { this.promisefunc({ page: this.pageIndex }) .then((res) => { let list = res ? res : []; if (list.length > 0) { //从list中取pageSize条数据出来 var tempList = []; for (let i = 0; i < this.pageSize; i++) { if (list.length > 0) { let tempIndex = parseInt(Math.random() * 1000) % list.length; tempList.push(list[tempIndex]); list.splice(tempIndex, 1); } } this.loadImagesHeight(tempList); //模拟预加载图片,获取图片高度 } else { this.loadImagesHeight(list); //处理数据 } }) .catch((res) => { //console.log("..fail: ", res); this.$toast.clear(); this.isLoading = false; //下拉刷新请求完成 this.loading = false; //上拉加载更多请求完成 }); }, loadImagesHeight(list) { var count = 0; //用来计数,表示是否所有图片高度已经获取 list.forEach((item, index) => { //创建图片对象,加载图片,计算图片高度 var img = new Image(); img.src = item.cover; img.onload = img.onerror = (e) => { count++; if (e.type == "load") { //图片加载成功 //计算图片缩放后的高度:图片原高度/原宽度 = 缩放后高度/缩放后宽度 list[index].imgHeight = Math.round( (img.height * this.boxWidth) / img.width ); // console.log('index: ', index, ', load suc, imgHeiht: ', list[index].imgHeight); } else { //图片加载失败,给一个默认高度50 list[index].imgHeight = 50; //console.log("index: ", index, ", 加载报错:", e); } //加载完成最后一个图片高度,开始下一步数据处理 if (count == list.length) { this.resolveDataList(list); } }; }); }, resolveDataList(list) { //处理数据 //下拉刷新,清空原数据 if (this.pageIndex <= 1) { this.itemCount = 0; this.dataList = []; this.lastRowHeights = [0, 0]; //存储每列的最后一行高度清0 } if (list.length >= this.pageSize) { this.pageIndex++; //还有下一页 } else { this.finished = true; //当前tab类型下所有数据已经加载完成 } //合并新老两个数组数据 this.dataList = [...this.dataList, ...list]; //判断页面是否有数据 this.haveData = this.dataList.length > 0 ? 2 : 1; // this.isLoading = false; //下拉刷新请求完成 // this.loading = false; //上拉加载更多请求完成 //console.log("...datalist: ", this.dataList); //console.log("...this.isLoading: ", this.isLoading); this.$nextTick(() => { setTimeout(() => { //渲染完成,计算每个item宽高,设置标签坐标定位 this.setItemElementPosition(); this.isLoading = false; //下拉刷新请求完成 this.loading = false; //上拉加载更多请求完成 }, 100); }); }, //获取每个item标签高度,设置item的定位 setItemElementPosition() { if (process.server) { return; } let parentEle = document.getElementById("data-list-box"); let boxEles = parentEle.getElementsByClassName("data-item"); for (let i = this.itemCount; i < boxEles.length; i++) { let tempEle = boxEles[i]; //上一个标签最小高度的列索引 let curColIndex = this.getMinHeightIndex(this.lastRowHeights); let boxTop = this.lastRowHeights[curColIndex] + this.boxMargin; let boxLeft = curColIndex * (this.boxWidth + this.boxMargin) + this.boxMargin; tempEle.style.left = boxLeft + "px"; tempEle.style.top = boxTop + "px"; this.lastRowHeights[curColIndex] = boxTop + tempEle.offsetHeight; // console.log('i = ', i, ', boxTop: ', boxTop, ', eleHeight: ', tempEle.offsetHeight); } this.itemCount = boxEles.length; //修改父级标签的高度 let maxHeight = Math.max.apply(null, this.lastRowHeights); parentEle.style.height = maxHeight + "px"; this.$toast.clear(); //console.log("...boxEles: ", boxEles.length, ", maxH: ", maxHeight); }, //获取数组中最小值的索引 getMinHeightIndex(arr) { var minHeight = Math.min.apply(null, arr); for (let i = 0; i < arr.length; i++) { if (arr[i] == minHeight) { return i; } } }, }, }; </script> <style lang="scss" scoped> .flow-box { background-color: #ffffff; width: 100vw; height: 100vh; } .flow-box .type-box { width: 100%; height: 40px; position: fixed; top: 0; font-size: 14px; background: #fff; color: rgba(0, 0, 0, 0.4); z-index: 99; border-bottom: 0.5px solid #dddddd; padding: 0 5px; } .type-box .type-list { white-space: nowrap; overflow-x: scroll; } .type-list .type-item { display: inline-block; padding: 0 12.5px; height: 40px; text-align: center; } .type-list .type-item .text { line-height: 37.5px; font-size: 14px; color: rgba(0, 0, 0, 0.4); margin: 0; } .type-list .type-item .line { background-color: #ffffff; } .type-list .type-item-on .text { font-size: 16px; color: #f0142d; } .type-list .type-item-on .line { width: 19px; height: 2px; margin: 0 auto; background-color: #f0142d; border-radius: 2px; } /* 隐藏滚动条 */ .type-list::webkit-scrollbar { display: none; } /* 列表数据样式 */ @keyframes data-item-ani { 0% { transform: scale(0.5); } 100% { transform: scale(1); } } .flow-box .data-list-box { position: relative; height: 100vh; /* margin-top: 40px; */ } .data-list-box .data-item { height: auto; position: absolute; background-color: #ffffff; /* box-shadow: 0px 0px 5px 2px rgba(0, 0, 0, 0.5); */ left: -1000px; animation: data-item-ani 0.4s; transition: left 0.6s, top 0.6s; transition-delay: 0.1s; } </style>使用方法<template> <Waterfall :dataSource="anlilist" :promisefunc="this.requestFunc || this.$api.requestZhuangxiuCase" > <template slot-scope="child"> <div class="hot-list" v-if="child.index === 5 && hotlist && hotlist.length" > <lookall :list="hotlist" /> </div> <div class="item-box"> <a href="/shop/" class="a"> <img :src="child.item.cover" class="data-cover" :alt="child.item.title" :style="{ width: '100%', height: child.item.imgHeight + 'px' }" /> <p class="p">{{ child.item.sign }}</p> <h3 class="h3">{{ child.item.title }}</h3> <i class="icon icon-an"></i> </a> <div class="news-list-info cf"> <a href="/shop/" class="source" ><img :src="child.item.iconimg" alt="" />{{ child.item.icontitle }}</a ><a href="" class="btn-zan">{{ child.item.count }}赞</a> </div> </div> </template> </Waterfall> </template> <script> import lookall from "~/components/jiajuhao/lookall.vue"; export default { components: { lookall, }, props: ["dataSource", "requestFunc", "hotlist"], data() { return { anlilist: this.dataSource || [], }; }, }; </script> <style lang="scss"> /* 全局样式 */ .hot-list { border-radius: 0.1rem; border: 1px solid #e5e5e5; overflow: hidden; background: #fff; padding: 0.35rem 0.2rem; margin-bottom: 0.25rem; .news-block { padding: 0; border: 0; h2 { border: 0; margin: 0; } ul { li { width: 100%; } } } } </style> <style lang="scss" scoped> .item-box { border-radius: 0.1rem; border: 1px solid #e5e5e5; overflow: hidden; background: #fff; .a { position: relative; display: block; height: auto; padding-bottom: 0.2rem; } .data-cover { margin-bottom: 0.1rem; display: block; } .p { padding: 0.05rem 0.2rem 0.1rem; font-size: 0.2rem; color: #999; } .h3 { padding: 0 0 0 0.2rem; font-size: 0.26rem; } .icon-an { width: 0.59rem; height: 0.33rem; background-position: 0 -2.5rem; position: absolute; top: 0; left: 0; } .data-name { font-size: 14px; padding: 5px 10px; text-align: left; } .news-list-info { margin: 0 0.1rem 0.1rem; } } </style>组件原地址转至:https://www.cnblogs.com/tandaxia/p/12189301.html
2021年12月28日
293 阅读
0 评论
0 点赞