600 lines
17 KiB
Vue
600 lines
17 KiB
Vue
<template>
|
||
<div v-if="isLoaded" class="rating-page">
|
||
<h2 class="rating-note">每页最多展示12个课程,一次最多展示10个页码。点击星星进行评分,星星记录的是您的历史评分。</h2>
|
||
|
||
<!-- 搜索、类别多选和排序区域 -->
|
||
<div class="search-sort-bar">
|
||
<input v-model="searchQuery" type="text" placeholder="请输入课程名" />
|
||
<input v-model="teacherQuery" type="text" style="width: 450px;" placeholder="请输入教师名,多个教师以英文逗号分隔,如'张三,李四'" />
|
||
|
||
<div class="category-checkboxes">
|
||
<label><input type="checkbox" v-model="selectedCategories" value="1" /> 通识选修类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="2" /> 人文选修类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="3" /> 专业方向类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="4" /> 体育类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="5" /> 学科基础类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="6" /> 暑期国际课</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="7" /> 数学与自然科学类</label>
|
||
<label><input type="checkbox" v-model="selectedCategories" value="8" /> 重修专栏</label>
|
||
</div>
|
||
|
||
<select v-model="sortBy">
|
||
<option value="rating">评分由高到低</option>
|
||
<option value="rating-asc">评分由低到高</option>
|
||
<option value="rating_count">评分人数由多到少</option>
|
||
</select>
|
||
|
||
|
||
<select v-model="selectedCollege">
|
||
<option value="">不限院系</option> <!-- 设置为空字符串 -->
|
||
<option v-for="([id, name]) in filteredColleges" :key="id" :value="id">
|
||
{{ name }}
|
||
</option>
|
||
</select>
|
||
|
||
<button @click="searchCourses">更新查询</button>
|
||
</div>
|
||
|
||
<!-- 课程卡片区域 -->
|
||
<div class="course-cards">
|
||
<div v-for="course in courses" :key="course.course_id" class="course-card" @click="showCourseDetail(course)">
|
||
<h3>
|
||
{{ course.course_name }}
|
||
<!-- 检查 titles 是否存在且不为空,并在外层元素使用 v-if -->
|
||
<template v-if="course.titles && course.titles.length > 0">
|
||
<span v-for="title in course.titles"
|
||
:key="title.title"
|
||
class="course-title"
|
||
:style="{ borderColor: title.color, color: title.color }">
|
||
{{ title.title }}
|
||
</span>
|
||
</template>
|
||
</h3>
|
||
<p class="card-type"><strong>分类:</strong> {{ getCategoryName(course.category_id) }}</p>
|
||
<p class="card-teachers"><strong>教师:</strong> {{ course.teachers }}</p>
|
||
<p class="card-college"><strong>开课院系:</strong> {{ getCollegeName(course.college) }}</p>
|
||
<p class="card-ratecount"><strong>评分人数:</strong> {{ course.rating_count }}</p>
|
||
<p class="card-topcomment" v-if="course.top_comment">"{{ formatTopComment(course.top_comment) }}"</p>
|
||
<p class="card-topcomment" v-else>暂无评论</p>
|
||
<p class="card-rate">{{ course.rating }}<span class="unit"> 分</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<!-- 页码栏 -->
|
||
<div class="pagination-bar">
|
||
<!-- 首页按钮 -->
|
||
<button @click="goToPage(1)" :class="{ active: currentPage === 1 }">首页</button>
|
||
|
||
<!-- 页码按钮 -->
|
||
<button
|
||
v-for="page in getPaginationPages()"
|
||
:key="page"
|
||
@click="goToPage(page)"
|
||
:class="{ active: currentPage === page }"
|
||
>
|
||
{{ page }}
|
||
</button>
|
||
|
||
<!-- 尾页按钮 -->
|
||
<button
|
||
@click="goToPage(totalPages)"
|
||
:class="{ active: currentPage === totalPages }"
|
||
:disabled="totalPages === 0"
|
||
>
|
||
尾页
|
||
</button>
|
||
|
||
</div>
|
||
|
||
|
||
<!-- 调用 CourseDetailModal 组件,传入选中的课程,并添加 v-if 指令 -->
|
||
<CourseDetailModal
|
||
v-if="selectedCourse"
|
||
:isVisible="showDetail"
|
||
:course="selectedCourse"
|
||
@close="closeCourseDetail"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 页面加载中状态 -->
|
||
<div v-else>
|
||
<p style="color: #666">加载中,请稍后...</p>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
// 引入 CourseDetailModal 组件
|
||
import CourseDetailModal from './CourseDetailModal.vue';
|
||
|
||
export default {
|
||
components: {
|
||
CourseDetailModal
|
||
},
|
||
data() {
|
||
return {
|
||
searchQuery: '', // 搜索框内容
|
||
teacherQuery: '', // 教师搜索框内容
|
||
sortBy: 'rating', // 默认排序方式为评分降序
|
||
sortOrder: 'desc', // 排序顺序,默认降序
|
||
selectedCategories: [], // 存储选中的类别
|
||
courses: [], // 存储课程数据
|
||
currentPage: 1, // 当前页
|
||
totalPages: 1, // 总页数
|
||
showDetail: false, // 是否显示课程详情弹窗
|
||
selectedCourse: null, // 选中的课程详情
|
||
courseComments: [], // 存储当前课程的评论
|
||
isLoaded: false, // 控制页面的加载状态
|
||
selectedCollege: '', // 存储用户选择的开课院系,默认为空
|
||
allColleges: [], // 存储所有院系的ID
|
||
collegeMap: {
|
||
59: '未填写',
|
||
1: '材料科学与工程学院',
|
||
2: '创新创业学院',
|
||
3: '档案馆',
|
||
4: '党委宣传部',
|
||
5: '党委组织部',
|
||
6: '发展规划与学科建设处',
|
||
7: '佛山研究生创新学院',
|
||
8: '工程训练中心',
|
||
9: '工会',
|
||
10: '工商管理学院',
|
||
11: '国防教育学院',
|
||
12: '国际教育学院',
|
||
13: '后勤服务中心',
|
||
14: '后勤管理处',
|
||
15: '浑南管委会',
|
||
16: '江河建筑学院',
|
||
17: '尖子班',
|
||
18: '教务处',
|
||
19: '基础学院',
|
||
20: '计划财经处',
|
||
21: '机器人科学与工程学院',
|
||
22: '计算机科学与工程学院',
|
||
23: '计算中心',
|
||
24: '纪委',
|
||
25: '机械工程与自动化学院',
|
||
26: '继续教育学院',
|
||
27: '科技产业集团',
|
||
28: '科学技术研究院',
|
||
29: '理学院',
|
||
30: '马克思主义学院',
|
||
31: '民族教育学院',
|
||
33: '人事处',
|
||
34: '人文选修课群',
|
||
35: '软件学院',
|
||
36: '生命科学与健康学院',
|
||
37: '体育部',
|
||
38: '体育场馆管理中心',
|
||
39: '团委',
|
||
40: '图书馆',
|
||
41: '外国语学院',
|
||
42: '外联处',
|
||
43: '网络教育学院',
|
||
44: '未来技术学院',
|
||
45: '文法学院',
|
||
46: '校长办公室',
|
||
47: '信息科学与工程学院',
|
||
48: '新知识课群',
|
||
49: '学生处',
|
||
50: '学生创新中心',
|
||
51: '学生指导服务中心',
|
||
52: '研究生院',
|
||
53: '冶金学院',
|
||
54: '艺术学院',
|
||
55: '医学与生物信息工程学院',
|
||
56: '医院',
|
||
57: '资产与实验室管理处',
|
||
58: '资源与土木工程学院'
|
||
}
|
||
};
|
||
},
|
||
computed: {
|
||
// 计算属性,过滤掉 "未填写" 的院系
|
||
filteredColleges() {
|
||
return Object.entries(this.collegeMap).filter(([id]) => id !== '59');
|
||
}
|
||
},
|
||
mounted() {
|
||
// 页面加载时自动调用搜索课程,延迟0.3秒后显示页面
|
||
setTimeout(() => {
|
||
this.isLoaded = true;
|
||
this.searchCourses();
|
||
}, 300);
|
||
|
||
// 初始化 allColleges 数组,包含所有院系 ID
|
||
this.allColleges = Object.keys(this.collegeMap).map(Number);
|
||
},
|
||
methods: {
|
||
async fetchCourses() {
|
||
let sortParam = this.sortBy === 'rating-asc' ? 'rating' : this.sortBy;
|
||
this.sortOrder = this.sortBy === 'rating-asc' ? 'asc' : 'desc';
|
||
|
||
const categoryParam = this.selectedCategories.length === 0
|
||
? '1,2,3,4,5,6,7,8'
|
||
: this.selectedCategories.join(',');
|
||
|
||
// 如果 selectedCollege 为空,不传递 college 参数
|
||
const collegeParam = this.selectedCollege
|
||
? `&college=${this.selectedCollege}`
|
||
: '';
|
||
|
||
// 对搜索参数进行编码
|
||
const encodedSearch = encodeURIComponent(this.searchQuery);
|
||
const encodedTeacher = encodeURIComponent(this.teacherQuery);
|
||
|
||
const response = await fetch(`https://coursesystem.xn--xhq44jb2fzpc.com/list/courses?search=${encodedSearch}&teacher=${encodedTeacher}&sortBy=${sortParam}&sort=${this.sortOrder}&category=${categoryParam}${collegeParam}&page=${this.currentPage}`);
|
||
|
||
const result = await response.json();
|
||
this.courses = result.courses; // 从新的响应格式中获取课程数组
|
||
this.currentPage = result.currentPage; // 获取当前页码
|
||
this.totalPages = result.totalPages; // 获取总页数
|
||
},
|
||
|
||
// 获取显示的页码列表
|
||
getPaginationPages() {
|
||
const pages = [];
|
||
const total = this.totalPages; // 总页数
|
||
const current = this.currentPage; // 当前页码
|
||
const maxVisiblePages = 10; // 最大显示10个页码按钮
|
||
|
||
// 如果总页数小于或等于10,则渲染所有页码
|
||
if (total <= maxVisiblePages) {
|
||
for (let i = 1; i <= total; i++) {
|
||
pages.push(i);
|
||
}
|
||
} else {
|
||
// 确保当前页位于中间,除非在边界
|
||
let startPage = Math.max(current - 5, 1); // 前5页
|
||
let endPage = Math.min(current + 4, total); // 后4页
|
||
|
||
// 当页码到头部时,显示前10页
|
||
if (current <= 6) {
|
||
startPage = 1;
|
||
endPage = 10;
|
||
}
|
||
|
||
// 当页码接近尾部时,显示最后10页
|
||
if (current >= total - 4) {
|
||
startPage = total - 9;
|
||
endPage = total;
|
||
}
|
||
|
||
// 生成页码数组
|
||
for (let i = startPage; i <= endPage; i++) {
|
||
pages.push(i);
|
||
}
|
||
}
|
||
return pages;
|
||
},
|
||
|
||
searchCourses() {
|
||
this.currentPage = 1;
|
||
this.fetchCourses();
|
||
},
|
||
|
||
goToPage(page) {
|
||
if (page < 1 || page > this.totalPages) {
|
||
return; // 防止跳转到无效的页码
|
||
}
|
||
this.currentPage = page;
|
||
this.fetchCourses(); // 根据页码重新获取课程数据
|
||
},
|
||
|
||
showCourseDetail(course) {
|
||
this.selectedCourse = { ...course }; // 清空选中的课程以显示加载动画
|
||
this.showDetail = true; // 打开模态框,显示加载动画
|
||
},
|
||
|
||
formatTopComment(comment) {
|
||
if (!comment) return '';
|
||
|
||
let length = 0;
|
||
let result = '';
|
||
|
||
for (let i = 0; i < comment.length; i++) {
|
||
// 汉字或者全角字符长度算1,其他算0.5
|
||
const charLength = comment.charCodeAt(i) > 255 ? 1 : 0.5;
|
||
if (length + charLength > 29) {
|
||
return result + '...';
|
||
}
|
||
length += charLength;
|
||
result += comment[i];
|
||
}
|
||
|
||
return result;
|
||
},
|
||
|
||
closeCourseDetail() {
|
||
this.showDetail = false;
|
||
this.selectedCourse = null;
|
||
},
|
||
|
||
getCategoryName(categoryId) {
|
||
const categoryMap = {
|
||
1: '通识选修类',
|
||
2: '人文选修类',
|
||
3: '专业方向类',
|
||
4: '体育类',
|
||
5: '学科基础类',
|
||
6: '暑期国际课',
|
||
7: '数学与自然科学类',
|
||
8: '重修专栏'
|
||
};
|
||
return categoryMap[categoryId] || '未知分类';
|
||
},
|
||
|
||
getCollegeName(collegeId) {
|
||
const collegeMap = {
|
||
59: '未填写',
|
||
1: '材料科学与工程学院',
|
||
2: '创新创业学院',
|
||
3: '档案馆',
|
||
4: '党委宣传部',
|
||
5: '党委组织部',
|
||
6: '发展规划与学科建设处',
|
||
7: '佛山研究生创新学院',
|
||
8: '工程训练中心',
|
||
9: '工会',
|
||
10: '工商管理学院',
|
||
11: '国防教育学院',
|
||
12: '国际教育学院',
|
||
13: '后勤服务中心',
|
||
14: '后勤管理处',
|
||
15: '浑南管委会',
|
||
16: '江河建筑学院',
|
||
17: '尖子班',
|
||
18: '教务处',
|
||
19: '基础学院',
|
||
20: '计划财经处',
|
||
21: '机器人科学与工程学院',
|
||
22: '计算机科学与工程学院',
|
||
23: '计算中心',
|
||
24: '纪委',
|
||
25: '机械工程与自动化学院',
|
||
26: '继续教育学院',
|
||
27: '科技产业集团',
|
||
28: '科学技术研究院',
|
||
29: '理学院',
|
||
30: '马克思主义学院',
|
||
31: '民族教育学院',
|
||
33: '人事处',
|
||
34: '人文选修课群',
|
||
35: '软件学院',
|
||
36: '生命科学与健康学院',
|
||
37: '体育部',
|
||
38: '体育场馆管理中心',
|
||
39: '团委',
|
||
40: '图书馆',
|
||
41: '外国语学院',
|
||
42: '外联处',
|
||
43: '网络教育学院',
|
||
44: '未来技术学院',
|
||
45: '文法学院',
|
||
46: '校长办公室',
|
||
47: '信息科学与工程学院',
|
||
48: '新知识课群',
|
||
49: '学生处',
|
||
50: '学生创新中心',
|
||
51: '学生指导服务中心',
|
||
52: '研究生院',
|
||
53: '冶金学院',
|
||
54: '艺术学院',
|
||
55: '医学与生物信息工程学院',
|
||
56: '医院',
|
||
57: '资产与实验室管理处',
|
||
58: '资源与土木工程学院'
|
||
};
|
||
return collegeMap[collegeId] || '未知院系';
|
||
}
|
||
|
||
}
|
||
};
|
||
</script>
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
<style scoped>
|
||
.rating-page {
|
||
padding: 20px;
|
||
}
|
||
|
||
.search-sort-bar {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
margin-bottom: 20px;
|
||
align-items: center;
|
||
}
|
||
|
||
.search-sort-bar input[type="text"] {
|
||
padding: 8px 12px;
|
||
font-size: 16px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
width: 200px; /* 控制输入框的宽度 */
|
||
}
|
||
|
||
.search-sort-bar button {
|
||
padding: 8px 16px;
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: transform 0.2s ease;
|
||
}
|
||
|
||
.search-sort-bar button:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.search-sort-bar button:active {
|
||
transform: scale(0.9);
|
||
}
|
||
|
||
.category-checkboxes {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.category-checkboxes label {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.category-checkboxes input[type="checkbox"] {
|
||
margin-right: 6px;
|
||
transform: scale(1.2); /* 放大复选框 */
|
||
cursor: pointer;
|
||
}
|
||
|
||
.search-sort-bar select {
|
||
padding: 8px 12px;
|
||
font-size: 16px;
|
||
border: 1px solid #ccc;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.search-sort-bar select:focus {
|
||
border-color: #007bff;
|
||
outline: none;
|
||
}
|
||
|
||
.category-checkboxes {
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
.course-cards {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 20px;
|
||
max-height: 55vh; /* 设置最大高度,根据需要调整数值 */
|
||
overflow-y: auto; /* 当内容超过最大高度时,实现内部滚动 */
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
.course-card {
|
||
border: 1px solid #ddd;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
transition: transform 0.3s;
|
||
box-shadow: 0px 4px 8px rgba(29, 28, 28, 0.2); /* 添加水平偏移、垂直偏移和模糊半径 */
|
||
max-width: 33vw;
|
||
}
|
||
|
||
.course-card:hover {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.course-title {
|
||
display: inline-flex;
|
||
align-items: center; /* 垂直居中 */
|
||
justify-content: center; /* 水平居中 */
|
||
padding: 2px 6px; /* 根据需要调整 */
|
||
border: 1px solid;
|
||
border-radius: 12px; /* 圆角大小 */
|
||
margin-left: 4px;
|
||
font-size: 0.85em;
|
||
min-height: 20px; /* 根据内容调整高度 */
|
||
}
|
||
|
||
|
||
|
||
/* 分页栏样式美化 */
|
||
.pagination-bar {
|
||
margin-top: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.pagination-bar button {
|
||
margin: 0 5px;
|
||
padding: 5px 10px;
|
||
cursor: pointer;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: #ffffff;
|
||
transition: all 0.3s ease; /* 添加过渡效果 */
|
||
}
|
||
|
||
.pagination-bar button:hover {
|
||
background-color: #0400f9;
|
||
border-color: #0400f9;
|
||
color: white; /* 悬浮时改变文字颜色 */
|
||
}
|
||
|
||
.pagination-bar .active {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border-color: #007bff;
|
||
}
|
||
|
||
.course-detail-modal {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-content {
|
||
background-color: white;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
width: 400px;
|
||
position: relative;
|
||
}
|
||
|
||
.modal-content .close {
|
||
position: absolute;
|
||
top: 10px;
|
||
right: 10px;
|
||
cursor: pointer;
|
||
font-size: 24px;
|
||
}
|
||
|
||
.card-rate {
|
||
font-size: 22px;
|
||
text-align: right;
|
||
color: red;
|
||
}
|
||
|
||
.unit{
|
||
font-size: 14px;
|
||
}
|
||
|
||
.card-topcomment{
|
||
font-size: 15px;
|
||
}
|
||
|
||
.card-teachers, .card-ratecount, .card-college{
|
||
font-size: 14px;
|
||
}
|
||
|
||
.card-type{
|
||
font-size: 14px;
|
||
}
|
||
|
||
.rating-note{
|
||
font-size: 18px;
|
||
}
|
||
|
||
|
||
</style>
|