更新记录
2.0.0(2024-11-25)
- 全新版本升级,适用大数据量下的列表渲染,超过性能体验
1.0.1(2024-06-27)
完善使用文档,增加懒加载机制,提升长列表渲染效率,提高用户体验
1.0.0(2024-06-23)
ml-list长列表代码提交
查看更多平台兼容性
Vue2 | Vue3 |
---|---|
√ | √ |
App | 快应用 | 微信小程序 | 支付宝小程序 | 百度小程序 | 字节小程序 | QQ小程序 |
---|---|---|---|---|---|---|
HBuilderX 4.0 app-vue app-nvue | × | √ | × | × | × | × |
钉钉小程序 | 快手小程序 | 飞书小程序 | 京东小程序 | 鸿蒙元服务 |
---|---|---|---|---|
× | × | × | × | × |
H5-Safari | Android Browser | 微信浏览器(Android) | QQ浏览器(Android) | Chrome | IE | Edge | Firefox | PC-Safari |
---|---|---|---|---|---|---|---|---|
√ | √ | √ | √ | √ | √ | √ | √ | √ |
ml-list
支持:vue2、vue3、app、h5、小程序
1、适用于大量数据情况下的列表渲染;
2、支持列数配置、文本行数配置;
3、支持一键可以返回到顶部;
4、支持插槽,可以自定义列表内容;

组件版本说明
特别说明
本组件适用大量数据下的列表渲染,渲染效率高,不卡顿;采用懒加载、虚拟渲染的思想,丝滑滑动渲染功能!
功能概览
可以一次性处理加载 成百上千
条数据,将数据传给 ml-list
处理,实现高性能渲染能力;
其他组件
更多组件 请前往 作者主页查看 :https://ext.dcloud.net.cn/publisher?id=1784252
ml-swiper :该版本的插件 为 ml-swiper
的第一版 插件 适用于 案例测试、组件学习、抖音功能分析等;该版本的插件 仅支持 VUE3;
ml-swiper-v2 :该 插件 是在 ml-swiper
版本上做的升级,支持 VUE2、VUE3,并且支持了 m3u8 流媒体资源;此版本的插件 适用于 个人或者公司 的二次开发 定制专属插件;插件内 功能、方法 均有详细注释说明,并且 组件内详细说明了 抖音APP
的实现逻辑,以及抖音官方的说明;(通过该 版本的组件 可以实现一个 适用于自己或者公司 专属的 扩展组件)
ml-swiper-v3 :该 插件 是在 ml-swiper-v2
的基础上 做出的最终版,并且优化了 大资源数据下 导致的上下滑动卡顿的问题。该组件不需要特殊配置,容易上手,内置插槽 便于实现个性化UI页面,并且提供了 很多组件方法 便于自定义实现更多的业务逻辑;
ml-swiper-x:该 插件是 uni-app-x
版,性能远远高于 uni-app 流畅度和体验度 大大提升,让短视频刷起来更加流程丝滑(注:这得益于uni-app-x
打包成原生代码,大大提升了性能)
组件支持情况
ml-list
:支持 VUE2、VUE3、APP、H5、微信小程序
真实运行示例
真实运行示例
1、基础功能展示

2、列数和文本行数展示

3、插槽的使用展示

4、行号、单列多列展示

安装方式
本组件符合easycom规范,
HBuilderX 2.5.5
起,只需将本组件导入项目,在页面template
中即可直接使用,无需在页面中import
和注册components
。
组件参数props
属性名 | 类型 | 默认值 | 说明 | 必须 |
---|---|---|---|---|
list |
Array | [] | 视频数据,默认空数组,参数详情下见options 介绍 |
是 |
columns |
Number | 2 | 显示列数,仅 type="row" 时生效 |
否 |
emptyText |
String | 暂无数据 | 列表为空时的默认展示文本 | 否 |
textLines |
Number | 2 | 文本内容最大显示行数 | 否 |
nomore |
Boolean | false | 是否没有更多,当为 true 时不在触发 loadmore 加载更多函数 |
否 |
showNum |
Boolean | false | 是否显示序号 | 否 |
type |
String | row | 列表展示形式:可选值 row 、column |
否 |
数据详情options
属性名 | 类型 | 默认值 | 说明 | 必须 |
---|---|---|---|---|
id |
String | Number | "" | 资源ID | 否 |
title |
String | "" | 文本描述 | 否 |
poster |
String | "" | 视频封面,支持JPG、PNG等常见图片文件 | 否 |
----- | ----- | 其他属性可根据需要自定义 | ----- | ----- |
。。。 | 。。。 | 。。。 | 。。。 | 。。。 |
组件事件Events
事件 | 参数 | 解释 | 说明 |
---|---|---|---|
loadmore () 加载更多事件 |
- | 当传入的数据滚动到底时,触发该事件 | |
rowclick (event) 点击事件 |
event = { item, index } | index:当前点击资源的索引 item:当前点击资源的数据 |
当点击了列表中的某一条数据时触发该事件 |
组件插槽slot
插槽name | 插槽参数 | 使用场景 |
---|---|---|
item | item :当前资源的数据index :当前资源的索引width :当前item 数据所占容器的宽度 |
自定义列表样式 |
backTop | - | 自定义返回到顶部插槽【nvue不支持】 |
组件方法methods
事件 | 参数 | 说明 |
---|---|---|
onScroll() 回到顶部 |
Page.PageScrollOption:onPageScroll(e:Page.PageScrollOption); | 显示回到初始位置图标 |
loadList() 继续渲染 |
- | 继续渲染页面事件 |
vue3 版本示例代码
直接复制粘贴即可运行体验
<template>
<view>
<view class="view">
数据条数:<input :value="Number(total)" @input="inputTotal" inputmode="numeric" class="input" />
</view>
<view class="view">
显示列数:<input :value="Number(columns)" @input="inputColumns" inputmode="numeric" class="input" />
</view>
<view class="view">
标题行数:<input :value="Number(textLines)" @input="inputLines" inputmode="numeric" class="input" />
</view>
<view class="view">
显示行号:
<switch :checked="showNum" @change="showNumChange" />
</view>
<view class="view">
使用插槽:
<switch :checked="useSlot" @change="useSlotChange" />
</view>
<view class="view">
列表样式:
<radio-group @change="listTypeChange" style="flex-direction: row;">
<label style="margin: 0 5px;">
<radio value="column" :checked="listType === 'column'" />单列
</label>
<label style="margin: 0 5px;">
<radio value="row" :checked="listType === 'row'" />多列
</label>
</radio-group>
</view>
<!-- 下面是 ml-list 的使用示例 -->
<ml-list
v-if="dataList && dataList.length > 0"
ref="mlListRef"
:type="listType"
:list="dataList"
:nomore="nomore"
:showNum="showNum"
:columns="columns"
:textLines="textLines"
emptyText="暂无数据"
@loadmore="loadmore"
@rowclick="rowclick"
>
<!-- 使用插槽 -->
<template v-if="useSlot" v-slot:item="{ item, index, width }">
<image :src="item?.poster" :style="`width:${width}px;height: 100px;`" mode="aspectFit" />
<text style="font-size: 14px;color: #5e5e5e;">{{ (index + 1) + '、' }} {{ item?.title }}</text>
<view :style="`position: absolute;top: 75px;left: 0px;`">
<text style="color: #fff; background: rgba(0, 0, 0, 0.5);padding: 3px;font-size: 13px;border-radius: 5px;">
{{ item?.price }}
</text>
</view>
</template>
</ml-list>
</view>
</template>
<script setup >
import { ref } from 'vue';
import { onLoad, onReachBottom, onPageScroll } from '@dcloudio/uni-app';
const dataList = ref([]); // 数据列表
const nomore = ref(false); // 是否没有更多了
const total = ref(1000); // 数据量,这里直接加载 1000 条数据
const columns = ref(2); // 列数,仅 type="row" 时生效
const textLines = ref(2); // 文本行数
const showNum = ref(true); // 是否显示行号
const useSlot = ref(false); // 是否使用插槽
const listType = ref("row"); // 列表样式:row、column
const mlListRef = ref(null);
// rowclick,数据列表点击事件
function rowclick(row) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: `点击了 ${row.index}`, icon: "none" });
console.log(row);
}
// loadmore 加载更多事件
function loadmore() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// getList(); // 继续加载更多
nomore.value = true; // 没有更多数据了
}
// inputColumns 列数
function inputColumns(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
columns.value = Math.abs(Number(e.detail.value));
}
// inputLines 文化行数
function inputLines(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
textLines.value = Math.abs(Number(e.detail.value));
}
// inputTotal 数据条数
function inputTotal(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
nomore.value = false;
total.value = Math.abs(Number(e.detail.value));
dataList.value = [];
getList();
}
// showNumChange 是否显示行号
function showNumChange(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
showNum.value = e.detail.value;
uni.showToast({ title: showNum.value ? "显示行号" : "取消行号", icon: "none", mask: false, duration: 1500 });
}
// useSlotChange 是否使用插槽
function useSlotChange(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
useSlot.value = e.detail.value;
uni.showToast({ title: useSlot.value ? "使用插槽" : "取消插槽", icon: "none", mask: false, duration: 1500 });
}
// listTypeChange 列表样式:row、column
function listTypeChange(e) {
listType.value = e.detail.value;
let title = "", lines = 2;
if (listType.value === "row") {
lines = 2;
title = "多列展示";
} else {
lines = 4;
title = "单列展示";
}
textLines.value = lines;
uni.showToast({ title: title, icon: "none", mask: false, duration: 1500 });
}
// getList 加载列表数据
function getList() {
for (var i = 0; i < total.value; i++) {
dataList.value.push({
id: i + 1,
price: "¥29.9",
poster: "https://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG",
title: "与选项卡绑定值 value 对应的标识符,表示选项卡别名。默认值是tab面板的序列号,如第一个 tab 是 0"
});
}
}
// 页面滚动时,显示回到顶部图标
onPageScroll((e) => { mlListRef.value?.onScroll(e); });
// 触底时,继续渲染更多数据
onReachBottom(() => { mlListRef.value?.loadList(); });
// 初始化数据列表
onLoad((_option) => {
dataList.value = [];
getList();
});
</script>
<style scoped>
.view {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 10px;
flex-flow: row nowrap;
margin-top: 10px;
}
.input {
width: 150px;
height: 35px;
border-radius: 5px;
padding-left: 8px;
border: 1rpx solid #e7e7e7;
}
</style>
vue2 版本示例代码
直接复制粘贴即可运行体验
<template>
<view>
<view class="view">
数据条数:<input :value="Number(total)" @input="inputTotal" inputmode="numeric" class="input" />
</view>
<view class="view">
显示列数:<input :value="Number(columns)" @input="inputColumns" inputmode="numeric" class="input" />
</view>
<view class="view">
标题行数:<input :value="Number(textLines)" @input="inputLines" inputmode="numeric" class="input" />
</view>
<view class="view">
显示行号:
<switch :checked="showNum" @change="showNumChange" />
</view>
<view class="view">
使用插槽:
<switch :checked="useSlot" @change="useSlotChange" />
</view>
<view class="view">
列表样式:
<radio-group @change="listTypeChange" style="flex-direction: row;">
<label style="margin: 0 5px;">
<radio value="column" :checked="listType === 'column'" />单列
</label>
<label style="margin: 0 5px;">
<radio value="row" :checked="listType === 'row'" />多列
</label>
</radio-group>
</view>
<!-- 下面是 ml-list 的使用示例 -->
<ml-list
v-if="dataList && dataList.length > 0"
ref="mlListRef"
:type="listType"
:list="dataList"
:nomore="nomore"
:showNum="showNum"
:columns="columns"
:textLines="textLines"
emptyText="暂无数据"
@loadmore="loadmore"
@rowclick="rowclick"
>
<!-- 使用插槽 -->
<template v-if="useSlot" v-slot:item="{ item, index, width }">
<image :src="item?.poster" :style="`width:${width}px;height: 100px;`" mode="aspectFit" />
<text style="font-size: 14px;color: #5e5e5e;">{{ (index + 1) + '、' }} {{ item?.title }}</text>
<view :style="`position: absolute;top: 75px;left: 0px;`">
<text style="color: #fff; background: rgba(0, 0, 0, 0.5);padding: 3px;font-size: 13px;border-radius: 5px;">
{{ item?.price }}
</text>
</view>
</template>
</ml-list>
</view>
</template>
<script >
export default {
data() {
return {
dataList: [], // 数据列表
nomore: false, // 没有更多数据
total: 1000, // 数据量,这里直接加载 1000 条数据
columns: 2, // 列数,仅 type="row" 时生效
textLines: 2, // 文本行数
showNum: true, // 显示行号
useSlot: false, // 使用插槽
listType: "row" // 列表样式:row、column
}
},
// 页面滚动时,显示回到顶部图标
onPageScroll(e) {
this.$refs.mlListRef?.onScroll(e);
},
// 触底时,继续渲染更多数据
onReachBottom() {
this.$refs.mlListRef?.loadList();
},
// 初始化数据列表
onLoad() {
this.dataList = [];
this.getList()
},
methods: {
// 加载数据列表
getList() {
for (var i = 0; i < this.total; i++) {
this.dataList.push({
id: i + 1,
price: "¥29.9",
poster: "https://gips2.baidu.com/it/u=195724436,3554684702&fm=3028&app=3028&f=JPEG",
title: "与选项卡绑定值 value 对应的标识符,表示选项卡别名。默认值是tab面板的序列号,如第一个 tab 是 0"
});
}
},
// inputColumns 列数
inputColumns(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
this.columns = Math.abs(Number(e.detail.value));
},
// inputLines 文本行数
inputLines(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
this.textLines = Math.abs(Number(e.detail.value));
},
// inputTotal 数据条数
inputTotal(e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
if (isNaN(e.detail.value)) return;
this.nomore = false;
this.total = Math.abs(Number(e.detail.value));
this.dataList = [];
this.getList();
},
// showNumChange 是否显示行号
showNumChange: function (e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
this.showNum = e.detail.value;
uni.showToast({ title: this.showNum ? "显示行号" : "取消行号", icon: "none", mask: false, duration: 1500 });
},
// useSlotChange 是否使用插槽
useSlotChange: function (e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
this.useSlot = e.detail.value;
uni.showToast({ title: this.useSlot ? "使用插槽" : "取消插槽", icon: "none", mask: false, duration: 1500 });
},
// listTypeChange 列表样式 row、column
listTypeChange: function (e) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
this.listType = e.detail.value;
let title = "", lines = 2;
if (this.listType === "row") {
lines = 2;
title = "多列展示";
} else {
lines = 4;
title = "单列展示";
}
this.textLines = lines;
uni.showToast({ title: title, icon: "none", mask: false, duration: 1500 });
},
// rowclick 数据列表点击事件
rowclick(row) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: `点击了 ${row.index}`, icon: "none" });
console.log(row);
console.log(row.item.title);
},
// loadmore 加载更多事件
loadmore() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// this.getList(); // 继续加载更多数据
this.nomore = true; // 没有更多数据了
}
}
}
</script>
<style scoped>
.view {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 0 10px;
flex-flow: row nowrap;
margin-top: 10px;
}
.input {
width: 150px;
height: 35px;
border-radius: 5px;
padding-left: 8px;
border: 1rpx solid #e7e7e7;
}
</style>
完整参数事件
<template>
<view>
<ml-list
ref="mlListRef"
:type="listType"
:list="dataList"
:nomore="nomore"
:showNum="showNum"
:columns="columns"
:textLines="textLines"
emptyText="暂无数据"
@loadmore="loadmore"
@rowclick="rowclick"
/>
</view>
</template>
<script setup >
import { ref } from 'vue';
const dataList = ref([]); // 数据列表
const nomore = ref(false); // 是否没有更多了
const total = ref(1000); // 数据量,这里直接加载 1000 条数据
const columns = ref(2); // 列数,仅 type="row" 时生效
const textLines = ref(2); // 文本行数
const showNum = ref(true); // 是否显示行号
const useSlot = ref(false); // 是否使用插槽
const listType = ref("row"); // 列表样式:row、column
const mlListRef = ref(null);
// rowclick,数据列表点击事件
function rowclick(row) { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
uni.showToast({ title: `点击了 ${row.index}`, icon: "none" });
console.log(row);
}
// loadmore 加载更多事件
function loadmore() { // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
// getList(); // 继续加载更多
nomore.value = true; // 没有更多数据了
}
// 页面滚动时,显示回到顶部图标
onPageScroll((e) => { mlListRef.value?.onScroll(e); });
// 触底时,继续渲染更多数据
onReachBottom(() => { mlListRef.value?.loadList(); });
</script>