OL-LearnPlatform-Frontend/src/views/TeacherDetail.vue
2025-08-14 17:32:28 +08:00

820 lines
18 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 = [...mockCourses]
// 按学科筛选
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 - b.id);
}
// 按专业筛选
if (selectedMajor.value !== '全部') {
filteredCourses = filteredCourses.filter(course =>
course.title.includes(selectedMajor.value) ||
course.description.includes(selectedMajor.value) ||
course.tags.some(tag => 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>