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
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>
|