<script setup> /** * 首页 视频页 */ import { ref, reactive, getCurrentInstance, computed, nextTick, } from 'vue'; import { onLoad, onReady, onHide, onShow, onUnload, } from '@dcloudio/uni-app' // 工具库 import util from '@/common/js/util'; // api import api from '@/api/index.js' // 顶部状态栏 import statusBar from '@/components/header/statusBar.vue' // 引入视频 import indexVideo from '@/components/index/indexVideo.vue' // 底部菜单 import footerMneu from '@/components/footerMenu/footerMenu.vue' // 计时闹钟弹窗 import timeAlt from '@/components/index/time.vue' // 评论弹窗 import commentAlt from '@/components/index/commentArea.vue' // 收藏弹窗 import collectAlt from '@/components/index/collect.vue' // 分享到好友弹窗 import shareFirendAlt from '@/components/index/shareFirend.vue' // 左侧菜单弹窗 import leftMenuAlt from "@/components/index/leftMenu.vue" // 有效读秒唱片 import disc from '@/components/index/disc.vue' // 长按更多菜单 import moreMenu from '@/components/index/moreMenu.vue' // 快捷收藏 import fastCollect from '@/components/index/fastCollect.vue'; // 青少年模式 import teen from '@/components/index/teen.vue' // 产品详情弹窗 import productAlt from '@/components/index/proDetailAlt.vue' // 产品规格弹窗 import productSpecAlt from '@/components/shop/detail/makeOrder.vue' const { proxy } = getCurrentInstance() const dom = uni.requireNativePlugin('dom') // 钟表提示弹窗 const oclockWindow = ref(false) // 有效读秒 const readSecond = reactive({ // 定时器 timer: null, }) // tab选项 const tab = reactive([ // { // name: '同城', // }, { name: '关注', load: false, listData: () => attList.data, getList: () => getAttList(), getMoreList: () => getMoreAttList(), refreshList: () => refreshAttList(), }, { name: '推荐', load: false, listData: () => recList.data, getList: () => getRecList(), getMoreList: () => getMoreRecList(), refreshList: () => refreshRecList(), }, ]) // tab下标 const tabIndex = ref(1) // 起始值 const startY = ref(0) // 上一个播放索引 const currentLast = reactive([0, 0]) // 当前播放索引 const current = reactive([0, 0]) // 列表 const recList = reactive({ data: [], pageNum: 1, total: 0, pageSize: 10, }) // 关注的视频列表 const attList = reactive({ data: [], pageSize: 10, pageNum: 1, total: 0, timer: null, }) // 容器高度 用来控制视频、遮罩等高度 用来解决nvue模式下没有100%高度的问题 const viewSize = reactive({ height: 0, width: 0, }) // 唱片顶部位置 const discOffsetTop = ref(0) // 有效读秒顶部位置 const complete2Top = ref(0) // 结束特效配置 const completeConfig = { 'complete1': { time: 2, }, 'complete2': { time: 1.5, }, } // 结束特效键 complete1优先任务特效 complete2有效读秒特效 const completeKey = ref('') // 底部菜单高度 const footerMenuHeight = ref(0) // 当前产品详情 const productDetail = reactive({}) // 当前任务 const task = computed(() => uni.$store.state.task) // 用户信息 const userinfo = computed(() => uni.$store.state.userinfo || {}) // 当前tab选中 const tabCurrent = computed(() => tab[tabIndex.value]) // 当前视频元素对象 const currentVideoRef = computed(() => proxy.$refs[`videoRef${tabIndex.value}`][current[tabIndex.value]]) // 系统配置 const config = computed(() => uni.$store.state.config) // 加载完成之后 onLoad(() => { // 设备信息 const systemInfo = uni.getSystemInfoSync() // 唱片高度 discOffsetTop.value = systemInfo.safeAreaInsets.top + 44 + 30 // 特效完成高度 complete2Top.value = systemInfo.safeAreaInsets.top + 44 + 150 // 获取列表 tabCurrent.value.getList() // 判断是否提醒过闹铃 if (!uni.getStorageSync('alarmAlt')) { setTimeout(() => { oclockWindow.value = true }, 1000) } // util.getMyTask() // 监听登录 uni.$on('login', () => { recList.data.length = 0 attList.data.length = 0 nextTick(() => { // 获取列表 tabCurrent.value.refreshList() }) }) // 监听登录 uni.$on('logout', () => { // 获取列表 tabCurrent.value.refreshList() }) // 视频数据被修改 uni.$on('updateVideo', (item) => { // 校验 if (!item && !item.videoId) return const list = tabCurrent.value.listData() const findIndex = list.findIndex(node => node.id == item.id) if (findIndex >= 0) list.splice(findIndex, 1, { ...tabCurrent.value.listData()[findIndex], ...item, }) }) // 视频用户关注 uni.$on('focusUser', (param) => { if (!param.userId) return // 重载关注列表 refreshAttList() // 切换推荐列表的关注状态 for (var index = 0; index < recList.data.length; i++) { const item = recList.data[index] if (item.userId == param.userId) { item.isAttention = param.result recList.data.splice(index, 1, item) } } }) }) onReady(() => { setTimeout(() => { // 获取视频容器节点信息 dom.getComponentRect(proxy.$refs.containerRef[0], (option) => { viewSize.height = option.size.height viewSize.width = option.size.width }) }, 50) // // proxy.$refs.productAltRef.init() handleProBuy({ productId: 42, }) }) onShow(() => { // 触发自定义tabbar函数 uni.$emit('changeMine', 'default') }) onHide(() => { // 暂停视频 if (proxy.$refs[`videoRef${tabIndex.value}`]) { proxy.$refs[`videoRef${tabIndex.value}`][current[tabIndex.value]].pause() } }) onUnload(() => { uni.$off('login') uni.$off('logout') uni.$off('updateVideo') uni.$off('focusUser') }) // 重载关注列表 function refreshAttList() { attList.pageNum = 1 attList.total = 0 getAttList() } // 获取更多关注列表 function getMoreAttList() { if (attList.total <= attList.data.length) return attList.pageNum++ getAttList() } // 获取关注列表 function getAttList() { // api.video.followVideo({ query: { pageSize: attList.pageSize, pageNum: attList.pageNum, }, }).then(rs => { handleListData(rs, attList) }) } // 重载推荐列表 function refreshRecList() { console.log('refreshRecList') recList.pageNum = 1 recList.total = 0 getRecList() } // 获取更多推荐视频 function getMoreRecList() { if (recList.total <= recList.data.length) return recList.pageNum++ getRecList() } // 获取推荐视频 function getRecList() { console.log('getRecList') // 获取首页分页视频 api.video.homeVideo({ query: { userId: userinfo.value.id || 0, pageNum: recList.pageNum, pageSize: recList.pageSize, } }).then(rs => { console.log('getRecList then') handleListData(rs, recList) }) } /** * 数据列表 * @param {Object} rs 接口返回数据 * @param {Object} obj 数据对象 */ function handleListData(rs, obj) { if (rs.code == 200) { // 如果第一页 if (obj.pageNum == 1) obj.data.length = 0 nextTick(() => { // 总数 obj.total = rs.total // 合并 obj.data.push(...rs.rows.map(item => { // 初始播放时间 item.readSecond = 0 return item })) // 延时监听播放 setTimeout(() => { const pages = getCurrentPages() // 判断是否当前页 if (pages[pages.length - 1].route != 'pages/index/index') { proxy.$refs[`videoRef${tabIndex.value}`][current[tabIndex.value]].playState.value = false proxy.$refs[`videoRef${tabIndex.value}`][current[tabIndex.value]].pause() } }, 500) console.log('listdata final', rs, obj) }) return } util.alert({ content: rs.msg, showCancel: false, }) } /** * 观看视频记录 * @param {Object} element 记录的视频元素对象 */ function browseLog(element) { util.isLogin().then(rs => { // if (readSecond.count == 0) return const data = { // 视频id videoId: element.item.id, // 有效读秒时间统计 viewingDuration: Math.floor(element.item.readSecond), // 视频秒数 videoDescription: Math.floor(element.videoTime.currentTime), // task: 0, } console.log('browseLog data', data) // api.video.browseLog({ data, }).then(rs => { if (rs.code == 200) { // 计数 const result = rs.data // 现在的有效读秒 const taskValue = task.value console.log('browseLog result', rs, taskValue) // 如果不是第一次统计 if (taskValue.viewingDuration != 0) { // 如果原来任务是优先任务 当前任务是有效读秒 if (taskValue.taskType == 0 && result.taskType == 1) { // 优先任务完成 播放烟花动画 console.log('优先任务完成 播放烟花动画') handleCompleteMode('complete1') } // 如果原来任务任务是有效读秒 并且新返回的数据小于当前的任务进度 else if (result.taskType == 1 && (result.viewingDuration < taskValue .viewingDuration)) { console.log('有效读秒完成 播放任务完成动画') handleCompleteMode('complete2') } } // uni.$store.commit('setState', { key: 'task', value: result, }) return } else { console.log('browseLog err', rs) } }) }) } /** * 触发完成特效 * @param {Object} mode ['complete1'|'complete2'] 需要触发的动画键 */ function handleCompleteMode(mode) { // 开启对应的特效 completeKey.value = mode setTimeout(() => { // 关闭特效 completeKey.value = '' }, completeConfig[mode].time * 1000) } // 有效读秒增加 function readSecondAdd() { clearInterval(readSecond.timer) // 当前视频对象 const item = tab[tabIndex.value].listData()[current[tabIndex.value]] // 开启计时器 readSecond.timer = setInterval(() => { // 当前视频有效读秒 小于等于 最大有效读秒限制(视频最大时长-2s,设置的有效读秒上限) if (item.readSecond < Math.min(Math.floor(item.videoDuration) - 2, config.value .EFFECTIVE_VIDEO_TIME)) { // 增加这条视频的有效读秒 item.readSecond++ } else { // 暂停有效读秒的统计 readSecondPause() } }, 1000) } // 暂停有效读秒的统计 function readSecondPause() { // 暂停唱片 proxy.$refs.discRef && proxy.$refs.discRef.pause() clearInterval(readSecond.timer) } /** * 触摸事件完成 滚动到当前位置 * @param {Number} target 滚动到下标的元素 */ function scrollTo(target) { // tab下标 const tab_index = tabIndex.value // 当前cell对象 const element = proxy.$refs[`cellRef${tab_index}`][target] // 上一个视频组件 const lastVideoRef = proxy.$refs[`videoRef${tab_index}`][currentLast[tab_index]] // 滚动到对应位置 dom.scrollToElement(element, { animated: true }) // 如果视频切换 if (current[tab_index] != currentLast[tab_index]) { // 停止当前有效读秒统计 readSecondPause() // 浏览记录 browseLog(lastVideoRef) // 开始记录 readSecondAdd() } } /** * 触摸开始 * @param {Object} ev 默认事件 * @param {Number} index 所在swiper滑块 */ function onTouchstart(ev, index) { Object.assign(currentLast, current) startY.value = ev.changedTouches[0].screenY } /** * 触摸结束 * @param {Object} ev 默认事件 * @param {Number} index 所在swiper滑块 */ function onTouchend(ev, index) { // 结束 const endY = ev.changedTouches[0].screenY // 列表 const list = tabCurrent.value.listData() if (!list[0]) return // 根据滑动距离 if (endY - startY.value < -50) { if (current[index] < list.length - 1) current[index]++ scrollTo(current[index]) } else if (endY - startY.value > 50) { if (current[index] > 0) current[index]-- scrollTo(current[index]) } else if (endY - startY.value == 0) { // } else { scrollTo(current[index]) } } /** * 切换tab下标 * @param {Number} index 点击的item */ function handle_tab(index) { // if (tabIndex.value === index) return // 如果是关注 但是没有用户id if (tab[index].name == '关注' && !userinfo.value.id) { uni.navigateTo({ url: '/pages/login/loginPhone' }) return } // 如果有 if (proxy.$refs[`videoRef${tabIndex.value}`]) { // 上一个视频对象 const lastVideoRef = proxy.$refs[`videoRef${tabIndex.value}`][current[tabIndex.value]] // 暂停当前播放的视频 lastVideoRef.pause() // 停止当前有效读秒统计 readSecondPause() // 浏览记录 browseLog(lastVideoRef) // 清空累计继续计时 readSecond.total += readSecond.count // 开始记录有效读秒 readSecondAdd() } tabIndex.value = index // 根据是否加载过判断 播放还是获取 if (tabCurrent.value.load && proxy.$refs[`videoRef${index}`]) proxy.$refs[`videoRef${index}`][current[index]] .play() else tabCurrent.value.refreshList() // 已加载 tab[tabIndex.value].load = true } // 打开计时闹钟弹窗 function handleShowTime() { proxy.$refs.timeRef.open() } /** * 打开评论弹窗 * @param {Object} item 当前列表项 */ function handleShowCommentAlt(item) { proxy.$refs.commentRef.open(item) // 暂停当前视频 currentVideoRef.value.pause() } /** * 打开收藏弹窗 * @param {Object} item 视频项下标 */ function handleShowCollectAlt(item) { proxy.$refs.collectRef.open(item) } /** * 打开快速收藏弹窗 * @param {Object} ev 视频对象和节点位置信息 */ function handleShowFastCollect(ev) { proxy.$refs.fastCollectRef.open(ev) } /** * 打开分享给好友弹窗 * @param {Object} item 当前列表项 */ function handleShowShareFirend(item) { proxy.$refs.shareFirendRef.open(item) } // 视频播放 function handleVideoOnPlay() { if (proxy.$refs.discRef) proxy.$refs.discRef.play() // 开始计时有效读秒 readSecondAdd() } // 视频暂停 function handleVideoOnPause() { if (proxy.$refs.discRef) proxy.$refs.discRef.pause() readSecondPause() } /** * 视频点赞 * @param {Object} param 见下 * @param {Number} param.index 操作的视频下标 * @param {Number|String} param.isLike 0.点赞 1.取消点赞 * @param {Number|String} param.likeType 点赞类型 0.公开赞 1.隐私赞 */ function videoLike(param) { // 当前项 const item = tabCurrent.value.listData()[param.index] console.log('data', { // 视频id videoId: item.id, // 点赞用户id likeUserId: userinfo.value.id, // 被点赞用户id targetUserId: item.userId, // 点赞类型 0.公开赞 1.隐私赞 likeType: param.likeType, // //点赞 0.点赞 1.取消点赞 isLike: param.isLike, }) // api.video.videoLike({ data: { // 视频id videoId: item.id, // 点赞用户id likeUserId: userinfo.value.id, // 被点赞用户id targetUserId: item.userId, // 点赞类型 0.公开赞 1.隐私赞 likeType: param.likeType, // //点赞 0.点赞 1.取消点赞 isLike: param.isLike, } }).then(rs => { if (rs.code == 200) { uni.$emit('updateVideo', { ...item, ...rs.data, }) return } util.alert({ content: rs.msg, showCancel: false, }) }) } // 设置闹铃 function setAlarm() { oclockWindow.value = false // 设置闹铃弹窗不再弹出 uni.setStorageSync('alarmAlt', true) } // 打开左侧菜单 function showLeftMenu() { util.isLogin().then(rs => { proxy.$refs.leftMenuRef.open() }).catch(() => { uni.navigateTo({ url: '/pages/login/loginPhone' }) }) } /** * 修改当前视频播放速度 * @param {Object} item */ function handleSpeed(item) { // 速度 const speed = item.value // const tab_index = tabIndex.value // 修改视频倍速 proxy.$refs[`videoRef${tab_index}`][current[tab_index]].videoCtx().playbackRate(speed) // 修改视频数据对象 tabCurrent.value.listData()[current[tab_index]].speed = speed } // 唤起闹铃 function showAlarm() { proxy.$refs.timeRef.open() } /** * 打开产品弹窗 * @param {Object} item 视频列表项 */ function handleShowProduct(item) { // 打开产品详情弹窗 proxy.$refs.productAltRef.init(item.productId) } /** * 商品弹窗购买 * @param {Object} product */ function handleProductBuy(product) { Object.assign(productDetail, {}, product) // proxy.$refs.productSpecAltRef.open() } /** * 商品购买 * @param {Object} video 视频列表 */ function handleProBuy(video) { // 获取商品详情 api.shop.productDetail({ query: { // 产品id productionId: video.productId, }, }).then(rs => { if (rs.code == 200) { // handleProductBuy(rs.data) return } util.alert({ content: rs.msg, showCancel: false, }) }) } /** * 立即下单 */ function handlePay(event) { // 产生待付款订单 api.shop.addOrder({ data: [{ // 地址id addressId: event.address.id, // 产品id productId: productDetail.id, // 规格id attrValueId: event.spec.id, // 数量 payNum: event.payNum, // 0-普通订单,1-视频号订单 orderType: 1, // 分享人id // shareId: userinfo.id, }], }).then(rs => { if (rs.code === 200) { // 跳转 if (rs.data && rs.data[0]) uni.navigateTo({ url: util.setUrl('/pages/shop/commodity/payment', { orderId: rs.data[0].orderId, }) }) return } util.alert({ content: rs.msg, showCancel: false, }) }) } </script> <template> <!-- 页面内容 --> <view class="page pr f1"> <!-- 顶部内容 --> <view class="top pf t0 l0 r0"> <statusBar /> <view class="menu head fdr jcsa aic plr40"> <view class="sider" @click="showLeftMenu"> <image class="wh40" src="@/static/indexList.png" mode="aspectFit" /> </view> <view class="f1"> <view class="tab fdr jcc" :key="tabIndex"> <view class="list" v-for="(item,index) in tab" :key="index" :class="[{'active': index === tabIndex}]" @click.stop="handle_tab(index)"> <view class="txt"> <text class="text">{{item.name}}</text> </view> <view class="line"></view> </view> </view> </view> <navigator url="/pages/index/search" class="search"> <image class="wh65" src="@/static/indexSearch.png" mode="aspectFit" /> </navigator> </view> </view> <!-- 优先任务结算 --> <template v-if="completeKey == 'complete1'"> <image class="complete1" src="/static/complete1.gif" mode="aspectFit" /> </template> <!-- 有效读秒结算 --> <view class="complete2" :style="{top: complete2Top + 'px'}" v-if="completeKey == 'complete2'"> <image class="image" src="/static/complete2.gif" mode="aspectFit" /> </view> <!-- 有效读秒唱片 --> <view class="disc pf r0" :style="{top: discOffsetTop+'px'}" v-if="userinfo.id"> <disc ref="discRef" /> </view> <template v-for="(item, index) in tab" :key="index"> <view class="container f1" v-if="tabIndex == index" ref="containerRef"> <!-- 主要内容区域 --> <list class="listBox f1" :show-scrollbar="false" @touchstart="onTouchstart($event,index)" @touchend="onTouchend($event,index)" @loadmore="item.getMoreList"> <cell class="cell" :style="[{height: viewSize.height + 'px'}]" :ref="`cellRef` + index" v-for="(secItem,secIndex) in item.listData()" :key="secItem.id" @click.stop> <!-- <template v-if="current[tabIndex] < secIndex + 2 && current[tabIndex] > secIndex - 2"> --> <!-- 视频 --> <indexVideo :ref="'videoRef' + index" :tabIndex="index" :current="current[tabIndex]" :width="viewSize.width" :height="viewSize.height" :item="secItem" :index="secIndex" @showTime="handleShowTime" @showComment="handleShowCommentAlt" @showCollect="handleShowCollectAlt" @showShareFirend="handleShowShareFirend" @onPlay="handleVideoOnPlay" @onPause="handleVideoOnPause" @like="videoLike" @longtap="$refs.moreMenuRef.open(secItem)" @showFastCollect="handleShowFastCollect" @showProduct="handleShowProduct" @proBuy="handleProBuy" /> <!-- </template> --> </cell> </list> </view> </template> <!-- 计时提示 --> <view class="oclockHint pa pfull fmid" @touchstart.stop="" @click.stop="setAlarm" v-if="oclockWindow"> <image class="image" src="/static/indexOclock.png" mode="widthFix" /> </view> <!-- 底部导航 --> <footerMenu ref="footerMenuRef" page="index" subject="dark" /> </view> <!-- 快捷收藏 --> <fastCollect ref="fastCollectRef" /> <!-- 青少年模式 --> <teen ref="teenRef" /> <!-- 长按菜单 --> <moreMenu ref="moreMenuRef" @changeSpeed="handleSpeed" /> <!-- 闹钟弹窗 --> <timeAlt ref="timeRef" /> <!-- 评论弹窗 --> <commentAlt ref="commentRef" /> <!-- 收藏弹窗 --> <collectAlt ref="collectRef" /> <!-- 分享到好友弹窗 --> <shareFirendAlt ref="shareFirendRef" /> <!-- 左侧菜单弹窗 --> <leftMenuAlt ref="leftMenuRef" /> <!-- 产品详情弹窗 --> <productAlt ref="productAltRef" @click="handleProductBuy" /> <!-- 产品立即下单弹窗 --> <productSpecAlt ref="productSpecAltRef" :detail="productDetail" @confirm="handlePay" /> </template> <style lang="scss" scoped> // .page { background-color: #161616; } // 优先任务完成 .complete1 { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 10; transform: translateY(100rpx); } // 有效读秒完成 .complete2 { position: fixed; justify-content: flex-end; right: 30rpx; z-index: 10; // .image { width: 80rpx; height: 80rpx; } } // 顶部导航 .top { z-index: 10; } // 分栏 .tab { .list { margin: 0 20rpx; &.active { .text { color: #fff; } .line { opacity: 1; } } .text { color: rgba(255, 255, 255, .7); } .line { margin: 0 10rpx; height: 5rpx; background-color: #fff; opacity: 0; } } } // 钟表提示 .oclockHint { background-color: rgba(0, 0, 0, .3); .image { margin-top: 300rpx; width: 750rpx; } } // 唱片盒子 .disc { margin-right: 20rpx; } </style>