course_system/src/views/RatingPage.vue
2025-01-31 20:28:45 +08:00

616 lines
17 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 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 组件传入选中的课程 -->
<CourseDetailModal
: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 response = await fetch(`https://coursesystem.xn--xhq44jb2fzpc.com/list/courses?search=${this.searchQuery}&teacher=${this.teacherQuery}&sortBy=${sortParam}&sort=${this.sortOrder}&category=${categoryParam}${collegeParam}&page=${this.currentPage}`);
const result = await response.json();
this.courses = result;
},
async fetchTotalPages() {
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 response = await fetch(`https://coursesystem.xn--xhq44jb2fzpc.com/list/courses/pages?search=${this.searchQuery}&teacher=${this.teacherQuery}&category=${categoryParam}${collegeParam}`);
const result = await response.json();
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();
this.fetchTotalPages();
},
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>