解决build问题
This commit is contained in:
parent
c0dbb2c97d
commit
a532924668
@ -9,6 +9,8 @@
|
||||
<meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js">
|
||||
<!-- CKPlayer CSS -->
|
||||
<link rel="stylesheet" href="/ckplayer/css/ckplayer.css">
|
||||
<!-- HLS.js for m3u8 playback in CKPlayer -->
|
||||
<script src="/ckplayer/hls.js/hls.min.js"></script>
|
||||
<!-- CKPlayer JS -->
|
||||
<script src="/ckplayer/js/ckplayer.js"></script>
|
||||
</head>
|
||||
|
@ -12,6 +12,9 @@ const checkNetworkStatus = (): boolean => {
|
||||
return true // 默认认为网络可用
|
||||
}
|
||||
|
||||
// 全局开关:强制使用Mock数据(当前需求:全部使用Mock,不访问后端)
|
||||
const FORCE_ENABLE_MOCK = true
|
||||
|
||||
// 消息提示函数 - 使用window.alert作为fallback,实际项目中应该使用UI库的消息组件
|
||||
const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => {
|
||||
// 这里可以替换为你使用的UI库的消息组件
|
||||
@ -26,7 +29,8 @@ const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'i
|
||||
|
||||
// 创建axios实例
|
||||
const request: AxiosInstance = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api',
|
||||
// 统一走 /api,由 Vite 代理到后端,避免CORS;若启用Mock,则只会走本地Mock
|
||||
baseURL: '/api',
|
||||
timeout: 30000, // 增加到30秒
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@ -454,8 +458,9 @@ export class ApiRequest {
|
||||
params?: any,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 检查是否启用Mock
|
||||
if (import.meta.env.VITE_ENABLE_MOCK === 'true') {
|
||||
const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
|
||||
// 优先:若显式启用Mock,直接使用Mock
|
||||
if (enableMock) {
|
||||
return handleMockRequest<T>(url, 'GET', params)
|
||||
}
|
||||
|
||||
@ -463,7 +468,7 @@ export class ApiRequest {
|
||||
return await retryRequest(() => request.get(url, { params, ...config }))
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,降级到Mock数据:', error)
|
||||
// 如果真实API失败,降级到Mock数据
|
||||
// 后备:真实API失败时,仍回落到Mock,保障页面可用
|
||||
return handleMockRequest<T>(url, 'GET', params)
|
||||
}
|
||||
}
|
||||
@ -474,8 +479,8 @@ export class ApiRequest {
|
||||
data?: any,
|
||||
config?: AxiosRequestConfig
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 检查是否启用Mock
|
||||
if (import.meta.env.VITE_ENABLE_MOCK === 'true') {
|
||||
const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
|
||||
if (enableMock) {
|
||||
return handleMockRequest<T>(url, 'POST', data)
|
||||
}
|
||||
|
||||
@ -483,7 +488,6 @@ export class ApiRequest {
|
||||
return await retryRequest(() => request.post(url, data, config))
|
||||
} catch (error) {
|
||||
console.warn('API请求失败,降级到Mock数据:', error)
|
||||
// 如果真实API失败,降级到Mock数据
|
||||
return handleMockRequest<T>(url, 'POST', data)
|
||||
}
|
||||
}
|
||||
|
@ -255,15 +255,7 @@ const selectedGroup = ref(0)
|
||||
// activeCategory.value = categoryId
|
||||
// }
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const statusMap = {
|
||||
'upcoming': '即将开始',
|
||||
'ongoing': '报名中',
|
||||
'ended': '已结束'
|
||||
}
|
||||
return statusMap[status as keyof typeof statusMap] || '未知状态'
|
||||
}
|
||||
// 已移除未使用的状态文本函数,避免构建时报未使用错误
|
||||
|
||||
// 加载活动详情
|
||||
const loadActivityDetail = async () => {
|
||||
|
@ -34,13 +34,13 @@
|
||||
<div class="video-player-section">
|
||||
<div class="video-player enrolled">
|
||||
<div class="video-container">
|
||||
<!-- CKPlayer 容器 -->
|
||||
<!-- CKPlayer 容器:使用 v-show 确保容器始终挂载,避免"未找到放置视频的容器" -->
|
||||
<div
|
||||
v-if="currentVideoUrl"
|
||||
v-show="!!currentVideoUrl"
|
||||
id="ckplayer_container"
|
||||
class="ckplayer-container">
|
||||
</div>
|
||||
<div v-else class="video-placeholder" :style="{ backgroundImage: course?.coverImage || course?.thumbnail ? `url(${course.coverImage || course.thumbnail})` : '' }">
|
||||
<div v-show="!currentVideoUrl" class="video-placeholder" :style="{ backgroundImage: course?.coverImage || course?.thumbnail ? `url(${course.coverImage || course.thumbnail})` : '' }">
|
||||
<div class="placeholder-content">
|
||||
<div class="play-icon">
|
||||
<svg width="60" height="60" viewBox="0 0 60 60">
|
||||
@ -97,7 +97,7 @@
|
||||
分类:<span class="category-link">{{ course.category?.name || '信息技术' }}</span>
|
||||
</span>
|
||||
<div class="meta-right">
|
||||
<button class="btn-notes">
|
||||
<button class="btn-notes" @click="openNotesModal">
|
||||
<i class="icon-note"></i>
|
||||
记笔记
|
||||
</button>
|
||||
@ -419,6 +419,9 @@ const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const courseId = ref(Number(route.params.id))
|
||||
|
||||
// 强制仅播放本地视频(如需关闭,置为 false)
|
||||
const FORCE_LOCAL_VIDEO = true
|
||||
|
||||
// 当前选中的章节和视频
|
||||
const currentSection = ref<CourseSection | null>(null)
|
||||
const currentVideoUrl = ref<string>('')
|
||||
@ -433,7 +436,11 @@ const VIDEO_CONFIG = {
|
||||
}
|
||||
|
||||
// 获取视频URL的函数
|
||||
const getVideoUrl = () => {
|
||||
const getVideoUrl = (section?: CourseSection) => {
|
||||
const outline = section?.outline?.trim()
|
||||
if (outline && (outline.endsWith('.mp4') || outline.endsWith('.m3u8'))) {
|
||||
return outline
|
||||
}
|
||||
// 当前使用本地视频,将来可以通过环境变量或配置切换
|
||||
return VIDEO_CONFIG.LOCAL
|
||||
// 服务器准备好后,可以改为:
|
||||
@ -675,6 +682,16 @@ const loadCourseSections = async () => {
|
||||
groupedSections.value = groupSectionsByChapter(response.data)
|
||||
console.log('章节数据设置成功:', courseSections.value)
|
||||
console.log('分组数据:', groupedSections.value)
|
||||
// 默认播放右侧第一个视频章节(当未强制使用本地视频时)
|
||||
if (!FORCE_LOCAL_VIDEO) {
|
||||
const firstVideo = courseSections.value.find(s => s.outline && (s.outline.includes('.m3u8') || s.outline.includes('.mp4')))
|
||||
if (firstVideo) {
|
||||
currentSection.value = firstVideo
|
||||
currentVideoUrl.value = getVideoUrl(firstVideo)
|
||||
await nextTick()
|
||||
initCKPlayer(currentVideoUrl.value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log('API返回的章节数据为空,使用模拟数据')
|
||||
loadMockData()
|
||||
@ -709,6 +726,15 @@ const loadMockData = () => {
|
||||
completed: completed,
|
||||
progress: progress.value
|
||||
})
|
||||
// 默认播放右侧第一个视频章节(模拟数据,未强制使用本地视频时)
|
||||
if (!FORCE_LOCAL_VIDEO) {
|
||||
const firstVideo = mockSections.find(s => s.outline && (s.outline.includes('.m3u8') || s.outline.includes('.mp4')))
|
||||
if (firstVideo) {
|
||||
currentSection.value = firstVideo
|
||||
currentVideoUrl.value = getVideoUrl(firstVideo)
|
||||
setTimeout(() => initCKPlayer(currentVideoUrl.value), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 切换章节展开/收起
|
||||
@ -785,7 +811,7 @@ const handleVideoPlay = async (section: CourseSection) => {
|
||||
console.log('播放视频:', section.name)
|
||||
|
||||
// 获取视频URL
|
||||
const videoUrl = getVideoUrl()
|
||||
const videoUrl = getVideoUrl(section)
|
||||
currentVideoUrl.value = videoUrl
|
||||
currentSection.value = section
|
||||
|
||||
@ -825,6 +851,14 @@ const initCKPlayer = (url: string) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 若容器暂未挂载,延迟重试一次,避免"未找到放置视频的容器"
|
||||
const containerEl = document.querySelector('#ckplayer_container') as HTMLElement | null
|
||||
if (!containerEl) {
|
||||
console.warn('Player container not found, retrying init...')
|
||||
setTimeout(() => initCKPlayer(url), 50)
|
||||
return
|
||||
}
|
||||
|
||||
// 判断视频类型
|
||||
const isMP4 = url.endsWith('.mp4')
|
||||
const isHLS = url.endsWith('.m3u8')
|
||||
@ -844,6 +878,7 @@ const initCKPlayer = (url: string) => {
|
||||
seek: 0, // 默认跳转秒数
|
||||
loaded: 'loadedHandler', // 播放器加载完成回调
|
||||
ended: 'endedHandler', // 播放结束回调
|
||||
error: 'errorHandler', // 播放错误回调
|
||||
title: currentSection.value?.name || '课程视频', // 视频标题
|
||||
controls: true, // 显示控制栏
|
||||
webFull: true, // 启用页面全屏
|
||||
@ -876,6 +911,16 @@ window.endedHandler = () => {
|
||||
|
||||
window.errorHandler = (error: any) => {
|
||||
console.error('CKPlayer error:', error)
|
||||
// 自动回退到本地视频,避免一直缓冲
|
||||
if (currentVideoUrl.value !== VIDEO_CONFIG.LOCAL) {
|
||||
try {
|
||||
currentVideoUrl.value = VIDEO_CONFIG.LOCAL
|
||||
// 重新初始化播放器播放本地视频
|
||||
initCKPlayer(VIDEO_CONFIG.LOCAL)
|
||||
} catch (e) {
|
||||
console.error('Fallback to local video failed:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理资源下载
|
||||
@ -965,9 +1010,16 @@ const saveNote = (content: string) => {
|
||||
// 这里可以添加保存笔记到服务器的逻辑
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
console.log('已报名课程详情页加载完成,课程ID:', courseId.value)
|
||||
initializeEnrolledState() // 初始化已报名状态
|
||||
// 若强制播放本地视频,优先初始化本地源
|
||||
if (FORCE_LOCAL_VIDEO) {
|
||||
currentSection.value = null
|
||||
currentVideoUrl.value = VIDEO_CONFIG.LOCAL
|
||||
await nextTick()
|
||||
initCKPlayer(currentVideoUrl.value)
|
||||
}
|
||||
loadCourseDetail()
|
||||
loadCourseSections()
|
||||
})
|
||||
|
@ -1173,13 +1173,7 @@ const showTimeUpAndSubmit = () => {
|
||||
// return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
// }
|
||||
|
||||
// 格式化时间显示为时分秒
|
||||
const formatTimeHMS = (seconds: number): string => {
|
||||
const hours = Math.floor(seconds / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const remainingSeconds = seconds % 60
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
}
|
||||
// 已移除未使用的时间格式化函数,避免构建时报未使用错误
|
||||
|
||||
// 组件卸载时清理计时器
|
||||
onUnmounted(() => {
|
||||
|
@ -94,6 +94,18 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 为教师项添加明确类型,包含可选的 courseCount 字段
|
||||
interface Teacher {
|
||||
id: number
|
||||
name: string
|
||||
position: string
|
||||
description: string
|
||||
specialty: string
|
||||
avatar: string
|
||||
featured?: boolean
|
||||
courseCount?: number
|
||||
}
|
||||
|
||||
// 横幅图片相关
|
||||
const bannerImageSrc = ref('/images/Teachers/师资力量切图-轮播区.png')
|
||||
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
|
||||
@ -138,7 +150,7 @@ const filterTabs = ref([
|
||||
const activeTab = ref('all')
|
||||
|
||||
// 师资数据
|
||||
const teachers = ref([
|
||||
const teachers = ref<Teacher[]>([
|
||||
{
|
||||
id: 1,
|
||||
name: '黄天羽',
|
||||
|
@ -2438,9 +2438,7 @@ const getFileIcon = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const createNewFolder = () => {
|
||||
message.info('新建文件夹功能开发中...')
|
||||
}
|
||||
// 已移除未使用的新建文件夹函数,避免构建时报未使用错误
|
||||
|
||||
const renameFile = (fileId: number) => {
|
||||
message.info(`重命名文件 ${fileId}`)
|
||||
|
@ -139,13 +139,7 @@ const itemsPerPage = 20
|
||||
const totalItems = computed(() => total.value)
|
||||
const totalPages = computed(() => Math.ceil(totalItems.value / itemsPerPage))
|
||||
|
||||
// 控制广告显示状态
|
||||
const showAdvertisement = ref(true)
|
||||
|
||||
// 关闭广告函数
|
||||
const closeAdvertisement = () => {
|
||||
showAdvertisement.value = false
|
||||
}
|
||||
// 已移除未使用的广告显隐逻辑,避免构建时报未使用错误
|
||||
|
||||
// 数字转中文
|
||||
const numberToChinese = (num: number): string => {
|
||||
@ -315,24 +309,7 @@ const clearAllFilters = () => {
|
||||
loadCourses()
|
||||
}
|
||||
|
||||
// 筛选功能
|
||||
const selectSubject = (subject: string) => {
|
||||
selectedSubject.value = subject
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
loadCourses()
|
||||
}
|
||||
|
||||
const selectMajor = (major: string) => {
|
||||
selectedMajor.value = major
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
loadCourses()
|
||||
}
|
||||
|
||||
const selectDifficulty = (difficulty: string) => {
|
||||
selectedDifficulty.value = difficulty
|
||||
currentPage.value = 1 // 重置到第一页
|
||||
loadCourses()
|
||||
}
|
||||
// 已移除未使用的筛选函数,避免构建时报未使用错误
|
||||
|
||||
// 排序功能
|
||||
const selectSortType = (type: string) => {
|
||||
|
@ -1,22 +1,36 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig, loadEnv } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
export default defineConfig(({ mode }) => {
|
||||
const env = loadEnv(mode, process.cwd(), '')
|
||||
const proxyTarget = env.VITE_PROXY_TARGET || 'http://110.42.96.65:55510'
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
vue(),
|
||||
vueDevTools(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true,
|
||||
proxy: {
|
||||
// 将以 /api 开头的请求代理到后端,避免浏览器CORS限制
|
||||
'/api': {
|
||||
target: proxyTarget,
|
||||
changeOrigin: true,
|
||||
// 如果后端接口不是以 /api 开头,可在这里改写路径
|
||||
// rewrite: (path) => path.replace(/^\/api/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user