Files
taimed-international-app/pages/course/details/chapter.vue

467 lines
10 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="chapter-detail-page">
<!-- 自定义导航栏 -->
<nav-bar :title="$t('courseDetails.chapter')" />
<!-- 页面内容 -->
<view class="page-content" :style="{ height: contentHeight }">
<!-- 视频播放器 -->
<view v-if="videoList.length > 0" class="video-section">
<VideoPlayer
ref="videoPlayerRef"
:videoList="videoList"
:currentIndex="currentVideoIndex"
:noRecored="noRecored"
@end="handleVideoEnd"
@fullscreen="handleFullscreen"
@change="handleVideoChange"
/>
</view>
<!-- 课程和章节信息 -->
<view class="info-section">
<view class="info-item">
<text class="label">{{ $t('courseDetails.courseInfo') }}</text>
<text class="value">{{ navTitle }}</text>
</view>
<view class="info-item">
<text class="label">{{ $t('courseDetails.chapterInfo') }}</text>
<text class="value">{{ chapterTitle }}</text>
</view>
</view>
<!-- 视频列表 -->
<view v-if="videoList.length > 0" class="video-list-section">
<view class="section-title">{{ $t('courseDetails.videoTeaching') }}</view>
<view class="video-list">
<view
v-for="(video, index) in videoList"
:key="video.id"
:class="['video-item', currentVideoIndex === index ? 'active' : '']"
@click="selectVideo(index)"
>
<view class="video-info">
<text class="video-title">{{ video.type == "2" ? "音频" : "视频" }}{{ index + 1 }}</text>
<!-- <wd-icon
v-if="currentVideoIndex === index"
name="play-circle"
color="#258feb"
size="14px"
/> -->
</view>
</view>
</view>
</view>
<!-- 选项卡 -->
<view v-if="tabList.length > 0" class="tabs-section">
<view class="tabs">
<view
v-for="(tab, index) in tabList"
:key="tab.id"
:class="['tab-item', currentTab === index ? 'active' : '']"
@click="switchTab(index)"
>
<text>{{ tab.name }}</text>
</view>
</view>
</view>
<!-- 选项卡内容 -->
<view class="tab-content">
<!-- 章节介绍 -->
<view v-show="currentTab === 0" class="intro-content">
<view class="section-title">{{ $t('courseDetails.chapterIntro') }}</view>
<view class="intro-wrapper">
<!-- 章节封面 -->
<image
v-if="chapterDetail?.imgUrl"
:src="chapterDetail.imgUrl"
mode="widthFix"
class="chapter-image"
@click="previewImage(chapterDetail.imgUrl)"
/>
<!-- 章节内容 -->
<view v-if="chapterDetail?.content" class="chapter-content" v-html="chapterDetail.content"></view>
</view>
<view class="copyright">
<text>{{ $t('courseDetails.copyright') }}</text>
</view>
</view>
<!-- 思考题 -->
<view v-show="currentTab === 1" class="question-content">
<view class="section-title">{{ $t('courseDetails.thinkingQuestion') }}</view>
<view v-if="chapterDetail?.questions" class="question-wrapper">
<view class="question-html" v-html="chapterDetail.questions"></view>
</view>
<view v-else class="no-question">
<wd-divider>{{ $t('courseDetails.noQuestion') }}</wd-divider>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { onLoad, onShow, onHide, onUnload } from '@dcloudio/uni-app'
import { courseApi } from '@/api/modules/course'
import VideoPlayer from '@/components/course/VideoPlayer.vue'
import NavBar from '@/components/nav-bar/nav-bar.vue'
import type { IChapterDetail, IVideo } from '@/types/course'
// 页面参数
const chapterId = ref<number>(0)
const courseId = ref<number>(0)
const navTitle = ref('')
const chapterTitle = ref('')
const noRecored = ref(false)
// 页面数据
const chapterDetail = ref<IChapterDetail | null>(null)
const videoList = ref<IVideo[]>([])
const currentVideoIndex = ref(0)
const currentTab = ref(0)
const isFullScreen = ref(false)
// 视频播放器引用
const videoPlayerRef = ref<any>(null)
// 选项卡列表
const tabList = computed(() => {
const tabs = [
{ id: '0', name: '章节介绍' }
]
// 如果有思考题,添加思考题选项卡
if (chapterDetail.value?.questions) {
tabs.push({ id: '1', name: '思考题' })
}
return tabs
})
// 内容高度(全屏时调整)
const contentHeight = computed(() => {
return isFullScreen.value ? '100vh' : 'auto'
})
/**
* 页面加载
*/
onLoad((options: any) => {
chapterId.value = parseInt(options.id)
courseId.value = parseInt(options.courseId)
navTitle.value = options.navTitle || ''
chapterTitle.value = options.title || ''
noRecored.value = options.noRecored === 'true'
loadChapterDetail()
})
/**
* 加载章节详情
*/
const loadChapterDetail = async () => {
try {
uni.showLoading({ title: '加载中...' })
const res = await courseApi.getChapterDetail(chapterId.value)
if (res.code === 0 && res.data) {
chapterDetail.value = res.data.detail
videoList.value = res.data.videos || []
// 如果有历史播放记录,定位到对应视频
if (res.data.current) {
const index = videoList.value.findIndex(v => v.id === res.data.current)
if (index !== -1) {
currentVideoIndex.value = index
}
}
}
} catch (error) {
console.error('加载章节详情失败:', error)
} finally {
uni.hideLoading()
}
}
/**
* 选择视频
*/
const selectVideo = (index: number) => {
if (index === currentVideoIndex.value) return
currentVideoIndex.value = index
}
/**
* 视频切换
*/
const handleVideoChange = (index: number) => {
currentVideoIndex.value = index
}
/**
* 视频播放结束
*/
const handleVideoEnd = () => {
// 视频播放结束的处理已在 VideoPlayer 组件中完成
}
/**
* 全屏变化
*/
const handleFullscreen = (fullscreen: boolean) => {
isFullScreen.value = fullscreen
// 全屏时锁定屏幕方向
// #ifdef APP-PLUS
if (fullscreen) {
plus.screen.unlockOrientation()
plus.screen.lockOrientation('landscape-primary')
} else {
plus.screen.unlockOrientation()
plus.screen.lockOrientation('portrait-primary')
}
// #endif
}
/**
* 切换选项卡
*/
const switchTab = (index: number) => {
currentTab.value = index
}
/**
* 预览图片
*/
const previewImage = (url: string) => {
uni.previewImage({
urls: [url],
current: url
})
}
/**
* 页面显示
*/
onShow(() => {
// 锁定竖屏
// #ifdef APP-PLUS
plus.screen.unlockOrientation()
plus.screen.lockOrientation('portrait-primary')
// #endif
})
/**
* 页面隐藏
*/
onHide(() => {
// 暂停视频
if (videoPlayerRef.value) {
videoPlayerRef.value.pause()
}
})
/**
* 页面卸载
*/
onUnload(() => {
// 停止视频
if (videoPlayerRef.value) {
videoPlayerRef.value.stop()
}
// 解锁屏幕方向
// #ifdef APP-PLUS
plus.screen.unlockOrientation()
// #endif
})
</script>
<style lang="scss" scoped>
.chapter-detail-page {
min-height: 100vh;
background-color: #f5f5f5;
}
.page-content {
padding-bottom: 100rpx;
}
.video-section {
background-color: #000;
}
.info-section {
padding: 20rpx;
background-color: #fff;
.info-item {
margin-bottom: 15rpx;
font-size: 26rpx;
color: #666;
&:last-child {
margin-bottom: 0;
}
.label {
color: #999;
}
.value {
color: #333;
}
}
}
.video-list-section {
padding: 20rpx;
background-color: #fff;
margin-top: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #2979ff;
margin-bottom: 20rpx;
}
.video-list {
display: flex;
flex-wrap: wrap;
justify-content: flex-start;
align-items: center;
gap: 10rpx;
.video-item {
padding: 20rpx;
margin-bottom: 10rpx;
background-color: #f7f8f9;
border-radius: 8rpx;
border: 2rpx solid transparent;
transition: all 0.3s;
&.active {
background-color: #e8f4ff;
border-color: #258feb;
}
.video-info {
display: flex;
justify-content: space-between;
align-items: center;
.video-title {
flex: 1;
font-size: 28rpx;
color: #333;
}
}
}
}
}
.tabs-section {
background-color: #fff;
margin-top: 20rpx;
border-bottom: 2rpx solid #2979ff;
.tabs {
display: flex;
.tab-item {
flex: 1;
text-align: center;
padding: 25rpx 0;
font-size: 30rpx;
color: #666;
position: relative;
transition: all 0.3s;
&.active {
color: #2979ff;
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60rpx;
height: 4rpx;
background-color: #2979ff;
border-radius: 2rpx;
}
}
}
}
}
.tab-content {
background-color: #fff;
padding: 20rpx;
.section-title {
font-size: 32rpx;
font-weight: 500;
color: #333;
margin-bottom: 20rpx;
}
.intro-content {
.intro-wrapper {
.chapter-image {
width: 100%;
display: block;
margin-bottom: 20rpx;
border-radius: 8rpx;
}
.chapter-content {
font-size: 28rpx;
line-height: 1.8;
color: #666;
text-align: justify;
word-break: break-all;
}
}
.copyright {
margin-top: 40rpx;
padding-top: 20rpx;
border-top: 1px solid #f0f0f0;
text-align: center;
text {
font-size: 24rpx;
color: #ff4444;
}
}
}
.question-content {
.question-wrapper {
.question-html {
font-size: 28rpx;
line-height: 1.8;
color: #666;
word-break: break-all;
}
}
.no-question {
padding: 80rpx 0;
text-align: center;
}
}
}
</style>