解决build问题

This commit is contained in:
QDKF 2025-08-11 10:03:56 +08:00
parent c0dbb2c97d
commit a532924668
9 changed files with 117 additions and 72 deletions

View File

@ -9,6 +9,8 @@
<meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js"> <meta name="keywords" content="在线学习,编程课程,技术培训,Vue.js,React,Node.js">
<!-- CKPlayer CSS --> <!-- CKPlayer CSS -->
<link rel="stylesheet" href="/ckplayer/css/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 --> <!-- CKPlayer JS -->
<script src="/ckplayer/js/ckplayer.js"></script> <script src="/ckplayer/js/ckplayer.js"></script>
</head> </head>

View File

@ -12,6 +12,9 @@ const checkNetworkStatus = (): boolean => {
return true // 默认认为网络可用 return true // 默认认为网络可用
} }
// 全局开关强制使用Mock数据当前需求全部使用Mock不访问后端
const FORCE_ENABLE_MOCK = true
// 消息提示函数 - 使用window.alert作为fallback实际项目中应该使用UI库的消息组件 // 消息提示函数 - 使用window.alert作为fallback实际项目中应该使用UI库的消息组件
const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => { const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info') => {
// 这里可以替换为你使用的UI库的消息组件 // 这里可以替换为你使用的UI库的消息组件
@ -26,7 +29,8 @@ const showMessage = (message: string, type: 'success' | 'error' | 'warning' | 'i
// 创建axios实例 // 创建axios实例
const request: AxiosInstance = axios.create({ 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秒 timeout: 30000, // 增加到30秒
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -454,8 +458,9 @@ export class ApiRequest {
params?: any, params?: any,
config?: AxiosRequestConfig config?: AxiosRequestConfig
): Promise<ApiResponse<T>> { ): Promise<ApiResponse<T>> {
// 检查是否启用Mock const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
if (import.meta.env.VITE_ENABLE_MOCK === 'true') { // 优先若显式启用Mock直接使用Mock
if (enableMock) {
return handleMockRequest<T>(url, 'GET', params) return handleMockRequest<T>(url, 'GET', params)
} }
@ -463,7 +468,7 @@ export class ApiRequest {
return await retryRequest(() => request.get(url, { params, ...config })) return await retryRequest(() => request.get(url, { params, ...config }))
} catch (error) { } catch (error) {
console.warn('API请求失败降级到Mock数据:', error) console.warn('API请求失败降级到Mock数据:', error)
// 如果真实API失败降级到Mock数据 // 后备真实API失败时仍回落到Mock保障页面可用
return handleMockRequest<T>(url, 'GET', params) return handleMockRequest<T>(url, 'GET', params)
} }
} }
@ -474,8 +479,8 @@ export class ApiRequest {
data?: any, data?: any,
config?: AxiosRequestConfig config?: AxiosRequestConfig
): Promise<ApiResponse<T>> { ): Promise<ApiResponse<T>> {
// 检查是否启用Mock const enableMock = FORCE_ENABLE_MOCK || ((import.meta as any).env?.VITE_ENABLE_MOCK === 'true')
if (import.meta.env.VITE_ENABLE_MOCK === 'true') { if (enableMock) {
return handleMockRequest<T>(url, 'POST', data) return handleMockRequest<T>(url, 'POST', data)
} }
@ -483,7 +488,6 @@ export class ApiRequest {
return await retryRequest(() => request.post(url, data, config)) return await retryRequest(() => request.post(url, data, config))
} catch (error) { } catch (error) {
console.warn('API请求失败降级到Mock数据:', error) console.warn('API请求失败降级到Mock数据:', error)
// 如果真实API失败降级到Mock数据
return handleMockRequest<T>(url, 'POST', data) return handleMockRequest<T>(url, 'POST', data)
} }
} }

View File

@ -255,15 +255,7 @@ const selectedGroup = ref(0)
// activeCategory.value = categoryId // activeCategory.value = categoryId
// } // }
// // 使使
const getStatusText = (status: string) => {
const statusMap = {
'upcoming': '即将开始',
'ongoing': '报名中',
'ended': '已结束'
}
return statusMap[status as keyof typeof statusMap] || '未知状态'
}
// //
const loadActivityDetail = async () => { const loadActivityDetail = async () => {

View File

@ -34,13 +34,13 @@
<div class="video-player-section"> <div class="video-player-section">
<div class="video-player enrolled"> <div class="video-player enrolled">
<div class="video-container"> <div class="video-container">
<!-- CKPlayer 容器 --> <!-- CKPlayer 容器使用 v-show 确保容器始终挂载避免"未找到放置视频的容器" -->
<div <div
v-if="currentVideoUrl" v-show="!!currentVideoUrl"
id="ckplayer_container" id="ckplayer_container"
class="ckplayer-container"> class="ckplayer-container">
</div> </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="placeholder-content">
<div class="play-icon"> <div class="play-icon">
<svg width="60" height="60" viewBox="0 0 60 60"> <svg width="60" height="60" viewBox="0 0 60 60">
@ -97,7 +97,7 @@
分类<span class="category-link">{{ course.category?.name || '信息技术' }}</span> 分类<span class="category-link">{{ course.category?.name || '信息技术' }}</span>
</span> </span>
<div class="meta-right"> <div class="meta-right">
<button class="btn-notes"> <button class="btn-notes" @click="openNotesModal">
<i class="icon-note"></i> <i class="icon-note"></i>
记笔记 记笔记
</button> </button>
@ -419,6 +419,9 @@ const router = useRouter()
const userStore = useUserStore() const userStore = useUserStore()
const courseId = ref(Number(route.params.id)) const courseId = ref(Number(route.params.id))
// false
const FORCE_LOCAL_VIDEO = true
// //
const currentSection = ref<CourseSection | null>(null) const currentSection = ref<CourseSection | null>(null)
const currentVideoUrl = ref<string>('') const currentVideoUrl = ref<string>('')
@ -433,7 +436,11 @@ const VIDEO_CONFIG = {
} }
// URL // 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 return VIDEO_CONFIG.LOCAL
// //
@ -675,6 +682,16 @@ const loadCourseSections = async () => {
groupedSections.value = groupSectionsByChapter(response.data) groupedSections.value = groupSectionsByChapter(response.data)
console.log('章节数据设置成功:', courseSections.value) console.log('章节数据设置成功:', courseSections.value)
console.log('分组数据:', groupedSections.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 { } else {
console.log('API返回的章节数据为空使用模拟数据') console.log('API返回的章节数据为空使用模拟数据')
loadMockData() loadMockData()
@ -709,6 +726,15 @@ const loadMockData = () => {
completed: completed, completed: completed,
progress: progress.value 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) console.log('播放视频:', section.name)
// URL // URL
const videoUrl = getVideoUrl() const videoUrl = getVideoUrl(section)
currentVideoUrl.value = videoUrl currentVideoUrl.value = videoUrl
currentSection.value = section currentSection.value = section
@ -825,6 +851,14 @@ const initCKPlayer = (url: string) => {
return 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 isMP4 = url.endsWith('.mp4')
const isHLS = url.endsWith('.m3u8') const isHLS = url.endsWith('.m3u8')
@ -844,6 +878,7 @@ const initCKPlayer = (url: string) => {
seek: 0, // seek: 0, //
loaded: 'loadedHandler', // loaded: 'loadedHandler', //
ended: 'endedHandler', // ended: 'endedHandler', //
error: 'errorHandler', //
title: currentSection.value?.name || '课程视频', // title: currentSection.value?.name || '课程视频', //
controls: true, // controls: true, //
webFull: true, // webFull: true, //
@ -876,6 +911,16 @@ window.endedHandler = () => {
window.errorHandler = (error: any) => { window.errorHandler = (error: any) => {
console.error('CKPlayer error:', error) 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) console.log('已报名课程详情页加载完成课程ID:', courseId.value)
initializeEnrolledState() // initializeEnrolledState() //
//
if (FORCE_LOCAL_VIDEO) {
currentSection.value = null
currentVideoUrl.value = VIDEO_CONFIG.LOCAL
await nextTick()
initCKPlayer(currentVideoUrl.value)
}
loadCourseDetail() loadCourseDetail()
loadCourseSections() loadCourseSections()
}) })

View File

@ -1173,13 +1173,7 @@ const showTimeUpAndSubmit = () => {
// return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}` // 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(() => { onUnmounted(() => {

View File

@ -94,6 +94,18 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useRouter } from 'vue-router' 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 bannerImageSrc = ref('/images/Teachers/师资力量切图-轮播区.png')
const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '') const hasBannerImage = computed(() => bannerImageSrc.value.trim() !== '')
@ -138,7 +150,7 @@ const filterTabs = ref([
const activeTab = ref('all') const activeTab = ref('all')
// //
const teachers = ref([ const teachers = ref<Teacher[]>([
{ {
id: 1, id: 1,
name: '黄天羽', name: '黄天羽',

View File

@ -2438,9 +2438,7 @@ const getFileIcon = () => {
} }
} }
const createNewFolder = () => { // 使使
message.info('新建文件夹功能开发中...')
}
const renameFile = (fileId: number) => { const renameFile = (fileId: number) => {
message.info(`重命名文件 ${fileId}`) message.info(`重命名文件 ${fileId}`)

View File

@ -139,13 +139,7 @@ const itemsPerPage = 20
const totalItems = computed(() => total.value) const totalItems = computed(() => total.value)
const totalPages = computed(() => Math.ceil(totalItems.value / itemsPerPage)) const totalPages = computed(() => Math.ceil(totalItems.value / itemsPerPage))
// 广 // 使广使
const showAdvertisement = ref(true)
// 广
const closeAdvertisement = () => {
showAdvertisement.value = false
}
// //
const numberToChinese = (num: number): string => { const numberToChinese = (num: number): string => {
@ -315,24 +309,7 @@ const clearAllFilters = () => {
loadCourses() 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) => { const selectSortType = (type: string) => {

View File

@ -1,22 +1,36 @@
import { fileURLToPath, URL } from 'node:url' import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools' import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [ const env = loadEnv(mode, process.cwd(), '')
vue(), const proxyTarget = env.VITE_PROXY_TARGET || 'http://110.42.96.65:55510'
vueDevTools(),
], return {
resolve: { plugins: [
alias: { vue(),
'@': fileURLToPath(new URL('./src', import.meta.url)) vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
}, },
}, server: {
server: { port: 3000,
port: 3000, open: true,
open: true proxy: {
// 将以 /api 开头的请求代理到后端避免浏览器CORS限制
'/api': {
target: proxyTarget,
changeOrigin: true,
// 如果后端接口不是以 /api 开头,可在这里改写路径
// rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
} }
}) })