Files
ks-app-employment-service/components/TikTok/TikTok.vue

313 lines
8.5 KiB
Vue
Raw Normal View History

2025-06-26 08:56:42 +08:00
<template>
<swiper
class="m-tiktok-video-swiper"
circular
@change="swiperChange"
:current="state.current"
:vertical="true"
duration="300"
>
<swiper-item v-for="(item, index) in state.displaySwiperList" :key="index">
<view class="swiper-item" @click="(e) => handleClick(index, e)">
<video
:id="`video__${index}`"
:controls="controls"
:autoplay="false"
:loop="loop"
@ended="ended"
@controlstoggle="controlstoggle"
@play="onPlay"
@error="emits('error')"
class="m-tiktok-video-player"
:src="item.src || item.explainUrl"
v-if="index === 0 || !state.isFirstLoad"
></video>
<view class="cover-triangle" v-if="pause"></view>
<image
v-if="item.poster && state.displayIndex != index"
:src="item.poster"
class="m-tiktok-video-poster"
mode="aspectFit"
></image>
<slot :item="item"></slot>
</view>
</swiper-item>
</swiper>
</template>
<script setup>
import { ref, reactive, getCurrentInstance, watch, nextTick } from 'vue';
import { onLoad, onUnload } from '@dcloudio/uni-app';
const _this = getCurrentInstance();
const emits = defineEmits(['play', 'error', 'loadMore', 'change', 'controlstoggle', 'click', 'ended']);
const lastTapTime = ref(0);
const pause = ref(false);
const props = defineProps({
/**
* 视频列表
*/
videoList: {
type: Array,
default: () => [],
},
/**
* 是否循环播放一个视频
*/
loop: {
type: Boolean,
default: true,
},
/**
* 显示原生控制栏
*/
controls: {
type: Boolean,
default: true,
},
/**
* 是否自动播放
*/
autoplay: {
type: Boolean,
default: true,
},
/**
* 是否自动滚动播放
*/
autoChange: {
type: Boolean,
default: false,
},
/**
* 滚动加载阈值即播放到剩余多少个之后触发加载更多
*/
loadMoreOffsetCount: {
type: Number,
default: 2,
},
/**
* 暂停 1 单机暂停 2 双击暂停 0关闭
*/
pauseType: {
type: Number,
default: 2,
},
});
const state = reactive({
originList: [], // 源数据
displaySwiperList: [], // swiper需要的数据
displayIndex: 0, // 用于显示swiper的真正的下标数值只有012。
originIndex: 0, // 记录源数据的下标
current: 0,
oid: 0,
showControls: '',
toggleShow: true, // 显示面板
videoContexts: [],
isFirstLoad: true,
});
const initVideoContexts = () => {
state.videoContexts = [
uni.createVideoContext('video__0', _this),
uni.createVideoContext('video__1', _this),
uni.createVideoContext('video__2', _this),
];
};
const onPlay = (e) => {
emits('play', e);
};
const setVideoRef = (el, index) => {
if (el) {
videoRefs.value[index] = el;
}
};
function handleClick(index, e) {
const now = Date.now();
switch (props.pauseType) {
case 1:
if (pause.value) {
state.videoContexts[index].play();
pause.value = false;
} else {
state.videoContexts[index].pause();
pause.value = true;
}
break;
case 2:
if (now - lastTapTime.value < 300) {
if (pause.value) {
state.videoContexts[index].play();
pause.value = false;
} else {
state.videoContexts[index].pause();
pause.value = true;
}
}
break;
}
lastTapTime.value = now;
state.toggleShow = !state.toggleShow;
emits('click', e);
}
function ended() {
// 自动切换下一个视频
if (props.autoChange) {
if (state.displayIndex < 2) {
state.current = state.displayIndex + 1;
} else {
state.current = 0;
}
}
emits('ended');
}
/**
* 初始一个显示的swiper数据
* @originIndex 从源数据的哪个开始显示默认0如从其他页面跳转进来要显示第n个这个参数就是他的下标
*/
function initSwiperData(originIndex = state.originIndex) {
const originListLength = state.originList.length; // 源数据长度
const displayList = [];
displayList[state.displayIndex] = state.originList[originIndex];
displayList[state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1] =
state.originList[originIndex - 1 == -1 ? originListLength - 1 : originIndex - 1];
displayList[state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1] =
state.originList[originIndex + 1 == originListLength ? 0 : originIndex + 1];
state.displaySwiperList = displayList;
if (state.oid >= state.originList.length) {
state.oid = 0;
}
if (state.oid < 0) {
state.oid = state.originList.length - 1;
}
// 暂停所有视频
state.videoContexts.map((item) => item?.stop());
setTimeout(() => {
// 当前视频
if (props.autoplay) {
uni.createVideoContext(`video__${state.displayIndex}`, _this).play();
}
}, 500);
// 数据改变
emits('change', {
index: originIndex,
detail: state.originList[originIndex],
});
// 加载更多
var pCount = state.originList.length - props.loadMoreOffsetCount;
if (originIndex == pCount) {
emits('loadMore');
}
}
/**
* swiper滑动时候
*/
function swiperChange(event) {
const { current } = event.detail;
state.isFirstLoad = false;
const originListLength = state.originList.length; // 源数据长度
// 向后滚动
if (state.displayIndex - current == 2 || state.displayIndex - current == -1) {
state.originIndex = state.originIndex + 1 == originListLength ? 0 : state.originIndex + 1;
state.displayIndex = state.displayIndex + 1 == 3 ? 0 : state.displayIndex + 1;
state.oid = state.originIndex - 1;
initSwiperData(state.originIndex);
}
// 如果两者的差为-2或者1则是向前滑动
else if (state.displayIndex - current == -2 || state.displayIndex - current == 1) {
state.originIndex = state.originIndex - 1 == -1 ? originListLength - 1 : state.originIndex - 1;
state.displayIndex = state.displayIndex - 1 == -1 ? 2 : state.displayIndex - 1;
state.oid = state.originIndex + 1;
initSwiperData(state.originIndex);
}
state.toggleShow = true;
}
function controlstoggle(e) {
state.showControls = e.detail.show;
emits('controlstoggle', e);
}
watch(
() => props.videoList,
() => {
if (props.videoList?.length) {
state.originList = props.videoList;
if (state.isFirstLoad || !state.videoContexts?.length) {
initSwiperData();
initVideoContexts();
}
}
},
{
immediate: true,
}
);
let loadTimer = null;
onLoad(() => {
// 为了首次只加载一条视频(提高首次加载性能),延迟加载后续视频
loadTimer = setTimeout(() => {
state.isFirstLoad = false;
clearTimeout(loadTimer);
}, 5000);
});
onUnload(() => {
clearTimeout(loadTimer);
});
defineExpose({
initSwiperData,
});
</script>
<style lang="stylus" scoped>
.m-tiktok-video-swiper,
.m-tiktok-video-player {
width: 100%;
height: 100%;
background-color: #000;
}
.m-tiktok-video-swiper {
.swiper-item {
position: relative;
height: 100%;
}
.m-tiktok-video-poster {
background-color: #000;
position: absolute;
width: 100%;
height: 100%;
}
}
.cover-triangle{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
width: 132rpx
height: 132rpx
border-radius: 50%;
background: rgba(0,0,0,0.3)
}
.cover-triangle::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-40%, -50%) rotate(90deg);
width: 0;
height: 0;
border-left: 40rpx solid transparent;
border-right: 40rpx solid transparent;
border-bottom: 60rpx solid #fff;
}
</style>