OL-LearnPlatform-Frontend/src/views/TeacherDetail.vue
2025-08-19 19:47:12 +08:00

1037 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="courses-page">
<!-- 页面头部横幅 -->
<div class="page-header">
<div class="header-content">
<h1 class="page-title">粉丝</h1>
<p class="page-subtitle">7832</p>
</div>
<div class="header-content">
<h1 class="page-title">课程</h1>
<p class="page-subtitle">12</p>
</div>
<div class="header-content">
<h1 class="page-title">播放量</h1>
<p class="page-subtitle">12.3</p>
</div>
</div>
<div class="title-lecturer">
<img src="/images/Teachers/师资力量1.png" alt="">
<h3>汪波</h3>
<p>云南师范大学教授平台签约讲师</p>
<button :class="{ 'disabled': isFollowing }" @click="toggleFollow">
{{ isFollowing ? '已关注' : '+ 关注' }}
</button>
</div>
<!-- 主要内容区域 -->
<div class="main-content">
<div class="container">
<!-- 课程导航 -->
<div class="text-wrapper_8 flex-row">
<span class="text_24">全部课程</span>
<div class="sort-options">
<span class="text_25" :class="{ 'active': sortType === 'hot' }" @click="selectSortType('hot')">最热</span>
<span class="text_26" :class="{ 'active': sortType === 'new' }" @click="selectSortType('new')">最新</span>
</div>
</div>
<!-- 加载状态 -->
<div class="loading-state" v-if="loading">
<div class="loading-content">
<p>正在加载课程...</p>
</div>
</div>
<!-- 课程网格 -->
<div class="courses-grid" v-else-if="allCourses.length > 0">
<div class="course-card" v-for="course in allCourses" :key="course.id">
<div class="course-image">
<img :src="course.thumbnail" :alt="course.title" />
</div>
<div class="course-info">
<h3 class="course-title">{{ getCourseTitle(course) }}</h3>
<div class="course-meta">
<div class="course-chapters">
<img src="/images/Teachers/chapter.png" alt="课程章节" class="meta-icon">
<span>共9章54节</span>
</div>
<div class="course-duration">
<img src="/images/Teachers/duration.png" alt="课程时长" class="meta-icon">
<span>12小时43分钟</span>
</div>
</div>
<div class="course-footer">
<div class="course-stats">
<span class="course-students">讲师: 刘莹</span>
</div>
<button class="enroll-btn" @click="goToCourseDetail(course)">去学习</button>
</div>
</div>
</div>
</div>
<!-- 空状态 -->
<div class="empty-state" v-else>
<div class="empty-content">
<p>暂无符合条件的课程</p>
<button class="clear-filters-btn" @click="clearAllFilters">清除筛选条件</button>
</div>
</div>
<!-- 分页组件 -->
<div class="pagination">
<span class="pagination-info">{{ currentPageText }} / 共{{ totalPages }}页 ({{ totalItems }}条数据)</span>
<span class="pagination-link prev" @click="goToPrevPage" :class="{ disabled: currentPage === 1 }">上一页</span>
<!-- 第一页 -->
<button v-if="totalPages > 0" class="pagination-btn page-num" :class="{ active: currentPage === 1 }"
@click="goToPage(1)">1</button>
<!-- 左侧省略号 -->
<span v-if="currentPage > 4" class="pagination-dots">...</span>
<!-- 当前页附近的页码 -->
<template v-for="page in visiblePages" :key="page">
<button v-if="page !== 1 && page !== totalPages" class="pagination-btn page-num"
:class="{ active: currentPage === page }" @click="goToPage(page)">{{ page }}</button>
</template>
<!-- 右侧省略号 -->
<span v-if="currentPage < totalPages - 3" class="pagination-dots">...</span>
<!-- 最后一页 -->
<button v-if="totalPages > 1" class="pagination-btn page-num" :class="{ active: currentPage === totalPages }"
@click="goToPage(totalPages)">{{ totalPages }}</button>
<span class="pagination-link next" @click="goToNextPage"
:class="{ disabled: currentPage === totalPages }">下一页</span>
<span class="pagination-link last" @click="goToPage(totalPages)">尾页</span>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
// 关注状态
const isFollowing = ref(false)
// 切换关注状态
const toggleFollow = () => {
isFollowing.value = !isFollowing.value
}
import { useRouter } from 'vue-router'
import type { Course } from '@/api/types'
// import { mockCourses } from '@/data/mockCourses'
const router = useRouter()
// 课程数据和加载状态
const courses = ref<Course[]>([])
const loading = ref(false)
const total = ref(0)
// 筛选和排序状态
const selectedSubject = ref('全部')
const selectedMajor = ref('全部')
const selectedDifficulty = ref('全部')
const sortType = ref('all') // all: 全部课程, hot: 最热, new: 最新
// 分页相关状态
const currentPage = ref(1)
const itemsPerPage = 20
const totalItems = computed(() => total.value)
const totalPages = computed(() => Math.ceil(totalItems.value / itemsPerPage))
// 已移除未使用的广告显隐逻辑,避免构建时报未使用错误
// 数字转中文
const numberToChinese = (num: number): string => {
const chineseNumbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十']
if (num <= 10) {
return chineseNumbers[num]
} else if (num < 20) {
return '十' + chineseNumbers[num - 10]
} else if (num < 100) {
const tens = Math.floor(num / 10)
const ones = num % 10
return chineseNumbers[tens] + '十' + (ones > 0 ? chineseNumbers[ones] : '')
}
return num.toString()
}
// 当前页面文本
const currentPageText = computed(() => {
if (currentPage.value === 1) {
return '首页'
} else {
return `${numberToChinese(currentPage.value)}`
}
})
// 可见的页码范围
const visiblePages = computed(() => {
const pages = []
const current = currentPage.value
const total = totalPages.value
// 显示当前页前后2页
const start = Math.max(2, current - 2)
const end = Math.min(total - 1, current + 2)
for (let i = start; i <= end; i++) {
pages.push(i)
}
return pages
})
// 加载课程数据(使用模拟数据)
const loadCourses = async () => {
try {
loading.value = true
// 模拟网络延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 筛选逻辑
let filteredCourses: Course[] = [
{
id: '1',
title: '教育心理学基础课程',
description: '本课程深入讲解教育心理学的基本理论和实践应用,帮助学生理解学习过程中的心理机制。',
thumbnail: '/images/courses/course1.png',
price: 0,
currency: 'CNY',
rating: 4.8,
ratingCount: 125,
studentsCount: 1250,
duration: '12小时43分钟',
totalLessons: 54,
level: 'beginner',
language: 'zh-CN',
category: { id: 1, name: '教育培训', slug: 'education-training' },
tags: ['心理学', '教育', '基础'],
skills: ['心理分析', '教育理论'],
requirements: ['无特殊要求'],
objectives: ['掌握教育心理学基础理论'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '资深教育心理学专家',
rating: 4.8,
studentsCount: 1250,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育心理学博士'],
certifications: ['高级心理咨询师', '教育技术专家']
},
status: 'published',
createdAt: '2024-01-15T10:00:00Z',
updatedAt: '2024-01-15T10:00:00Z'
},
{
id: '2',
title: '现代教育技术应用',
description: '探索现代教育技术在课堂教学中的应用,包括多媒体教学、在线教育平台等。',
thumbnail: '/images/courses/course2.png',
price: 0,
currency: 'CNY',
rating: 4.6,
ratingCount: 89,
studentsCount: 890,
duration: '10小时20分钟',
totalLessons: 42,
level: 'intermediate',
language: 'zh-CN',
category: { id: 2, name: '技术应用', slug: 'tech-application' },
tags: ['教育技术', '多媒体', '在线教育'],
skills: ['多媒体制作', '在线教学'],
requirements: ['基础计算机操作'],
objectives: ['掌握现代教育技术应用'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '资深教育技术专家',
rating: 4.6,
studentsCount: 890,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育技术博士'],
certifications: ['高级教育技术专家', '多媒体制作师']
},
status: 'published',
createdAt: '2024-02-20T14:30:00Z',
updatedAt: '2024-02-20T14:30:00Z'
},
{
id: '3',
title: '课程设计与开发',
description: '学习如何设计和开发高质量的课程内容,包括教学目标制定、教学内容组织等。',
thumbnail: '/images/courses/course3.png',
price: 0,
currency: 'CNY',
rating: 4.7,
ratingCount: 67,
studentsCount: 567,
duration: '15小时30分钟',
totalLessons: 68,
level: 'advanced',
language: 'zh-CN',
category: { id: 3, name: '课程设计', slug: 'course-design' },
tags: ['课程设计', '教学开发', '教育'],
skills: ['课程规划', '教学设计'],
requirements: ['教育理论基础'],
objectives: ['掌握课程设计方法'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '课程设计专家',
rating: 4.7,
studentsCount: 567,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '课程设计博士'],
certifications: ['高级课程设计师', '教育专家']
},
status: 'published',
createdAt: '2024-03-10T09:15:00Z',
updatedAt: '2024-03-10T09:15:00Z'
},
{
id: '4',
title: '教育研究方法论',
description: '系统介绍教育研究的基本方法和技巧,培养学生进行教育研究的能力。',
thumbnail: '/images/courses/course4.png',
price: 0,
currency: 'CNY',
rating: 4.5,
ratingCount: 43,
studentsCount: 432,
duration: '8小时15分钟',
totalLessons: 36,
level: 'intermediate',
language: 'zh-CN',
category: { id: 4, name: '研究方法', slug: 'research-methods' },
tags: ['研究方法', '教育', '学术'],
skills: ['研究设计', '数据分析'],
requirements: ['统计学基础'],
objectives: ['掌握教育研究方法'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '教育研究专家',
rating: 4.5,
studentsCount: 432,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育研究博士'],
certifications: ['高级教育研究专家', '学术顾问']
},
status: 'published',
createdAt: '2024-01-25T16:45:00Z',
updatedAt: '2024-01-25T16:45:00Z'
},
{
id: '5',
title: '学生心理辅导技巧',
description: '学习如何对学生进行心理辅导,掌握基本的心理咨询和辅导技巧。',
thumbnail: '/images/courses/course5.png',
price: 0,
currency: 'CNY',
rating: 4.9,
ratingCount: 78,
studentsCount: 678,
duration: '6小时45分钟',
totalLessons: 28,
level: 'beginner',
language: 'zh-CN',
category: { id: 5, name: '心理辅导', slug: 'psychological-counseling' },
tags: ['心理辅导', '学生', '技巧'],
skills: ['心理咨询', '沟通技巧'],
requirements: ['心理学基础'],
objectives: ['掌握心理辅导技巧'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '心理咨询专家',
rating: 4.9,
studentsCount: 678,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '心理学博士'],
certifications: ['高级心理咨询师', '心理治疗师']
},
status: 'published',
createdAt: '2024-02-05T11:20:00Z',
updatedAt: '2024-02-05T11:20:00Z'
},
{
id: '6',
title: '教育评估与测量',
description: '学习教育评估的基本理论和方法,掌握教育测量的技术和工具。',
thumbnail: '/images/courses/course5.png',
price: 0,
currency: 'CNY',
rating: 4.4,
ratingCount: 34,
studentsCount: 345,
duration: '9小时20分钟',
totalLessons: 40,
level: 'advanced',
language: 'zh-CN',
category: { id: 6, name: '教育评估', slug: 'education-assessment' },
tags: ['教育评估', '测量', '技术'],
skills: ['评估设计', '测量技术'],
requirements: ['教育统计学'],
objectives: ['掌握教育评估方法'],
instructor: {
id: 1,
name: '汪波',
avatar: '/images/Teachers/师资力量1.png',
title: '云南师范大学教授',
bio: '教育评估专家',
rating: 4.4,
studentsCount: 345,
coursesCount: 6,
experience: '15年',
education: ['云南师范大学', '教育评估博士'],
certifications: ['高级教育评估师', '测量技术专家']
},
status: 'published',
createdAt: '2024-03-15T13:10:00Z',
updatedAt: '2024-03-15T13:10:00Z'
}
] // 使用模拟数据后续可以从API获取
// 按学科筛选
if (selectedSubject.value !== '全部') {
filteredCourses = filteredCourses.filter(course => {
switch (selectedSubject.value) {
case '计算机':
return course.category.name === '编程开发' || course.category.name === '前端开发' || course.category.name === '后端开发'
case '教育学':
return course.category.name === '教育培训'
default:
return true
}
})
}
// 排序逻辑
if (sortType.value === 'hot') {
// 按热度排序假设课程有studentsCount字段表示学生数量
filteredCourses.sort((a, b) => {
const studentsA = a.studentsCount || 0;
const studentsB = b.studentsCount || 0;
return studentsB - studentsA;
});
} else if (sortType.value === 'new') {
// 按最新排序假设课程有createdAt字段表示创建时间
filteredCourses.sort((a, b) => {
const dateA = new Date(a.createdAt || 0);
const dateB = new Date(b.createdAt || 0);
return dateB.getTime() - dateA.getTime();
});
} else {
// 默认排序按课程ID
filteredCourses.sort((a, b) => a.id.localeCompare(b.id));
}
// 按专业筛选
if (selectedMajor.value !== '全部') {
filteredCourses = filteredCourses.filter(course =>
course.title.includes(selectedMajor.value) ||
course.description.includes(selectedMajor.value) ||
course.tags?.some((tag: string) => tag.includes(selectedMajor.value))
)
}
// 按难度筛选
if (selectedDifficulty.value !== '全部') {
const difficultyMap: { [key: string]: string } = {
'初级': 'beginner',
'中级': 'intermediate',
'高级': 'advanced'
}
const targetLevel = difficultyMap[selectedDifficulty.value]
if (targetLevel) {
filteredCourses = filteredCourses.filter(course => course.level === targetLevel)
}
}
// 分页处理
total.value = filteredCourses.length
const startIndex = (currentPage.value - 1) * itemsPerPage
const endIndex = startIndex + itemsPerPage
courses.value = filteredCourses.slice(startIndex, endIndex)
console.log('课程加载成功:', courses.value.length, '条课程,总计:', total.value)
} catch (error) {
console.error('加载课程失败:', error)
courses.value = []
total.value = 0
} finally {
loading.value = false
}
}
// 根据学科获取分类ID这里需要根据实际后端分类来映射
// const getCategoryIdBySubject = (subject: string): number | undefined => {
// const categoryMap: Record<string, number> = {
// '必修课': 1,
// '高分课': 2,
// '名师课堂': 3,
// '训练营': 4,
// '无考试': 5,
// '专题讲座': 6
// }
// return categoryMap[subject]
// }
// 跳转到指定页面
const goToPage = (page: number) => {
if (page >= 1 && page <= totalPages.value) {
currentPage.value = page
loadCourses()
}
}
// 上一页
const goToPrevPage = () => {
if (currentPage.value > 1) {
goToPage(currentPage.value - 1)
}
}
// 下一页
const goToNextPage = () => {
if (currentPage.value < totalPages.value) {
goToPage(currentPage.value + 1)
}
}
// 清除所有筛选条件
const clearAllFilters = () => {
selectedSubject.value = '全部'
selectedMajor.value = '全部'
selectedDifficulty.value = '全部'
currentPage.value = 1
loadCourses()
}
// 已移除未使用的筛选函数,避免构建时报未使用错误
// 排序功能
const selectSortType = (type: string) => {
sortType.value = type
currentPage.value = 1 // 重置到第一页
loadCourses()
}
// 当前页显示的课程数据直接使用从API获取的数据
const allCourses = computed(() => {
return courses.value
})
// 获取课程标题的函数
const getCourseTitle = (course: Course) => {
return course.title
}
// 跳转到课程详情页
const goToCourseDetail = (course: Course) => {
router.push({
name: 'CourseDetail',
params: { id: course.id }
})
}
// 组件挂载时加载数据
onMounted(() => {
loadCourses()
})
</script>
<style scoped>
.courses-page {
min-height: 100vh;
background: #fff;
}
.page-header {
background-image: url('/images/Teachers/teacher-bg.png');
background-size: cover;
background-position: center;
text-align: center;
color: #000;
position: relative;
overflow: hidden;
display: flex;
justify-content: flex-end;
align-items: center;
padding-right: 320px;
height: 165px;
}
.page-header::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 300px;
height: 100%;
background: url('/images/header-decoration.png') no-repeat center right;
background-size: contain;
opacity: 0.3;
}
.header-content {
height: 40px;
padding: 0 20px;
position: relative;
z-index: 1;
border-right: 1px solid #D8D8D8;
}
/* 最后一个 */
.header-content:last-child {
border-right: none;
}
.page-title {
font-size: 16px;
margin: -10px 0 5px 0;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.page-subtitle {
font-size: 24px;
opacity: 0.9;
margin-bottom: 35px;
font-weight: 700;
}
.main-content {
padding: 40px 0;
}
.container {
max-width: 1420px;
margin: 0 auto;
padding: 0 0;
box-sizing: border-box;
}
.text-wrapper_8 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.flex-row {
flex-direction: row;
}
.sort-options {
display: flex;
justify-content: flex-end;
}
.text_24 {
font-size: 24px;
color: #333;
margin-right: 0;
}
.text_25,
.text_26 {
margin-left: 30px;
cursor: pointer;
font-size: 14px;
padding-bottom: 5px;
position: relative;
color: #666;
}
.text_25.active,
.text_26.active {
color: #1890ff;
font-weight: 500;
}
.text_25.active::after,
.text_26.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 2px;
background-color: #1890ff;
}
.text_25,
.text_26 {
color: #666;
}
.text_25:hover,
.text_26:hover {
color: #1890ff;
}
.sort-tab.active {
color: #1890ff;
border-bottom-color: #1890ff;
font-weight: 500;
}
.courses-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 20px;
margin-bottom: 40px;
width: 100%;
box-sizing: border-box;
}
.course-card {
background: white;
border-radius: 2px;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
cursor: pointer;
/* min-height: 350px; */
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.course-image {
width: 100%;
height: 200px;
overflow: hidden;
border-radius: 5px 5px 0 0;
}
.course-image img {
width: 100%;
height: 100%;
object-fit: cover;
}
.course-info {
padding: 16px;
}
.course-title {
font-size: 16px;
font-weight: 500;
color: #333;
margin: 0 0 12px 0;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.course-meta {
display: flex;
justify-content: left;
gap: 10px;
font-size: 12px;
color: #999;
margin-bottom: 16px;
}
.course-duration {
color: #666;
}
.course-price {
color: #ff4d4f;
font-weight: 500;
}
.course-stats {
display: flex;
align-items: center;
}
.course-students {
color: #999;
font-size: 14px;
}
.course-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
.course-chapters,
.course-duration {
display: flex;
align-items: center;
font-size: 14px;
color: #666;
}
.meta-icon {
width: 16px;
height: 16px;
margin-right: 4px;
vertical-align: middle;
}
.course-teacher {
font-size: 12px;
color: #999;
margin-bottom: 12px;
}
.course-tags {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.course-tag {
background: transparent;
color: #999;
padding: 0;
border-radius: 0;
font-size: 12px;
white-space: nowrap;
}
.enroll-btn {
background: #0286D5;
color: white;
border: none;
padding: 6px 10px;
border-radius: 20px;
cursor: pointer;
font-size: 12px;
transition: background 0.2s;
white-space: nowrap;
}
.enroll-btn:hover {
background: #40a9ff;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
padding: 20px 0;
margin-top: 40px;
}
.pagination-info {
color: #666;
font-size: 14px;
margin-right: 20px;
}
.pagination-btn {
padding: 8px 12px;
border: 1px solid #d9d9d9;
background: white;
color: #666;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
min-width: 40px;
text-align: center;
margin: 0 6px;
}
.pagination-btn:hover {
border-color: #1890ff;
color: #1890ff;
}
.pagination-btn.active {
background: #1890ff;
border-color: #1890ff;
color: white;
}
/* 移除了 .pagination-btn.last 样式,因为现在尾页是文本链接 */
.pagination-btn:disabled {
background: #f5f5f5;
color: #ccc;
border-color: #e6e6e6;
cursor: not-allowed;
}
.pagination-btn:disabled:hover {
background: #f5f5f5;
color: #ccc;
border-color: #e6e6e6;
}
.pagination-link {
color: #666;
font-size: 14px;
cursor: pointer;
padding: 8px 12px;
margin: 0 6px;
transition: color 0.2s;
user-select: none;
}
.pagination-link:hover {
color: #1890ff;
}
.pagination-link.disabled {
color: #ccc;
cursor: not-allowed;
}
.pagination-link.disabled:hover {
color: #ccc;
}
.pagination-dots {
color: #999;
padding: 0 8px;
font-size: 14px;
margin: 0 6px;
}
.stats-grid {
display: flex;
align-items: center;
width: 100%;
margin: 0 auto;
padding: 0 247px;
}
.stat-item {
flex: 1;
display: flex;
align-items: center;
gap: 20px;
padding: 0 45px 0 25px;
}
.stat-item:last-child {
border-right: none;
}
.stat-item:last-child {
border-right: none;
}
.line {
width: 1px;
height: 50px;
background: #D0E8F5;
}
.stat-icon {
flex-shrink: 0;
width: 74px;
height: 74px;
display: flex;
align-items: center;
justify-content: center;
}
.stat-icon img {
width: 74px;
height: 74px;
object-fit: contain;
display: block;
}
.title-lecturer {
position: relative;
text-align: center;
}
.title-lecturer img {
position: absolute;
left: 50%;
top: -32%;
transform: translateX(-50%);
width: 128px;
height: 128px;
border-radius: 50%;
}
.title-lecturer h3 {
padding-top: 80px;
font-size: 24px;
font-weight: 600;
}
.title-lecturer p {
font-size: 14px;
color: #999;
margin-bottom: 20px;
}
.title-lecturer button {
width: 96px;
height: 40px;
background: #0088D1;
border: none;
border-radius: 5px;
font-size: 16px;
color: white;
}
.title-lecturer .disabled {
border: 1px solid #0088D1;
background-color: #E2F5FF;
color: #0088D1;
}
@media (max-width: 768px) {
.container {
margin: 0 20px;
}
}
@media (max-width: 480px) {
.courses-grid {
grid-template-columns: 1fr;
}
.container {
margin: 0 16px;
}
.filter-section {
padding: 16px;
}
}
</style>