智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

514 lines
11 KiB

<template>
<view class="course-detail-container">
<!-- 课程基本信息 -->
<view class="course-info-card">
<view class="course-header">
<text class="course-title">{{ courseInfo.course_name || '课程详情' }}</text>
<view class="course-status" :class="getStatusClass(courseInfo.status)">
{{ getStatusText(courseInfo.status) }}
</view>
</view>
<view class="course-stats">
<view class="stat-item">
<text class="stat-label">总课时</text>
<text class="stat-value">{{ courseInfo.total_hours || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-label">赠送课时</text>
<text class="stat-value">{{ courseInfo.gift_hours || 0 }}</text>
</view>
<view class="stat-item">
<text class="stat-label">已用课时</text>
<text class="stat-value">{{ (courseInfo.use_total_hours || 0) + (courseInfo.use_gift_hours || 0) }}</text>
</view>
<view class="stat-item">
<text class="stat-label">剩余课时</text>
<text class="stat-value remaining">{{ getRemainingHours() }}</text>
</view>
</view>
<view class="course-dates">
<text class="date-item">开始日期{{ courseInfo.start_date || '--' }}</text>
<text class="date-item">结束日期{{ courseInfo.end_date || '--' }}</text>
</view>
</view>
<!-- 课程安排列表 -->
<view class="section-title">
<text class="title-text">课程安排</text>
<text class="title-count">({{ scheduleList.length }})</text>
</view>
<view class="schedule-list">
<view
v-for="(item, index) in scheduleList"
:key="item.id"
class="schedule-item"
@tap="showScheduleDetail(item)"
>
<view class="schedule-date">
<text class="date-text">{{ formatDate(item.course_date) }}</text>
<text class="time-text">{{ item.time_slot }}</text>
</view>
<view class="schedule-info">
<view class="schedule-type">
<text class="type-tag" :class="getScheduleTypeClass(item.schedule_type)">
{{ getScheduleTypeText(item.schedule_type) }}
</text>
<text class="course-type-tag" :class="getCourseTypeClass(item.course_type)">
{{ getCourseTypeText(item.course_type) }}
</text>
</view>
<view class="schedule-status">
<text class="status-text" :class="getScheduleStatusClass(item.status)">
{{ getScheduleStatusText(item.status) }}
</text>
</view>
</view>
<view class="schedule-arrow">
<text class="arrow-icon">></text>
</view>
</view>
</view>
<!-- 课程使用记录 -->
<view class="section-title">
<text class="title-text">使用记录</text>
<text class="title-count">({{ usageList.length }})</text>
</view>
<view class="usage-list">
<view
v-for="(item, index) in usageList"
:key="item.id"
class="usage-item"
>
<view class="usage-date">
<text class="date-text">{{ formatDate(item.usage_date) }}</text>
<text class="time-text">{{ item.time_slot || '--' }}</text>
</view>
<view class="usage-info">
<text class="usage-hours">消耗课时:{{ item.hours_used || 0 }}</text>
<text class="usage-type">{{ item.usage_type || '正常上课' }}</text>
</view>
<view class="usage-status">
<text class="status-text confirmed">已确认</text>
</view>
</view>
</view>
<!-- 无数据提示 -->
<view v-if="scheduleList.length === 0 && usageList.length === 0" class="no-data">
<text class="no-data-text">暂无课程安排和使用记录</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
courseId: '',
courseInfo: {},
scheduleList: [],
usageList: []
}
},
onLoad(options) {
this.courseId = options.courseId || ''
if (this.courseId) {
this.loadCourseDetail()
}
},
methods: {
// 加载课程详情
async loadCourseDetail() {
try {
uni.showLoading({ title: '加载中...' })
const res = await this.$http.get('/xy/course/detail', {
course_id: this.courseId
})
if (res.data.code === 1) {
this.courseInfo = res.data.data.course_info || {}
this.scheduleList = res.data.data.schedule_list || []
this.usageList = res.data.data.usage_list || []
} else {
uni.showToast({
title: res.data.msg || '加载失败',
icon: 'none'
})
}
} catch (error) {
console.error('加载课程详情失败:', error)
uni.showToast({
title: '加载失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
// 计算剩余课时
getRemainingHours() {
const total = (this.courseInfo.total_hours || 0) + (this.courseInfo.gift_hours || 0)
const used = (this.courseInfo.use_total_hours || 0) + (this.courseInfo.use_gift_hours || 0)
return Math.max(0, total - used)
},
// 获取课程状态文本
getStatusText(status) {
const statusMap = {
1: '有效',
2: '过期',
3: '等待期',
4: '延期'
}
return statusMap[status] || '未知'
},
// 获取课程状态样式
getStatusClass(status) {
const classMap = {
1: 'status-active',
2: 'status-expired',
3: 'status-waiting',
4: 'status-delayed'
}
return classMap[status] || 'status-unknown'
},
// 获取课程安排类型文本
getScheduleTypeText(type) {
const typeMap = {
1: '临时课',
2: '固定课'
}
return typeMap[type] || '未知'
},
// 获取课程安排类型样式
getScheduleTypeClass(type) {
const classMap = {
1: 'type-temp',
2: 'type-fixed'
}
return classMap[type] || 'type-unknown'
},
// 获取课程类型文本
getCourseTypeText(type) {
const typeMap = {
1: '加课',
2: '补课',
3: '等待位'
}
return typeMap[type] || '正常课'
},
// 获取课程类型样式
getCourseTypeClass(type) {
const classMap = {
1: 'course-add',
2: 'course-makeup',
3: 'course-waiting'
}
return classMap[type] || 'course-normal'
},
// 获取课程安排状态文本
getScheduleStatusText(status) {
const statusMap = {
0: '待上课',
1: '已上课',
2: '请假'
}
return statusMap[status] || '未知'
},
// 获取课程安排状态样式
getScheduleStatusClass(status) {
const classMap = {
0: 'schedule-pending',
1: 'schedule-completed',
2: 'schedule-leave'
}
return classMap[status] || 'schedule-unknown'
},
// 格式化日期
formatDate(dateStr) {
if (!dateStr) return '--'
const date = new Date(dateStr)
const month = (date.getMonth() + 1).toString().padStart(2, '0')
const day = date.getDate().toString().padStart(2, '0')
return `${month}-${day}`
},
// 显示课程安排详情
showScheduleDetail(item) {
// 可以跳转到课程安排详情页面
console.log('查看课程安排详情:', item)
}
}
}
</script>
<style lang="scss" scoped>
.course-detail-container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.course-info-card {
background: white;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.course-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
}
.course-title {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
.course-status {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-active {
background: #e8f5e8;
color: #52c41a;
}
&.status-expired {
background: #fff2f0;
color: #ff4d4f;
}
&.status-waiting {
background: #f6ffed;
color: #faad14;
}
&.status-delayed {
background: #f0f5ff;
color: #1890ff;
}
}
.course-stats {
display: flex;
justify-content: space-between;
margin-bottom: 20rpx;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
}
.stat-label {
font-size: 24rpx;
color: #666;
margin-bottom: 8rpx;
}
.stat-value {
font-size: 28rpx;
font-weight: 600;
color: #333;
&.remaining {
color: #1890ff;
}
}
.course-dates {
display: flex;
justify-content: space-between;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.date-item {
font-size: 24rpx;
color: #666;
}
.section-title {
display: flex;
align-items: center;
margin: 30rpx 0 16rpx;
}
.title-text {
font-size: 28rpx;
font-weight: 600;
color: #333;
}
.title-count {
font-size: 24rpx;
color: #666;
margin-left: 8rpx;
}
.schedule-list, .usage-list {
background: white;
border-radius: 16rpx;
overflow: hidden;
margin-bottom: 20rpx;
}
.schedule-item, .usage-item {
display: flex;
align-items: center;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
}
.schedule-date, .usage-date {
display: flex;
flex-direction: column;
width: 140rpx;
margin-right: 20rpx;
}
.date-text {
font-size: 28rpx;
font-weight: 600;
color: #333;
margin-bottom: 4rpx;
}
.time-text {
font-size: 24rpx;
color: #666;
}
.schedule-info, .usage-info {
flex: 1;
}
.schedule-type {
display: flex;
gap: 8rpx;
margin-bottom: 8rpx;
}
.type-tag, .course-type-tag {
padding: 4rpx 8rpx;
border-radius: 8rpx;
font-size: 20rpx;
&.type-temp {
background: #fff7e6;
color: #fa8c16;
}
&.type-fixed {
background: #f6ffed;
color: #52c41a;
}
&.course-add {
background: #e6f7ff;
color: #1890ff;
}
&.course-makeup {
background: #f9f0ff;
color: #722ed1;
}
&.course-waiting {
background: #fff2f0;
color: #ff4d4f;
}
}
.schedule-status, .usage-status {
display: flex;
align-items: center;
}
.status-text {
font-size: 24rpx;
font-weight: 500;
&.schedule-pending {
color: #faad14;
}
&.schedule-completed {
color: #52c41a;
}
&.schedule-leave {
color: #ff4d4f;
}
&.confirmed {
color: #52c41a;
}
}
.schedule-arrow {
margin-left: 16rpx;
}
.arrow-icon {
font-size: 24rpx;
color: #ccc;
}
.usage-hours {
font-size: 26rpx;
color: #333;
margin-bottom: 4rpx;
}
.usage-type {
font-size: 24rpx;
color: #666;
}
.no-data {
text-align: center;
padding: 100rpx 0;
}
.no-data-text {
font-size: 24rpx;
color: #999;
}
</style>