326 lines
8.1 KiB
Vue
326 lines
8.1 KiB
Vue
<template>
|
|
<div class="course-card" :class="{ 'loading': isLoading }">
|
|
<!-- 标题栏 -->
|
|
<div class="course-card-header">相关课程</div>
|
|
|
|
<div v-if="isLoading" class="loading-spinner">
|
|
<div class="spinner"></div>
|
|
</div>
|
|
<div v-else-if="error" class="error-message">
|
|
当前课程似乎不存在呢~
|
|
</div>
|
|
<div v-else-if="course" class="course-content">
|
|
<!-- 标题和标签行 -->
|
|
<div class="course-header">
|
|
<h3 class="course-title">{{ course.course_name }}</h3>
|
|
<div class="course-tags">
|
|
<span
|
|
v-for="(tag, index) in course.titles"
|
|
:key="index"
|
|
class="course-tag"
|
|
:style="{ backgroundColor: tag.color }"
|
|
>
|
|
{{ tag.title }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 课程信息 -->
|
|
<div class="course-info">
|
|
<div class="course-category">{{ getCategoryName(course.category_id) }}</div>
|
|
<div class="course-teachers">{{ course.teachers }}</div>
|
|
</div>
|
|
|
|
<!-- 评分区域 -->
|
|
<div class="course-rating-container">
|
|
<div class="course-rating">
|
|
<span class="rating-number">{{ course.rating }}</span>
|
|
<div class="rating-stars">
|
|
<span class="stars" :style="{ width: (parseFloat(course.rating) / 5 * 100) + '%' }"></span>
|
|
</div>
|
|
<span class="rating-count">({{ course.rating_count }}人评)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 详情按钮 -->
|
|
<div class="course-actions">
|
|
<button class="view-detail-button" @click="viewCourseDetail">查看详情</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import messageBox from '@/utils/messageBox.js'
|
|
export default {
|
|
name: 'CourseCard',
|
|
props: {
|
|
courseId: {
|
|
type: [Number, String],
|
|
required: true
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
course: null,
|
|
isLoading: true,
|
|
error: null,
|
|
categoryMap: {
|
|
1: '通识选修类',
|
|
2: '人文选修类',
|
|
3: '专业方向类',
|
|
4: '体育类',
|
|
5: '学科基础类',
|
|
6: '暑期国际课',
|
|
7: '数学与自然科学类',
|
|
8: '重修专栏',
|
|
9: '数学与自然科学类(必修)',
|
|
10: '人文社会科学类(必修)',
|
|
11: '学科基础类(必修)',
|
|
12: '专业方向类(必修)',
|
|
13: '实践类(必修)'
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.fetchCourseDetails();
|
|
},
|
|
methods: {
|
|
async fetchCourseDetails() {
|
|
this.isLoading = true;
|
|
this.error = null;
|
|
|
|
try {
|
|
const response = await fetch(`https://coursesystem.xn--xhq44jb2fzpc.com/course-detail?course_id=${this.courseId}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('获取课程信息失败');
|
|
}
|
|
|
|
this.course = await response.json();
|
|
} catch (err) {
|
|
this.error = err.message;
|
|
console.error('获取课程详情出错:', err);
|
|
} finally {
|
|
this.isLoading = false;
|
|
}
|
|
},
|
|
getCategoryName(categoryId) {
|
|
return this.categoryMap[categoryId] || '未知类别';
|
|
},
|
|
viewCourseDetail() {
|
|
// 检测是否为移动设备
|
|
// const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) || window.innerWidth < 768;
|
|
const isMobile = false;
|
|
|
|
if (isMobile) {
|
|
// 移动设备显示确认对话框
|
|
messageBox.confirm("建议在电脑端查看课程评分系统以获得最佳体验。仍要继续吗?", "提示")
|
|
.then(() => {
|
|
// 用户确认,跳转到投稿页面
|
|
// 将课程ID转为字符串并进行base64编码
|
|
// const courseIdBase64 = btoa(String(this.courseId));
|
|
// 构建URL并在新窗口打开
|
|
const url = `https://course.东北大学.com/detail/${this.courseId}`;
|
|
window.open(url, '_blank');
|
|
})
|
|
.catch(() => {
|
|
// 用户取消,不执行任何操作
|
|
console.log("用户取消了在移动端查看课程评分系统");
|
|
});
|
|
} else {
|
|
// 将课程ID转为字符串并进行base64编码
|
|
// const courseIdBase64 = btoa(String(this.courseId));
|
|
// 构建URL并在新窗口打开
|
|
const url = `https://course.东北大学.com/detail/${this.courseId}`;
|
|
window.open(url, '_blank');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.course-card {
|
|
width: 100%;
|
|
max-width: 400px;
|
|
min-height: 180px;
|
|
border-radius: 8px;
|
|
background: #ffffff;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
|
padding: 16px;
|
|
transition: all 0.3s ease;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
|
|
&:hover {
|
|
transform: translateY(-4px);
|
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
|
|
}
|
|
|
|
&.loading {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
}
|
|
|
|
.course-card-header {
|
|
font-size: 0.85rem;
|
|
color: #666;
|
|
margin-bottom: 10px;
|
|
padding-bottom: 8px;
|
|
border-bottom: 1px dashed #eaeaea;
|
|
text-align: left;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.loading-spinner {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
min-height: 180px;
|
|
|
|
.spinner {
|
|
width: 40px;
|
|
height: 40px;
|
|
border: 4px solid rgba(0, 0, 0, 0.1);
|
|
border-radius: 50%;
|
|
border-top-color: #3273dc;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
}
|
|
|
|
.error-message {
|
|
color: #e74c3c;
|
|
text-align: center;
|
|
padding: 20px;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.course-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-grow: 1;
|
|
}
|
|
|
|
.course-header {
|
|
display: flex;
|
|
flex-direction: column;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.course-title {
|
|
font-size: 1.25rem;
|
|
font-weight: 600;
|
|
color: #333;
|
|
margin: 0 0 8px 0;
|
|
line-height: 1.4;
|
|
margin-top: 0.5rem !important; /* 添加顶部间距 */
|
|
margin-bottom: 0.5rem !important; /* 添加底部间距 */
|
|
}
|
|
|
|
.course-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
// margin-bottom: 8px;
|
|
|
|
.course-tag {
|
|
font-size: 0.75rem;
|
|
padding: 2px 8px;
|
|
border-radius: 4px;
|
|
color: white;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
.course-info {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 16px;
|
|
color: #666;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.course-category {
|
|
padding: 2px 8px;
|
|
background-color: #f3f4f6;
|
|
border-radius: 4px;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
.course-teachers {
|
|
// font-style: italic;
|
|
}
|
|
|
|
.course-rating-container {
|
|
margin-top: auto;
|
|
padding-top: 12px;
|
|
border-top: 1px solid #eee;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.course-rating {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.rating-number {
|
|
font-size: 1.5rem;
|
|
font-weight: 700;
|
|
color: #ff9800;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.rating-stars {
|
|
position: relative;
|
|
display: inline-block;
|
|
width: 80px;
|
|
height: 16px;
|
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZTBlMGUwIj48cGF0aCBkPSJNMTIgMTcuMjdsNi4xOCAzLjczLTEuNjQtNy4wM0wyMiA5LjI0bC03LjE5LS42MUwxMiAyIDkuMTkgOC42MyAyIDkuMjRsNS40NiA0LjczLTEuNjQgNy4wM3oiLz48L3N2Zz4=') repeat-x;
|
|
background-size: 16px 16px;
|
|
|
|
.stars {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
height: 100%;
|
|
background: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSIjZmZhMDAwIj48cGF0aCBkPSJNMTIgMTcuMjdsNi4xOCAzLjczLTEuNjQtNy4wM0wyMiA5LjI0bC03LjE5LS42MUwxMiAyIDkuMTkgOC42MyAyIDkuMjRsNS40NiA0LjczLTEuNjQgNy4wM3oiLz48L3N2Zz4=') repeat-x;
|
|
background-size: 16px 16px;
|
|
}
|
|
}
|
|
|
|
.rating-count {
|
|
font-size: 0.8rem;
|
|
color: #888;
|
|
margin-left: 8px;
|
|
}
|
|
}
|
|
|
|
.course-actions {
|
|
display: flex;
|
|
justify-content: center;
|
|
|
|
.view-detail-button {
|
|
background-color: #3273dc;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
padding: 8px 16px;
|
|
font-size: 0.9rem;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
|
|
&:hover {
|
|
background-color: #2366c9;
|
|
}
|
|
}
|
|
}
|
|
</style>
|