feat:播放器添加清晰度,然后讨论添加icon
This commit is contained in:
parent
0de7531c1a
commit
9b6ad60913
@ -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 {
|
||||
|
@ -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.js已在index.html中加载,无需动态加载函数
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// 加载 DPlayer
|
||||
const loadDPlayer = (): Promise<void> => {
|
||||
@ -153,8 +139,24 @@ 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.js已在index.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' // 统一使用auto,让DPlayer自动检测
|
||||
},
|
||||
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('⚠️ 部分清晰度数据无效,已过滤')
|
||||
}
|
||||
|
||||
// 暂时不配置DPlayer原生清晰度,使用手动按钮
|
||||
// dplayerConfig.quality = { ... }
|
||||
|
||||
} else {
|
||||
console.log('🔧 单一清晰度或无清晰度数据')
|
||||
}
|
||||
|
||||
// 如果是HLS流,添加HLS.js支持
|
||||
if (isHLS) {
|
||||
console.log('🔧 配置HLS支持')
|
||||
// 如果是HLS流,配置HLS.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
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
// 如果有多个清晰度,启用DPlayer原生清晰度切换
|
||||
console.log('🔍 检查清晰度配置:', {
|
||||
// 最终配置检查
|
||||
console.log('🔍 最终DPlayer配置检查:', {
|
||||
videoQualities: props.videoQualities,
|
||||
currentQuality: props.currentQuality,
|
||||
qualitiesLength: props.videoQualities?.length
|
||||
})
|
||||
|
||||
// 使用自定义清晰度选择器,不依赖DPlayer原生quality功能
|
||||
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)
|
||||
|
||||
// 让DPlayer自动处理HLS,不手动集成
|
||||
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)
|
||||
|
||||
// 检查DPlayer是否正确加载了quality配置
|
||||
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
|
||||
})
|
||||
|
||||
// 延迟检查,因为DPlayer可能需要时间来初始化quality功能
|
||||
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)
|
||||
if (player && player.video) {
|
||||
console.log('🔧 当前视频源:', player.video.src)
|
||||
console.log('🔧 切换到新视频源:', quality.url)
|
||||
|
||||
// 使用DPlayer的switchVideo方法来切换视频源
|
||||
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)
|
||||
|
||||
player.video.removeEventListener('canplay', handleCanPlay)
|
||||
}
|
||||
}, 500)
|
||||
}).catch(error => {
|
||||
console.error('❌ 重新初始化播放器失败:', error)
|
||||
})
|
||||
|
||||
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) {
|
||||
|
@ -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) => {
|
||||
// 优先根据type字段判断:5=练习
|
||||
if (section.type === 5) {
|
||||
// 优先根据type字段判断:4=练习
|
||||
if (section.type === 4) {
|
||||
return true
|
||||
}
|
||||
// 如果type为null,则根据名称判断
|
||||
@ -2248,7 +2297,11 @@ const isExamLesson = (section: CourseSection) => {
|
||||
}
|
||||
|
||||
const isDiscussionLesson = (section: CourseSection) => {
|
||||
// 根据名称判断是否为讨论
|
||||
// 优先根据type字段判断:5=讨论
|
||||
if (section.type === 5) {
|
||||
return true
|
||||
}
|
||||
// 如果type为null,则根据名称判断
|
||||
return section.name.includes('讨论')
|
||||
}
|
||||
|
||||
@ -2443,61 +2496,40 @@ 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 = exerciseData
|
||||
currentPracticeSection.value = section
|
||||
practiceMode.value = true
|
||||
practiceStarted.value = true // 直接开始练习,不需要点击开始按钮
|
||||
practiceFinished.value = false
|
||||
currentQuestionIndex.value = 0
|
||||
|
||||
// 初始化答案数组
|
||||
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('✅ 练习模式已启动,题目数量:', practiceQuestions.value.length)
|
||||
} else {
|
||||
console.warn('⚠️ 练习数据格式异常:', exerciseData)
|
||||
message.warning('练习数据格式异常')
|
||||
}
|
||||
]
|
||||
|
||||
// 设置练习数据
|
||||
practiceQuestions.value = mockQuestions
|
||||
currentPracticeSection.value = section
|
||||
practiceMode.value = true
|
||||
practiceStarted.value = true // 直接开始练习,不需要点击开始按钮
|
||||
practiceFinished.value = false
|
||||
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('')
|
||||
|
||||
console.log('✅ 练习模式已启动')
|
||||
} 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 容器样式 */
|
||||
@ -9567,4 +9566,4 @@ onActivated(() => {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
Loading…
x
Reference in New Issue
Block a user