<template> <BaseContainer> <NavBar title="素材详情" /> <view class="source-detail-page" v-if="source"> <view class="player-box"> <view v-if="source.type === 3"> <video id="myVideo" @loadedmetadata="handleReady" @ended="handleEnded" @timeupdate="handleTimeupdateVideo" class="player-self" :poster="source.image" :src="source.link"></video> </view> <view v-show="tryShow" class="try"> <i class="iconfont iconbofang"></i>免费试看 </view> <image :src="source.image" class="cover" mode="aspectFill" /> </view> <view class="title-wrap"> <view class="title">{{ source.title }}</view> <view class="wrap"> <view class="learn">{{ source.play_count + 1 }}次学习</view> <view v-if="isWeiXin" class="share" @click="share = true"> <view class="iconfont iconfenxiang"></view> 分享 </view> </view> </view> <view v-if="source.type === 2" class="audio-player"> <view v-if="!(!source.videoId && !source.link)" class="control"> <view @click="toggleTask"> <i class="iconfont icon" :class="isPause ? 'iconbofang' : 'iconzanting'"></i> </view> <view class="timeline" @touchmove="moveTask" @touchend="moveEndTask"> <view>{{ currentTime | formatTime(duration) }}</view> <view class="progress"> <view :style="{ width: taskRange + '%' }" class="inner"> <view class="thumb"></view> </view> </view> <view>{{ duration | formatTime }} </view> </view> </view> </view> <view class="main"> <view class="title">详情</view> <mp-html class="wrap" container-style="padding: 30rpx;background: #ffffff;" :content="source.type == 1 && source.is_try ? source.try_content : source.detail"></mp-html> </view> <view class="third"> <view class="group flex"> <view class="btn flex flex-center-y flex-column" @click="goPage(1)"> <image :src="getImgPath('/wap/first/zsff/images/special01.png')" /> <view>首页</view> </view> <!-- 抖音客服 --> <view class="btn flex flex-center-y flex-column" v-if="TOUTIAO_STATUS"> <button class="im" open-type="im" :data-im-id="source.d_im" bindim="imCallback" binderror="onimError"> <image :src="getImgPath('/wap/first/zsff/images/special02.png')" /> 客服</button> </view> <!-- 系统客服 --> <view class="btn flex flex-center-y flex-column" @click="goPage(2)" v-else> <image :src="getImgPath('/wap/first/zsff/images/special02.png')" /> <view>客服</view> </view> </view> <view v-if="course.length > 0" @click="relatedCourses" class="submit-btn flex flex-center">相关课程</view> <view v-else class="submit-btn flex flex-center" @click="relatedCourses">更多课程</view> </view> <image v-show="share" class="share-mask" :src="getImgPath('/wap/first/zsff/images/share-info.png')" @touchmove.prevent @click="share = false" /> <view :class="{ mask: dialog }" @touchmove.prevent @click="dialog = false"></view> <view class="dialog" :class="{ active: dialog }" @touchmove.prevent> <view class="ul" v-if="course.length"> <view class="li" v-for="item in course" :key="item.id"> <navigator :url="`/pages/special/details?id=${item.id}`"> <view class="figure"> <image :src="item.image" /> <text>{{ item.type_name }}</text> </view> <view class="figcaption"> <view class="title">{{ item.title }}</view> <view class="mark"> <text v-for="itm in item.label">{{ itm }}</text> </view> <view class="info"> <view class="money"> ¥<text>{{ item.money }}</text> </view> <view class="lesson">共{{ item.count }}节</view> <view class="learn">{{ item.record }}次学习</view> </view> </view> </navigator> </view> </view> <image v-else class="empty" :src="getImgPath('/wap/first/zsff/images/no_data_available.png')" /> </view> </view> </BaseContainer> </template> <script> import { getSourceDetail, getSpecialRelatedCourses, getVideoPlayCredentials, } from "@/api/special"; import mpHtml from "mp-html/dist/uni-app/components/mp-html/mp-html.vue"; import dayjs from "dayjs"; import { CUSTOMER_DETAIL_TYPE, CUSTOMER_SUPPORT_TYPE } from "@/constants/customer-type"; export default { filters: { formatTime(time, sibling) { let duration = dayjs.duration(time * 1000); let hours = duration.hours(); let siblingHours = sibling ? dayjs.duration(sibling * 1000).hours() : 0; return dayjs({ h: hours, m: duration.minutes(), s: duration.seconds(), }).format((hours || siblingHours ? "HH:" : "") + "mm:ss"); }, }, components: { mpHtml, }, computed: { taskRange: function () { return Math.floor(this.currentTime / this.duration * 100); } }, data() { const { screenHeight: height } = this.$util.getSystemInfo(); return { currentTime: 0, duration: 0, id: "", source: null, course: [], isPause: true, dialog: false, appear: true, share: false, width: 0, height: height - 400 + "rpx", track: null, audio: null, paused: true, aliplayer: null, isWeiXin: false, tryShow: true, player: null, TOUTIAO_STATUS:false }; }, onLoad({ id }) { this.id = id; this.getSourceDetail(); this.getRelateCourse(); /* #ifdef MP-TOUTIAO */ this.TOUTIAO_STATUS = true; /* #endif */ }, onUnload() { this.aliplayer.dispose(); }, methods: { relatedCourses() { if (this.course.length > 0) { this.dialog = true; } else { uni.navigateTo({ url: "/pages/course/special_cate", }); } }, async handlePlayVideo() { if (this.source.videoId) { uni.showLoading({ mask: true }); try { const { msg } = await getVideoPlayCredentials(this.source.videoId, 2); uni.hideLoading(); uni.request({ url: msg, dataType: "json", success: (res) => { if (res.status === 200) { this.handleCreatePlayer(res.data.PlayAuth); } else { this.$util.showMsg(res.Message); } }, fail: (err) => { this.$util.showMsg(err.msg); }, }); } catch (err) { uni.hideLoading(); this.$util.showMsg(err.msg); } } else { } }, goPage(value) { switch (value) { case 1: uni.switchTab({ url: "/pages/index/index", }); break; case 2: this.$util.goSupport(CUSTOMER_DETAIL_TYPE.GOOD, CUSTOMER_SUPPORT_TYPE.SOURCE, this.id); break; } }, // 创建播放器 createPlayer() { this.player = uni.createVideoContext("myVideo"); }, createAudioPlayer() { if (this.player) { this.player.stop(); this.player = null; } const innerAudioContext = uni.createInnerAudioContext(); innerAudioContext.autoplay = false; innerAudioContext.src = this.source.link; innerAudioContext.onCanplay(() => { let intervalID = setInterval(() => { if (innerAudioContext.duration !== 0) { clearInterval(intervalID); this.duration = innerAudioContext.duration; } }, 100); }); innerAudioContext.onEnded(() => { this.handleEnded(); }); innerAudioContext.onTimeUpdate(() => { this.handleTimeupdate(); }); this.player = innerAudioContext; }, handleEnded() { }, handleTimeupdate() { this.currentTime = this.player.currentTime; var floorTime = Math.floor(this.currentTime); if (floorTime && floorTime !== this.floorTime && !(floorTime % 10)) { this.floorTime = floorTime; } }, onUnload() { if(!this.player) return; this.player.pause() }, // 播放/暂停音频 toggleTask: function () { this.isPause = !this.isPause; this.isPause ? this.player.pause() : this.player.play(); }, // 滑动音频 moveTask: function (event) { if (!this.player || !this.player.paused) { this.isPause = true; this.player.pause(); } this.$util.getClientRect(".progress").then(({ left, width }) => { let range = Math.floor(((event.touches[0].pageX - left) / width) * 100); if (range > 100) { range = 100; } this.audioRange = range; }); }, // 滑动音频停止 moveEndTask: function () { this.player.seek((this.duration * this.audioRange) / 100); if (this.player.paused) { this.isPause = false this.player.play(); } }, // 获取素材 async getSourceDetail() { uni.showLoading({ mask: true }); try { const { data } = await getSourceDetail(this.id); uni.hideLoading(); this.source = data; if (this.source.type === 2) { this.createAudioPlayer(); } else if (this.source.type === 3) { this.createPlayer(); } } catch (err) { uni.hideLoading(); this.$util.showMsg(err.msg); } }, // 获取关联课程 async getRelateCourse() { uni.showLoading({ mask: true }); try { const { data } = await getSpecialRelatedCourses(this.id); uni.hideLoading(); const course = data; course.forEach((item) => { switch (item.type) { case 1: item.type_name = "图文"; break; case 2: item.type_name = "音频"; break; default: item.type_name = "视频"; break; } }); this.course = course; } catch (err) { uni.hideLoading(); this.$util.showMsg(err.msg); } }, }, }; </script> <style> .uni-video-video { object-fit: cover !important; } </style> <style scoped lang="scss"> p { box-sizing: border-box; } .prism-player video { object-fit: cover; } .source-detail-page .handle-wrap svg { color: #2c8eff; } .player-box { position: relative; } .player-box .try { position: absolute; display: flex; right: 30rpx; bottom: 30rpx; z-index: 13; padding: 11rpx 16rpx 11rpx 14rpx; border-radius: 23rpx; background-color: rgba(0, 0, 0, 0.6); font-size: 22rpx; line-height: 24rpx; color: #ffffff; } .player-box .try .iconfont { margin-right: 8rpx; vertical-align: middle; font-size: 24rpx; } /* 素材详情 */ .source-detail-page .head { background-color: #ffffff; } .source-detail-page .head image { display: block; width: 100%; height: 420rpx; object-fit: cover; pointer-events: none; } .source-detail-page .foot { position: fixed; bottom: 0; left: 0; z-index: 99; display: flex; align-items: center; width: 100%; height: 100rpx; padding-right: 35rpx; padding-left: 50rpx; border-top: 1px solid #eeeeee; background-color: #ffffff; } .source-detail-page .foot .link { font-size: 18rpx; line-height: 25rpx; text-align: center; color: #666666; cursor: pointer; } .source-detail-page .foot .link .link { margin-left: 60rpx; } .source-detail-page .foot image { height: 34rpx; margin-bottom: 9rpx; } .source-detail-page .foot .button { flex: 1; min-width: 0; height: 76rpx; border-radius: 38rpx; margin-left: 55rpx; background: linear-gradient(90deg, #409dff 0%, #1e85fb 100%); font-size: 28rpx; line-height: 76rpx; text-align: center; color: #ffffff; cursor: pointer; } .source-detail-page .dialog { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1002; height: 914rpx; padding: 30rpx 30rpx 130rpx 30rpx; border-top-left-radius: 20rpx; border-top-right-radius: 20rpx; background-color: #ffffff; overflow: auto; -webkit-overflow-scrolling: touch; transform: translateY(100%); transition: transform 0.3s; } .source-detail-page .dialog.active { transform: translateY(0); } .source-detail-page .mask { z-index: 1001; background-color: rgba(0, 0, 0, 0.5); } .source-detail-page .dialog .li+.li { margin-top: 23rpx; } .source-detail-page .dialog navigator { display: flex; align-items: center; } .source-detail-page .dialog .figure { position: relative; } .source-detail-page .dialog image { display: block; width: 250rpx; height: 140rpx; border-radius: 10rpx; object-fit: cover; pointer-events: none; -webkit-touch-callout: none; } .source-detail-page .dialog .figure text { position: absolute; right: 10rpx; bottom: 10rpx; width: 66rpx; height: 34rpx; border-radius: 3rpx; background-color: rgba(0, 0, 0, 0.5); font-size: 22rpx; line-height: 34rpx; text-align: center; color: #ffffff; } .source-detail-page .dialog .figcaption { flex: 1; min-width: 0; margin-left: 17rpx; } .source-detail-page .dialog .title { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 30rpx; line-height: 42rpx; color: #333333; } .source-detail-page .dialog .mark { margin-top: 8rpx; font-size: 0; } .source-detail-page .dialog .mark text { display: inline-block; height: 40rpx; padding-right: 10rpx; padding-left: 10rpx; border-radius: 10rpx; background-color: rgba(44, 142, 255, 0.06); font-size: 20rpx; line-height: 40rpx; color: #2c8eff; } .source-detail-page .dialog .mark text text { margin-left: 17rpx; } .source-detail-page .dialog .info { display: flex; align-items: center; margin-top: 22rpx; } .source-detail-page .dialog .money { font-weight: bold; font-size: 24rpx; color: #ff6b00; } .source-detail-page .dialog .money text { font-size: 32rpx; line-height: 45rpx; } .source-detail-page .dialog .lesson { flex: 1; min-height: 0; margin-left: 9rpx; font-size: 22rpx; color: #999999; } .source-detail-page .dialog .learn { font-size: 22rpx; color: #999999; } /* 素材详情 */ .source-detail-page .cover { display: block; width: 100%; object-fit: cover; pointer-events: none; -webkit-touch-callout: none; } .source-detail-page .title-wrap { padding-top: 25rpx; padding-right: 30rpx; padding-left: 30rpx; } .source-detail-page .title-wrap .title { -webkit-line-clamp: 2; overflow: hidden; font-size: 32rpx; line-height: 50rpx; color: #333333; } .source-detail-page .title-wrap .wrap { display: flex; justify-content: space-between; align-items: flex-end; padding-top: 18rpx; padding-bottom: 18rpx; } .source-detail-page .title-wrap .learn { font-size: 22rpx; line-height: 45rpx; color: #999999; } .source-detail-page .title-wrap .share { font-size: 26rpx; line-height: 37rpx; text-align: center; color: #999999; cursor: pointer; } .source-detail-page .title-wrap .iconfenxiang { margin-bottom: 13rpx; font-size: 40rpx; color: #707070; } .source-detail-page .main { border-top: 14rpx solid #f5f5f5; } .source-detail-page .main .title { padding-top: 25rpx; padding-bottom: 26rpx; padding-left: 28rpx; box-shadow: 0 6rpx 20rpx rgba(0, 0, 0, 0.03); font-weight: bold; font-size: 32rpx; line-height: 45rpx; color: #282828; } .source-detail-page .main .wrap { padding: 30rpx 30rpx 130rpx; } .source-detail-page .main .wrap image { display: block; width: 100%; pointer-events: none; -webkit-touch-callout: none; } .source-detail-page .footer { position: fixed; right: 0; bottom: 0; left: 0; z-index: 1001; display: flex; align-items: center; height: 100rpx; padding-right: 35rpx; padding-left: 50rpx; border-top: 1px solid #eeeeee; background-color: #ffffff; } .source-detail-page .footer button { flex: 1; height: 76rpx; border-radius: 38rpx; margin-left: 55rpx; background: linear-gradient(90deg, #409dff 0%, #1e85fb 100%); font-size: 28rpx; color: #ffffff; } .source-detail-page .footer view { font-size: 20rpx; line-height: 36rpx; text-align: center; color: #666666; } .source-detail-page .footer view view { margin-left: 60rpx; cursor: pointer; } .source-detail-page .footer image { display: block; height: 44rpx; margin: 0 auto; pointer-events: none; -webkit-touch-callout: none; } .source-detail-page .share-mask { position: fixed; top: 0; left: 0; z-index: 1003; width: 100%; height: 100%; cursor: pointer; } .source-detail-page .progress { // display: flex; // align-items: center; // padding-top: 25rpx; // padding-right: 30rpx; // padding-left: 30rpx; } .source-detail-page .progress .time { width: 80rpx; font-size: 28rpx; line-height: 40rpx; color: #2c8eff; } .source-detail-page .progress .time:last-child { text-align: right; } .source-detail-page .progress .track { flex: 1; height: 4rpx; border-radius: 2rpx; margin-right: 16rpx; margin-left: 16rpx; background-color: rgba(44, 142, 255, 0.3); } .source-detail-page .progress .range { position: relative; width: 50%; height: 100%; background-color: rgba(44, 142, 255, 0.8); cursor: pointer; } .source-detail-page .progress .thumb { // position: absolute; // top: -6rpx; // right: -6rpx; // width: 16rpx; // height: 16rpx; // border-radius: 50%; // background-color: #2c8eff; } .source-detail-page .handle-wrap { display: flex; justify-content: center; align-items: center; padding-top: 35rpx; padding-bottom: 50rpx; } .source-detail-page .handle-wrap .iconfont { font-size: 36rpx; color: #cccccc; } .source-detail-page .handle-wrap button:disabled .iconfont { color: #46505b; } .source-detail-page .handle-wrap button:nth-child(2) { margin-right: 88rpx; margin-left: 88rpx; } .source-detail-page .handle-wrap svg { font-size: 110rpx; } .source-detail-page .empty { position: absolute; top: 50%; left: 50%; width: 414rpx; height: 336rpx; transform: translate(-50%, -50%); } .third { position: fixed; right: 0; bottom: 0; left: 0; z-index: 54; display: flex; align-items: center; padding-right: 30rpx; padding-bottom: var(--safe-bottom); border-top: 1px solid #eeeeee; background-color: #ffffff; } .third .group { padding: 0 22rpx; } .third .group .btn { height: 100rpx; padding: 0 28rpx; font-size: 18rpx; line-height: 24rpx; color: #333333; } .third .group image { width: 40rpx; height: 40rpx; margin-bottom: 5rpx; vertical-align: middle; } .third .submit-btn { flex: 1; height: 76rpx; border-radius: 38rpx; background: linear-gradient(90deg, #409dff 0%, #1e85fb 100%); font-size: 28rpx; color: #ffffff; } .player-self { width: 100%; } .audio-player { display: flex; flex-direction: column; justify-content: center; } .audio-player .image { display: block; width: 464rpx; margin: 0 auto; height: 160rpx; } .audio-player .control { display: flex; align-items: center; padding: 40rpx 30rpx 0; } .audio-player .icon { font-size: 64rpx; color: #2C8EFF; } .audio-player .timeline { flex: 1; display: flex; align-items: center; margin-left: 30rpx; font-size: 28rpx; color: #2C8EFF; } .audio-player .progress { flex: 1; height: 4rpx; border-radius: 2rpx; margin: 0 18rpx; background-color: rgba(44, 142, 255, 0.2); } .audio-player .inner { position: relative; width: 0; height: 4rpx; border-radius: 2rpx; background-color: #2C8EFF; } .audio-player .thumb { position: absolute; top: 50%; right: 0; width: 16rpx; height: 16rpx; border-radius: 50%; background-color: #2C8EFF; transform: translate(50%, -50%); } .icon { height: auto !important; } </style>