智慧教务系统UniApp前端项目(使用中2025-0517)
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.
 
 
 
 
 

755 lines
18 KiB

<!--课程-详情列表-->
<template>
<view class="main_box">
<view class="main_section">
<view class="section_1">
<view class="title">日期{{courseInfo.course_date}}</view>
<view class="title">时间{{courseInfo.time_slot}}</view>
<view class="title">地点{{courseInfo.venue.venue_name}}</view>
<view class="title">课程{{courseInfo.course.course_name}}</view>
<view class="title">教练{{courseInfo.coach.name}}</view>
<view class="title">人数{{courseInfo.venue.capacity}}</view>
<view class="isbtn" @click="addStudent">
添加学员
</view>
<view v-if="isNowBetween(courseInfo.student_courses[0].start_date, courseInfo.student_courses[0].end_date)" class="tag" style="background-color: #FAD24E;">上课中
</view>
<view v-if="!isCourseFuture(courseInfo.student_courses[0].end_date)" class="tag" style="background-color: #e2e2e2;">已结束
</view>
<view v-if="isCourseFuture(courseInfo.student_courses[0].start_date)" class="tag" style="background-color: #1cd188;">未开始
</view>
</view>
<view class="section_2">
<view :class="['table', tableType==1 ? 'select':'']" @click="switchTag(1)">学员情况</view>
<view :class="['table', tableType==2 ? 'select':'']" @click="switchTag(2)">作业情况</view>
</view>
<!--签到情况-->
<view class="section_3" v-if="tableType == 1">
<view class="tip_title" v-if="courseInfo.student_courses.length == 0">暂无数据</view>
<view class="item" v-for="(v,k) in courseInfo.student_courses" :key="k">
<view class="left" @click="openViewCourseInfo(v)">
<image class="pic" model="aspectFit" :src="$util.img(v.avatar)"></image>
<view class="box">
<view class="title">{{v.name}}</view>
<view class="title">课程截止时间{{v.end_date}}</view>
</view>
</view>
<view class="right" v-if="courseInfo.status == 'pending'">
<view class="tag leave-tag" @click="written(v.student_id)" v-if="!isWithinTimeSlot(courseInfo.time_slot) && v.status !== 'signed'">请假</view>
<view class="tag signin-tag" @click="signIn(v.student_id)" v-if="isWithinTimeSlot(courseInfo.time_slot) && v.status !== 'signed'">签到</view>
<view class="tag signed-tag" v-if="v.status === 'signed'">已签到</view>
</view>
</view>
</view>
<!--作业情况-->
<view class="section_4" v-if="tableType == 2">
<view class="item_box">
<!-- 待批改-->
<fui-collapse-item @change="changeCollapse(1)" open="true" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>待批改({{courseInfo.groupedByStatus1.length}})</text>
</view>
<template v-slot:content>
<view class="ul">
<view v-for="(v,k) in courseInfo.groupedByStatus1" :key="k" class="li"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<view>{{v.created_at}}</view>
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
<!-- 未提交-->
<view class="item_box">
<fui-collapse-item @change="changeCollapse(2)" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>未提交({{courseInfo.groupedByStatus2.length}})</text>
</view>
<template v-slot:content>
<view class="ul">
<view class="li" v-for="(v,k) in courseInfo.groupedByStatus2" :key="k"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<!-- <view>{{v.created_at}}</view> -->
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
<!-- 已提交-->
<view class="item_box">
<fui-collapse-item @change="changeCollapse(3)" isBorder="false" contentBg="#434544">
<view class="title_box">
<text>已提交({{courseInfo.groupedByStatus3.length}})</text>
</view>
<template v-slot:content>
<view class="ul">
<view class="li" v-for="(v,k) in courseInfo.groupedByStatus3" :key="k"
@click="openViewWorkDetails(v)">
<view class="left">
<image class="pic" model="aspectFit"
:src="$util.img(v.student.customerResources.member.headimg)"></image>
<view class="box">
<view class="title">{{v.student.name}}</view>
<view class="title">
<view>提交时间</view>
<view>{{v.created_at}}</view>
</view>
</view>
</view>
<!-- <view class="right">
<view class="btn">查看并批改</view>
</view> -->
</view>
</view>
</template>
</fui-collapse-item>
</view>
</view>
</view>
<fui-picker :options="StudentList" :linkage="true" :show="show" :layer="1" @change="change"
@cancel="cancel"></fui-picker>
<fui-actionsheet :show="written_show" :tips="written_tips" :isCancel="isCancel"
:itemList="itemList" @cancel="written_cancel" @click="written_click"></fui-actionsheet>
</view>
</template>
<script>
import memberApi from '@/api/member.js';
import AQTabber from "@/components/AQ/AQTabber.vue"
import apiRoute from '@/api/apiRoute.js';
export default {
components: {
AQTabber,
},
data() {
return {
course_id: '', //课程id
courseInfo: {
sign_list: [], //签到列表
assignments: {
dpg_list: [], // 待批改作业列表
wtj_list: [], // 未提交作业列表
ypg_list: [] // 已批改作业列表
},
}, //课时详情
tableType: 1, //1=签到情况,2=作业情况
show: false,
StudentList: [],
written_show: false,
written_tips: '确认请假',
isCancel: false,
itemList: ['确认', '取消'],
qingjia_student_id: 0
}
},
onLoad(options) {
this.course_id = options.id
},
onShow() {
this.init()
},
methods: {
//初始化
async init() {
this.getCourseInfo()
},
isNowBetween(start_date, end_date) {
const now = new Date();
const start = new Date(start_date);
const end = new Date(end_date);
return now >= start && now <= end;
},
isCourseFuture(courseDate) {
const courseTime = new Date(courseDate).getTime();
const nowTime = new Date().getTime();
return courseTime > nowTime;
},
//教练端-课程详情
async getCourseInfo() {
// let res = await apiRoute.courseInfo({
// id: this.course_id
// })
// if (res.code != 1) {
// uni.showToast({
// title: res.msg,
// icon: 'none'
// })
// return
// }
// this.courseInfo = res.data
// console.log('课程详情', this.courseInfo)
// 使用模拟数据
this.courseInfo = {
id: this.course_id || '1',
course_date: '2025-07-25',
time_slot: '14:00-15:30',
status: 'pending',
venue: {
venue_name: '总部校区 305教室',
capacity: 20
},
course: {
course_name: '少儿英语基础班'
},
coach: {
name: '王教练'
},
student_courses: [
{
student_id: '1001',
name: '张三',
avatar: '',
start_date: '2025-07-25',
end_date: '2025-10-25',
status: 'pending'
},
{
student_id: '1002',
name: '李四',
avatar: '',
start_date: '2025-07-25',
end_date: '2025-10-25',
status: 'pending'
}
],
groupedByStatus1: [], // 待批改
groupedByStatus2: [], // 未提交
groupedByStatus3: [] // 已提交
};
console.log('课程详情(模拟数据)', this.courseInfo);
},
//切换标签
switchTag(type) {
this.tableType = type
},
//打开课时详情页
openViewCourseInfo(item) {
// this.$navigateTo({
// url: '/pages/coach/course/info'
// })
uni.navigateTo({
url: `/pages/market/clue/clue_info?resource_sharing_id=25`
});
},
// 作业情况
changeCollapse(type) {},
//跳转页面-作业详情
openViewWorkDetails(item) {
let id = item.id
this.$navigateTo({
url: `/pages/coach/student/work_details?id=${id}`
})
},
//获取添加学员列表
async addStudent() {
// let res = await apiRoute.addStudentList({
// id: this.course_id
// })
// if (res.code != 1) {
// uni.showToast({
// title: res.msg,
// icon: 'none'
// })
// return
// }
// this.StudentList = res.data
// if (this.StudentList.length == 0) {
// uni.showToast({
// title: '暂无可填加的学员',
// icon: 'none'
// })
// return
// } else {
// this.show = true
// }
// 使用模拟数据
this.StudentList = [
{
text: '张三',
value: '1001'
},
{
text: '李四',
value: '1002'
},
{
text: '王五',
value: '1003'
},
{
text: '赵六',
value: '1004'
},
{
text: '钱七',
value: '1005'
}
];
this.show = true;
},
async change(e) {
this.show = false
// let res = await apiRoute.addStudent({
// student_id: e.value,
// schedule_id: this.course_id,
// time_slot: this.courseInfo.time_slot,
// course_date: this.courseInfo.course_date
// })
// if (res.code != 1) {
// uni.showToast({
// title: res.msg,
// icon: 'none'
// })
// return
// }
// this.init()
// 使用模拟数据,直接添加到学生列表中
const selectedStudent = this.StudentList.find(student => student.value === e.value);
if (!selectedStudent) return;
// 添加新学生到签到列表
if (!this.courseInfo.student_courses) {
this.courseInfo.student_courses = [];
}
// 创建当前日期和未来日期(课程结束日期)
const now = new Date();
const futureDate = new Date();
futureDate.setMonth(futureDate.getMonth() + 3); // 设置为3个月后
// 添加新学生
this.courseInfo.student_courses.push({
student_id: e.value,
name: selectedStudent.text,
avatar: '', // 默认头像
start_date: now.toISOString().split('T')[0],
end_date: futureDate.toISOString().split('T')[0],
status: 'pending' // 待上课状态
});
uni.showToast({
title: '添加学员成功',
icon: 'success'
});
},
cancel() {
this.show = false
},
//请假
written(student_id) {
this.qingjia_student_id = student_id
this.written_show = true
},
written_cancel() {
this.written_show = false
},
async written_click(e) {
if(e.text == '取消'){
this.written_show = false
} else {
let res = await apiRoute.delStudentCourse({
student_id: this.qingjia_student_id,
course_id: this.course_id,
})
if (res.data) {
uni.showToast({
title: '请假成功',
icon: 'none'
})
this.init()
} else {
// uni.showToast({
// title: '学员不存在',
// icon: 'none'
// })
}
this.written_show = false
}
},
isWithinTimeSlot(time_slot) {
const [startStr, endStr] = time_slot.split('-');
const [startHours, startMinutes] = startStr.split(':').map(Number);
const [endHours, endMinutes] = endStr.split(':').map(Number);
const now = new Date();
const startTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), startHours, startMinutes);
const endTime = new Date(now.getFullYear(), now.getMonth(), now.getDate(), endHours, endMinutes);
return now >= startTime && now < endTime;
},
signIn(student_id) {
uni.showToast({
title: '签到成功',
icon: 'success'
});
// 在实际应用中,这里应该调用签到API
// 例如:
// apiRoute.studentSignIn({
// student_id: student_id,
// course_id: this.course_id,
// }).then(res => {
// if (res.code == 1) {
// uni.showToast({
// title: '签到成功',
// icon: 'success'
// });
// this.init();
// } else {
// uni.showToast({
// title: res.msg,
// icon: 'none'
// });
// }
// });
// 更新当前学生的状态(模拟)
const studentIndex = this.courseInfo.student_courses.findIndex(s => s.student_id === student_id);
if (studentIndex !== -1) {
this.courseInfo.student_courses[studentIndex].status = 'signed';
}
}
}
}
</script>
<style lang="less" scoped>
.main_box {
background: #292929;
}
//自定义导航栏
.navbar_section {
display: flex;
justify-content: center;
align-items: center;
background: #292929;
.title {
padding: 40rpx 0rpx;
/* 小程序端样式 */
// #ifdef MP-WEIXIN
padding: 80rpx 0rpx;
// #endif
font-size: 30rpx;
color: #fff;
}
}
.main_section {
min-height: 100vh;
background: #292929 100%;
padding: 30rpx 24rpx;
padding-top: 40rpx;
padding-bottom: 150rpx;
font-size: 24rpx;
color: #FFFFFF;
.section_1 {
position: relative;
padding: 18rpx 34rpx 50rpx;
display: flex;
flex-direction: column;
gap: 15rpx;
color: #fff;
font-size: 24rpx;
background-color: #434544;
border-radius: 22rpx;
.tag {
position: absolute;
top: 0;
right: 0;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
border-radius: 0rpx 24rpx 0rpx 24rpx;
color: #fff;
font-size: 24rpx;
text-align: center;
}
}
.section_2 {
margin-top: 44rpx;
color: #fff;
font-size: 30rpx;
display: flex;
justify-content: center;
align-items: center;
.table {
width: 50%;
height: 64rpx;
background-color: #1684FCFF;
line-height: 62rpx;
text-align: center;
}
.table:nth-child(1) {
border-top-left-radius: 8rpx;
border-bottom-left-radius: 8rpx;
}
.table:nth-child(2) {
border-top-right-radius: 8rpx;
border-bottom-right-radius: 8rpx;
}
.select {
background-color: #fff;
color: #1684FCFF;
}
}
.section_3 {
margin-top: 44rpx;
min-height: 20vh;
padding: 30rpx 0;
border-radius: 22rpx;
background-color: #434544;
display: flex;
flex-direction: column;
//提示
.tip_title {
text-align: center;
font-size: 30rpx;
color: #FFFFFF;
}
.item {
border-top: 1px solid #D7D7D7;
padding: 20rpx 33rpx 20rpx 14rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left {
width: 80%;
display: flex;
align-items: center;
gap: 26rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
background-color: #797979FF;
}
.box {
display: flex;
flex-direction: column;
gap: 10rpx;
.title {
color: #fff;
font-size: 24rpx;
}
}
}
.right {
.tag {
width: 93rpx;
height: 93rpx;
border-radius: 50%;
line-height: 90rpx;
font-size: 26rpx;
text-align: center;
border: 0rpx;
//旋转45°
transform: rotate(-30deg);
}
.leave-tag {
background-color: rgba(254, 250, 131, 0.62);
color: rgba(255, 255, 255, 1);
}
.signin-tag {
background-color: #1cd188;
color: #FFFFFF;
}
.signed-tag {
background-color: #a4adb3;
color: #FFFFFF;
transform: none;
}
}
}
.item:nth-child(1) {
border-top: 0px;
}
}
.section_4 {
margin-top: 44rpx;
padding: 30rpx 0;
border-radius: 22rpx;
background-color: #434544;
display: flex;
flex-direction: column;
.item_box {
margin-bottom: 30rpx;
padding-left: 24rpx;
padding-right: 32rpx;
font-size: 28rpx;
color: #fff;
background-color: #434544;
::v-deep .fui-collapse-item__title {
background-color: #434544 !important;
}
::v-deep .fui-collapse__border-color {
border-top: none !important;
/* 取消上边框 */
background: #434544 !important;
}
.title_box {}
.ul {
background-color: #434544;
.li {
background-color: #434544;
border-top: 1px solid #D7D7D7;
padding: 20rpx 33rpx 20rpx 14rpx;
display: flex;
justify-content: space-between;
align-items: center;
.left {
width: 80%;
display: flex;
align-items: center;
gap: 26rpx;
.pic {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
background-color: #797979FF;
}
.box {
display: flex;
flex-direction: column;
gap: 10rpx;
.title {
color: #fff;
font-size: 24rpx;
}
}
}
.right {
.btn {
background-color: #a4adb3;
color: #fff;
width: 160rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 8rpx;
background-color: rgba(164, 173, 179, 1);
color: rgba(255, 255, 255, 1);
font-size: 24rpx;
text-align: center;
}
}
}
.li:nth-child(1) {
border-top: none !important;
/* 取消上边框 */
}
}
}
}
}
.isbtn {
border: 1px solid #FAD04D;
border-radius: 10rpx;
background: #434544;
color: #FAD04D;
width: 110rpx;
height: 60rpx;
line-height: 55rpx;
text-align: center;
font-size: 24rpx;
position: absolute;
bottom: 20rpx;
right: 15rpx;
}
</style>