更新记录

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 列表展示形式:可选值 rowcolumn


数据详情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>

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问