783 lines
17 KiB
Vue
783 lines
17 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,
|
|
},
|
|
// 是否统计 0不是 1是
|
|
statistic: {
|
|
type: [String, Number],
|
|
default: 0,
|
|
},
|
|
})
|
|
//
|
|
const emit = defineEmits(['showTime', 'showComment', 'showCollect', 'showFastCollect', 'showShareFirend', 'onPlay',
|
|
'onPause', 'like', 'detailMenu', 'onEnd', 'longtap', 'dataCenter'
|
|
])
|
|
|
|
// 视频上下文对象
|
|
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 showAd = 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
|
|
})
|
|
const userinfo = uni.$store.state.userinfo
|
|
|
|
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(() => {
|
|
let a = tapList.length
|
|
tapList.length = 0
|
|
|
|
switch (a) {
|
|
case 1:
|
|
changeVideoPlay()
|
|
break;
|
|
case 2:
|
|
console.log('公开赞')
|
|
emit('like', {
|
|
likeType: 0,
|
|
index: props.index,
|
|
isLike: props.item.isLike
|
|
})
|
|
break;
|
|
case 3:
|
|
console.log('隐私赞')
|
|
emit('like', {
|
|
likeType: 1,
|
|
index: props.index,
|
|
isLike: props.item.isLike
|
|
})
|
|
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.deleteVideo({
|
|
data: {
|
|
// 视频id
|
|
videoId: detail.id,
|
|
},
|
|
}).then(rs => {
|
|
if (rs.code == 200) {
|
|
uni.$emit('updateVideo', {
|
|
...detail,
|
|
...rs.data,
|
|
})
|
|
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} likeType 点赞类型 0.公开赞 1.隐私赞
|
|
* @param {Number|String} isLike 0.点赞 1.取消点赞
|
|
*/
|
|
function handleLike(index, likeType, isLike) {
|
|
util.isLogin().then(rs => {
|
|
emit('like', {
|
|
index,
|
|
likeType,
|
|
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,
|
|
likeType: 1,
|
|
isLike: 0,
|
|
})
|
|
else emit('like', {
|
|
index,
|
|
likeType: 2,
|
|
isLike: 0,
|
|
})
|
|
})
|
|
}).catch(() => {
|
|
uni.navigateTo({
|
|
url: '/pages/login/loginPhone'
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 详情菜单
|
|
* @param {Object} item
|
|
*/
|
|
function handleDetailMenu(item) {
|
|
emit('detailMenu')
|
|
}
|
|
|
|
// 播放变化
|
|
function handleTimeupdate(ev) {
|
|
videoTime.value = ev.detail
|
|
// 如果有商品id 并且 当前播放时间大于商家设定的广告弹出时间
|
|
if (props.item.productId && (videoTime.value.currentTime > props.item.popupTime)) {
|
|
showAd.value = true
|
|
}
|
|
// 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)
|
|
}
|
|
|
|
//
|
|
function handleData() {
|
|
emit('dataCenter', props.item)
|
|
}
|
|
|
|
//
|
|
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">
|
|
<!-- 数据中心 -->
|
|
<view class="item fmid" @click="handleData" v-if="0">
|
|
<view class="col">
|
|
<image class="wh50" src="/static/statistic.png" mode="aspectFit" />
|
|
</view>
|
|
|
|
<view class="txt mt20"></view>
|
|
</view>
|
|
|
|
<!-- 用户头像 -->
|
|
<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">
|
|
<template v-if="item.isLike">
|
|
<!-- 公开赞 -->
|
|
<image class="wh50" src="/static/indexLike1.png" mode="aspectFit"
|
|
v-if="item.likeType == 0" @click="handleLike(index,0, 1)" />
|
|
<!-- 私密赞的图标 -->
|
|
<image class="wh50" src="/static/privateLike.png" mode="aspectFit"
|
|
v-else-if="item.likeType == 1" @click="handleLike(index,1,1)" />
|
|
</template>
|
|
<template v-else>
|
|
<image class="wh50" src="/static/indexLike.png" mode="aspectFit"
|
|
@click="handleLike(index, 0, 0)" @longpress="handlePrivateLike(index)" />
|
|
</template>
|
|
</view>
|
|
|
|
<!-- 只有公开赞显示点赞数 -->
|
|
<view class="txt mt10" v-if="item.isLike && item.likeType == 0">
|
|
<text class="text">{{ item.likeCount }}</text>
|
|
</view>
|
|
<view class="txt mt10" v-else>
|
|
<text class="text">点赞</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.reviewCount }}</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">收藏</text>
|
|
<!-- <text class="text">{{ item.collectCount }}</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" :class="{'active': showAd}" v-if="showAd">
|
|
<!-- 商品图片 -->
|
|
<image class="image wh100 mr15 br10" :src="item.productImage" mode="aspectFill" />
|
|
|
|
<view class="df fdc jcsb f1">
|
|
<view class="name">
|
|
<text class="text cfff f28">{{item.productName}}</text>
|
|
</view>
|
|
<view class="info df fdr aic mr10">
|
|
<text class="price mr10 cfff f28 b">¥{{item.productPrice}}</text>
|
|
<text class="cfff f1 f20">已售{{item.sales}}单</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;
|
|
|
|
// 图片
|
|
.image {
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
}
|
|
|
|
// 产品
|
|
.goods {
|
|
padding: 10rpx;
|
|
width: 480rpx;
|
|
margin-bottom: 20rpx;
|
|
background-color: rgba(0, 0, 0, .3);
|
|
opacity: 0;
|
|
transition-duration: .5s;
|
|
|
|
//
|
|
&.active {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
// 产品图片
|
|
.name .text {
|
|
lines: 2;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
|
|
// 视频播放进度条
|
|
.videoProgress {
|
|
position: absolute;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
}
|
|
</style> |