jiuyiUniapp/jiuyi2/pages/index/index.nvue

935 lines
22 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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
// 重载关注列表
if (tab[0].load) refreshAttList()
console.log(param)
// 切换推荐列表的关注状态
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]
const 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,
}).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" @buy="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>