diff --git a/index.html b/index.html index ce2cb04..30ff4fa 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,8 @@ + + diff --git a/src/api/request.ts b/src/api/request.ts index 66428e3..46d13a9 100644 --- a/src/api/request.ts +++ b/src/api/request.ts @@ -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> { - // 检查是否启用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(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(url, 'GET', params) } } @@ -474,8 +479,8 @@ export class ApiRequest { data?: any, config?: AxiosRequestConfig ): Promise> { - // 检查是否启用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(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(url, 'POST', data) } } diff --git a/src/views/ActivityDetail.vue b/src/views/ActivityDetail.vue index 158e955..86b2e74 100644 --- a/src/views/ActivityDetail.vue +++ b/src/views/ActivityDetail.vue @@ -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 () => { diff --git a/src/views/CourseDetailEnrolled.vue b/src/views/CourseDetailEnrolled.vue index e1b67c7..db46e9e 100644 --- a/src/views/CourseDetailEnrolled.vue +++ b/src/views/CourseDetailEnrolled.vue @@ -34,13 +34,13 @@
- +
-
+
@@ -97,7 +97,7 @@ 分类:{{ course.category?.name || '信息技术' }}
- @@ -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(null) const currentVideoUrl = ref('') @@ -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() }) diff --git a/src/views/Exam.vue b/src/views/Exam.vue index 96fe4e8..8db422f 100644 --- a/src/views/Exam.vue +++ b/src/views/Exam.vue @@ -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(() => { diff --git a/src/views/Faculty.vue b/src/views/Faculty.vue index add527e..6b92295 100644 --- a/src/views/Faculty.vue +++ b/src/views/Faculty.vue @@ -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([ { id: 1, name: '黄天羽', diff --git a/src/views/Profile.vue b/src/views/Profile.vue index 83a101f..391d604 100644 --- a/src/views/Profile.vue +++ b/src/views/Profile.vue @@ -2438,9 +2438,7 @@ const getFileIcon = () => { } } -const createNewFolder = () => { - message.info('新建文件夹功能开发中...') -} +// 已移除未使用的新建文件夹函数,避免构建时报未使用错误 const renameFile = (fileId: number) => { message.info(`重命名文件 ${fileId}`) diff --git a/src/views/TeacherDetail.vue b/src/views/TeacherDetail.vue index a44816f..2164192 100644 --- a/src/views/TeacherDetail.vue +++ b/src/views/TeacherDetail.vue @@ -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) => { diff --git a/vite.config.ts b/vite.config.ts index 93263d3..66a9fa7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -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/, '') + } + } + } } })