uniapp-app/pages_app/videoDetail/videoDetail.vue
2025-11-17 11:44:17 +08:00

1015 lines
24 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>
<view class="navbox">
<view class="status_bar"></view>
<uni-nav-bar
left-icon="left"
title="视频详情"
@clickLeft="goBack"
color="#8B2316"
:border="false"
backgroundColor="#eeeeee"
>
<template #right>
<view class="nav-actions">
<view class="collect-img" @click="shareToggle">
<image class="share-img-icon" :src="shareIcon" mode="aspectFill" />
</view>
<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>
<video
v-if="showVideo"
class="player-wrapper"
style="height: 220px !important;"
:src="videoSrc"
controls
autoplay
></video>
<!-- 标签切换 -->
<cover-view class="tabs">
<cover-view
class="tab"
:class="{ active: activeTab === 'info' }"
@click="switchTab('info')"
>
视频简介
<view-cover class="tab-line" v-if="activeTab === 'info'"></view-cover>
</cover-view>
<cover-view
class="tab"
:class="{ active: activeTab === 'comment' }"
@click="switchTab('comment')"
>
评论
<view-cover class="tab-line" v-if="activeTab === 'comment'"></view-cover>
</cover-view
>
</cover-view>
<view class="video-detail-page">
<!-- <scroll-view
class="content-scroll"
scroll-y
:show-scrollbar="false"
@scrolltolower="onScrollToLower"
lower-threshold="60"
> -->
<view class="content-scroll">
<!-- 内容区 -->
<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> -->
</view>
</view>
<!-- 底部区域信息页为下载条评论页为上传图片+输入+发送 -->
<!-- <view v-if="activeTab === 'info'" class="bottom-download" @click="onDownload">
<text class="download-text">点击下载(限时<text class="discount">5</text>仅需{{videoInfo.point}}积分)</text>
</view> -->
<view v-if="activeTab !== 'info'" 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>
<!-- 分享弹窗 -->
<uni-popup ref="shareRef" type="bottom" safeArea backgroundColor="#fff" >
<view class="share-popup">
<view class="share-title">分享到</view>
<view class="share-content">
<view class="share-item" @click="shareToWechat">
<view class="share-icon wechat-icon">
<image class="share-img" :src="wxImg" mode="aspectFill" />
</view>
<text class="share-text">微信</text>
</view>
<view class="share-item" @click="shareToMoments">
<view class="share-icon moments-icon">
<image class="share-img" :src="friendImg" mode="aspectFill" />
</view>
<text class="share-text">朋友圈</text>
</view>
<view class="share-item" @click="shareToWeibo">
<view class="share-icon weibo-icon">
<image class="share-img" :src="sinaImg" mode="aspectFill" />
</view>
<text class="share-text">新浪微博</text>
</view>
</view>
<view class="share-cancel" @click="closeShare">
<text>取消</text>
</view>
</view>
</uni-popup>
</template>
<script setup>
import { ref,nextTick } 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 shareIcon from "@/static/icon_share.png";
import sinaImg from "@/static/share_sina.png";
import wxImg from "@/static/share_weixin.png";
import friendImg from "@/static/share_wxc.png";
import logoImg from "@/static/weiboShare.png";
import api from "@/api/api";
import docUrl from "@/utils/docUrl";
import navTo from "@/utils/navTo";
const shareLink = ref("");
import otherHost from "@/utils/otherHost";
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 showVideo = ref(false);
//import DomVideoPlayer from 'uniapp-video-player'
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 onLoadedmetadata = (e) => {
un.showModal({
content: '加载完成:'+JSON.stringify(e),
showCancel: false
})
};
const onVideoError = (e) => {
uni.showModal({
content: JSON.stringify(e),
showCancel: false
})
};
// 接收页面参数
const pageParams = ref({});
const videoDetail = async () => {
const res = await api.videoDetail({ video_uuid: video_uuid.value });
if (res.code == 200) {
const userInfo = uni.getStorageSync("userInfo");
videoInfo.value = res.video;
let vid = res.video.polyv_uuid;
let uuid = vid.substring(0, 10);
let index = vid.lastIndexOf("_");
let f = vid.substring(index - 1, index);
let id = vid.substring(0, index + 1);
//videoSrc.value = 'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4';
videoSrc.value = `http://mpv.videocc.net/${uuid}/${f}/${id}1.mp4`;
showVideo.value = true;
shareLink.value =
otherHost +
"/video/hcp_detial?share=1&uuid=" +
video_uuid.value +
"&expertshare=videoshare&fromtype=doctor";
}
};
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.hideLoading();
});
const shareRef = ref();
// 分享APP
const shareToggle = () => {
shareRef.value.open();
};
// 关闭分享弹窗
const closeShare = () => {
shareRef.value.close();
};
// 分享到微信
const shareToWechat = () => {
// #ifdef APP-PLUS
// 使用系统分享
uni.share({
provider: "weixin",
scene: "WXSceneSession",
type: 0,
title: videoInfo.value.name,
summary: videoInfo.value.note,
href: shareLink.value,
imageUrl: docUrl + videoInfo.value.imgpath,
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
// #endif
// #ifdef H5
// H5环境下的分享
if (navigator.share) {
navigator
.share({
title: "肝胆相照APP",
text: "专业的医疗健康平台,快来下载体验吧!",
url: shareLink.value,
})
.then(() => {
uni.showToast({
title: "分享成功",
icon: "success",
});
})
.catch(() => {
uni.showToast({
title: "分享失败",
icon: "none",
});
});
} else {
// 复制到剪贴板
uni.setClipboardData({
data:
"肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接" +
shareLink.value,
success: () => {
uni.showToast({
title: "已复制到剪贴板",
icon: "success",
});
},
});
}
// #endif
// #ifdef MP-WEIXIN
// 微信小程序分享
uni.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"],
});
// #endif
closeShare();
};
// 分享到朋友圈
const shareToMoments = () => {
// #ifdef APP-PLUS
uni.share({
provider: "weixin",
scene: "WXSceneTimeline",
type: 0,
title: videoInfo.value.name,
summary: videoInfo.value.note,
href: shareLink.value,
imageUrl: docUrl + videoInfo.value.imgpath,
success: function (res) {
console.log("success:" + JSON.stringify(res));
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
// #endif
// #ifdef H5
// 复制到剪贴板
uni.setClipboardData({
data: "肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接https://www.igandan.com",
success: () => {
uni.showToast({
title: "已复制到剪贴板,可分享到朋友圈",
icon: "success",
});
},
});
// #endif
// #ifdef MP-WEIXIN
uni.showShareMenu({
withShareTicket: true,
menus: ["shareAppMessage", "shareTimeline"],
});
// #endif
closeShare();
};
// 分享到新浪微博
const shareToWeibo = () => {
// #ifdef APP-PLUS
uni.share({
provider: "sinaweibo",
type: 0,
title: videoInfo.value.name,
summary: videoInfo.value.note,
href: shareLink.value,
imageUrl: logoImg,
success: function (res) {
console.log("分享成功");
},
fail: function (err) {
console.log("fail:" + JSON.stringify(err));
},
});
// plus.share.sendWithSystem({
// type: 'text',
// content: '肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接https://www.igandan.com'
// }, () => {
// uni.showToast({
// title: '分享成功',
// icon: 'success'
// })
// }, (err) => {
// console.log('分享失败:', err)
// uni.showToast({
// title: '分享失败',
// icon: 'none'
// })
// })
// #endif
// #ifdef H5
// 复制到剪贴板
uni.setClipboardData({
data: "肝胆相照APP - 专业的医疗健康平台,快来下载体验吧!\n下载链接https://www.igandan.com",
success: () => {
uni.showToast({
title: "已复制到剪贴板,可分享到微博",
icon: "success",
});
},
});
// #endif
closeShare();
};
// 使用uni-app的onLoad生命周期
onLoad((options) => {
console.log(options);
video_uuid.value = options.id;
addVideoWatchRecord();
VideoDownloadRecord();
getWelfareNum();
});
const videoSrc = ref("");
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}积分兑换,若删除可以再次缓存`;
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 {
/* margin-top:50rpx; */
}
.navbox{
:deep(.uni-navbar__header-btns-right){
width: 100px!important;
}
:deep(.uni-navbar__header-btns-left){
min-width: 100px;
width: 100px!important;
}
}
.share-img-icon {
width: 40rpx;
height: 40rpx;
}
.collect-img-icon {
width: 40rpx;
height: 40rpx;
}
.nav-actions {
display: flex;
align-items: center;
gap: 20rpx;
}
.video-detail-page {
background: $bg-color;
overflow: hidden;
}
.content-scroll {
height: 100%;
}
.player-wrapper {
background: #fff;
position: fixed;
top:calc(var(--status-bar-height) + 44px);
left: 0;
right: 0;
z-index:0;
width: 100%;
height: 220px;
overflow: hidden;
}
.share-img {
width: 100rpx;
height: 100rpx;
}
.tabs {
background: #fff;
display: flex;
justify-content: center;
position: fixed;
top: calc(var(--status-bar-height) + 44px + 220px);
padding: 24rpx 0 0;
border-bottom: 2rpx solid #cccccc;
width: 100%;
z-index: 99;
.tab {
width:120rpx;
position: relative;
display: flex;
justify-content: center;
align-items: center;
padding: 16rpx 0 24rpx;
color: $text-secondary;
font-size: 28rpx;
.tab-line{
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 64rpx;
height: 6rpx;
border-radius: 6rpx;
background: $theme-color;
}
&:nth-child(2){
margin-left: 200rpx;
}
&.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;
// }
}
}
.video-detail-page{
position: fixed;
width: 100%;
background: #fff;
// padding: 24rpx 28rpx 40rpx;
top: calc(var(--status-bar-height) + 44px + 220px + 88rpx);
height: calc(100vh - var(--status-bar-height) - 44px - 220px - 90rpx);
overflow-y: scroll;
// background: red;
}
.intro {
// background: #fff;
padding: 24rpx 28rpx 40rpx;
// margin-top: calc(var(--status-bar-height) + 44px + 220px + 88rpx);
// height: calc(100vh - var(--status-bar-height) - 44px - 220px - 88rpx);
// overflow-y: scroll;
// padding-bottom: 100rpx;
.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;
// margin-top: calc(var(--status-bar-height) + 44px + 220px + 88rpx);
// height: calc(100vh - var(--status-bar-height) - 44px - 220px - 88rpx);
// overflow-y: scroll;
// padding-bottom: 100rpx;
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;
}
}
/* 分享弹窗样式 */
.share-popup {
background-color: #fff;
border-radius: 20rpx 20rpx 0 0;
padding: 40rpx 0 0;
}
.share-title {
text-align: center;
font-size: 32rpx;
color: #333;
margin-bottom: 40rpx;
font-weight: 500;
}
.share-content {
display: flex;
justify-content: space-around;
padding: 0 40rpx 40rpx;
}
.share-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.share-icon {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.wechat-icon {
background-color: #07c160;
}
.moments-icon {
background-color: #07c160;
}
.weibo-icon {
background-color: #e6162d;
}
.qq-icon {
background-color: #12b7f5;
}
.share-icon-text {
font-size: 50rpx;
color: #fff;
font-weight: bold;
}
.share-text {
font-size: 24rpx;
color: #666;
}
.share-cancel {
height: 100rpx;
display: flex;
align-items: center;
justify-content: center;
border-top: 1rpx solid #f0f0f0;
font-size: 32rpx;
color: #333;
}
.share-cancel:active {
background-color: #f5f5f5;
}
</style>