uniapp-app/pages_app/videoDetail/videoDetail.vue
2025-09-16 16:19:29 +08:00

588 lines
14 KiB
Vue
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.

<template>
<uni-nav-bar
left-icon="left"
title="视频详情"
@clickLeft="goBack"
fixed
color="#8B2316"
height="140rpx"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<uni-icons type="share" size="22" color="#8B2316" />
<view class="collect-img" @click="toCollection">
<image class="collect-img-icon" :src="videoInfo.isCollection?collectImg:discollectImg" mode="aspectFill" />
</view>
</view>
</template>
</uni-nav-bar>
<view class="video-detail-page">
<scroll-view class="content-scroll" scroll-y :show-scrollbar="false" @scrolltolower="onScrollToLower" lower-threshold="60">
<!-- 视频区域 -->
<view class="player-wrapper">
<!-- #ifdef APP -->
<uniVideo></uniVideo>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<video class="player" :src="videoSrc" :poster="poster" controls objectFit="contain"></video>
<!-- #endif -->
</view>
<!-- 标签切换 -->
<view class="tabs">
<view class="tab" :class="{ active: activeTab === 'info' }" @click="switchTab('info')">视频简介</view>
<view class="tab" :class="{ active: activeTab === 'comment' }" @click="switchTab('comment')">评论</view>
</view>
<!-- 内容区 -->
<view v-if="activeTab === 'info'" class="intro">
<!-- 显示传递过来的视频信息 -->
<view v-if="pageParams.title" class="video-info">
<view class="video-title">{{ decodeURIComponent(pageParams.title) }}</view>
<view v-if="pageParams.author" class="video-author">{{ decodeURIComponent(pageParams.author) }}</view>
</view>
<view class="speaker">{{videoInfo.public_name}}</view>
<text class="intro-text">{{ videoInfo.note }}</text>
</view>
<view v-else class="comments">
<view v-if="commentList.length === 0" class="empty">暂无评论</view>
<view v-else>
<view class="comment-item" v-for="(c, idx) in commentList" :key="idx">
<image class="avatar" :src="c.avatar" mode="aspectFill" />
<view class="meta">
<view class="name">{{ c.name }}</view>
<view class="content">{{ c.content }}</view>
<view class="time">{{ c.time }}</view>
<view v-if="c.children && c.children.length" class="child-list">
<view class="child-item" v-for="(r,i) in c.children" :key="i">
<image class="avatar small" :src="r.avatar" mode="aspectFill" />
<view class="meta">
<view class="name">{{ r.name }}</view>
<view class="content">{{ r.content }}</view>
<view class="time">{{ r.time }}</view>
</view>
</view>
</view>
</view>
<view class="reply-btn" @click="onReply(c)">回复</view>
</view>
<!-- 加载状态/没有更多 -->
<view v-if="loading" class="list-loading">加载中...</view>
<view v-if="noMore" class="list-no-more">没有更多了</view>
</view>
</view>
<!-- 留白避免被底部按钮遮挡 -->
<!-- <view class="bottom-spacer"></view> -->
</scroll-view>
</view>
<!-- 底部区域信息页为下载条评论页为上传图片+输入+发送 -->
<view v-if="activeTab === 'info'" class="bottom-download" @click="onDownload">
<text class="download-text">点击下载(限时<text class="discount">5</text>仅需{{videoInfo.point-welfareNum}}积分)</text>
</view>
<view v-else class="bottom-comment">
<input class="comment-input" v-model="commentText" placeholder="我也说一句" confirm-type="send" @confirm="sendComment" />
<view class="send-btn" @click="sendComment">发送</view>
</view>
<unidialog :visible="networkVisible" :content="networkContent" @close="networkVisible=false" @confirm="networkConfirm"></unidialog>
<unidialog :visible="pointVisible" :content="pointContent" @close="pointVisible=false" @confirm="pointConfirm"></unidialog>
<unidialog :visible="notEnoughVisible" :content="notEnoughContent" @close="notEnoughVisible=false" @confirm="notEnoughConfirm"></unidialog>
</template>
<script setup>
import { ref } from 'vue';
import uniVideo from '@/components/uniVideo/uniVideo.vue';
import { onLoad,onShow } from "@dcloudio/uni-app";
import unidialog from '@/components/dialog/dialog.vue';
import collectImg from '@/static/icon_book_collect_sel.png';
import discollectImg from '@/static/icon_book_collect_nor.png';
import api from '@/api/api';
import docUrl from '@/utils/docUrl'
import navTo from '@/utils/navTo'
const video_uuid=ref('');
const videoInfo=ref({});
const networkVisible=ref(false);
const networkContent=ref('');
const pointVisible=ref(false);
const pointContent=ref('');
const notEnoughVisible=ref(false);
const notEnoughContent=ref('');
const welfareNum=ref(0);
const notEnoughConfirm=()=>{
notEnoughVisible.value=false;
navTo({
url:'/pages_app/buyPoint/buyPoint'
})
}
const pointConfirm=()=>{
pointVisible.value=false;
payVideoDownload();
}
const toCollection=()=>{
if(videoInfo.value.isCollection==1){
discollection();
}else{
collection();
}
}
const networkConfirm=()=>{
networkVisible.value=false;
pointVisible.value=true;
}
const addVideoWatchRecord=async()=>{
const res=await api.addVideoWatchRecord({
video_uuid:video_uuid.value
})
if(res.code==200){
}
}
// 接收页面参数
const pageParams = ref({});
const videoDetail=async()=>{
const res=await api.videoDetail({video_uuid:video_uuid.value})
if(res.code==200){
videoInfo.value=res.video;
}
}
const collection=async()=>{
const res=await api.collection({
other_uuid:video_uuid.value,
type:5
})
if(res.code==200){
uni.showToast({ title: '收藏成功', icon: 'none' })
videoDetail()
}
}
const discollection=async()=>{
const res=await api.discollection({
other_uuid:video_uuid.value,
type:5
})
if(res.code==200){
uni.showToast({ title: '取消收藏成功', icon: 'none' })
videoDetail()
}
};
const isVideoDownloadRecord=ref(false);
const VideoDownloadRecord=async()=>{
const res=await api.isVideoDownloadRecord({video_uuid:video_uuid.value})
if(res.code==200){
isVideoDownloadRecord.value=res.result==0?false:true;
}
}
const getWelfareNum=async()=>{
const res=await api.getWelfareNum({
type:1
})
if(res.code==1){
welfareNum.value=res.WelfareNum;
}
}
const videoCommentListV2= async()=>{
loading.value = true;
try{
const res = await api.videoCommentListV2({
uuid: video_uuid.value
});
if(res && res.code==200 && Array.isArray(res.data)){
const mapped = res.data.map(mapComment);
commentList.value = mapped.reverse();
noMore.value = true;
}else{
noMore.value = true;
}
}finally{
loading.value = false;
}
}
onShow(()=>{
if(activeTab.value=='comment'){
videoCommentListV2();
}
videoDetail()
})
// 使用uni-app的onLoad生命周期
onLoad((options) => {
console.log(options)
video_uuid.value=options.id;
addVideoWatchRecord();
VideoDownloadRecord();
getWelfareNum();
});
const videoSrc = ref('');
const poster = ref('/static/livebg.png');
const activeTab = ref('info');
const introText = ref(
'首都医科大学附属北京佑安医院肝病中心四科主任疑难肝病与人工肝中心主任主任医师教授博士生导师。中华医学会肝病学分会副秘书长、委员、终末期肝病学组副组长北京医学会肝病学分会常委肝衰竭及人工肝学组副组长北京肝胆相照公益基金会副理事长等。发表论文500余篇累计SCI论文150余篇出版专著20余部获得科研奖项10余项获得发明专利7项。'
);
// 示例评论数据
const commentList = ref([]);
const switchTab = (tab) => {
activeTab.value = tab;
if(tab=='comment'){
videoCommentListV2();
}
};
const payVideoDownload=async()=>{
const res=await api.payVideoDownload({video_uuid:video_uuid.value})
if(res.code==200){
navTo({
url:'/pages_app/myDownLoad/myDownLoad'
})
}else if(res.code==106){
notEnoughVisible.value=true;
notEnoughContent.value=`您的积分不足,是否购买积分?`
}
};
const onDownload = () => {
console.log(isVideoDownloadRecord.value);
if(!isVideoDownloadRecord.value){
pointContent.value=`当前需要${videoInfo.value.point-welfareNum.value}积分兑换,若删除可以再次缓存`
uni.getNetworkType({
success: function (res) {
console.log(res);
if(res.networkType!='none'){
networkVisible.value=true;
networkContent.value=`当前为${res.networkType}网络,确定下载?`
console.log(11111)
}else{
uni.showToast({title:'当前未联网,请检查网络',icon:'none'})
}
}
});
}else{
navTo({
url:'/pages_app/myDownLoad/myDownLoad'
})
}
};
const goBack = () => {
uni.navigateBack();
};
const addCommentV2=async()=>{
const res=await api.addCommentV2({
article_uuid:video_uuid.value,
type: "8",
comment:commentText.value
})
if(res.code==200){
uni.showToast({ title: '评论成功', icon: 'none' })
commentText.value = '';
videoCommentListV2();
}
}
const onReply = (c) => {
navTo({
url:'/pages_app/reply/reply?comment_partent='+c.content+'&name='+c.name+'&video_uuid='+video_uuid.value
})
};
// 评论输入
const commentText = ref('');
const sendComment = () => {
if (!commentText.value.trim()) {
uni.showToast({ title: '请输入内容', icon: 'none' });
return;
}
addCommentV2();
};
// 上拉加载
const page = ref(1);
const pageSize = ref(10);
const loading = ref(false);
const noMore = ref(false);
const onScrollToLower = async () => {
if (activeTab.value !== 'comment' || loading.value || noMore.value) return;
// 如需分页,可在此按页码调用接口并 push 结果
};
const toFullUrl=(p)=>{
if(!p) return '/static/icon_home_my_public.png';
return p.startsWith('http')?p:(docUrl+p);
}
const mapComment=(item)=>{
return {
avatar: toFullUrl(item.photo||''),
name: item.name || '匿名',
content: item.content || '',
time: item.create_date || '',
children: Array.isArray(item.childs)? item.childs.map(mapComment): []
}
}
</script>
<style lang="scss" scoped>
$nav-h: 140rpx;
$bottom-h: 100rpx;
$bg-color: #f7f7f7;
$text-primary: #333;
$text-secondary: #666;
$theme-color: #8B2316;
.collect-img-icon{
width: 40rpx;
height: 40rpx;
}
.nav-actions {
display: flex;
align-items: center;
gap: 20rpx;
}
.video-detail-page {
height: calc(100vh - #{$nav-h} - #{$bottom-h});
background: $bg-color;
overflow: hidden;
}
.content-scroll {
height: 100%;
}
.player-wrapper {
background: #fff;
}
.player {
width: 100%;
height: 420rpx;
background: #000;
border-bottom: 1rpx solid #eee;
}
.tabs {
background: #fff;
display: flex;
justify-content: center;
gap: 240rpx;
padding: 24rpx 0 0;
position: sticky;
border-bottom:2rpx solid #cccccc;
top: 0;
z-index: 5;
.tab {
position: relative;
padding: 16rpx 0 24rpx;
color: $text-secondary;
font-size: 28rpx;
&.active {
color: $theme-color;
}
&.active::after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 64rpx;
height: 6rpx;
border-radius: 6rpx;
background: $theme-color;
}
}
}
.intro {
background: #fff;
padding: 24rpx 28rpx 40rpx;
.video-info {
margin-bottom: 20rpx;
padding: 20rpx;
background: #f8f9fa;
border-radius: 8rpx;
.video-title {
font-size: 32rpx;
color: #333;
font-weight: bold;
margin-bottom: 8rpx;
line-height: 1.4;
}
.video-author {
font-size: 26rpx;
color: #666;
}
}
.speaker {
font-size: 32rpx;
color: $text-primary;
margin: 12rpx 0 20rpx;
}
.intro-text {
font-size: 30rpx;
line-height: 1.8;
color: $text-primary;
}
}
.comments {
background: #fff;
min-height: 300rpx;
padding: 20rpx 20rpx 40rpx;
.empty {
text-align: center;
color: $text-secondary;
padding: 60rpx 0;
}
.comment-item {
display: flex;
align-items: flex-start;
padding: 24rpx 10rpx;
border-bottom: 2rpx solid #f0f0f0;
.avatar {
width: 100rpx;
height: 100rpx;
border-radius: 20rpx;
margin-right: 20rpx;
background: #eee;
}
.meta {
flex: 1;
min-width: 0;
.name {
font-size: 30rpx;
color: #1f1f1f;
margin-bottom: 6rpx;
}
.content {
font-size: 30rpx;
color: #666;
line-height: 1.6;
margin-bottom: 12rpx;
word-break: break-word;
}
.time {
font-size: 24rpx;
color: #909399;
}
}
.reply-btn {
margin-left: 16rpx;
border: 2rpx solid #8B2316;
color: #8B2316;
border-radius: 36rpx;
padding: 10rpx 24rpx;
font-size: 26rpx;
white-space: nowrap;
}
}
.list-loading,
.list-no-more {
text-align: center;
color: #9aa0a6;
padding: 20rpx 0;
font-size: 26rpx;
}
.child-list {
margin-top: 8rpx;
margin-left: -10rpx;
}
.child-item {
display: flex;
align-items: flex-start;
margin-top: 16rpx;
}
.avatar.small {
width: 64rpx;
height: 64rpx;
border-radius: 12rpx;
margin-right: 16rpx;
}
}
.bottom-spacer {
height: 40rpx;
}
.bottom-download {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: $bottom-h;
background: #00cac1;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
.discount{
color:#d4ff00;
font-size: 40rpx;
}
.download-text {
color: #fff;
font-size: 30rpx;
}
}
.bottom-comment {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 100rpx;
background: #ffffff;
display: flex;
align-items: center;
padding: 0 20rpx;
gap: 16rpx;
z-index: 10;
.comment-input {
flex: 1;
height: 72rpx;
border-radius: 10rpx;
background: #ffffff;
border: 2rpx solid #cacaca;
padding: 0 24rpx;
font-size: 28rpx;
color: #333;
}
.send-btn {
padding: 0 28rpx;
height: 72rpx;
border-radius: 36rpx;
background: #8B2316;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
}
}
</style>