解决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">
<!-- 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>

View File

@ -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)
}
}

View File

@ -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 () => {

View File

@ -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()
})

View File

@ -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(() => {

View File

@ -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: '黄天羽',

View File

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

View File

@ -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) => {

View File

@ -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/, '')
}
}
}
}
})