完成资源案例
This commit is contained in:
@@ -32,7 +32,7 @@
|
|||||||
<div class="courses-grid">
|
<div class="courses-grid">
|
||||||
<div
|
<div
|
||||||
class="course-card"
|
class="course-card"
|
||||||
v-for="course in initCourses"
|
v-for="(course, index) in initCourses"
|
||||||
:key="course.id"
|
:key="course.id"
|
||||||
@click="selectCourse(course)"
|
@click="selectCourse(course)"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
@@ -69,7 +69,7 @@
|
|||||||
<div class="courses-grid">
|
<div class="courses-grid">
|
||||||
<div
|
<div
|
||||||
class="course-card"
|
class="course-card"
|
||||||
v-for="course in onlineCourses"
|
v-for="(course, index) in onlineCourses"
|
||||||
:key="course.id"
|
:key="course.id"
|
||||||
@click="selectCourse(course)"
|
@click="selectCourse(course)"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
@@ -142,10 +142,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="chapters[chapter.id]?.expanded && chapter.children?.length" class="sub-chapters-container">
|
<div v-if="chapters[chapter.id]?.expanded && chapter.children?.length" class="sub-chapters-container">
|
||||||
<div v-for="sub in chapter.children.sort((a,b)=>a.sort-b.sort)" :key="sub.id" class="sub-chapter-item">
|
<div v-for="sub in chapter.children.sort((a: Chapter, b: Chapter) => a.sort - b.sort)" :key="sub.id" class="sub-chapter-item">
|
||||||
<div class="sub-chapter-title">{{ sub.title }}</div>
|
<div class="sub-chapter-title">{{ sub.title }}</div>
|
||||||
<div v-if="sub.children?.length" class="grand-children-container">
|
<div v-if="sub.children?.length" class="grand-children-container">
|
||||||
<div v-for="grand in sub.children.sort((a,b)=>a.sort-b.sort)" :key="grand.id" class="grand-child-title">
|
<div v-for="grand in sub.children.sort((a: Chapter, b: Chapter) => a.sort - b.sort)" :key="grand.id" class="grand-child-title">
|
||||||
{{ grand.title }}
|
{{ grand.title }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -417,21 +417,105 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
// ==================== 核心类型定义 ====================
|
||||||
|
/** 线上课程类型 */
|
||||||
|
interface Course {
|
||||||
|
id: number;
|
||||||
|
cover_url: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
intro: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 教学案例类型 */
|
||||||
|
interface TeachingCase {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
tutor_name: string;
|
||||||
|
tutor_title: string;
|
||||||
|
student_names?: string;
|
||||||
|
content: string;
|
||||||
|
cover_url: string;
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 视频案例类型 */
|
||||||
|
interface VideoCase {
|
||||||
|
id: number;
|
||||||
|
video_url: string;
|
||||||
|
title: string;
|
||||||
|
intro: string;
|
||||||
|
designer_names: string;
|
||||||
|
tutor_names: string;
|
||||||
|
create_time: string;
|
||||||
|
update_time: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 课程章节类型 */
|
||||||
|
interface Chapter {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
children?: Chapter[];
|
||||||
|
sort: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 拓展资源类型 */
|
||||||
|
interface Resource {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
resource_url: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 学生活动类型 */
|
||||||
|
interface Activity {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 教师类型 */
|
||||||
|
interface Teacher {
|
||||||
|
id: number;
|
||||||
|
avatar: string;
|
||||||
|
name: string;
|
||||||
|
title: string;
|
||||||
|
intro: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 接口响应基础类型 */
|
||||||
|
interface ApiResponse<T = any> {
|
||||||
|
code?: number;
|
||||||
|
message?: string;
|
||||||
|
data?: T;
|
||||||
|
list?: T[];
|
||||||
|
total?: number;
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 全局状态 ====================
|
// ==================== 全局状态 ====================
|
||||||
const activeTab = ref('online');
|
const activeTab = ref<'online' | 'teaching' | 'video'>('online');
|
||||||
const selectedCourse = ref(null);
|
const selectedCourse = ref<Course | null>(null);
|
||||||
const selectedCase = ref(null);
|
const selectedCase = ref<TeachingCase | null>(null);
|
||||||
const playingVideo = ref(null);
|
const playingVideo = ref<VideoCase | null>(null);
|
||||||
const activeDetailTab = ref('content');
|
const activeDetailTab = ref<'content' | 'resource' | 'activity' | 'team'>('content');
|
||||||
const chapters = ref({});
|
const chapters = ref<Record<number, { expanded: boolean }>>({});
|
||||||
|
|
||||||
// 线上课程
|
// 线上课程
|
||||||
const onlineCourses = ref([]);
|
const onlineCourses = ref<Course[]>([]);
|
||||||
const initCourses = ref([]);
|
const initCourses = ref<Course[]>([]);
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
const pageSize = ref(10);
|
const pageSize = ref(10);
|
||||||
const totalCount = ref(0);
|
const totalCount = ref(0);
|
||||||
@@ -443,7 +527,7 @@ const showFullCourseList = ref(false);
|
|||||||
const searchKeyword = ref('');
|
const searchKeyword = ref('');
|
||||||
|
|
||||||
// 教学案例
|
// 教学案例
|
||||||
const caseList = ref([]);
|
const caseList = ref<TeachingCase[]>([]);
|
||||||
const casePage = ref(1);
|
const casePage = ref(1);
|
||||||
const caseSize = 20;
|
const caseSize = 20;
|
||||||
const caseTotal = ref(0);
|
const caseTotal = ref(0);
|
||||||
@@ -454,7 +538,7 @@ const caseError = ref('');
|
|||||||
const caseKeyword = ref('');
|
const caseKeyword = ref('');
|
||||||
|
|
||||||
// 视频案例
|
// 视频案例
|
||||||
const videoList = ref([]);
|
const videoList = ref<VideoCase[]>([]);
|
||||||
const videoPage = ref(1);
|
const videoPage = ref(1);
|
||||||
const videoSize = 10;
|
const videoSize = 10;
|
||||||
const videoTotal = ref(0);
|
const videoTotal = ref(0);
|
||||||
@@ -465,53 +549,53 @@ const videoError = ref('');
|
|||||||
const videoKeyword = ref('');
|
const videoKeyword = ref('');
|
||||||
|
|
||||||
// 课程详情
|
// 课程详情
|
||||||
const courseChapters = ref([]);
|
const courseChapters = ref<Chapter[]>([]);
|
||||||
const loadingChapters = ref(false);
|
const loadingChapters = ref(false);
|
||||||
const chapterError = ref('');
|
const chapterError = ref('');
|
||||||
const resourceList = ref([]);
|
const resourceList = ref<Resource[]>([]);
|
||||||
const resourcePage = ref(1);
|
const resourcePage = ref(1);
|
||||||
const resourcePageSize = 20;
|
const resourcePageSize = 20;
|
||||||
const hasMoreResources = ref(true);
|
const hasMoreResources = ref(true);
|
||||||
const loadingResources = ref(false);
|
const loadingResources = ref(false);
|
||||||
const loadingMoreResources = ref(false);
|
const loadingMoreResources = ref(false);
|
||||||
const resourceError = ref('');
|
const resourceError = ref('');
|
||||||
const activityList = ref([]);
|
const activityList = ref<Activity[]>([]);
|
||||||
const activityPage = ref(1);
|
const activityPage = ref(1);
|
||||||
const activityPageSize = 10;
|
const activityPageSize = 10;
|
||||||
const hasMoreActivities = ref(true);
|
const hasMoreActivities = ref(true);
|
||||||
const loadingActivities = ref(false);
|
const loadingActivities = ref(false);
|
||||||
const loadingMoreActivities = ref(false);
|
const loadingMoreActivities = ref(false);
|
||||||
const activityError = ref('');
|
const activityError = ref('');
|
||||||
const teacherList = ref([]);
|
const teacherList = ref<Teacher[]>([]);
|
||||||
const teacherPage = ref(1);
|
const teacherPage = ref(1);
|
||||||
const teacherPageSize = 10;
|
const teacherPageSize = 10;
|
||||||
const hasMoreTeachers = ref(true);
|
const hasMoreTeachers = ref(true);
|
||||||
const loadingTeachers = ref(false);
|
const loadingTeachers = ref(false);
|
||||||
const loadingMoreTeachers = ref(false);
|
const loadingMoreTeachers = ref(false);
|
||||||
const teacherError = ref('');
|
const teacherError = ref('');
|
||||||
const currentCourseId = ref(null);
|
const currentCourseId = ref<number | null>(null);
|
||||||
|
|
||||||
// ==================== 工具函数 ====================
|
// ==================== 工具函数 ====================
|
||||||
const formatDate = (time) => (time ? time.split(' ')[0] : '');
|
const formatDate = (time: string | undefined) => (time ? time.split(' ')[0] : '');
|
||||||
const formatTime = (timeStr) => (timeStr ? timeStr.split('.')[0].replace(' ', ' ') : '');
|
const formatTime = (timeStr: string | undefined) => (timeStr ? timeStr.split('.')[0]?.replace(' ', ' ') : '');
|
||||||
const onAvatarError = (e) => { e.target.src = 'https://via.placeholder.com/80?text=头像'; };
|
const onAvatarError = (e: Event) => { (e.target as HTMLImageElement).src = 'https://via.placeholder.com/80?text=头像'; };
|
||||||
const onVideoError = (e) => { console.error('视频加载失败:', e); alert('视频加载失败'); };
|
const onVideoError = (e: Event) => { console.error('视频加载失败:', e); alert('视频加载失败'); };
|
||||||
const onVideoLoaded = (e) => { e.target.currentTime = 0.1; };
|
const onVideoLoaded = (e: Event) => { (e.target as HTMLVideoElement).currentTime = 0.1; };
|
||||||
|
|
||||||
const getFileExtension = (url) => {
|
const getFileExtension = (url: string | undefined) => {
|
||||||
if (!url) return '';
|
if (!url) return '';
|
||||||
const fileName = url.split('/').pop().split('?')[0];
|
const fileName = url.split('/').pop()?.split('?')[0] || '';
|
||||||
const parts = fileName.split('.');
|
const parts = fileName.split('.');
|
||||||
return parts.length > 1 ? parts.pop().toLowerCase() : '';
|
return parts.length > 1 ? parts.pop()?.toLowerCase() || '' : '';
|
||||||
};
|
};
|
||||||
const getDisplayTitle = (item) => {
|
const getDisplayTitle = (item: Resource) => {
|
||||||
const ext = getFileExtension(item.resource_url);
|
const ext = getFileExtension(item.resource_url);
|
||||||
return ext ? `${item.title}.${ext}` : item.title;
|
return ext ? `${item.title}.${ext}` : item.title;
|
||||||
};
|
};
|
||||||
const openResource = (url) => { if (url) window.open(url, '_blank'); };
|
const openResource = (url: string | undefined) => { if (url) window.open(url, '_blank'); };
|
||||||
|
|
||||||
// ==================== 统一 tab 切换 ====================
|
// ==================== 统一 tab 切换 ====================
|
||||||
const setActiveTab = (tab) => {
|
const setActiveTab = (tab: 'content' | 'resource' | 'activity' | 'team') => {
|
||||||
if (activeDetailTab.value === tab) return;
|
if (activeDetailTab.value === tab) return;
|
||||||
activeDetailTab.value = tab;
|
activeDetailTab.value = tab;
|
||||||
if (!selectedCourse.value) return;
|
if (!selectedCourse.value) return;
|
||||||
@@ -522,7 +606,7 @@ const setActiveTab = (tab) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// ==================== 导航切换 ====================
|
// ==================== 导航切换 ====================
|
||||||
const handleTabChange = (tab) => {
|
const handleTabChange = (tab: 'online' | 'teaching' | 'video') => {
|
||||||
activeTab.value = tab;
|
activeTab.value = tab;
|
||||||
selectedCourse.value = null;
|
selectedCourse.value = null;
|
||||||
selectedCase.value = null;
|
selectedCase.value = null;
|
||||||
@@ -538,16 +622,16 @@ const initOnlineCourses = async () => {
|
|||||||
loadingInit.value = true;
|
loadingInit.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCoursePage(1, '');
|
const res = await fetchCoursePage(1, '');
|
||||||
onlineCourses.value = res.list;
|
onlineCourses.value = res.list as Course[];
|
||||||
totalCount.value = res.total;
|
totalCount.value = res.total || 0;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
hasMore.value = currentPage.value * pageSize.value < totalCount.value;
|
hasMore.value = currentPage.value * pageSize.value < totalCount.value;
|
||||||
initCourses.value = onlineCourses.value.slice(0, 3);
|
initCourses.value = onlineCourses.value.slice(0, 3);
|
||||||
} catch { errorMsg.value = '加载失败'; } finally { loadingInit.value = false; }
|
} catch { errorMsg.value = '加载失败'; } finally { loadingInit.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchCoursePage = async (page, keyword) => {
|
const fetchCoursePage = async (page: number, keyword: string): Promise<ApiResponse<Course>> => {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/courses/list', {
|
const { data } = await axios.post<ApiResponse<Course>>('http://localhost:8080/api/courses/list', {
|
||||||
page, size: pageSize.value, status: 1, keyword: keyword || ''
|
page, size: pageSize.value, status: 1, keyword: keyword || ''
|
||||||
});
|
});
|
||||||
if (!data?.list) throw new Error();
|
if (!data?.list) throw new Error();
|
||||||
@@ -558,8 +642,8 @@ const handleCourseSearch = async () => {
|
|||||||
loadingInit.value = true;
|
loadingInit.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCoursePage(1, searchKeyword.value);
|
const res = await fetchCoursePage(1, searchKeyword.value);
|
||||||
onlineCourses.value = res.list;
|
onlineCourses.value = res.list as Course[];
|
||||||
totalCount.value = res.total;
|
totalCount.value = res.total || 0;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
hasMore.value = currentPage.value * pageSize.value < totalCount.value;
|
hasMore.value = currentPage.value * pageSize.value < totalCount.value;
|
||||||
} catch { errorMsg.value = '搜索失败'; } finally { loadingInit.value = false; }
|
} catch { errorMsg.value = '搜索失败'; } finally { loadingInit.value = false; }
|
||||||
@@ -570,9 +654,10 @@ const loadNextPage = async () => {
|
|||||||
loadingMore.value = true;
|
loadingMore.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCoursePage(currentPage.value + 1, searchKeyword.value);
|
const res = await fetchCoursePage(currentPage.value + 1, searchKeyword.value);
|
||||||
onlineCourses.value.push(...res.list);
|
const newCourses = res.list as Course[];
|
||||||
|
onlineCourses.value.push(...newCourses);
|
||||||
currentPage.value++;
|
currentPage.value++;
|
||||||
hasMore.value = currentPage.value * pageSize.value < totalCount.value;
|
hasMore.value = currentPage.value * pageSize.value < (res.total || 0);
|
||||||
} catch { errorMsg.value = '加载失败'; } finally { loadingMore.value = false; }
|
} catch { errorMsg.value = '加载失败'; } finally { loadingMore.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -587,15 +672,15 @@ const initCases = async () => {
|
|||||||
caseError.value = '';
|
caseError.value = '';
|
||||||
try {
|
try {
|
||||||
const res = await fetchCasePage(1, caseKeyword.value);
|
const res = await fetchCasePage(1, caseKeyword.value);
|
||||||
caseList.value = res.list;
|
caseList.value = res.list as TeachingCase[];
|
||||||
caseTotal.value = res.total;
|
caseTotal.value = res.total || 0;
|
||||||
casePage.value = 1;
|
casePage.value = 1;
|
||||||
caseHasMore.value = casePage.value * caseSize < caseTotal.value;
|
caseHasMore.value = casePage.value * caseSize < caseTotal.value;
|
||||||
} catch { caseError.value = '加载失败'; } finally { caseLoading.value = false; }
|
} catch { caseError.value = '加载失败'; } finally { caseLoading.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchCasePage = async (page, keyword) => {
|
const fetchCasePage = async (page: number, keyword: string): Promise<ApiResponse<TeachingCase>> => {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/teaching-cases/list', {
|
const { data } = await axios.post<ApiResponse<TeachingCase>>('http://localhost:8080/api/teaching-cases/list', {
|
||||||
page, size: caseSize, keyword: keyword || '', sort: 0
|
page, size: caseSize, keyword: keyword || '', sort: 0
|
||||||
});
|
});
|
||||||
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
||||||
@@ -606,8 +691,8 @@ const searchCases = async () => {
|
|||||||
caseLoading.value = true;
|
caseLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCasePage(1, caseKeyword.value);
|
const res = await fetchCasePage(1, caseKeyword.value);
|
||||||
caseList.value = res.list;
|
caseList.value = res.list as TeachingCase[];
|
||||||
caseTotal.value = res.total;
|
caseTotal.value = res.total || 0;
|
||||||
casePage.value = 1;
|
casePage.value = 1;
|
||||||
caseHasMore.value = casePage.value * caseSize < caseTotal.value;
|
caseHasMore.value = casePage.value * caseSize < caseTotal.value;
|
||||||
} catch { caseError.value = '搜索失败'; } finally { caseLoading.value = false; }
|
} catch { caseError.value = '搜索失败'; } finally { caseLoading.value = false; }
|
||||||
@@ -618,13 +703,14 @@ const loadMoreCases = async () => {
|
|||||||
caseLoadingMore.value = true;
|
caseLoadingMore.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchCasePage(casePage.value + 1, caseKeyword.value);
|
const res = await fetchCasePage(casePage.value + 1, caseKeyword.value);
|
||||||
caseList.value.push(...res.list);
|
const newCases = res.list as TeachingCase[];
|
||||||
|
caseList.value.push(...newCases);
|
||||||
casePage.value++;
|
casePage.value++;
|
||||||
caseHasMore.value = casePage.value * caseSize < caseTotal.value;
|
caseHasMore.value = casePage.value * caseSize < (res.total || 0);
|
||||||
} catch { caseError.value = '加载更多失败'; } finally { caseLoadingMore.value = false; }
|
} catch { caseError.value = '加载更多失败'; } finally { caseLoadingMore.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectCase = (item) => { selectedCase.value = item; };
|
const selectCase = (item: TeachingCase) => { selectedCase.value = item; };
|
||||||
const backToCaseList = () => { selectedCase.value = null; };
|
const backToCaseList = () => { selectedCase.value = null; };
|
||||||
|
|
||||||
// ==================== 视频案例 ====================
|
// ==================== 视频案例 ====================
|
||||||
@@ -633,15 +719,15 @@ const initVideos = async () => {
|
|||||||
videoError.value = '';
|
videoError.value = '';
|
||||||
try {
|
try {
|
||||||
const res = await fetchVideoPage(1, videoKeyword.value);
|
const res = await fetchVideoPage(1, videoKeyword.value);
|
||||||
videoList.value = res.list;
|
videoList.value = res.list as VideoCase[];
|
||||||
videoTotal.value = res.total;
|
videoTotal.value = res.total || 0;
|
||||||
videoPage.value = 1;
|
videoPage.value = 1;
|
||||||
videoHasMore.value = videoPage.value * videoSize < videoTotal.value;
|
videoHasMore.value = videoPage.value * videoSize < videoTotal.value;
|
||||||
} catch { videoError.value = '加载失败'; } finally { videoLoading.value = false; }
|
} catch { videoError.value = '加载失败'; } finally { videoLoading.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchVideoPage = async (page, keyword) => {
|
const fetchVideoPage = async (page: number, keyword: string): Promise<ApiResponse<VideoCase>> => {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/video-cases/list', {
|
const { data } = await axios.post<ApiResponse<VideoCase>>('http://localhost:8080/api/video-cases/list', {
|
||||||
page, size: videoSize, keyword: keyword || '', sort: 0
|
page, size: videoSize, keyword: keyword || '', sort: 0
|
||||||
});
|
});
|
||||||
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
||||||
@@ -652,8 +738,8 @@ const searchVideos = async () => {
|
|||||||
videoLoading.value = true;
|
videoLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchVideoPage(1, videoKeyword.value);
|
const res = await fetchVideoPage(1, videoKeyword.value);
|
||||||
videoList.value = res.list;
|
videoList.value = res.list as VideoCase[];
|
||||||
videoTotal.value = res.total;
|
videoTotal.value = res.total || 0;
|
||||||
videoPage.value = 1;
|
videoPage.value = 1;
|
||||||
videoHasMore.value = videoPage.value * videoSize < videoTotal.value;
|
videoHasMore.value = videoPage.value * videoSize < videoTotal.value;
|
||||||
} catch { videoError.value = '搜索失败'; } finally { videoLoading.value = false; }
|
} catch { videoError.value = '搜索失败'; } finally { videoLoading.value = false; }
|
||||||
@@ -664,95 +750,160 @@ const loadMoreVideos = async () => {
|
|||||||
videoLoadingMore.value = true;
|
videoLoadingMore.value = true;
|
||||||
try {
|
try {
|
||||||
const res = await fetchVideoPage(videoPage.value + 1, videoKeyword.value);
|
const res = await fetchVideoPage(videoPage.value + 1, videoKeyword.value);
|
||||||
videoList.value.push(...res.list);
|
const newVideos = res.list as VideoCase[];
|
||||||
|
videoList.value.push(...newVideos);
|
||||||
videoPage.value++;
|
videoPage.value++;
|
||||||
videoHasMore.value = videoPage.value * videoSize < videoTotal.value;
|
videoHasMore.value = videoPage.value * videoSize < (res.total || 0);
|
||||||
} catch { videoError.value = '加载更多失败'; } finally { videoLoadingMore.value = false; }
|
} catch { videoError.value = '加载更多失败'; } finally { videoLoadingMore.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const playVideo = (item) => { playingVideo.value = item; };
|
const playVideo = (item: VideoCase) => { playingVideo.value = item; };
|
||||||
const backToVideoList = () => { playingVideo.value = null; };
|
const backToVideoList = () => { playingVideo.value = null; };
|
||||||
|
|
||||||
// ==================== 课程详情 ====================
|
// ==================== 课程详情 ====================
|
||||||
const fetchCourseContent = async (id) => {
|
const fetchCourseContent = async (id: number) => {
|
||||||
loadingChapters.value = true;
|
loadingChapters.value = true;
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/course-content/list', { course_id: id, parent_id: 0 });
|
const { data } = await axios.post<ApiResponse<Chapter>>('http://localhost:8080/api/course-content/list', { course_id: id, parent_id: 0 });
|
||||||
if (data.code === 0 && Array.isArray(data.data)) {
|
if (data.code === 0 && Array.isArray(data.data)) {
|
||||||
courseChapters.value = data.data.sort((a,b)=>a.sort-b.sort);
|
courseChapters.value = data.data.sort((a: Chapter, b: Chapter) => a.sort - b.sort);
|
||||||
courseChapters.value.forEach(c => chapters.value[c.id] = { expanded: false });
|
courseChapters.value.forEach(c => chapters.value[c.id] = { expanded: false });
|
||||||
}
|
}
|
||||||
} catch { chapterError.value = '加载失败'; } finally { loadingChapters.value = false; }
|
} catch { chapterError.value = '加载失败'; } finally { loadingChapters.value = false; }
|
||||||
};
|
};
|
||||||
|
|
||||||
const toggleChapter = (id) => {
|
const toggleChapter = (id: number) => {
|
||||||
if (!chapters.value[id]) chapters.value[id] = { expanded: false };
|
if (!chapters.value[id]) chapters.value[id] = { expanded: false };
|
||||||
chapters.value[id].expanded = !chapters.value[id].expanded;
|
chapters.value[id].expanded = !chapters.value[id].expanded;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchResources = async (page = 1, reset = false) => {
|
const fetchResources = async (page = 1, reset = false) => {
|
||||||
if (reset) { resourceList.value = []; resourcePage.value = 1; hasMoreResources.value = true; resourceError.value = ''; loadingResources.value = true; }
|
if (!selectedCourse.value) return;
|
||||||
else loadingMoreResources.value = true;
|
if (reset) {
|
||||||
|
resourceList.value = [];
|
||||||
|
resourcePage.value = 1;
|
||||||
|
hasMoreResources.value = true;
|
||||||
|
resourceError.value = '';
|
||||||
|
loadingResources.value = true;
|
||||||
|
} else {
|
||||||
|
loadingMoreResources.value = true;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/course-resource/list', {
|
const { data } = await axios.post<ApiResponse<Resource>>('http://localhost:8080/api/course-resource/list', {
|
||||||
course_id: selectedCourse.value.id, page, page_size: resourcePageSize
|
course_id: selectedCourse.value.id,
|
||||||
|
page,
|
||||||
|
page_size: resourcePageSize
|
||||||
});
|
});
|
||||||
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
if (!data?.list || typeof data.total !== 'number') throw new Error();
|
||||||
if (reset) resourceList.value = data.list; else resourceList.value.push(...data.list);
|
const newResources = data.list as Resource[];
|
||||||
|
if (reset) resourceList.value = newResources;
|
||||||
|
else resourceList.value.push(...newResources);
|
||||||
resourcePage.value = page;
|
resourcePage.value = page;
|
||||||
hasMoreResources.value = page * resourcePageSize < data.total;
|
hasMoreResources.value = page * resourcePageSize < data.total;
|
||||||
currentCourseId.value = selectedCourse.value.id;
|
currentCourseId.value = selectedCourse.value.id;
|
||||||
} catch { resourceError.value = reset ? '加载失败' : '加载更多失败'; }
|
} catch {
|
||||||
finally { loadingResources.value = false; loadingMoreResources.value = false; }
|
resourceError.value = reset ? '加载失败' : '加载更多失败';
|
||||||
|
} finally {
|
||||||
|
loadingResources.value = false;
|
||||||
|
loadingMoreResources.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMoreResources = () => { if (loadingMoreResources.value || !hasMoreResources.value) return; fetchResources(resourcePage.value + 1); };
|
const loadMoreResources = () => {
|
||||||
|
if (loadingMoreResources.value || !hasMoreResources.value) return;
|
||||||
|
fetchResources(resourcePage.value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const fetchActivities = async (page = 1, reset = false) => {
|
const fetchActivities = async (page = 1, reset = false) => {
|
||||||
if (reset) { activityList.value = []; activityPage.value = 1; hasMoreActivities.value = true; activityError.value = ''; loadingActivities.value = true; }
|
if (!selectedCourse.value) return;
|
||||||
else loadingMoreActivities.value = true;
|
if (reset) {
|
||||||
|
activityList.value = [];
|
||||||
|
activityPage.value = 1;
|
||||||
|
hasMoreActivities.value = true;
|
||||||
|
activityError.value = '';
|
||||||
|
loadingActivities.value = true;
|
||||||
|
} else {
|
||||||
|
loadingMoreActivities.value = true;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/course-activity/list', {
|
const { data } = await axios.post<ApiResponse<Activity>>('http://localhost:8080/api/course-activity/list', {
|
||||||
course_id: selectedCourse.value.id, page, page_size: activityPageSize
|
course_id: selectedCourse.value.id,
|
||||||
|
page,
|
||||||
|
page_size: activityPageSize
|
||||||
});
|
});
|
||||||
if (!data || typeof data.total !== 'number' || !Array.isArray(data.list)) throw new Error();
|
if (!data || typeof data.total !== 'number' || !Array.isArray(data.list)) throw new Error();
|
||||||
if (reset) activityList.value = data.list; else activityList.value.push(...data.list);
|
const newActivities = data.list as Activity[];
|
||||||
|
if (reset) activityList.value = newActivities;
|
||||||
|
else activityList.value.push(...newActivities);
|
||||||
activityPage.value = page;
|
activityPage.value = page;
|
||||||
hasMoreActivities.value = page * activityPageSize < data.total;
|
hasMoreActivities.value = page * activityPageSize < data.total;
|
||||||
currentCourseId.value = selectedCourse.value.id;
|
currentCourseId.value = selectedCourse.value.id;
|
||||||
} catch { activityError.value = reset ? '加载失败' : '加载更多失败'; }
|
} catch {
|
||||||
finally { loadingActivities.value = false; loadingMoreActivities.value = false; }
|
activityError.value = reset ? '加载失败' : '加载更多失败';
|
||||||
|
} finally {
|
||||||
|
loadingActivities.value = false;
|
||||||
|
loadingMoreActivities.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMoreActivities = () => { if (loadingMoreActivities.value || !hasMoreActivities.value) return; fetchActivities(activityPage.value + 1); };
|
const loadMoreActivities = () => {
|
||||||
|
if (loadingMoreActivities.value || !hasMoreActivities.value) return;
|
||||||
|
fetchActivities(activityPage.value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const fetchTeachers = async (page = 1, reset = false) => {
|
const fetchTeachers = async (page = 1, reset = false) => {
|
||||||
if (reset) { teacherList.value = []; teacherPage.value = 1; hasMoreTeachers.value = true; teacherError.value = ''; loadingTeachers.value = true; }
|
if (!selectedCourse.value) return;
|
||||||
else loadingMoreTeachers.value = true;
|
if (reset) {
|
||||||
|
teacherList.value = [];
|
||||||
|
teacherPage.value = 1;
|
||||||
|
hasMoreTeachers.value = true;
|
||||||
|
teacherError.value = '';
|
||||||
|
loadingTeachers.value = true;
|
||||||
|
} else {
|
||||||
|
loadingMoreTeachers.value = true;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const { data } = await axios.post('http://localhost:8080/api/course-teacher/list', {
|
const { data } = await axios.post<ApiResponse<Teacher>>('http://localhost:8080/api/course-teacher/list', {
|
||||||
course_id: selectedCourse.value.id, page, page_size: teacherPageSize
|
course_id: selectedCourse.value.id,
|
||||||
|
page,
|
||||||
|
page_size: teacherPageSize
|
||||||
});
|
});
|
||||||
if (!data || typeof data.total !== 'number' || !Array.isArray(data.list)) throw new Error();
|
if (!data || typeof data.total !== 'number' || !Array.isArray(data.list)) throw new Error();
|
||||||
if (reset) teacherList.value = data.list; else teacherList.value.push(...data.list);
|
const newTeachers = data.list as Teacher[];
|
||||||
|
if (reset) teacherList.value = newTeachers;
|
||||||
|
else teacherList.value.push(...newTeachers);
|
||||||
teacherPage.value = page;
|
teacherPage.value = page;
|
||||||
hasMoreTeachers.value = page * teacherPageSize < data.total;
|
hasMoreTeachers.value = page * teacherPageSize < data.total;
|
||||||
currentCourseId.value = selectedCourse.value.id;
|
currentCourseId.value = selectedCourse.value.id;
|
||||||
} catch { teacherError.value = reset ? '加载失败' : '加载更多失败'; }
|
} catch {
|
||||||
finally { loadingTeachers.value = false; loadingMoreTeachers.value = false; }
|
teacherError.value = reset ? '加载失败' : '加载更多失败';
|
||||||
|
} finally {
|
||||||
|
loadingTeachers.value = false;
|
||||||
|
loadingMoreTeachers.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadMoreTeachers = () => { if (loadingMoreTeachers.value || !hasMoreTeachers.value) return; fetchTeachers(teacherPage.value + 1); };
|
const loadMoreTeachers = () => {
|
||||||
|
if (loadingMoreTeachers.value || !hasMoreTeachers.value) return;
|
||||||
|
fetchTeachers(teacherPage.value + 1);
|
||||||
|
};
|
||||||
|
|
||||||
const selectCourse = (course) => {
|
const selectCourse = (course: Course) => {
|
||||||
selectedCourse.value = course;
|
selectedCourse.value = course;
|
||||||
currentCourseId.value = course.id;
|
currentCourseId.value = course.id;
|
||||||
courseChapters.value = []; resourceList.value = []; activityList.value = []; teacherList.value = []; chapters.value = {};
|
courseChapters.value = [];
|
||||||
|
resourceList.value = [];
|
||||||
|
activityList.value = [];
|
||||||
|
teacherList.value = [];
|
||||||
|
chapters.value = {};
|
||||||
fetchCourseContent(course.id);
|
fetchCourseContent(course.id);
|
||||||
activeDetailTab.value = 'content';
|
activeDetailTab.value = 'content';
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBackToList = () => { selectedCourse.value = null; currentCourseId.value = null; };
|
const handleBackToList = () => {
|
||||||
|
selectedCourse.value = null;
|
||||||
|
currentCourseId.value = null;
|
||||||
|
};
|
||||||
|
|
||||||
// ==================== 初始化 ====================
|
// ==================== 初始化 ====================
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user