726 lines
15 KiB
Vue
726 lines
15 KiB
Vue
<script setup>
|
||
/**
|
||
* 首页视频组件
|
||
* @property {Object} item 视频数据对象
|
||
* @property {Number} index 当前视频列表索引
|
||
* @property {Number} current 当前预览索引
|
||
*/
|
||
|
||
import {
|
||
onMounted,
|
||
ref,
|
||
reactive,
|
||
getCurrentInstance,
|
||
watch,
|
||
defineEmits,
|
||
computed,
|
||
nextTick
|
||
} from 'vue';
|
||
const {
|
||
proxy
|
||
} = getCurrentInstance()
|
||
// 顶部状态栏
|
||
import statusBar from '@/components/header/statusBar.vue'
|
||
// 工具库
|
||
import util from '@/common/js/util';
|
||
//
|
||
import api from '@/api/index.js';
|
||
// 视频进度条
|
||
import videoProgress from '@/components/index/videoProgress';
|
||
|
||
const props = defineProps({
|
||
// 当前视频对象
|
||
item: {
|
||
type: Object,
|
||
},
|
||
// 列表中的视频下标
|
||
index: {
|
||
type: Number,
|
||
},
|
||
// 当前列表的index
|
||
current: {
|
||
type: Number,
|
||
},
|
||
// 当前tab的index
|
||
tabIndex: {
|
||
type: Number,
|
||
},
|
||
// 模式 list列表 detail详情
|
||
mode: {
|
||
type: String,
|
||
default: 'list'
|
||
},
|
||
// 是否我自己 0不是 1是
|
||
isMine: {
|
||
type: [String, Number],
|
||
default: 0,
|
||
},
|
||
width: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
height: {
|
||
type: Number,
|
||
default: 0,
|
||
},
|
||
})
|
||
//
|
||
const emit = defineEmits(['showTime', 'showComment', 'showCollect', 'showFastCollect', 'showShareFirend', 'onPlay',
|
||
'onPause', 'like', 'detailMenu', 'onEnd', 'longtap'
|
||
])
|
||
|
||
// 视频上下文对象
|
||
const videoCtx = ref(null)
|
||
// 是否播放
|
||
const playState = ref(true)
|
||
// 是否显示快速收藏列表
|
||
const collectFirst = ref(false)
|
||
// 视频时间
|
||
const videoTime = ref({
|
||
// 总长
|
||
duration: 0,
|
||
// 当前时间
|
||
currentTime: 0,
|
||
})
|
||
// 记录点击的数组
|
||
const tapList = reactive([])
|
||
// 点击时间戳
|
||
const tapTimer = ref(null)
|
||
// 是否在计数
|
||
const isTap = ref(false)
|
||
// 长按
|
||
const isLong = ref(false)
|
||
// 收藏按钮判定
|
||
const collectBtnActive = ref(false)
|
||
// 设置时间
|
||
const alarmTime = computed(() => {
|
||
let result = uni.$store.state.alarmTime
|
||
return result
|
||
})
|
||
// 填充
|
||
const fit = computed(() => {
|
||
const ratio1 = parseInt(props.width) / parseInt(props.height)
|
||
const ratio2 = props.item.breadth / props.item.height
|
||
let result = 'contain'
|
||
if (Math.abs(formatNumber(ratio2) - formatNumber(ratio1)) < 1) result = 'cover'
|
||
return result
|
||
})
|
||
|
||
watch(() => props.current, (nV) => {
|
||
if (nV == props.index) play()
|
||
else pause()
|
||
})
|
||
|
||
// 挂载后调用
|
||
onMounted(() => {
|
||
// 视频上下文对象
|
||
videoCtx.value = uni.createVideoContext(`video${props.tabIndex}${props.index}`)
|
||
})
|
||
|
||
// 格式化
|
||
function formatNumber(result) {
|
||
result = parseFloat(result) * 10
|
||
return result
|
||
}
|
||
|
||
// 手指触摸视频容器
|
||
function onTouchStart() {
|
||
// 是否计数
|
||
if (isTap.value) {
|
||
return
|
||
tapList.length = 0
|
||
changeVideoPlay()
|
||
} else {
|
||
isTap.value = true
|
||
// 时间
|
||
let time = new Date().getTime()
|
||
tapList.push(time)
|
||
}
|
||
clearTimeout(tapTimer.value)
|
||
}
|
||
|
||
// 手指离开视频容器
|
||
function onTouchEnd() {
|
||
// 如果不是重复点击的状态
|
||
if (isTap.value) {
|
||
isTap.value = false
|
||
|
||
let time = new Date().getTime()
|
||
let diff = time - tapList[tapList.length - 1]
|
||
|
||
// 判断长按
|
||
if (diff > 350) {
|
||
// 取消视频倍速播放的状态
|
||
if (isLong.value) {
|
||
isLong.value = false
|
||
videoCtx.value.playbackRate(1)
|
||
}
|
||
// 清空计数
|
||
tapList.length = 0
|
||
return
|
||
}
|
||
|
||
//
|
||
tapTimer.value = setTimeout(() => {
|
||
// 是否点赞
|
||
const isLike = props.item.isLike
|
||
|
||
let a = tapList.length
|
||
tapList.length = 0
|
||
|
||
switch (a) {
|
||
case 1:
|
||
changeVideoPlay()
|
||
break;
|
||
case 2:
|
||
console.log('公开赞')
|
||
return
|
||
emit('like', {
|
||
index: props.index,
|
||
isLike: isLike == 0 ? 0 : 1
|
||
})
|
||
break;
|
||
case 3:
|
||
console.log('隐私赞')
|
||
return
|
||
emit('like', {
|
||
index: props.index,
|
||
isLike: isLike == 0 ? 3 : 1
|
||
})
|
||
break;
|
||
}
|
||
}, 200)
|
||
}
|
||
}
|
||
|
||
// 手指中断视频容器
|
||
function onTouchCancel() {
|
||
isTap.value = false
|
||
tapList.length = 0
|
||
clearTimeout(tapTimer.value)
|
||
}
|
||
|
||
// 切换视频播放
|
||
function changeVideoPlay() {
|
||
// 根据播放状态切换播放暂停
|
||
if (playState.value) pause()
|
||
else play()
|
||
}
|
||
|
||
// 视频播放
|
||
function play() {
|
||
let pages = getCurrentPages();
|
||
let page = pages[pages.length - 1];
|
||
if (props.index != props.current || !['pages/index/index', 'pages/index/videoDetail'].includes(page.route)) return
|
||
videoCtx.value.play()
|
||
}
|
||
|
||
// 视频暂停
|
||
function pause() {
|
||
videoCtx.value.pause()
|
||
}
|
||
|
||
// 视频播放回调
|
||
function onVideoPlay() {
|
||
playState.value = true
|
||
emit('onPlay')
|
||
}
|
||
|
||
// 视频暂停回调
|
||
function onVideoPause() {
|
||
playState.value = false
|
||
emit('onPause')
|
||
}
|
||
|
||
// 打开评论
|
||
function handleComment() {
|
||
emit('showComment', props.item)
|
||
}
|
||
|
||
// 打开计时闹钟
|
||
function handleTime() {
|
||
emit('showTime')
|
||
}
|
||
|
||
// 打开收藏弹窗
|
||
function showCollect() {
|
||
util.isLogin().then(rs => {
|
||
emit('showCollect', props.item)
|
||
}).catch(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/login/loginPhone'
|
||
})
|
||
})
|
||
}
|
||
|
||
// 切换快速收藏
|
||
function handleCollectFirst(ev) {
|
||
// 获取当前元素的宽高
|
||
const changedTouches = ev.changedTouches[0]
|
||
let x = getNumber(props.width) - getNumber(changedTouches.screenX) + getNumber(changedTouches.pageX)
|
||
let y = getNumber(changedTouches.screenY) - getNumber(changedTouches.pageY)
|
||
|
||
emit('showFastCollect', {
|
||
item: props.item,
|
||
position: {
|
||
x,
|
||
y,
|
||
}
|
||
})
|
||
return
|
||
}
|
||
|
||
/**
|
||
* 手指触摸收藏
|
||
* @param {Object} ev
|
||
*/
|
||
function handleCollectStar(ev) {
|
||
collectBtnActive.value = true
|
||
|
||
util.isLogin().then(rs => {
|
||
//
|
||
setTimeout(() => {
|
||
// 判断是否抬起
|
||
if (!collectBtnActive.value) {
|
||
// 收藏状态
|
||
if (!props.item.isCollect) {
|
||
// 单击出菜单
|
||
handleCollectFirst(ev)
|
||
return
|
||
} else cancelCollect()
|
||
} else {
|
||
setTimeout(() => {
|
||
// 长按出弹窗
|
||
showCollect()
|
||
}, 350)
|
||
}
|
||
}, 350)
|
||
}).catch(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/login/loginPhone'
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 手指触摸结束
|
||
* @param {Object} str
|
||
*/
|
||
function handleCollectEnd() {
|
||
collectBtnActive.value = false
|
||
}
|
||
|
||
// 获取数字
|
||
function getNumber(str) {
|
||
let result = Math.floor(Number(str))
|
||
return result
|
||
}
|
||
|
||
// 取消收藏
|
||
function cancelCollect() {
|
||
const detail = {
|
||
...props.item
|
||
}
|
||
|
||
// 取消收藏
|
||
api.video.cancelCollect({
|
||
query: {
|
||
// 视频id
|
||
videoId: detail.videoId,
|
||
},
|
||
}).then(rs => {
|
||
if (rs.code == 200) {
|
||
detail.isCollect = false
|
||
detail.collect--
|
||
uni.$emit('updateVideo', detail)
|
||
return
|
||
}
|
||
//
|
||
util.alert({
|
||
content: rs.msg,
|
||
showCancel: false,
|
||
})
|
||
})
|
||
}
|
||
|
||
// 分享到好友
|
||
function handleShareFirend() {
|
||
util.isLogin().then(rs => {
|
||
emit('showShareFirend', props.item)
|
||
}).catch(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/login/loginPhone'
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 点赞
|
||
* @param {Number} index 操作的视频下标
|
||
* @param {Number|String} isLike 点赞操作
|
||
*/
|
||
function handleLike(index, isLike) {
|
||
util.isLogin().then(rs => {
|
||
emit('like', {
|
||
index,
|
||
isLike,
|
||
})
|
||
}).catch(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/login/loginPhone'
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 私密赞
|
||
* @param {Object} index
|
||
*/
|
||
function handlePrivateLike(index) {
|
||
util.isLogin().then(rs => {
|
||
util.alert({
|
||
title: '提示',
|
||
content: '请确认,是否为隐私赞(隐私赞仅自己和作者可见)?',
|
||
confirmText: '隐私赞',
|
||
cancelText: '公开赞',
|
||
}).then(rs => {
|
||
if (rs.confirm) emit('like', {
|
||
index,
|
||
isLike: 3,
|
||
})
|
||
else emit('like', {
|
||
index,
|
||
isLike: 0
|
||
})
|
||
})
|
||
}).catch(() => {
|
||
uni.navigateTo({
|
||
url: '/pages/login/loginPhone'
|
||
})
|
||
})
|
||
}
|
||
|
||
/**
|
||
* 详情菜单
|
||
* @param {Object} item
|
||
*/
|
||
function handleDetailMenu(item) {
|
||
emit('detailMenu')
|
||
}
|
||
|
||
// 播放变化
|
||
function handleTimeupdate(ev) {
|
||
videoTime.value = ev.detail
|
||
// console.log('videoTime.value', videoTime.value)
|
||
}
|
||
|
||
// 进度条拖拽结束
|
||
function onProgressEnd(ev) {
|
||
videoCtx.value.seek(parseInt(ev.time))
|
||
}
|
||
|
||
/**
|
||
* 查看用户主页
|
||
* @param {Object} item
|
||
*/
|
||
function handleUser(item) {
|
||
uni.navigateTo({
|
||
url: util.setUrl('/pages/index/videoHome', {
|
||
userId: item.userId
|
||
})
|
||
})
|
||
}
|
||
|
||
// 视频出现缓冲
|
||
function handleWaiting(ev) {
|
||
if (props.index == props.current) play()
|
||
else pause()
|
||
}
|
||
|
||
// 长按
|
||
function longtap(ev) {
|
||
if (isLong.value) return
|
||
play()
|
||
isLong.value = true
|
||
videoCtx.value.playbackRate(2)
|
||
}
|
||
|
||
//
|
||
defineExpose({
|
||
play,
|
||
pause,
|
||
videoTime,
|
||
item: props.item,
|
||
playState,
|
||
videoCtx: () => videoCtx.value,
|
||
})
|
||
</script>
|
||
|
||
<template>
|
||
<view class="container f1 pr" ref="videoBoxRef">
|
||
<!-- 视频层 -->
|
||
<view class="main f1">
|
||
<view class="videoBox f1" @touchmove.stop="" @touchstart="onTouchStart" @touchend="onTouchEnd"
|
||
@touchcancel="onTouchCancel" @longpress="longtap">
|
||
<statusBar />
|
||
|
||
<!-- 视频 增加判断防止重复加载 -->
|
||
<template v-if="item.videoUrl">
|
||
<video class="video f1" :id="'video' + tabIndex + index" :src="item.videoUrl"
|
||
:poster="item.coverUrl" :http-cache="true" :show-fullscreen-btn="false"
|
||
:enable-progress-gesture="false" :controls="false" @play="onVideoPlay" @pause="onVideoPause"
|
||
:show-center-play-btn="false" @timeupdate="handleTimeupdate" @waiting="handleWaiting"
|
||
:play-strategy="2" :loop="true" :object-fit="fit" />
|
||
</template>
|
||
</view>
|
||
|
||
<!-- 视频进度条 -->
|
||
<view class="videoProgress" @touchmove.stop="" @touchstart.stop="" @touchend.stop="">
|
||
<videoProgress :time="videoTime" @change="onProgressEnd" :viewWidth="width" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 倍速播放提示 -->
|
||
<view class="speedBox" v-if="isLong">
|
||
<view class="speed ptb5 plr10">
|
||
<text class="f22 cfff">2倍速播放中...</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 暂停蒙版 -->
|
||
<view class="pausePanel pfull fmid" v-if="!playState">
|
||
<!-- 暂停按钮 -->
|
||
<image class="pauseImg" src="@/static/pause.png" mode="aspectFit" />
|
||
</view>
|
||
|
||
<!-- 右侧操作区 -->
|
||
<view class="panelRight pa t0 b0 r0">
|
||
<statusBar />
|
||
<view class="head"></view>
|
||
<view class="f1 jcr pl5 pt40 pr20">
|
||
|
||
<!-- 操作台 -->
|
||
<view class="operate f1">
|
||
<!-- 用户头像 -->
|
||
<navigator :url="util.setUrl('/pages/index/videoHome',{userId:item.userId})" class="item pr mb10">
|
||
<view class="col">
|
||
<image class="wh80 cir" :src="item.avatar" mode="aspectFill" />
|
||
<view class="focus pa" v-if="!item.isAttention">
|
||
<image class="wh40" src="@/static/indexAtt.png" mode="aspectFit" />
|
||
</view>
|
||
</view>
|
||
</navigator>
|
||
|
||
<!-- 点赞 -->
|
||
<view class="item">
|
||
<view class="col">
|
||
<view class="pr">
|
||
<image class="wh50" src="/static/indexLike.png" mode="aspectFit" v-if="item.isLike == 0"
|
||
@click="handleLike(index, 0)" @longpress="handlePrivateLike(index)" />
|
||
<image class="wh50" src="/static/indexLike1.png" mode="aspectFit"
|
||
v-else-if="item.isLike == 1" @click="handleLike(index, 1)" />
|
||
<!-- 私密赞的图标 -->
|
||
<image class="wh50" src="/static/privateLike.png" mode="aspectFit"
|
||
v-else-if="item.isLike == 3" @click="handleLike(index, 1)" />
|
||
</view>
|
||
<view class="txt mt10">
|
||
<text class="text">{{ item.publicLikeCount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 留言 -->
|
||
<view class="item" @click="handleComment">
|
||
<view class="col">
|
||
<image class="wh50" src="@/static/indexMsg.png" mode="aspectFit" />
|
||
<view class="txt mt10">
|
||
<text class="text">{{ item.commentCount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 收藏 -->
|
||
<view class="item df fdr">
|
||
<view class="col" @touchstart="handleCollectStar" @touchend="handleCollectEnd" ref="collectBtn">
|
||
<image class="wh50" src="@/static/indexCollect1.png" mode="aspectFit"
|
||
v-if="item.isCollect" />
|
||
<image class="wh50" src="@/static/indexCollect.png" mode="aspectFit" v-else />
|
||
<view class="txt mt10">
|
||
<text class="text">{{ item.favoriteCount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 分享 -->
|
||
<view class="item" @click="handleShareFirend">
|
||
<view class="col">
|
||
<image class="wh50" src="@/static/indexShare.png" mode="aspectFit" />
|
||
<view class="txt mt10">
|
||
<text class="text">分享</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 闹钟 -->
|
||
<view class="item money" @click="handleTime" v-if="mode == 'list'">
|
||
<view class="col">
|
||
<image class="wh80" src="/static/indexMoney1.png" mode="aspectFit" v-if="alarmTime" />
|
||
<image class="wh80" src="/static/indexMoney.png" mode="aspectFit" v-else />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 详情菜单 -->
|
||
<view class="item money" @click="handleTime" v-if="mode == 'detail' && isMine == 1">
|
||
<view class="col wh90 fmid tac" @click="handleDetailMenu">
|
||
<uni-icons type="more-filled" color="#d8d8d8" size="70rpx" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部用户信息 -->
|
||
<view class="panelBottom pa l0 r0 b0 pl40 pb30">
|
||
<!-- 商品信息 -->
|
||
<view class="goods df fdr mb20 br10" v-if="0">
|
||
<!-- 商品图片 -->
|
||
<image class="image wh100 mr15 br10" src="/static/openPage.png" mode="aspectFill" />
|
||
|
||
<view class="df fdc jcsb f1">
|
||
<view class="">
|
||
<text class="cfff f28">果农大王霹雳美味榴莲果子</text>
|
||
</view>
|
||
<view class="info df fdr aic mr10">
|
||
<text class="price mr10 cfff f28 b">超低价¥6.66</text>
|
||
<text class="cfff f1 f20">已售666+单</text>
|
||
<uni-icons type="close" color="#fff" />
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户 -->
|
||
<view class="user" @click="handleUser(item)">
|
||
<text class="cfff f36">@{{ item.userNickname }}</text>
|
||
</view>
|
||
|
||
<!-- 简介 -->
|
||
<view class="desc mt5">
|
||
<text class="t2hd cfff f28">{{ item.title }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
// 视频盒子
|
||
.container {
|
||
background-color: #000;
|
||
}
|
||
|
||
// 视频时长
|
||
.duration {
|
||
margin-top: 120rpx;
|
||
width: 750rpx;
|
||
background-color: rgba(255, 255, 255, .3);
|
||
|
||
// 时间时长
|
||
.line {
|
||
width: 0;
|
||
height: 2rpx;
|
||
background-color: rgba(255, 255, 255, .8);
|
||
transition-duration: .25s;
|
||
}
|
||
}
|
||
|
||
// 倍速播放
|
||
.speedBox {
|
||
position: absolute;
|
||
top: 200rpx;
|
||
left: 0;
|
||
right: 0;
|
||
align-items: center;
|
||
|
||
// 倍速
|
||
.speed {
|
||
background-color: rgba(0, 0, 0, .8);
|
||
border-radius: 5rpx;
|
||
opacity: .6;
|
||
}
|
||
}
|
||
|
||
// 暂停蒙层
|
||
.pausePanel {
|
||
background-color: rgba(0, 0, 0, .5);
|
||
|
||
//
|
||
.pauseImg {
|
||
width: 140rpx;
|
||
height: 140rpx;
|
||
}
|
||
}
|
||
|
||
// 右侧屏幕
|
||
.panelRight {
|
||
align-items: flex-end;
|
||
|
||
.text {
|
||
text-align: center;
|
||
color: #fff;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
// 操作台
|
||
.operate {
|
||
flex: 1;
|
||
justify-content: flex-end;
|
||
align-items: flex-end;
|
||
text-align: center;
|
||
padding-bottom: 130rpx;
|
||
|
||
.item {
|
||
margin: 5rpx 0;
|
||
padding: 10rpx 5rpx;
|
||
|
||
&.money {
|
||
padding: 0;
|
||
}
|
||
|
||
//
|
||
.col {
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 80rpx;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 关注的小加号
|
||
.focus {
|
||
align-items: center;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
}
|
||
}
|
||
|
||
// 底部
|
||
.panelBottom {
|
||
padding-right: 150rpx;
|
||
|
||
//
|
||
.goods {
|
||
padding: 10rpx;
|
||
width: 480rpx;
|
||
margin-bottom: 20rpx;
|
||
background-color: rgba(0, 0, 0, .3);
|
||
}
|
||
}
|
||
|
||
//
|
||
.videoProgress {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
}
|
||
</style> |