2025-08-10 22:42:56 +08:00
|
|
|
|
<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>
|
2025-08-12 16:58:22 +08:00
|
|
|
|
<button :class="{ 'disabled': isFollowing }" @click="toggleFollow">
|
|
|
|
|
{{ isFollowing ? '已关注' : '+ 关注' }}
|
|
|
|
|
</button>
|
2025-08-10 22:42:56 +08:00
|
|
|
|
</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'
|
2025-08-12 16:58:22 +08:00
|
|
|
|
|
|
|
|
|
// 关注状态
|
|
|
|
|
const isFollowing = ref(false)
|
|
|
|
|
|
|
|
|
|
// 切换关注状态
|
|
|
|
|
const toggleFollow = () => {
|
|
|
|
|
isFollowing.value = !isFollowing.value
|
|
|
|
|
}
|
2025-08-10 22:42:56 +08:00
|
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
import type { Course } from '@/api/types'
|
2025-08-15 13:58:25 +08:00
|
|
|
|
// import { mockCourses } from '@/data/mockCourses'
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
|
2025-08-11 10:03:56 +08:00
|
|
|
|
// 已移除未使用的广告显隐逻辑,避免构建时报未使用错误
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
// 数字转中文
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
|
|
// 筛选逻辑
|
2025-08-15 13:58:25 +08:00
|
|
|
|
let filteredCourses: Course[] = [] // 暂时使用空数组,后续可以从API获取
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
// 按学科筛选
|
|
|
|
|
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)
|
2025-08-15 13:58:25 +08:00
|
|
|
|
filteredCourses.sort((a, b) => a.id.localeCompare(b.id));
|
2025-08-10 22:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按专业筛选
|
|
|
|
|
if (selectedMajor.value !== '全部') {
|
|
|
|
|
filteredCourses = filteredCourses.filter(course =>
|
|
|
|
|
course.title.includes(selectedMajor.value) ||
|
|
|
|
|
course.description.includes(selectedMajor.value) ||
|
2025-08-15 13:58:25 +08:00
|
|
|
|
course.tags?.some((tag: string) => tag.includes(selectedMajor.value))
|
2025-08-10 22:42:56 +08:00
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 按难度筛选
|
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-11 10:03:56 +08:00
|
|
|
|
// 已移除未使用的筛选函数,避免构建时报未使用错误
|
2025-08-10 22:42:56 +08:00
|
|
|
|
|
|
|
|
|
// 排序功能
|
|
|
|
|
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;
|
2025-08-14 17:32:28 +08:00
|
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
2025-08-10 22:42:56 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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>
|