更新记录

1.1.7(2024-11-15)

  • 提供是否自动切换下一个视频的属性 autoChange;提供 slideDuration 滑动动画时长属性

1.1.6(2024-10-31)

  • 提供自定义 进度条报错案例说明 插槽,详细请看文档

1.1.5(2024-10-30)

  • 提供vu2-nvue代码示例,更新插槽
查看更多

平台兼容性

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-swiper-v2、ml-swiper-v3 均支持 VUE2、VUE3

下文提供了 vue3 版本代码示例,末尾提供了 vue2 版本代码示例

特别说明:

  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 后面的 -v{n} 仅表示 ml-swiper 组件的版本 并不代表 vue 的版本
  • ml-swiper-v2、ml-swiper-v3 均支持 VUE2、VUE3

特别说明

特别说明:

  • 收费代码有版权问题,且发布后的插件会被自动加密,如非购买本插件出现的各种问题,均与此有关。
  • 如正常购买了插件,出现了运行报错,或者案例代码运行报错,均可入群与作者联系,作者将提供帮助,并解决问题。
  • 该组件将持续维护,有任何问题均可留言。
  • 非购买出现的问题,非组件本身问题 作者将不予处理和解决,切勿恶意差评,创作不易 还请理解。
  • 购买普通版权后,想要源码的可插件市场直接购买源码,如需要继续扩展 可以直接联系作者 索要底层源代码 以供扩展使用。
  • 使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。
  • 推荐使用使用 ml-swiper-v3 组件,底层已经封装完成,无需考虑数据量的问题,开箱即用。

功能概览

  • 支持视频双击点赞功能,单击暂停播放和播放功能
  • 支持配置 是否自动切换下一个视频,当前视频播放完成后 会自动切换到下一个视频
  • 支持 m3u8 流媒体视频资源,可以直接使用,无需特殊配置
  • 提供可自定义进度条插槽,可以实现自定义进度条功能,不想自定义可以使用默认进度条功能
  • 支持微信小程序、H5、和 APP
  • 提供了多种插槽,可以完全实现自定义各种组件

以上只是简单几个小功能,更多具体功能请详细阅读文档

案例报错说明

缺少函数名

网页示例缺少函数名,可以直接使用readme.md中的示例代码

// @ondblclick 
function (e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释 被转义掉的函数名为:
    console.log(e);
}
  • 使用案例时特别注意,案例中的函数名会被转义为空,报错时 请先检查是否存在函数名为空的,为空时补全后重新运行即可。

试用时报错

试用时报错:Failed to mount component: template or render function not defined. found in......

如果试用时组件运行报错 [Vue warn]: Failed to mount component: template or render function not defined. found in ...... 请注意,原因是试用组件 被加密了,导致无法被解析到,正常购买不会有问题,如有其他问题可在群里提出,作者看到后会帮助解决处理。

初始值为空

初始值为空:[system] TypeError: Cannot read properties of undefined (reading 'includes')at ......

如果购买了插件,运行时报错:[system] TypeError: Cannot read properties of undefined (reading 'includes')at ......,原因是datas[0]?.includes初始值为空,导致的报错,请修改示例代码

<!-- 点赞:修改前 -->
<uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
<!-- 修改后 -->
<uni-icons type="heart-filled" size="35" color="#fff" />

<!-- 收藏:修改前 -->
<uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
<!-- 修改后 -->
<uni-icons type="star-filled" size="35" color="#fff" />

扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3



组件支持情况

ml-swiper-v2版本:支持 VUE2、VUE3

H5:支持 m3u8 流媒体资源




组件其他版本

更多组件 请前往 作者主页查看 :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打包成原生代码,大大提升了性能)




扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3




真实运行示例


APP-雷电模拟器




手机端-QQ浏览器




组件参数props

属性名 类型 默认值 说明 必须
list Array [] 视频数据,默认空数组,参数详情下见options介绍
width String 100% 组件宽度,默认与设备同宽
height String 100% 组件高度,默认与设备同高
criticalVal Number 2 临界值,当 list.length - currentIndex >= criticalVal时触发加载更多事件
progress Boolean true 是否显示进度条,默认显示进度条
duration Boolean true 是否显示播放时间,默认显示播放时长
customProgress Boolean false 自定义进度条
autoChange Boolean false 是否自动切换下一个视频
slideDuration Number 500 滑动动画时长,默认 500 与uniapp官方保持一致


视频数据详情options

属性名 类型 默认值 说明 必须
url String "" 视频资源地址
title String "" 视频标题
poster String "" 视频封面,支持JPG、PNG等常见图片文件
----- ----- 其他属性可根据需要自定义 ----- -----
比如: userInfo = {} 用户相关数据
比如: commentList = [] 评论数据列表
比如: date 视频发布时间
比如: location IP属地信息
。。。 。。。 。。。 。。。 。。。

组件事件Events

事件 参数 解释 说明
onchange (event)
滑动事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
当视频上下滑动时,触发 onchange事件
onplay (event)
播放事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
playing:当前播放状态
当前视频播放时,触发 onplay事件
onpause (event)
暂停事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:正在播放的视频数据
playing:当前播放状态
当前视频暂停时,触发 onpause事件
onended (event)
结束事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
当前视频播放结束时,触发 onpause事件
ontimeupdate (event)
进度变更事件
event事件 event:APP、小程序、H5 原生事件 视频进度条变化时触发ontimeupdate事件,可用来自定义进度条
onwaiting (event)
出现缓冲事件
event = {index, context, video} index:当前视频的索引
context:video的上下文对象
当前视频播放出现缓冲时,触发 onwaiting事件
onerror (event)
播放出错事件
event = {index, context, video, error, event} index:当前视频的索引
context:video的上下文对象
当前视频播放出错时,触发 onerror事件
onclick (event)
视频单击事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:当前视频的数据信息
playing:是否正在播放
当单击视频时触发onclick事件
ondblclick (event)
视频双击事件
event = {index, context, video, playing} index:当前视频的索引
context:video的上下文对象
video:当前视频的数据信息
当双击视频时触发ondblclick事件
loadmore(event)
加载更多数据
event = { index } index:当前视频的索引 list.length - currentIndex >= criticalVal时触发加载更多事件

组件插槽slot

插槽name 插槽参数 使用场景
default item:当前视频资源数据
index:当前视频索引
视频上方自定义播放按钮或者其他功能
right item:当前视频资源数据
index:当前视频索引
可以实现抖音右侧工具条:点赞、收藏、评论、设置等
bottom item:当前视频资源数据
index:当前视频索引
可以实现抖音作者名称,视频标题等
progress current:当前时间(s)
duration:总时长(s)
percentage:进度比
可以配合ontimeupdate拿到视频时长和当前时长 实现自定义进度条,需要progresscustomProgress 都为 true

插槽示例代码

default默认插槽

      <template v-slot:default="{ index }">
        <view class="body">
          <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
        </view>
      </template>

right右侧工具条

      <template v-slot:right="{ item }">
        <view class="right">
          <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
          <!-- 喜欢 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" color="#fff" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" color="#fff" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>

bottom底部视频标题

      <template v-slot:bottom="{ item }">
        <view class="bottom">
          <text class="title">{{ item?.title }}</text>
        </view>
      </template>

progress底部自定义进度条,需要customProgressprogress 同时设置为 true

      <template v-slot:progress="{ current, duration, percentage }">
       <view v-if="percentage" class="progress-box">
          <text>{{ current }}</text> 
          <progress class="progress" :percent="percentage" font-size="13"
            stroke-width="2" activeColor="#3ea5ff" backgroundColor="#EBEBEB" 
            active active-mode="forwards" />
          <text>{{ duration }}</text>
       </view>
      </template>

组件方法methods

事件 参数 说明
fullScreen() 进入全屏 - 全屏播放
exitFullScreen() 退出全屏 - 退出全屏播放
setRate(rate) 设置倍速 Number: 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0 当前视频倍速,不同平台支持不一样
setSeek(val) 指定播放时间 Number: 60 指定播放时间,单位秒,从 60秒处 播放


方法使用示例

<!-- ...省略... -->
<ml-swiper-v2 ref="mlSwiperRef"></ml-swiper-v2>
<!-- ...省略... -->
<script setup>
  import { ref } from 'vue';
  const mlSwiperRef = ref(null);

  // ...省略...
  /**
   * @fullScreen 全屏
   */
  function fullScreen() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    mlSwiperRef.value?.fullScreen();
    // context?.requestFullScreen();
  }

  /**
   * @exitFullScreen 退出全屏
   */
  function exitFullScreen() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    mlSwiperRef.value?.exitFullScreen();
    // context?.exitFullScreen();
  }

  /**
   * @setRate 倍速
   * @param {Number} rate 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0
   */
  function setRate(rate) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    mlSwiperRef.value?.setRate(rate);
    // context?.playbackRate(rate);
  }

  /**
   * @setSeek 跳转到指定位置播放
   * @param {Number} val 单位秒
   */
  function setSeek(val) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    mlSwiperRef.value?.setSeek(val);
    // context?.seek(val);
  }
  /// ...省略...
</script>



扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3



vue3 版本示例代码

组件示例代码

微信小程序 和 H5 网页端示例代码(注:H5端 需要安装 hls,详见下文: H5端使用说明

# H5 端 安装插件
npm install hls.js

示例代码中用到了 uni-icons 请自行安装

index.vue

<template>
  <template v-if="type == 'v2'">
    <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
    <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
      <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
      <template v-slot:default="{ index }">
        <view class="body">
          <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
        </view>
      </template>
      <!-- 右侧 -->
      <template v-slot:right="{ item }">
        <view class="right">
          <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
          <!-- 喜欢 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>
      <!-- 底部 -->
      <template v-slot:bottom="{ item }">
        <view class="bottom">
          <text class="title">{{ item?.title }}</text>
        </view>
      </template>
    </ml-swiper-v2>
  </template>

  <template v-if="type == 'v3'">
    <ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
      <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
      <template v-slot:default="{ index }">
        <view class="body">
          <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
        </view>
      </template>
      <!-- 右侧 -->
      <template v-slot:right="{ item }">
        <view class="right">
          <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
          <!-- 喜欢 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>
      <!-- 底部 -->
      <template v-slot:bottom="{ item }">
        <view class="bottom">
          <text class="title">{{ item?.title }}</text>
        </view>
      </template>
    </ml-swiper-v3>
  </template>
</template>

<script setup>
  import { onMounted, reactive, ref } from 'vue';
  import { onHide } from "@dcloudio/uni-app";

  const lists = ref([]);
  const type = ref("v2"); // 当前 ml-swiper版本 (当前版本是 ml-swiper-v2)
  const datas = reactive({0:[],1:[]});

  let count = 0, context = null;
  // @onplay 视频播放
  function onplay(event){
    context = event?.context;
    console.log("onplay:", event?.playing);
  }
  // @avatarClick
  function avatarClick() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }
  // @comment
  function comment(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
  }
  // @forward
  function forward(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }
  // @iconClick
  function iconClick(type, id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }
  // @ondblclick
  function ({ video }) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log(video?.videoId);
    iconClick(0, video?.videoId);
  }

  // @loadmore
  function loadmore() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }

  // 页面隐藏时,暂停播放
  onHide(() => {
    if (context) { context?.pause(); }
  });

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试(模拟请求后台获取数据)
    uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
    setTimeout(() => {
      let size = type.value == 'v2' ? 2 : 50;
      for (var i = 0; i < size; i++) {
        getList().forEach((item) => {
          count = count + 1;
          item.title = `【${count}】` + item.title;
          lists.value.push(item);
        });
      }
      uni.hideToast();
    }, 1500);
  });

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>

<style scoped lang="scss">
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .progress {
    /* #ifndef APP-NVUE */
    width: 80%;
    /* #endif */
    height: 2px;
  }

  .progress-box {
    /* #ifndef APP-NVUE */
    width: 100%;
    display: flex;
    /* #endif */
    color: #fff;
    align-items: center;
    justify-content: space-around;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>



H5端使用说明

如果需要编译为 H5 端,则需要安装插件 hls,APP端 和 微信小程序 不需要安装

npm install hls.js

测试说明:H5 端测试了 QQ浏览器 支持良好,夸克浏览器 不支持该插件(夸克浏览器只支持内置播放器且悬浮于顶层)。 其他浏览器均为测试,如需使用 请自行测试。



APP代码示例

app端

APP端开发,暂不支持 在 vue 页面中使用 ml-swiper-v3 组件,如需开发 APP 应用,请将 vue 页面 改为 nvue 页面,其他不变即可正常使用该组件。

特别说明:APP 端 不支持动画效果的 progress 进度条,其他功能依然可以正常使用

示例代码中用到了 uni-icons 请自行安装

index.nvue

<template>
  <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
  <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
    <!-- 右侧 -->
    <template v-slot:right="{ item }">
      <view class="right">
        <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
        <!-- 喜欢 -->
        <view class="icon">
          <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
          <text class="icon-val">666</text>
        </view>
        <!-- 评论 -->
        <view class="icon">
          <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
          <text class="icon-val">668</text>
        </view>
        <!-- 收藏 -->
        <view class="icon">
          <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
          <text class="icon-val">888</text>
        </view>
        <!-- 转发 -->
        <view class="icon">
          <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
          <text class="icon-val">999</text>
        </view>
      </view>
    </template>
    <!-- 底部 -->
    <template v-slot:bottom="{ item }">
      <view class="bottom">
        <text class="title">{{ item?.title }}</text>
      </view>
    </template>
  </ml-swiper-v2>
</template>
<script setup >
  import { onMounted, reactive, ref } from 'vue';
  import { onHide } from "@dcloudio/uni-app";

  const lists = ref([]);
  const type = ref("v3");
  const datas = reactive({0:[],1:[]});

  let count = 0, context = null;
  // @onplay 视频播放
  function onplay(event){
    context = event?.context;
    console.log("onplay:", event?.playing);
  }
  // @avatarClick
  function avatarClick() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }
  // @comment
  function comment(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
  }
  // @forward
  function forward(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }
  // @iconClick
  function iconClick(type, id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }
  // @ondblclick
  function ({ video }) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log(video?.videoId);
    iconClick(0, video?.videoId);
  }
  // @loadmore
  function loadmore() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }
  // 页面隐藏时 暂停播放
  onHide(() => {
    if (context) { context?.pause(); }
  });

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试(模拟请求后台获取数据)
    uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
    setTimeout(() => {
      let size = type.value == 'v2' ? 2 : 50;
      for (var i = 0; i < size; i++) {
        getList().forEach((item) => {
          count = count + 1;
          item.title = `【${count}】` + item.title;
          lists.value.push(item);
        });
      }
      uni.hideToast();
    }, 1500);
  });

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>
<style scoped lang="scss">
  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px 0;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .progress {
    /* #ifndef APP-NVUE */
    width: 80%;
    /* #endif */
    height: 2px;
  }

  .progress-box {
    /* #ifndef APP-NVUE */
    width: 100%;
    display: flex;
    /* #endif */
    color: #fff;
    align-items: center;
    justify-content: space-around;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>



完整参数事件

以下是 ml-swiper-v3 中 所有的 参数、事件和方法的完整示例,示例代码中用到了 uni-icons 请自行安装

<template>
  <ml-swiper-v2 
    v-if="lists && lists.length > 0"
    ref="mlSwiperRef"
    :list="lists"
    :width="width"
    :height="height"
    :criticalVal="criticalVal"
    :progress="progress"
    :duration="duration"
    :autoChange="autoChange"
    :customProgress="customProgress"
    @onchange="onchange"
    @loadmore="loadmore"
    @onclick="onclick"
    @ondblclick=""
    @onplay="onplay"
    @onpause="onpause"
    @onended=""
    @ontimeupdate=""
    @onwaiting=""
    @onerror="onerror"
  >
    <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
    <template v-slot:default="{ index }">
      <view class="body">
        <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
      </view>
    </template>
    <!-- 右侧 -->
    <template v-slot:right="{ item }">
      <view class="right">
        <image class="userAvatar" :src="item?.author?.avatar" @click="avatarClick"></image>
        <!-- 喜欢 -->
        <view class="icon">
          <uni-icons type="heart-filled" size="35" :color="datas[0]?.includes(item.videoId) ? '#ff0004' : '#fff'" @tap="iconClick(0, item.videoId)" />
          <text class="icon-val">666</text>
        </view>
        <!-- 评论 -->
        <view class="icon">
          <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
          <text class="icon-val">668</text>
        </view>
        <!-- 收藏 -->
        <view class="icon">
          <uni-icons type="star-filled" size="35" :color="datas[1]?.includes(item.videoId) ? '#ff0' : '#fff'" @tap="iconClick(1, item.videoId)" />
          <text class="icon-val">888</text>
        </view>
        <!-- 转发 -->
        <view class="icon">
          <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
          <text class="icon-val">999</text>
        </view>
      </view>
    </template>
    <!-- 底部 -->
    <template v-slot:bottom="{ item }">
      <view class="bottom">
        <text class="title">{{ item?.title }}</text>
      </view>
    </template>
  </ml-swiper-v2>
</template>

<script setup>
  import { onMounted, reactive, ref } from 'vue';
  import { onHide } from "@dcloudio/uni-app";

  const lists = ref([]); // 视频资源列表 [{url: "资源链接必须", title: "标题", poster: "封面"}]
  const windowInfo = uni.getWindowInfo();
  const width = ref(windowInfo.windowWidth + 'px'); // 组件宽度(不写默认设备同宽)
  const height = ref(windowInfo.windowHeight + 'px'); // 组件高度(不写默认设备同高)
  const criticalVal = ref(2); // 临界值(当观看到倒数 2 个视频时,触发加载更多事件,不写默认 2)
  const progress = ref(true); // 进度条(不写默认 true,APP 端不显示进度动画)
  const duration = ref(true); // 播放时间(不写默认 true,显示播放时间)
  const mlSwiperRef = ref(null);
  const autoChange = ref(false); // 是否自动切换下一个视频
  const customProgress = ref(false); // 是否自定义进度条
  const datas = reactive({0:[],1:[]});
  let count = 0;
  let context = null;

  onMounted(() => {
    // 这里直接生成 200 条视频数据进行测试
    for (var i = 0; i < 50; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  });
  // @avatarClick
  function avatarClick() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
  }
  // @comment
  function comment(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
    fullScreen();
  }
  // @forward
  function forward(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "转发", icon: "none", duration: 1000 });
  }
  // @iconClick
  function iconClick(type, id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    datas[type] = (datas[type] || []);
    let index = datas[type]?.indexOf(id);
    if (index >= 0) {
      datas[type].splice(index, 1);
    } else {
      datas[type].push(id);
    }
  }
  // @onchange
  function onchange(e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("滑动事件:", e);
  }

  /**
   * @loadmore 加载更多数据(请求后台 获取更多数据)
   */
  function loadmore() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
    for (var i = 0; i < 2; i++) {
      getList().forEach((item) => {
        count = count + 1;
        item.title = `【${count}】` + item.title;
        lists.value.push(item);
      });
    }
  }
  // @onclick
  function onclick(e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("单击事件:", e);
  }
  // @ondblclick
  function (e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("双击事件:", e);
  }
  // @onplay
  function onplay(e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("播放事件:", e);
    context = e.context;
  }
  // @onpause
  function onpause(e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("暂停事件:", e);
  }
  // @onended
  function (e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("结束事件:", e);
  }
  // @ontimeupdate
  function (e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    // console.log("加载事件:", e);
  }
  // @onwaiting
  function (e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("缓冲事件:", e);
  }
  // @onerror
  function onerror(e) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    console.log("报错事件:", e);
  }

  /**
   * @fullScreen 全屏
   */
  function fullScreen() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "全屏", icon: "none", duration: 1000 });
    mlSwiperRef.value?.fullScreen();
    // context?.requestFullScreen();
  }

  /**
   * @exitFullScreen 退出全屏
   */
  function exitFullScreen() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "退出全屏", icon: "none", duration: 1000 });
    mlSwiperRef.value?.exitFullScreen();
    // context?.exitFullScreen();
  }

  /**
   * @setRate 倍速
   * @param {Number} rate 0.5 | 0.8 | 1.0 | 1.25 | 1.5 | 2.0
   */
  function setRate(rate) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    uni.showToast({ title: "倍速" + rate, icon: "none", duration: 1000 });
    mlSwiperRef.value?.setRate(rate);
    // context?.playbackRate(rate);
  }

  /**
   * @setSeek 跳转到指定位置播放
   * @param {Number} val 单位秒
   */
  function setSeek(val) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
    mlSwiperRef.value?.setSeek(val);
    // context?.seek(val);
  }
  // 页面隐藏时 暂停播放
  onHide(() => {
    if (context) { context?.pause(); }
  });

  const getList = () => {
    return [
      {
        videoId: lists.value.length + 1,
        title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
        uploadTime: "2023-11-08 19:41",
        ipLocation: "上海",
        author: {
          authorId: 101,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "陌路",
          genderName: "男"
        }
      },
      {
        videoId: lists.value.length + 2,
        title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
        uploadTime: "2023-10-02 09:41",
        ipLocation: "贵州",
        author: {
          authorId: 102,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "御姐呀",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 3,
        title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
        uploadTime: "2023-08-23 00:41",
        ipLocation: "广州",
        author: {
          authorId: 103,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "野花猫",
          genderName: "女"
        }
      },
      {
        videoId: lists.value.length + 4,
        title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
        poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
        url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
        uploadTime: "2023-07-02 14:41",
        ipLocation: "山西",
        author: {
          authorId: 104,
          avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
          nickName: "蓝姬",
          genderName: "女"
        }
      }
    ];
  }
</script>

<style scoped lang="scss">
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .progress {
    /* #ifndef APP-NVUE */
    width: 80%;
    /* #endif */
    height: 2px;
  }

  .progress-box {
    /* #ifndef APP-NVUE */
    width: 100%;
    display: flex;
    /* #endif */
    color: #fff;
    align-items: center;
    justify-content: space-around;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }
</style>


扩展或自定义


扩展或自定义 请阅读本插件中的:抖音官方示例.md

无需扩展和自定义 推荐使用:ml-swiper-v3

vue2 版本示例代码

vue代码示例

示例代码中用到了 uni-icons 请自行安装

<template>
  <view class="wh-full">
    <template v-if="type == 'v2'">
      <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
      <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
        <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
        <template v-slot:default="{ index }">
          <view class="body">
            <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
          </view>
        </template>
        <!-- 右侧 -->
        <template v-slot:right="{ item }">
          <view class="right" v-show="item != null">
            <!-- 用户头像 -->
            <image v-show="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
            </image>
            <!-- 点赞 -->
            <view class="icon">
              <uni-icons type="heart-filled" size="35" :color="datas[0].includes(item.videoId) ? '#ff0004' : '#fff'"
                @tap="iconClick(0, item.videoId)" />
              <text class="icon-val">666</text>
            </view>
            <!-- 评论 -->
            <view class="icon">
              <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
              <text class="icon-val">668</text>
            </view>
            <!-- 收藏 -->
            <view class="icon">
              <uni-icons type="star-filled" size="35" :color="datas[1].includes(item.videoId) ? '#ff0' : '#fff'"
                @tap="iconClick(1, item.videoId)" />
              <text class="icon-val">888</text>
            </view>
            <!-- 转发 -->
            <view class="icon">
              <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
              <text class="icon-val">999</text>
            </view>
          </view>
        </template>
        <!-- 底部 -->
        <template v-slot:bottom="{ item }">
          <view class="bottom" v-show="item != null">
            <text class="title">{{ item.title }}</text>
          </view>
        </template>
      </ml-swiper-v2>
    </template>
    <template v-if="type == 'v3'">
      <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
      <ml-swiper-v3 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
        <!-- 自定义内容,这里设置100%后 层级高于video,会导致单击、双击功能失效 -->
        <template v-slot:default="{ index }">
          <view class="body">
            <text class="text">{{ index + 1 }} / {{ lists.length }}</text>
          </view>
        </template>
        <!-- 右侧 -->
        <template v-slot:right="{ item }">
          <view class="right" v-show="item != null">
            <!-- 用户头像 -->
            <image v-show="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
            </image>
            <!-- 点赞 -->
            <view class="icon">
              <uni-icons type="heart-filled" size="35" :color="datas[0].includes(item.videoId) ? '#ff0004' : '#fff'"
                @tap="iconClick(0, item.videoId)" />
              <text class="icon-val">666</text>
            </view>
            <!-- 评论 -->
            <view class="icon">
              <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
              <text class="icon-val">668</text>
            </view>
            <!-- 收藏 -->
            <view class="icon">
              <uni-icons type="star-filled" size="35" :color="datas[1].includes(item.videoId) ? '#ff0' : '#fff'"
                @tap="iconClick(1, item.videoId)" />
              <text class="icon-val">888</text>
            </view>
            <!-- 转发 -->
            <view class="icon">
              <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
              <text class="icon-val">999</text>
            </view>
          </view>
        </template>
        <!-- 底部 -->
        <template v-slot:bottom="{ item }">
          <view class="bottom" v-show="item != null">
            <text class="title">{{ item.title }}</text>
          </view>
        </template>
      </ml-swiper-v3>
    </template>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        lists: [], // 资源数据
        datas: {0:[],1:[]}, // 点赞、收藏等数据
        count: 0, // 计数器
        context: null,
        type: "v2" // 使用的组件版本,v2:ml-swiper-v2、v3:ml-swiper-v3
      }
    },
    onHide() {
      if (this.context) { this.context?.pause(); }
    },
    onLoad() {
      // (模拟请求后台获取数据)
      uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
      setTimeout(() => {
        let size = this.type == "v3" ? 50 : 2;
        for (var i = 0; i < size; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
        uni.hideToast();
      }, 1500);
    },
    methods: {
      // @avatarClick
      avatarClick() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
        uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
      },
      // @comment
      comment(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
        uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
      },
      // @forward
      forward(_id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
        uni.showToast({ title: "转发", icon: "none", duration: 1000 });
      },
      // @iconClick
      iconClick(type, id) {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
        this.datas[type] = (this.datas[type] || []);
        let index = this.datas[type]?.indexOf(id);
        if (index >= 0) {
          this.datas[type].splice(index, 1);
        } else {
          this.datas[type].push(id);
        }
      },
      // @loadmore
      loadmore() {  // 这里的函数名可能会被转义掉,如果函数名为空 请看注释
        uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
        for (var i = 0; i < 2; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
      },
      // @ondblclick
      ({ video }) {
        console.log(video?.videoId);
        this.iconClick(0, video?.videoId);
      },
      getList() {
        return [
          {
            videoId: this.lists.length + 1,
            title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
            uploadTime: "2023-11-08 19:41",
            ipLocation: "上海",
            author: {
              authorId: 101,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "陌路",
              genderName: "男"
            }
          },
          {
            videoId: this.lists.length + 2,
            title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
            uploadTime: "2023-10-02 09:41",
            ipLocation: "贵州",
            author: {
              authorId: 102,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "御姐呀",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 3,
            title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
            uploadTime: "2023-08-23 00:41",
            ipLocation: "广州",
            author: {
              authorId: 103,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "野花猫",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 4,
            title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
            uploadTime: "2023-07-02 14:41",
            ipLocation: "山西",
            author: {
              authorId: 104,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "蓝姬",
              genderName: "女"
            }
          }
        ];
      }
    }
  }
</script>

<style scoped>
  .body {
    position: absolute;
    top: 0;
    margin: 0 auto;
  }

  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0 auto;
  }

  .icon {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px auto;
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .progress {
    /* #ifndef APP-NVUE */
    width: 80%;
    /* #endif */
    height: 2px;
  }

  .progress-box {
    /* #ifndef APP-NVUE */
    width: 100%;
    display: flex;
    /* #endif */
    color: #fff;
    align-items: center;
    justify-content: space-around;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }

  .wh-full {
    width: 100%;
    height: 100%;
    padding: 0;
    margin: 0 auto;
    overflow: hidden;
  }
</style>

nvue代码示例

示例代码中用到了 uni-icons 请自行安装

<template>
  <view class="wh-full" :style="screenFull">
    <!-- 这里使用 v-if 是为了防止 请求后台时还没返回数据 导致组件初始化时没有视频资源 而出现错误 -->
    <ml-swiper-v2 v-if="lists && lists.length > 0" :list="lists" @loadmore="loadmore" @ondblclick="" @onplay="onplay">
      <!-- 右侧 -->
      <template v-slot:right="{ item }">
        <view class="right" v-if="item != null">
          <!-- 用户头像 -->
          <image v-if="item.author != null" class="userAvatar" :src="item.author.avatar" @click="avatarClick">
          </image>
          <!-- 点赞 -->
          <view class="icon">
            <uni-icons type="heart-filled" size="35" color="#fff" @tap="iconClick('0', item.videoId)" />
            <text class="icon-val">666</text>
          </view>
          <!-- 评论 -->
          <view class="icon">
            <uni-icons type="chat-filled" size="35" color="#fff" @tap="comment(item.videoId)" />
            <text class="icon-val">668</text>
          </view>
          <!-- 收藏 -->
          <view class="icon">
            <uni-icons type="star-filled" size="35" color="#fff" @tap="iconClick('1', item.videoId)" />
            <text class="icon-val">888</text>
          </view>
          <!-- 转发 -->
          <view class="icon">
            <uni-icons type="redo-filled" size="35" color="#fff" @tap="forward(item.videoId)" />
            <text class="icon-val">999</text>
          </view>
        </view>
      </template>
      <!-- 底部 -->
      <template v-slot:bottom="{ item }">
        <view class="bottom" v-if="item != null">
          <text class="title">{{ item.title }}</text>
        </view>
      </template>
    </ml-swiper-v2>
  </view>
</template>

<script>
  export default {
    data() {
      return {
        lists: [], // 资源数据
        datas: {0:[],1:[]}, // 点赞、收藏等数据
        count: 0, // 计数器
        context: null,
        type: "v2" // 使用的组件版本,v2:ml-swiper-v2、v3:ml-swiper-v3
      }
    },
    computed: {
      screenFull: function() {
        const uniWin = uni.getWindowInfo();
        const maxWidth = uniWin.windowWidth;
        const maxHeight = uniWin.windowHeight;
        return `width: ${maxWidth}px; height: ${maxHeight}px;`;
      }
    },
    onHide() {
      if (this.context) { this.context?.pause(); }
    },
    onLoad() {
      this.datas["0"] = [], this.datas["1"] = [];
      // (模拟请求后台获取数据)
      uni.showToast({ title: "加载中...", icon: "loading", duration: 1500 });
      setTimeout(() => {
        let size = this.type == "v3" ? 50 : 2;
        for (var i = 0; i < size; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
        uni.hideToast();
      }, 1500);
    },
    methods: {
      avatarClick() {
        uni.showToast({ title: "点击头像", icon: "none", duration: 1000 });
      },
      comment(_id) {
        uni.showToast({ title: "查看评论", icon: "none", duration: 1000 });
      },
      forward(_id) {
        uni.showToast({ title: "转发", icon: "none", duration: 1000 });
      },
      iconClick(type, _id) {
        if (type == "0") {
          uni.showToast({ title: "点赞", icon: "none", duration: 1000 });
        } else if(type == "1") {
          uni.showToast({ title: "收藏", icon: "none", duration: 1000 });
        }
      },
      loadmore() {
        uni.showToast({ title: "加载更多", icon: "none", duration: 1000 });
        for (var i = 0; i < 2; i++) {
          this.getList().forEach((item) => {
            this.count = this.count + 1;
            item.title = `【${this.count}】` + item.title;
            this.lists.push(item);
          });
        }
      },
      ({ video }) {
        console.log(video?.videoId);
        this.iconClick(0, video?.videoId);
      },
      getList() {
        return [
          {
            videoId: this.lists.length + 1,
            title: `抖音美女主播,JK超短裙学生妆美女跳舞展示,爱了爱了。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/11/08/19/BMjAyMDExMDgxOTQxNTlfNTIzNDczMzQ0XzM4OTQ1MDk5MTI4XzFfMw==_b_Bc770a92f0cf153407d60a2eddffeae2a.mp4",
            uploadTime: "2023-11-08 19:41",
            ipLocation: "上海",
            author: {
              authorId: 101,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "陌路",
              genderName: "男"
            }
          },
          {
            videoId: this.lists.length + 2,
            title: `御姐美女抖音作品,来个自拍视频把,好美啊。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov2.a.yximgs.com/upic/2020/10/02/09/BMjAyMDEwMDIwOTAwMDlfMTIyMjc0NTk0Ml8zNjk3Mjg0NjcxOF8xXzM=_b_B28a4518e86e2cf6155a6c1fc9cf79c6d.mp4",
            uploadTime: "2023-10-02 09:41",
            ipLocation: "贵州",
            author: {
              authorId: 102,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "御姐呀",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 3,
            title: `抖音主播可爱妹子新学的舞蹈,超可爱的美女主播。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://txmov6.a.yximgs.com/upic/2020/08/23/00/BMjAyMDA4MjMwMDMyNDRfMTYzMzY5MDA0XzM0ODI4MDcyMzQ5XzFfMw==_b_B9a1c9d4e3a090bb2815994d7f33a906a.mp4",
            uploadTime: "2023-08-23 00:41",
            ipLocation: "广州",
            author: {
              authorId: 103,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "野花猫",
              genderName: "女"
            }
          },
          {
            videoId: this.lists.length + 4,
            title: `多个美女带着遮阳帽出去散步自拍视频,好好看。`,
            // poster: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
            url: "https://alimov2.a.yximgs.com/upic/2020/07/02/14/BMjAyMDA3MDIxNDUyMDlfOTExMjIyMjRfMzE1OTEwNjAxNTRfMV8z_b_Bf3005d42ce9c01c0687147428c28d7e6.mp4",
            uploadTime: "2023-07-02 14:41",
            ipLocation: "山西",
            author: {
              authorId: 104,
              avatar: "https://i02piccdn.sogoucdn.com/2acf176d90718d73",
              nickName: "蓝姬",
              genderName: "女"
            }
          }
        ];
      }
    }
  }
</script>

<style scoped>
  .text {
    font-size: 50px;
    color: #ff5918;
  }

  .right {
    width: 50px;
    padding: 10px 0;
    margin: 0;
  }

  .icon {
    flex-wrap: wrap;
    flex-direction: column;
    justify-content: center;
    margin: 8px 0;
    /* #ifndef APP-NVUE */
    display: flex;
    margin: 8px auto;
    /* #endif */
  }

  .icon-val {
    color: #fff;
    font-size: 13px;
    text-align: center;
  }

  .userAvatar {
    width: 40px;
    height: 40px;
    border-radius: 100%;
  }

  .progress {
    /* #ifndef APP-NVUE */
    width: 80%;
    /* #endif */
    height: 2px;
  }

  .progress-box {
    color: #fff;
    /* #ifndef APP-NVUE */
    width: 100%;
    display: flex;
    /* #endif */
    align-items: center;
    justify-content: space-around;
  }

  .bottom {
    margin: 10px;
  }

  .title {
    color: #fff;
    font-size: 13px;
  }

  .wh-full {
    /* #ifndef APP-NVUE */
    width: 100%;
    height: 100%;
    /* #endif */
    padding: 0;
    margin: 0;
    overflow: hidden;
  }
</style>

隐私、权限声明

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

APP端需要配置manifest.json -> App模块配置 -> 勾选VideoPlay(视频播放)。

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

插件不采集任何数据

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

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