更新记录
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" />
组件支持情况
ml-swiper-v3
版本:支持 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
打包成原生代码,大大提升了性能)
真实运行示例
APP-雷电模拟器
APP端代码示例
手机端-微信小程序
手机端-QQ浏览器
小程序、H5网页端代码示例
组件参数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 拿到视频时长和当前时长 实现自定义进度条,需要progress 和 customProgress 都为 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
底部自定义进度条,需要customProgress
和 progress
同时设置为 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-v3 ref="mlSwiperRef"></ml-swiper-v3>
<!-- ...省略... -->
<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>
vue3 版本示例代码
组件示例代码
微信小程序 和 H5 网页端示例代码
(注:H5端 需要安装 hls,详见下文: H5端使用说明
)
# H5 端 安装插件
npm install hls.js
示例代码中用到了 uni-icons
请自行安装
index.vue
<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">
<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 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>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import { onHide } from "@dcloudio/uni-app";
const lists = ref([]);
const type = ref("v3"); // 当前 ml-swiper版本(当前版本是 ml-swiper-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">
.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;
color: #fff;
/* #endif */
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-v3 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-v3>
</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;
color: #fff;
/* #endif */
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-v3
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-v3>
</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;
color: #fff;
/* #endif */
align-items: center;
justify-content: space-around;
}
.bottom {
margin: 10px;
}
.title {
color: #fff;
font-size: 13px;
}
</style>
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: {
// @onplay
onplay(event) {
this.context = event?.context;
},
// @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;
color: #fff;
/* #endif */
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-v3 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-v3>
</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: {
// @onplay
onplay(event) {
this.context = event?.context;
},
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 {
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
color: #fff;
/* #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>