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.
521 lines
11 KiB
521 lines
11 KiB
<template>
|
|
<view class="sign-in-container">
|
|
<uni-nav-bar title="课程点名" left-icon="left" fixed="true" background-color="#292929" color="#FFFFFF"
|
|
@clickLeft="goBack"></uni-nav-bar>
|
|
|
|
<view class="content">
|
|
<!-- 课程信息 -->
|
|
<view class="course-info-card" v-if="scheduleInfo">
|
|
<view class="course-title">{{ scheduleInfo.course_name }}</view>
|
|
<view class="course-time">{{ scheduleInfo.course_date }} {{ scheduleInfo.time_slot }}</view>
|
|
<view class="course-detail">
|
|
<view class="detail-item">
|
|
<text class="detail-label">授课教练:</text>
|
|
<text class="detail-value">{{ scheduleInfo.coach_name }}</text>
|
|
</view>
|
|
<view class="detail-item">
|
|
<text class="detail-label">上课场地:</text>
|
|
<text class="detail-value">{{ scheduleInfo.venue_name }}</text>
|
|
</view>
|
|
<view class="detail-item">
|
|
<text class="detail-label">学员人数:</text>
|
|
<text class="detail-value">{{ studentList.length }}人</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 学员列表 -->
|
|
<view class="student-section">
|
|
<view class="section-header">
|
|
<view class="section-title">学员点名</view>
|
|
<view class="action-buttons">
|
|
<fui-button type="primary" size="small" @click="checkAllStudents">全部签到</fui-button>
|
|
<fui-button type="danger" size="small" @click="uncheckAllStudents">全部取消</fui-button>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="empty-list" v-if="studentList.length === 0">
|
|
<image :src="$util.img('/static/icon-img/empty.png')" mode="aspectFit" class="empty-img"></image>
|
|
<text class="empty-text">暂无学员数据</text>
|
|
</view>
|
|
|
|
<view class="student-list" v-else>
|
|
<view class="student-item" v-for="(student, index) in studentList" :key="index"
|
|
@click="toggleStudentStatus(index)">
|
|
<view class="student-avatar">
|
|
<image :src="student.avatar || $util.img('/static/icon-img/avatar.png')" mode="aspectFill"></image>
|
|
<view :class="['status-badge',student.statusClass]"></view>
|
|
</view>
|
|
|
|
<view class="student-info">
|
|
<text class="student-name">{{ student.name }}</text>
|
|
<text class="student-phone">{{ student.phone_number || '无联系电话' }}</text>
|
|
</view>
|
|
|
|
<view class="status-container">
|
|
<view class="status-select">
|
|
<view class="status-option" :class="{ active: student.status === 1 }"
|
|
@click.stop="setStudentStatus(index, 1)">
|
|
已到
|
|
</view>
|
|
<view class="status-option" :class="{ active: student.status === 2 }"
|
|
@click.stop="setStudentStatus(index, 2)">
|
|
请假
|
|
</view>
|
|
<view class="status-option" :class="{ active: student.status === 0 }"
|
|
@click.stop="setStudentStatus(index, 0)">
|
|
未到
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 点名备注 -->
|
|
<view class="remark-section">
|
|
<view class="section-title">点名备注</view>
|
|
<fui-textarea v-model="signInRemark" placeholder="请输入点名备注(可选)" maxlength="200"></fui-textarea>
|
|
</view>
|
|
|
|
<!-- 提交按钮 -->
|
|
<view class="submit-btn">
|
|
<fui-button type="primary" @click="submitSignIn" :loading="submitting">提交点名</fui-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import api from '@/api/apiRoute.js';
|
|
|
|
export default {
|
|
data() {
|
|
return {
|
|
// 课程ID
|
|
scheduleId: null,
|
|
|
|
// 课程信息
|
|
scheduleInfo: null,
|
|
|
|
// 学员列表
|
|
studentList: [],
|
|
|
|
// 点名备注
|
|
signInRemark: '',
|
|
|
|
// 提交状态
|
|
submitting: false
|
|
};
|
|
},
|
|
computed: {
|
|
statusClass() {
|
|
const statusMap = {
|
|
'pending': 'status-pending',
|
|
'upcoming': 'status-upcoming',
|
|
'ongoing': 'status-ongoing',
|
|
'completed': 'status-completed'
|
|
};
|
|
return statusMap[this.scheduleInfo.status] || '';
|
|
},
|
|
studentList() {
|
|
const statusMap = {
|
|
0: 'status-absent',
|
|
1: 'status-present',
|
|
2: 'status-leave'
|
|
};
|
|
|
|
return this.studentListRaw.map(student => ({
|
|
...student,
|
|
statusClass: statusMap[student.status] || 'status-absent',
|
|
status_text: this.getStatusText(student.status)
|
|
}));
|
|
}
|
|
},
|
|
onLoad(options) {
|
|
if (options.id) {
|
|
this.scheduleId = options.id;
|
|
this.loadScheduleInfo();
|
|
} else {
|
|
uni.showToast({
|
|
title: '参数错误',
|
|
icon: 'none'
|
|
});
|
|
setTimeout(() => {
|
|
uni.navigateBack();
|
|
}, 1500);
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
// 返回上一页
|
|
goBack() {
|
|
uni.navigateBack();
|
|
},
|
|
|
|
// 加载课程安排信息
|
|
async loadScheduleInfo() {
|
|
uni.showLoading({
|
|
title: '加载中...'
|
|
});
|
|
|
|
try {
|
|
const res = await api.getCourseScheduleInfo({
|
|
schedule_id: this.scheduleId
|
|
});
|
|
|
|
if (res.code === 1) {
|
|
this.scheduleInfo = res.data;
|
|
|
|
// 处理学员列表
|
|
if (this.scheduleInfo.students && this.scheduleInfo.students.length > 0) {
|
|
this.studentList = [...this.scheduleInfo.students];
|
|
}
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '获取课程安排信息失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('获取课程安排信息失败:', error);
|
|
uni.showToast({
|
|
title: '获取课程安排信息失败',
|
|
icon: 'none'
|
|
});
|
|
} finally {
|
|
uni.hideLoading();
|
|
}
|
|
},
|
|
|
|
// 获取学员状态样式
|
|
getStatusClass(status) {
|
|
const statusMap = {
|
|
0: 'status-absent',
|
|
1: 'status-present',
|
|
2: 'status-leave'
|
|
};
|
|
|
|
return statusMap[status] || 'status-absent';
|
|
},
|
|
// 获取状态文本
|
|
getStatusText(status) {
|
|
const statusTextMap = {
|
|
0: '待上课',
|
|
1: '已上课',
|
|
2: '请假'
|
|
};
|
|
return statusTextMap[status] || '未知状态';
|
|
},
|
|
// 切换学员状态
|
|
toggleStudentStatus(index) {
|
|
const student = this.studentList[index];
|
|
|
|
// 状态循环:未到 -> 已到 -> 请假 -> 未到
|
|
let newStatus = 0;
|
|
|
|
if (student.status === 0) {
|
|
newStatus = 1;
|
|
} else if (student.status === 1) {
|
|
newStatus = 2;
|
|
} else {
|
|
newStatus = 0;
|
|
}
|
|
|
|
this.setStudentStatus(index, newStatus);
|
|
},
|
|
|
|
// 设置学员状态
|
|
setStudentStatus(index, status) {
|
|
if (index >= 0 && index < this.studentList.length) {
|
|
this.studentList[index].status = status;
|
|
|
|
// 更新状态文本
|
|
const statusTextMap = {
|
|
0: '待上课',
|
|
1: '已上课',
|
|
2: '请假'
|
|
};
|
|
|
|
this.studentList[index].status_text = statusTextMap[status];
|
|
}
|
|
},
|
|
|
|
// 全部签到
|
|
checkAllStudents() {
|
|
this.studentList.forEach((student, index) => {
|
|
this.setStudentStatus(index, 1);
|
|
});
|
|
},
|
|
|
|
// 全部取消
|
|
uncheckAllStudents() {
|
|
this.studentList.forEach((student, index) => {
|
|
this.setStudentStatus(index, 0);
|
|
});
|
|
},
|
|
|
|
// 提交点名
|
|
async submitSignIn() {
|
|
// 准备提交数据
|
|
const studentData = this.studentList.map(student => ({
|
|
student_id: student.student_id,
|
|
resource_id: student.resource_id,
|
|
status: student.status
|
|
}));
|
|
|
|
const submitData = {
|
|
schedule_id: this.scheduleId,
|
|
students: studentData,
|
|
remark: this.signInRemark
|
|
};
|
|
|
|
this.submitting = true;
|
|
|
|
try {
|
|
// 使用API进行点名
|
|
const res = await api.submitScheduleSignIn(submitData);
|
|
|
|
if (res.code === 1) {
|
|
uni.showToast({
|
|
title: '点名成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 延迟返回
|
|
setTimeout(() => {
|
|
uni.navigateBack();
|
|
}, 1500);
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '点名失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('点名失败:', error);
|
|
uni.showToast({
|
|
title: '点名失败,请重试',
|
|
icon: 'none'
|
|
});
|
|
} finally {
|
|
this.submitting = false;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.sign-in-container {
|
|
min-height: 100vh;
|
|
background-color: #18181c;
|
|
padding-top: 88rpx;
|
|
}
|
|
|
|
.content {
|
|
padding: 30rpx;
|
|
}
|
|
|
|
.course-info-card {
|
|
background-color: #23232a;
|
|
border-radius: 12rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.course-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
margin-bottom: 10rpx;
|
|
}
|
|
|
|
.course-time {
|
|
font-size: 26rpx;
|
|
color: #29d3b4;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.course-detail {
|
|
background-color: #2a2a2a;
|
|
border-radius: 8rpx;
|
|
padding: 16rpx;
|
|
}
|
|
|
|
.detail-item {
|
|
display: flex;
|
|
margin-bottom: 10rpx;
|
|
font-size: 26rpx;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
.detail-label {
|
|
color: #999;
|
|
width: 140rpx;
|
|
}
|
|
|
|
.detail-value {
|
|
color: #fff;
|
|
flex: 1;
|
|
}
|
|
|
|
.student-section {
|
|
background-color: #23232a;
|
|
border-radius: 12rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24rpx;
|
|
}
|
|
|
|
.section-title {
|
|
font-size: 30rpx;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
}
|
|
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: 16rpx;
|
|
}
|
|
|
|
.student-list {
|
|
max-height: 600rpx;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.student-item {
|
|
display: flex;
|
|
align-items: center;
|
|
background-color: #2a2a2a;
|
|
border-radius: 8rpx;
|
|
padding: 16rpx;
|
|
margin-bottom: 16rpx;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
}
|
|
|
|
.student-avatar {
|
|
width: 80rpx;
|
|
height: 80rpx;
|
|
border-radius: 40rpx;
|
|
overflow: hidden;
|
|
position: relative;
|
|
margin-right: 20rpx;
|
|
|
|
image {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
}
|
|
|
|
.status-badge {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
width: 24rpx;
|
|
height: 24rpx;
|
|
border-radius: 12rpx;
|
|
background-color: #999;
|
|
border: 2rpx solid #fff;
|
|
}
|
|
|
|
.status-absent {
|
|
background-color: #ff3b30;
|
|
}
|
|
|
|
.status-present {
|
|
background-color: #34c759;
|
|
}
|
|
|
|
.status-leave {
|
|
background-color: #ff9500;
|
|
}
|
|
|
|
.student-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.student-name {
|
|
font-size: 28rpx;
|
|
color: #fff;
|
|
margin-bottom: 6rpx;
|
|
}
|
|
|
|
.student-phone {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.status-container {
|
|
margin-left: 16rpx;
|
|
}
|
|
|
|
.status-select {
|
|
display: flex;
|
|
gap: 10rpx;
|
|
}
|
|
|
|
.status-option {
|
|
padding: 8rpx 16rpx;
|
|
font-size: 24rpx;
|
|
border-radius: 30rpx;
|
|
background-color: #3a3a3a;
|
|
color: #fff;
|
|
|
|
&.active {
|
|
background-color: #29d3b4;
|
|
}
|
|
}
|
|
|
|
.empty-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 60rpx 0;
|
|
|
|
.empty-img {
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.empty-text {
|
|
font-size: 28rpx;
|
|
color: #999;
|
|
}
|
|
}
|
|
|
|
.remark-section {
|
|
background-color: #23232a;
|
|
border-radius: 12rpx;
|
|
padding: 24rpx;
|
|
margin-bottom: 40rpx;
|
|
|
|
.section-title {
|
|
font-size: 30rpx;
|
|
font-weight: bold;
|
|
color: #fff;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
}
|
|
|
|
.submit-btn {
|
|
margin-top: 40rpx;
|
|
padding-bottom: 40rpx;
|
|
}
|
|
</style>
|