|
|
|
@ -31,7 +31,7 @@ |
|
|
|
</view> |
|
|
|
<view class="info-item"> |
|
|
|
<text class="item-label">课程状态:</text> |
|
|
|
<text :class="['item-value',statusClass]">{{ scheduleInfo.status_text }}</text> |
|
|
|
<text :class="['item-value',statusClass]">{{ scheduleInfo.status_text || statusText }}</text> |
|
|
|
</view> |
|
|
|
<view class="info-item"> |
|
|
|
<text class="item-label">班级:</text> |
|
|
|
@ -170,8 +170,12 @@ |
|
|
|
|
|
|
|
<!-- 操作按钮 --> |
|
|
|
<view class="action-buttons"> |
|
|
|
<fui-button type="primary" @click="handleEditCourse">编辑课程</fui-button> |
|
|
|
<fui-button type="success" @click="handleAddNewCourse">新增课程</fui-button> |
|
|
|
<view class="action-button edit-btn" @click="handleEditCourse"> |
|
|
|
<text class="btn-text">编辑课程</text> |
|
|
|
</view> |
|
|
|
<view class="action-button add-btn" @click="handleAddNewCourse"> |
|
|
|
<text class="btn-text">新增课程</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
</view> |
|
|
|
@ -203,7 +207,7 @@ |
|
|
|
<view class="option-btn sign-in" @click="handleAttendanceAction('sign_in')"> |
|
|
|
<text>签到</text> |
|
|
|
</view> |
|
|
|
<view class="option-btn leave" @click="handleAttendanceAction('leave')"> |
|
|
|
<view v-if="!selectedStudent.isTrialStudent" class="option-btn leave" @click="handleAttendanceAction('leave')"> |
|
|
|
<text>请假</text> |
|
|
|
</view> |
|
|
|
<view class="option-btn cancel" @click="closeAttendanceModal"> |
|
|
|
@ -236,6 +240,33 @@ |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</fui-modal> |
|
|
|
|
|
|
|
<!-- 删除课程安排确认弹窗 --> |
|
|
|
<fui-modal :show="showDeleteSchedulesModal" title="删除确认" @cancel="closeDeleteSchedulesModal" :buttons="[]" :zIndex="10001"> |
|
|
|
<view class="delete-confirm-modal" v-if="studentToDelete"> |
|
|
|
<view class="confirm-content"> |
|
|
|
<view class="error-icon">⚠️</view> |
|
|
|
<view class="error-message"> |
|
|
|
{{ deleteErrorMessage }} |
|
|
|
</view> |
|
|
|
<view class="confirm-text"> |
|
|
|
是否删除学员 <text class="student-name-highlight">{{ studentToDelete.name }}</text> 的所有未来课程安排? |
|
|
|
</view> |
|
|
|
<view class="confirm-tip"> |
|
|
|
删除后,该学员的所有未来课程安排将被清除,但历史记录会保留 |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<view class="confirm-buttons"> |
|
|
|
<view class="confirm-btn cancel-btn" @click="closeDeleteSchedulesModal"> |
|
|
|
<text>取消</text> |
|
|
|
</view> |
|
|
|
<view class="confirm-btn delete-btn" @click="confirmDeleteSchedules"> |
|
|
|
<text>确认删除</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</fui-modal> |
|
|
|
</fui-modal> |
|
|
|
</template> |
|
|
|
|
|
|
|
@ -273,6 +304,15 @@ |
|
|
|
}; |
|
|
|
return statusMap[this.scheduleInfo?.status] || ''; |
|
|
|
}, |
|
|
|
statusText() { |
|
|
|
const statusTextMap = { |
|
|
|
'pending': '待开始', |
|
|
|
'upcoming': '即将开始', |
|
|
|
'ongoing': '进行中', |
|
|
|
'completed': '已结束' |
|
|
|
}; |
|
|
|
return statusTextMap[this.scheduleInfo?.status] || '待开始'; |
|
|
|
}, |
|
|
|
studentList() { |
|
|
|
const statusMap = { |
|
|
|
0: 'status-absent', |
|
|
|
@ -310,7 +350,10 @@ |
|
|
|
selectedStudentIndex: -1, |
|
|
|
showUpgradeConfirm: false, |
|
|
|
upgradeStudent: null, |
|
|
|
upgradeStudentIndex: -1 |
|
|
|
upgradeStudentIndex: -1, |
|
|
|
showDeleteSchedulesModal: false, |
|
|
|
deleteErrorMessage: '', |
|
|
|
studentToDelete: null |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
@ -375,7 +418,7 @@ |
|
|
|
coach_avatar: data.schedule_info.coach_avatar || '', |
|
|
|
// 课程状态 |
|
|
|
status: data.schedule_info.status, |
|
|
|
status_text: data.schedule_info.status_text || '待定', |
|
|
|
status_text: data.schedule_info.status_text || this.getStatusTextFromCode(data.schedule_info.status), |
|
|
|
// 班级信息 |
|
|
|
class_info: data.schedule_info.class_info || null, |
|
|
|
// 时间信息 |
|
|
|
@ -427,7 +470,7 @@ |
|
|
|
coach_avatar: data.coach_avatar || '', |
|
|
|
// 课程状态 |
|
|
|
status: data.status, |
|
|
|
status_text: data.status_text || '待定', |
|
|
|
status_text: data.status_text || this.getStatusTextFromCode(data.status), |
|
|
|
// 班级信息 |
|
|
|
class_info: data.class_info || null, |
|
|
|
// 时间信息 |
|
|
|
@ -546,40 +589,39 @@ |
|
|
|
this.upgradeStudentIndex = -1; |
|
|
|
}, |
|
|
|
|
|
|
|
// 将等待位学员转为正式课学员(使用统一API - 对接admin端) |
|
|
|
// 将等待位学员转为正式课学员(处理客户资源升级为正式学员) |
|
|
|
async convertWaitingToFormal(student, index) { |
|
|
|
try { |
|
|
|
uni.showLoading({ |
|
|
|
title: '升级中...' |
|
|
|
}); |
|
|
|
|
|
|
|
// 使用新的统一升级API,与admin端保持一致 |
|
|
|
console.log('升级学员信息:', student); |
|
|
|
|
|
|
|
// 构建升级数据 - 使用我刚实现的后端接口 |
|
|
|
const upgradeData = { |
|
|
|
schedule_id: this.scheduleId, |
|
|
|
person_id: student.person_id || student.id, |
|
|
|
person_id: student.person_id || null, // person_id可能为空 |
|
|
|
person_type: student.person_type || 'customer_resource', |
|
|
|
from_type: 2, // 从等待位 |
|
|
|
to_type: 1 // 到正式位 |
|
|
|
from_schedule_type: 2, // 客户资源类型 |
|
|
|
to_schedule_type: 1, // 正式学员类型 |
|
|
|
resources_id: student.resources_id || student.resource_id || student.id |
|
|
|
}; |
|
|
|
|
|
|
|
// 根据学员类型添加对应的ID |
|
|
|
// 如果是正式学员,添加student_id |
|
|
|
if (student.person_type === 'student') { |
|
|
|
upgradeData.student_id = student.student_id || student.id; |
|
|
|
} else { |
|
|
|
upgradeData.resources_id = student.resources_id || student.resource_id || student.id; |
|
|
|
} |
|
|
|
|
|
|
|
console.log('升级学员数据:', upgradeData); |
|
|
|
|
|
|
|
// 调用新的统一升级接口 |
|
|
|
const response = await api.upgradeStudentInArrangement(upgradeData); |
|
|
|
// 调用升级接口 |
|
|
|
const response = await api.upgradeStudentSchedule(upgradeData); |
|
|
|
|
|
|
|
if (response.code === 1) { |
|
|
|
// 重新获取课程详情以更新学员列表 |
|
|
|
await this.fetchScheduleDetail(); |
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: `学员 ${student.name} 升级成功!已移至正式位`, |
|
|
|
title: `学员 ${student.name} 升级成功!已转为正式学员`, |
|
|
|
icon: 'success', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
@ -591,7 +633,7 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('升级等待位学员失败:', error); |
|
|
|
console.error('升级学员失败:', error); |
|
|
|
|
|
|
|
let errorMessage = '升级失败,请重试'; |
|
|
|
if (error.message) { |
|
|
|
@ -619,6 +661,75 @@ |
|
|
|
this.selectedStudentIndex = -1; |
|
|
|
}, |
|
|
|
|
|
|
|
// 显示删除课程安排确认弹窗 |
|
|
|
showDeleteSchedulesConfirm(errorMessage) { |
|
|
|
this.deleteErrorMessage = errorMessage; |
|
|
|
this.studentToDelete = this.selectedStudent; |
|
|
|
this.showDeleteSchedulesModal = true; |
|
|
|
}, |
|
|
|
|
|
|
|
// 关闭删除确认弹窗 |
|
|
|
closeDeleteSchedulesModal() { |
|
|
|
this.showDeleteSchedulesModal = false; |
|
|
|
this.deleteErrorMessage = ''; |
|
|
|
this.studentToDelete = null; |
|
|
|
}, |
|
|
|
|
|
|
|
// 确认删除学员所有未来课程安排 |
|
|
|
async confirmDeleteSchedules() { |
|
|
|
if (!this.studentToDelete) return; |
|
|
|
|
|
|
|
try { |
|
|
|
uni.showLoading({ |
|
|
|
title: '删除中...' |
|
|
|
}); |
|
|
|
|
|
|
|
// 调用删除API |
|
|
|
const deleteData = { |
|
|
|
student_id: this.studentToDelete.id, |
|
|
|
reason: '体验课次数用完,清理未来课程安排' |
|
|
|
}; |
|
|
|
|
|
|
|
// 如果有resources_id,也传过去 |
|
|
|
if (this.studentToDelete.resources_id) { |
|
|
|
deleteData.resources_id = this.studentToDelete.resources_id; |
|
|
|
} |
|
|
|
|
|
|
|
const response = await api.deleteStudentFutureSchedules(deleteData); |
|
|
|
|
|
|
|
if (response.code === 1) { |
|
|
|
// 删除成功 |
|
|
|
const deletedCount = response.data?.deleted_count || 0; |
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: `成功删除${deletedCount}个未来课程安排`, |
|
|
|
icon: 'success', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
|
|
|
|
// 刷新课程详情 |
|
|
|
this.fetchScheduleDetail(); |
|
|
|
} else { |
|
|
|
uni.showToast({ |
|
|
|
title: response.msg || '删除失败', |
|
|
|
icon: 'none', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('删除课程安排失败:', error); |
|
|
|
uni.showToast({ |
|
|
|
title: '删除失败,请重试', |
|
|
|
icon: 'none', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
} finally { |
|
|
|
uni.hideLoading(); |
|
|
|
this.closeDeleteSchedulesModal(); |
|
|
|
this.closeAttendanceModal(); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 处理点名操作(使用统一API - 对接admin端) |
|
|
|
async handleAttendanceAction(action) { |
|
|
|
if (!this.selectedStudent) return; |
|
|
|
@ -687,11 +798,17 @@ |
|
|
|
}); |
|
|
|
} else { |
|
|
|
// API调用失败 |
|
|
|
uni.showToast({ |
|
|
|
title: response.msg || `${actionMap[action].text}失败`, |
|
|
|
icon: 'none', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
// 检查是否是体验课次数用完的错误 |
|
|
|
if (response.msg && response.msg.includes('体验课次数已用完') && action === 'checkin') { |
|
|
|
// 弹窗询问是否删除该学员的所有课程安排 |
|
|
|
this.showDeleteSchedulesConfirm(response.msg); |
|
|
|
} else { |
|
|
|
uni.showToast({ |
|
|
|
title: response.msg || `${actionMap[action].text}失败`, |
|
|
|
icon: 'none', |
|
|
|
duration: 3000 |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error(`${action} API调用失败:`, error); |
|
|
|
@ -737,6 +854,17 @@ |
|
|
|
return statusTextMap[status] || '未知状态'; |
|
|
|
}, |
|
|
|
|
|
|
|
// 根据课程状态代码获取状态文本 |
|
|
|
getStatusTextFromCode(statusCode) { |
|
|
|
const statusTextMap = { |
|
|
|
'pending': '待开始', |
|
|
|
'upcoming': '即将开始', |
|
|
|
'ongoing': '进行中', |
|
|
|
'completed': '已结束' |
|
|
|
}; |
|
|
|
return statusTextMap[statusCode] || '待开始'; |
|
|
|
}, |
|
|
|
|
|
|
|
// 批量签到 |
|
|
|
batchCheckIn() { |
|
|
|
uni.navigateTo({ |
|
|
|
@ -1019,9 +1147,59 @@ |
|
|
|
|
|
|
|
.action-buttons { |
|
|
|
display: flex; |
|
|
|
justify-content: space-around; |
|
|
|
justify-content: space-between; |
|
|
|
margin-top: 30rpx; |
|
|
|
gap: 30rpx; |
|
|
|
gap: 20rpx; |
|
|
|
width: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
.action-button { |
|
|
|
flex: 1; |
|
|
|
height: 80rpx; |
|
|
|
border-radius: 12rpx; |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
justify-content: center; |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.3s ease; |
|
|
|
position: relative; |
|
|
|
overflow: hidden; |
|
|
|
|
|
|
|
&:active { |
|
|
|
transform: scale(0.98); |
|
|
|
} |
|
|
|
|
|
|
|
.btn-text { |
|
|
|
font-size: 28rpx; |
|
|
|
font-weight: 600; |
|
|
|
color: #fff; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.edit-btn { |
|
|
|
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%); |
|
|
|
border: 1px solid #007bff; |
|
|
|
|
|
|
|
&:hover { |
|
|
|
background: linear-gradient(135deg, #0056b3 0%, #004085 100%); |
|
|
|
} |
|
|
|
|
|
|
|
&:active { |
|
|
|
background: linear-gradient(135deg, #004085 0%, #002752 100%); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.add-btn { |
|
|
|
background: linear-gradient(135deg, #29d3b4 0%, #22a68b 100%); |
|
|
|
border: 1px solid #29d3b4; |
|
|
|
|
|
|
|
&:hover { |
|
|
|
background: linear-gradient(135deg, #22a68b 0%, #1a8b6f 100%); |
|
|
|
} |
|
|
|
|
|
|
|
&:active { |
|
|
|
background: linear-gradient(135deg, #1a8b6f 0%, #136045 100%); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1138,8 +1316,8 @@ |
|
|
|
|
|
|
|
/* 学员卡片网格样式 */ |
|
|
|
.cards-grid { |
|
|
|
display: grid; |
|
|
|
grid-template-columns: 1fr 1fr; |
|
|
|
display: flex; |
|
|
|
flex-wrap: wrap; |
|
|
|
gap: 16rpx; |
|
|
|
margin-top: 20rpx; |
|
|
|
} |
|
|
|
@ -1153,6 +1331,12 @@ |
|
|
|
cursor: pointer; |
|
|
|
transition: all 0.3s ease; |
|
|
|
border: 2px solid transparent; |
|
|
|
width: calc(50% - 8rpx); |
|
|
|
flex: 0 0 calc(50% - 8rpx); |
|
|
|
box-sizing: border-box; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
} |
|
|
|
|
|
|
|
.student-card.filled { |
|
|
|
@ -1242,6 +1426,7 @@ |
|
|
|
font-weight: 600; |
|
|
|
font-size: 24rpx; |
|
|
|
margin-bottom: 12rpx; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
|
|
|
|
.waiting-avatar { |
|
|
|
@ -1252,6 +1437,10 @@ |
|
|
|
.student-info { |
|
|
|
font-size: 22rpx; |
|
|
|
line-height: 1.4; |
|
|
|
width: 100%; |
|
|
|
box-sizing: border-box; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
} |
|
|
|
|
|
|
|
.student-name { |
|
|
|
@ -1262,6 +1451,8 @@ |
|
|
|
overflow: hidden; |
|
|
|
text-overflow: ellipsis; |
|
|
|
white-space: nowrap; |
|
|
|
width: 100%; |
|
|
|
box-sizing: border-box; |
|
|
|
} |
|
|
|
|
|
|
|
.student-age, |
|
|
|
@ -1425,4 +1616,46 @@ |
|
|
|
background: linear-gradient(45deg, #7c3aed, #9333ea); |
|
|
|
transform: scale(0.98); |
|
|
|
} |
|
|
|
|
|
|
|
/* 删除确认弹窗样式 */ |
|
|
|
.delete-confirm-modal { |
|
|
|
padding: 40rpx 30rpx; |
|
|
|
text-align: center; |
|
|
|
} |
|
|
|
|
|
|
|
.error-icon { |
|
|
|
font-size: 60rpx; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
color: #ef4444; |
|
|
|
animation: shake 2s ease-in-out infinite; |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes shake { |
|
|
|
0%, 100% { transform: translateX(0); } |
|
|
|
25% { transform: translateX(-5rpx); } |
|
|
|
75% { transform: translateX(5rpx); } |
|
|
|
} |
|
|
|
|
|
|
|
.error-message { |
|
|
|
font-size: 28rpx; |
|
|
|
color: #ef4444; |
|
|
|
font-weight: 600; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
padding: 20rpx; |
|
|
|
background: rgba(239, 68, 68, 0.1); |
|
|
|
border-radius: 16rpx; |
|
|
|
border: 2rpx solid rgba(239, 68, 68, 0.2); |
|
|
|
} |
|
|
|
|
|
|
|
.delete-btn { |
|
|
|
background: linear-gradient(45deg, #ef4444, #f87171); |
|
|
|
color: #fff; |
|
|
|
border: none; |
|
|
|
transition: all 0.3s ease; |
|
|
|
|
|
|
|
&:active { |
|
|
|
transform: scale(0.95); |
|
|
|
box-shadow: 0 2rpx 8rpx rgba(239, 68, 68, 0.4); |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |