feat:播放器添加清晰度,然后讨论添加icon

This commit is contained in:
小张 2025-09-23 14:31:39 +08:00
parent 0de7531c1a
commit 9b6ad60913
3 changed files with 618 additions and 290 deletions

View File

@ -901,6 +901,36 @@ export class CourseApi {
}
}
// 获取章节练习
static async getSectionExercise(courseId: string, sectionId: string): Promise<ApiResponse<any>> {
try {
console.log('🚀 调用章节练习API课程ID:', courseId, '章节ID:', sectionId)
const response = await ApiRequest.get<any>(`/aiol/aiolCourse/${courseId}/section_exercise/${sectionId}`)
console.log('🔍 章节练习API响应:', response)
return response
} catch (error) {
console.error('❌ 获取章节练习失败:', error)
throw error
}
}
// 获取章节讨论
static async getSectionDiscussion(courseId: string, sectionId: string): Promise<ApiResponse<any>> {
try {
console.log('🚀 调用章节讨论API课程ID:', courseId, '章节ID:', sectionId)
const response = await ApiRequest.get<any>(`/aiol/aiolCourse/${courseId}/section_discussion/${sectionId}`)
console.log('🔍 章节讨论API响应:', response)
return response
} catch (error) {
console.error('❌ 获取章节讨论失败:', error)
throw error
}
}
// 获取章节资料
static async getSectionDocument(courseId: string, sectionId: string): Promise<ApiResponse<any>> {
try {

View File

@ -18,28 +18,7 @@
</div>
<!-- 播放器下方的清晰度选择器 -->
<div v-show="props.videoQualities.length > 1" class="video-controls-bottom">
<div class="quality-selector-bottom">
<span class="quality-label">清晰度</span>
<div class="quality-dropdown-bottom">
<button class="quality-btn-bottom" @click="showQualityMenu = !showQualityMenu">
{{ getCurrentQualityLabel() }}
<svg width="12" height="12" viewBox="0 0 12 12" class="quality-dropdown-icon" :class="{ 'rotated': showQualityMenu }">
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.5" fill="none" />
</svg>
</button>
<div v-if="showQualityMenu" class="quality-menu-bottom">
<div v-for="quality in props.videoQualities" :key="quality.value"
class="quality-option-bottom"
:class="{ active: quality.value === props.currentQuality }"
@click="switchQuality(quality); showQualityMenu = false">
{{ quality.label }}
</div>
</div>
</div>
</div>
</div>
<!-- 已删除自定义清晰度选择器只使用手动创建的按钮 -->
</div>
@ -89,7 +68,14 @@ const isPlaying = ref(false)
//
const isPictureInPicture = ref(false)
const showQualityMenu = ref(false)
const qualityButtonCreated = ref(false) //
// HLS.jsindex.html
// DPlayer
const loadDPlayer = (): Promise<void> => {
@ -153,6 +139,22 @@ const initializePlayer = async (videoUrl?: string) => {
try {
await loadDPlayer()
const url = videoUrl || props.videoUrl
// url
if (!url || typeof url !== 'string') {
console.error('❌ 视频URL无效:', url)
throw new Error('Invalid video URL')
}
const isHLS = url.includes('.m3u8')
// HLS.jsindex.html
if (isHLS) {
console.log('🔧 检测到HLS流使用index.html中的HLS.js')
}
await nextTick()
if (!dplayerContainer.value) return
@ -171,7 +173,6 @@ const initializePlayer = async (videoUrl?: string) => {
}
const DPlayer = (window as any).DPlayer
const url = videoUrl || props.videoUrl
//
if (player) {
@ -183,16 +184,30 @@ const initializePlayer = async (videoUrl?: string) => {
player = null
}
// HLS
const isHLS = url.includes('.m3u8')
console.log('🔍 视频类型检测:', { url, isHLS })
// HLS URL访
if (isHLS) {
console.log('🔍 测试HLS URL可访问性...')
fetch(url, { method: 'HEAD' })
.then(response => {
console.log('✅ HLS URL可访问:', {
status: response.status,
statusText: response.statusText,
headers: Object.fromEntries(response.headers.entries())
})
})
.catch(error => {
console.error('❌ HLS URL不可访问:', error)
})
}
// DPlayer
const dplayerConfig: any = {
container: dplayerContainer.value,
video: {
url: url,
type: isHLS ? 'hls' : 'auto'
type: 'auto' // 使autoDPlayer
},
autoplay: props.autoplay,
theme: '#007bff',
@ -203,6 +218,9 @@ const initializePlayer = async (videoUrl?: string) => {
playbackSpeed: [0.5, 0.75, 1, 1.25, 1.5, 2],
loop: false,
screenshot: true, //
mutex: true, //
airplay: true, // AirPlay
chromecast: true, // Chromecast
contextmenu: [
{
text: '截屏',
@ -215,40 +233,62 @@ const initializePlayer = async (videoUrl?: string) => {
]
}
//
// DPlayer
if (props.videoQualities && props.videoQualities.length > 1) {
console.log('🔧 配置多清晰度支持:', props.videoQualities)
dplayerConfig.quality = {
default: props.currentQuality || props.videoQualities[0]?.value,
options: props.videoQualities.map(q => ({
name: q.label,
url: q.url,
type: isHLS ? 'hls' : 'auto'
}))
}
console.log('🔧 检测到多清晰度但暂时禁用DPlayer原生清晰度功能以避免错误')
console.log('🔧 清晰度数据:', props.videoQualities)
//
const validQualities = props.videoQualities.filter(q =>
q && q.url && typeof q.url === 'string' && q.label
)
console.log('🔧 有效的清晰度数据:', validQualities)
if (validQualities.length !== props.videoQualities.length) {
console.warn('⚠️ 部分清晰度数据无效,已过滤')
}
// HLSHLS.js
if (isHLS) {
console.log('🔧 配置HLS支持')
// DPlayer使
// dplayerConfig.quality = { ... }
} else {
console.log('🔧 单一清晰度或无清晰度数据')
}
// HLSHLS.js
if (isHLS && (window as any).Hls) {
console.log('🔧 配置HLS.js支持')
// DPlayer使'hls'HLS.js
dplayerConfig.video.type = 'hls'
// HLS.js
dplayerConfig.pluginOptions = {
hls: {
// HLS.js
}
enableWorker: true,
lowLatencyMode: false,
backBufferLength: 90
}
}
// DPlayer
console.log('🔍 检查清晰度配置:', {
console.log('✅ HLS.js配置完成:', {
hlsAvailable: !!(window as any).Hls,
hlsSupported: (window as any).Hls?.isSupported(),
videoType: dplayerConfig.video.type
})
} else if (isHLS) {
console.warn('⚠️ HLS流但HLS.js未加载尝试原生播放')
dplayerConfig.video.type = 'auto'
}
//
console.log('🔍 最终DPlayer配置检查:', {
videoQualities: props.videoQualities,
currentQuality: props.currentQuality,
qualitiesLength: props.videoQualities?.length
})
// 使DPlayerquality
console.log('✅ 使用自定义清晰度选择器:', {
mainVideoUrl: url,
availableQualities: props.videoQualities?.length || 0
qualitiesLength: props.videoQualities?.length,
hasQualityConfig: !!dplayerConfig.quality,
qualityConfig: dplayerConfig.quality
})
console.log('🔨 创建DPlayer实例配置:', {
@ -257,14 +297,67 @@ const initializePlayer = async (videoUrl?: string) => {
})
player = new DPlayer(dplayerConfig)
// DPlayerHLS
if (isHLS) {
console.log('🔧 检测到HLS流让DPlayer自动处理')
}
//
setTimeout(() => {
const videoElement = player.video
if (videoElement) {
console.log('🔧 强制设置视频样式以填满容器')
videoElement.style.width = '100%'
videoElement.style.height = '100%'
videoElement.style.objectFit = 'cover' //
//
const videoWrap = dplayerContainer.value?.querySelector('.dplayer-video-wrap')
if (videoWrap) {
;(videoWrap as HTMLElement).style.width = '100%'
;(videoWrap as HTMLElement).style.height = '100%'
}
// DPlayer
const dplayerElement = dplayerContainer.value?.querySelector('.dplayer')
if (dplayerElement) {
;(dplayerElement as HTMLElement).style.width = '100%'
;(dplayerElement as HTMLElement).style.height = '100%'
}
}
}, 100)
// DPlayerquality
console.log('🔍 DPlayer实例创建完成:', {
hasQuality: !!player.quality,
qualityOptions: player.quality?.options,
qualityDefault: player.quality?.default,
videoElement: player.video
videoElement: player.video,
playerMethods: Object.keys(player),
dplayerConfigQuality: dplayerConfig.quality
})
// DPlayerquality
setTimeout(() => {
console.log('🔍 延迟检查DPlayer质量对象:', {
hasQuality: !!player.quality,
qualityObject: player.quality,
containerHTML: dplayerContainer.value?.innerHTML?.substring(0, 200) + '...'
})
// DOM
const qualityElements = dplayerContainer.value?.querySelectorAll('[class*="quality"]')
console.log('🔍 DOM中的质量相关元素:', qualityElements)
//
if (props.videoQualities && props.videoQualities.length > 1) {
console.log('🔧 创建手动清晰度按钮(多清晰度支持)')
createManualQualityButton()
} else {
console.log('🔧 单一清晰度,不需要清晰度按钮')
}
}, 2000)
//
player.on('play', () => {
console.log('🎬 视频开始播放')
@ -344,17 +437,46 @@ const initializePlayer = async (videoUrl?: string) => {
player.on('loadeddata', () => {
console.log('✅ 视频数据加载完成:', url)
//
//
const videoElement = player.video
if (videoElement) {
console.log('🔧 视频加载完成后强制设置样式以填满容器')
videoElement.style.width = '100%'
videoElement.style.height = '100%'
videoElement.style.objectFit = 'fill'
}
// DPlayer
if (props.videoQualities && props.videoQualities.length > 1) {
setTimeout(() => {
console.log('🔍 尝试手动设置清晰度选项')
console.log('🔍 检查DPlayer原生清晰度功能')
if (player && player.quality) {
console.log('✅ DPlayer quality对象存在:', player.quality)
console.log('✅ DPlayer quality对象存在:', {
quality: player.quality,
options: player.quality.options,
current: player.quality.current
})
// DPlayer
player.on('quality_start', (quality: any) => {
console.log('🔄 DPlayer原生清晰度切换开始:', quality)
})
player.on('quality_end', (quality: any) => {
console.log('✅ DPlayer原生清晰度切换完成:', quality)
//
const qualityValue = props.videoQualities.find(q => q.label === quality.name)?.value
if (qualityValue) {
emit('qualityChange', qualityValue)
}
})
} else {
console.log('❌ DPlayer quality对象不存在尝试其他方法')
console.log('❌ DPlayer quality对象不存在检查DOM结构')
// DOM
const qualityBtn = dplayerContainer.value?.querySelector('.dplayer-quality-button')
console.log('🔍 查找清晰度按钮:', qualityBtn)
const qualityList = dplayerContainer.value?.querySelector('.dplayer-quality-list')
console.log('🔍 DOM中的清晰度元素:', { qualityBtn, qualityList })
}
}, 1000)
}
@ -438,6 +560,147 @@ const togglePictureInPicture = async () => {
//
const createManualQualityButton = () => {
if (!dplayerContainer.value || !player) return
//
if (qualityButtonCreated.value) {
const existingButton = dplayerContainer.value.querySelector('.manual-quality .dplayer-quality-button')
if (existingButton) {
existingButton.textContent = getCurrentQualityLabel()
console.log('✅ 更新已存在按钮文字为:', getCurrentQualityLabel())
}
return
}
console.log('🔧 首次创建清晰度按钮')
// DOM
const existingButton = dplayerContainer.value.querySelector('.manual-quality')
if (existingButton) {
console.log('🔧 DOM中已存在按钮标记为已创建')
qualityButtonCreated.value = true
return
}
// DPlayer
const controlBar = dplayerContainer.value.querySelector('.dplayer-controller')
if (!controlBar) {
console.error('❌ 找不到DPlayer控制栏')
return
}
//
const qualityContainer = document.createElement('div')
qualityContainer.className = 'dplayer-quality manual-quality'
qualityContainer.style.cssText = `
position: absolute !important;
right: 180px !important;
top: 50% !important;
transform: translateY(-50%) !important;
z-index: 1000 !important;
display: inline-block;
cursor: pointer;
min-width: 50px;
text-align: center;
background: none;
`
//
const qualityButton = document.createElement('button')
qualityButton.className = 'dplayer-quality-button'
qualityButton.textContent = getCurrentQualityLabel()
qualityButton.style.cssText = `
background: none;
border: none;
color: white;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
user-select: none;
white-space: nowrap;
min-width: 50px;
text-align: center;
transition: none;
`
//
const qualityMenu = document.createElement('div')
qualityMenu.className = 'dplayer-quality-list'
qualityMenu.style.cssText = `
position: absolute;
bottom: 100%;
right: 0;
background: rgba(0, 0, 0, 0.9);
border-radius: 3px;
margin-bottom: 8px;
display: none;
min-width: 80px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
`
//
props.videoQualities.forEach(quality => {
const option = document.createElement('div')
option.className = 'dplayer-quality-item'
option.textContent = quality.label
option.style.cssText = `
padding: 8px 12px;
color: #fff;
font-size: 13px;
cursor: pointer;
transition: background-color 0.2s;
text-align: center;
${quality.value === props.currentQuality ? 'background: #007bff;' : ''}
`
option.addEventListener('mouseenter', () => {
if (quality.value !== props.currentQuality) {
option.style.backgroundColor = 'rgba(255, 255, 255, 0.1)'
}
})
option.addEventListener('mouseleave', () => {
if (quality.value !== props.currentQuality) {
option.style.backgroundColor = 'transparent'
}
})
option.addEventListener('click', () => {
console.log('🔄 手动切换清晰度到:', quality.label)
switchQuality(quality)
qualityMenu.style.display = 'none'
qualityButton.textContent = quality.label
})
qualityMenu.appendChild(option)
})
//
qualityButton.addEventListener('click', (e) => {
e.stopPropagation()
const isVisible = qualityMenu.style.display === 'block'
qualityMenu.style.display = isVisible ? 'none' : 'block'
})
//
document.addEventListener('click', () => {
qualityMenu.style.display = 'none'
})
qualityContainer.appendChild(qualityButton)
qualityContainer.appendChild(qualityMenu)
// 使
controlBar.appendChild(qualityContainer)
//
qualityButtonCreated.value = true
console.log('✅ 手动清晰度按钮创建完成')
}
//
const getCurrentQualityLabel = () => {
const current = props.videoQualities.find(q => q.value === props.currentQuality)
@ -445,13 +708,38 @@ const getCurrentQualityLabel = () => {
}
const switchQuality = (quality: any) => {
console.log('🔄 开始切换清晰度:', {
quality: quality,
hasPlayer: !!player,
hasUrl: !!quality.url,
qualityValue: quality.value,
qualityLabel: quality.label
})
if (!quality || !quality.url) {
console.error('❌ 清晰度数据无效:', quality)
return
}
// URL
isSwitchingQuality.value = true
console.log('🔧 设置切换清晰度标志,防止播放器重新初始化')
//
setTimeout(() => {
if (isSwitchingQuality.value) {
console.log('⚠️ 切换清晰度超时,强制重置标志')
isSwitchingQuality.value = false
}
}, 10000) // 10
if (player && quality.url) {
try {
//
const currentTime = player.video?.currentTime || 0
const wasPlaying = !player.video?.paused
console.log('🔄 开始切换清晰度:', {
console.log('🔄 切换清晰度详情:', {
from: getCurrentQualityLabel(),
to: quality.label,
currentTime: currentTime,
@ -464,35 +752,85 @@ const switchQuality = (quality: any) => {
player.pause()
}
//
if (player) {
player.destroy()
player = null
}
//
console.log('🔄 直接切换视频源,不重建播放器')
// 使URL
initializePlayer(quality.url).then(() => {
console.log('✅ 播放器重新初始化完成新URL:', quality.url)
//
setTimeout(() => {
if (player && player.video) {
player.seek(currentTime)
console.log('🔧 当前视频源:', player.video.src)
console.log('🔧 切换到新视频源:', quality.url)
// 使DPlayerswitchVideo
if (typeof player.switchVideo === 'function') {
console.log('🔧 使用DPlayer的switchVideo方法')
player.switchVideo({
url: quality.url,
type: quality.url.includes('.m3u8') ? 'hls' : 'auto'
})
//
const handleCanPlay = () => {
console.log('✅ 视频切换完成,恢复播放状态')
player.video.currentTime = currentTime
if (wasPlaying) {
player.play()
}
console.log('✅ 恢复播放状态:', { currentTime, wasPlaying })
}
//
setTimeout(() => {
isSwitchingQuality.value = false
console.log('🔄 清晰度切换完成,重置标志')
}, 500)
}).catch(error => {
console.error('❌ 重新初始化播放器失败:', error)
})
player.video.removeEventListener('canplay', handleCanPlay)
}
player.video.addEventListener('canplay', handleCanPlay, { once: true })
} else {
console.log('🔧 DPlayer不支持switchVideo使用原生方法')
//
player.video.src = quality.url
player.video.load()
const handleLoadedMetadata = () => {
player.video.currentTime = currentTime
if (wasPlaying) {
player.play()
}
console.log('✅ 视频源切换完成,播放状态已恢复')
//
setTimeout(() => {
isSwitchingQuality.value = false
console.log('🔄 清晰度切换完成,重置标志')
}, 500)
}
player.video.addEventListener('loadedmetadata', handleLoadedMetadata, { once: true })
}
//
const existingButton = dplayerContainer.value?.querySelector('.manual-quality .dplayer-quality-button')
if (existingButton) {
existingButton.textContent = quality.label
console.log('✅ 清晰度按钮文字已立即更新为:', quality.label)
}
}
//
emit('qualityChange', quality.value)
console.log('✅ 切换清晰度到:', quality.label)
} catch (error) {
console.error('❌ 切换清晰度失败:', error)
//
isSwitchingQuality.value = false
}
} else {
console.error('❌ 播放器未初始化或清晰度URL无效')
}
}
@ -505,18 +843,36 @@ const setVolume = (volume: number) => {
const destroy = () => {
if (player) {
try {
//
const manualQuality = dplayerContainer.value?.querySelector('.manual-quality')
if (manualQuality) {
console.log('🧹 清理手动创建的清晰度按钮')
manualQuality.remove()
}
player.destroy()
} catch (e) {
console.log('销毁播放器时出错:', e)
}
player = null
playerInitialized.value = false
qualityButtonCreated.value = false //
}
}
//
const isSwitchingQuality = ref(false)
// URL
watch(() => props.videoUrl, (newUrl) => {
//
if (isSwitchingQuality.value) {
console.log('🔄 正在切换清晰度忽略URL变化')
return
}
if (newUrl && playerInitialized.value) {
console.log('🔄 视频URL变化重新初始化播放器:', newUrl)
initializePlayer(newUrl)
}
})
@ -550,23 +906,58 @@ onUnmounted(() => {
<style scoped>
.dplayer-video {
width: 100%;
height: 100%;
width: 100% !important;
height: 100% !important;
}
.video-container {
position: relative;
width: 100%;
height: 100%; /* 填满父容器 */
background: transparent;
height: 100%;
background: #000;
overflow: hidden;
}
.dplayer-wrapper {
width: 100%;
height: 100%;
width: 100% !important;
height: 100% !important;
background: transparent;
}
/* 强制DPlayer和视频填满整个容器 */
.dplayer-wrapper :deep(.dplayer) {
width: 100% !important;
height: 100% !important;
background: #000 !important;
}
.dplayer-wrapper :deep(.dplayer-video-wrap) {
width: 100% !important;
height: 100% !important;
background: #000 !important;
}
.dplayer-wrapper :deep(.dplayer-video) {
width: 100% !important;
height: 100% !important;
object-fit: cover !important; /* 裁剪上下黑边,保持比例填满容器 */
background: #000 !important;
}
/* 全屏时也裁剪黑边 */
.dplayer-wrapper :deep(.dplayer-fullscreen) .dplayer-video {
object-fit: cover !important;
}
/* 更强的样式覆盖,裁剪上下黑边 */
:deep(.dplayer-video) {
object-fit: cover !important;
}
:deep(.dplayer-fullscreen .dplayer-video) {
object-fit: cover !important;
}
.video-placeholder {
position: absolute;
top: 0;
@ -731,99 +1122,7 @@ onUnmounted(() => {
color: #fff;
}
/* 播放器下方的清晰度选择器 */
.video-controls-bottom {
margin-top: 12px;
padding: 12px 16px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.quality-selector-bottom {
display: flex;
align-items: center;
gap: 12px;
}
.quality-label {
font-size: 14px;
color: #495057;
font-weight: 500;
}
.quality-dropdown-bottom {
position: relative;
}
.quality-btn-bottom {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
min-width: 80px;
background: #fff;
color: #495057;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
justify-content: space-between;
}
.quality-btn-bottom:hover {
border-color: #007bff;
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
}
.quality-dropdown-icon {
transition: transform 0.2s ease;
}
.quality-dropdown-icon.rotated {
transform: rotate(180deg);
}
.quality-menu-bottom {
position: absolute;
top: 100%;
left: 0;
right: 0;
margin-top: 4px;
background: #fff;
border: 1px solid #ced4da;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
z-index: 1000;
overflow: hidden;
}
.quality-option-bottom {
padding: 10px 12px;
font-size: 14px;
color: #495057;
cursor: pointer;
transition: background-color 0.2s ease;
border-bottom: 1px solid #f8f9fa;
}
.quality-option-bottom:last-child {
border-bottom: none;
}
.quality-option-bottom:hover {
background-color: #f8f9fa;
}
.quality-option-bottom.active {
background-color: #007bff;
color: #fff;
}
.quality-option-bottom.active:hover {
background-color: #0056b3;
}
/* 已删除播放器下方的清晰度选择器相关样式 */
/* 响应式设计 */
@media (max-width: 768px) {

View File

@ -933,6 +933,11 @@
<img src="/public/images/courses/examination-enroll.png" alt="考试" width="14"
height="14">
</button>
<!-- 讨论图标 - 可点击 -->
<button v-else-if="isDiscussionLesson(section)" class="lesson-action-btn discussion-btn"
@click.stop="handleDiscussion(section)">
<img src="/public/logo/discussion.png" alt="讨论" width="14" height="14">
</button>
</div>
</div>
</div>
@ -1907,15 +1912,47 @@ const loadSectionVideo = async (section: CourseSection) => {
if (response.data && response.data.length > 0) {
const video = response.data[0] //
currentVideo.value = video
videoQualities.value = video.qualities
currentQuality.value = video.defaultQuality
console.log('🔍 原始视频数据:', video)
console.log('🔍 原始清晰度数据:', video.qualities)
console.log('🔍 原始默认清晰度:', video.defaultQuality)
//
videoQualities.value = video.qualities || []
currentQuality.value = video.defaultQuality || '360'
console.log('🔍 处理后的清晰度数据:', videoQualities.value)
console.log('🔍 处理后的当前清晰度:', currentQuality.value)
//
if (videoQualities.value.length > 0) {
const firstQuality = videoQualities.value[0]
console.log('🔍 第一个清晰度对象结构:', firstQuality)
console.log('🔍 是否有必要字段:', {
hasValue: 'value' in firstQuality,
hasLabel: 'label' in firstQuality,
hasUrl: 'url' in firstQuality
})
//
videoQualities.value.forEach((quality, index) => {
console.log(`🔍 清晰度 ${index}:`, {
value: quality.value,
label: quality.label,
url: quality.url,
urlValid: !!quality.url && quality.url.length > 0
})
})
}
console.log('🔍 即将传递给DPlayerVideo的props:', {
videoQualities: videoQualities.value,
currentQuality: currentQuality.value,
qualitiesCount: videoQualities.value.length
})
// URL
const defaultQualityVideo = video.qualities.find((q: any) => q.value === video.defaultQuality)
const defaultQualityVideo = video.qualities?.find((q: any) => q.value === video.defaultQuality)
if (defaultQualityVideo) {
currentVideoUrl.value = defaultQualityVideo.url
currentVideoSection.value = section
@ -1985,7 +2022,19 @@ const onDanmakuSend = (text: string) => {
//
const onQualityChange = (newQuality: string) => {
console.log('🔄 清晰度已切换到:', newQuality)
console.log('🔍 当前可用清晰度:', videoQualities.value)
//
currentQuality.value = newQuality
// URL
const newQualityVideo = videoQualities.value.find((q: any) => q.value === newQuality)
if (newQualityVideo) {
console.log('✅ 找到新清晰度视频:', newQualityVideo)
currentVideoUrl.value = newQualityVideo.url
} else {
console.warn('⚠️ 未找到对应清晰度的视频URL:', newQuality)
}
}
//
@ -2178,8 +2227,8 @@ const toggleChapter = (chapterIndex: number) => {
//
const isPracticeLesson = (section: CourseSection) => {
// type5=
if (section.type === 5) {
// type4=
if (section.type === 4) {
return true
}
// typenull
@ -2248,7 +2297,11 @@ const isExamLesson = (section: CourseSection) => {
}
const isDiscussionLesson = (section: CourseSection) => {
//
// type5=
if (section.type === 5) {
return true
}
// typenull
return section.name.includes('讨论')
}
@ -2443,46 +2496,17 @@ const handlePractice = async (section: CourseSection) => {
}
try {
//
const mockQuestions = [
{
id: 1,
type: '单选题',
title: '以下哪个是JavaScript的数据类型',
options: ['String', 'Integer', 'Float', 'Character'],
score: 10
},
{
id: 2,
type: '多选题',
title: '以下哪些是前端框架?',
options: ['Vue.js', 'React', 'Angular', 'Django'],
score: 15
},
{
id: 3,
type: '判断题',
title: 'JavaScript是一种编译型语言。',
options: ['正确', '错误'],
score: 5
},
{
id: 4,
type: '填空题',
title: 'JavaScript中声明变量使用关键字____。',
blanks: 1,
score: 10
},
{
id: 5,
type: '简答题',
title: '请简述JavaScript闭包的概念和作用。',
score: 20
}
]
// API
const response = await CourseApi.getSectionExercise(courseId.value, section.id.toString())
if (response.data && (response.data.code === 200 || response.data.code === 0)) {
console.log('✅ 获取章节练习成功:', response.data)
//
const exerciseData = response.data.result
if (exerciseData && Array.isArray(exerciseData)) {
//
practiceQuestions.value = mockQuestions
practiceQuestions.value = exerciseData
currentPracticeSection.value = section
practiceMode.value = true
practiceStarted.value = true //
@ -2490,14 +2514,22 @@ const handlePractice = async (section: CourseSection) => {
currentQuestionIndex.value = 0
//
practiceAnswers.value = new Array(mockQuestions.length).fill(null).map(() => [])
fillAnswers.value = new Array(mockQuestions.length).fill(null).map(() => [])
essayAnswers.value = new Array(mockQuestions.length).fill('')
practiceAnswers.value = new Array(exerciseData.length).fill(null).map(() => [])
fillAnswers.value = new Array(exerciseData.length).fill(null).map(() => [])
essayAnswers.value = new Array(exerciseData.length).fill('')
console.log('✅ 练习模式已启动')
console.log('✅ 练习模式已启动,题目数量:', practiceQuestions.value.length)
} else {
console.warn('⚠️ 练习数据格式异常:', exerciseData)
message.warning('练习数据格式异常')
}
} else {
console.error('❌ 获取章节练习失败:', response.data?.message || response.message)
message.error(response.data?.message || response.message || '获取练习失败')
}
} catch (error) {
console.error('❌ 启动练习失败:', error)
message.error('启动练习失败,请稍后重试')
console.error('❌ 获取章节练习异常:', error)
message.error('获取练习失败,请稍后重试')
}
}
@ -2746,63 +2778,32 @@ const handleDiscussion = (section: CourseSection) => {
const loadDiscussionData = async (section: CourseSection) => {
try {
console.log('加载讨论数据:', section.name)
//
discussionList.value = [
{
id: 1,
username: '春暖花开',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=40&q=80',
content: '为了让科学教育有效,它必须符合科学的本质,而且应该让学生认识科学的本质,家庭和社会如何配合学校实现科学教育的目标。',
time: '2025.07.23 16:28',
likes: 23,
replies: []
},
{
id: 2,
username: '春暖花开',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=40&q=80',
content: '为了让科学教育有效,它必须符合科学的本质,而且应该让学生认识科学的本质,家庭和社会如何配合学校实现科学教育的目标。',
time: '2025.07.23 16:29',
likes: 23,
replies: []
},
{
id: 3,
username: '汪深',
avatar: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&auto=format&fit=crop&w=40&q=80',
content: '这位同学的观点很好,希望能够继续深入讨论',
time: '2025.07.23 16:28',
likes: 0,
isTeacher: true,
replies: []
},
{
id: 4,
username: '春暖花开',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=40&q=80',
content: '为了让科学教育有效,它必须符合科学的本质,而且应该让学生认识科学的本质,家庭和社会如何配合学校实现科学教育的目标。',
time: '2025.07.23 16:28',
likes: 23,
images: [
'https://images.unsplash.com/photo-1581091226825-a6a2a5aee158?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80',
'https://images.unsplash.com/photo-1518186285589-2f7649de83e0?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80',
'https://images.unsplash.com/photo-1485827404703-89b55fcc595e?ixlib=rb-4.0.3&auto=format&fit=crop&w=200&q=80'
],
replies: []
},
{
id: 5,
username: '春暖花开',
avatar: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?ixlib=rb-4.0.3&auto=format&fit=crop&w=40&q=80',
content: '为了让科学教育有效,它必须符合科学的本质,而且应该让学生认识科学的本质,家庭和社会如何配合学校实现科学教育的目标。',
time: '2025.07.23 16:28',
likes: 23,
replies: []
console.log('🗣️ 加载讨论数据:', section.name)
// API
const response = await CourseApi.getSectionDiscussion(courseId.value, section.id.toString())
if (response.data && (response.data.code === 200 || response.data.code === 0)) {
console.log('✅ 获取章节讨论成功:', response.data)
//
const discussionData = response.data.result
if (discussionData && Array.isArray(discussionData)) {
discussionList.value = discussionData
console.log('✅ 讨论数据加载完成,讨论数量:', discussionList.value.length)
} else {
console.warn('⚠️ 讨论数据格式异常:', discussionData)
discussionList.value = []
}
} else {
console.error('❌ 获取章节讨论失败:', response.data?.message || response.message)
message.error(response.data?.message || response.message || '获取讨论失败')
discussionList.value = []
}
]
} catch (error) {
console.error('加载讨论数据失败:', error)
console.error('❌ 获取章节讨论异常:', error)
message.error('获取讨论失败,请稍后重试')
discussionList.value = []
}
}
@ -3968,7 +3969,6 @@ onActivated(() => {
}
.video-player.enrolled {
background: #000;
/* 移除固定高度,让内容自适应 */
}
@ -3976,7 +3976,6 @@ onActivated(() => {
position: relative;
width: 100%;
height: 450px; /* 使用固定高度,确保播放器能正常工作 */
background: #000;
}
/* DPlayer 容器样式 */