|
|
|
@ -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> |
|
|
|
@ -65,31 +65,31 @@ |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="cards-grid" v-if="formalStudents && formalStudents.length > 0"> |
|
|
|
<view class="student-card filled" v-for="(student, index) in formalStudents" :key="index" |
|
|
|
<view class="student-card filled" v-for="(student, index) in formalStudents" :key="index" |
|
|
|
@click="handleStudentClick(student, index)"> |
|
|
|
<!-- 续费提醒徽章 --> |
|
|
|
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 体验课学员标识 --> |
|
|
|
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view> |
|
|
|
|
|
|
|
|
|
|
|
<view class="avatar">{{ student.name.charAt(0) }}</view> |
|
|
|
<view class="student-info"> |
|
|
|
<view class="student-name">{{ student.name }}</view> |
|
|
|
<view class="student-age">年龄:{{ student.age }}岁</view> |
|
|
|
<view class="course-status">课程状态:{{ student.courseStatus }}</view> |
|
|
|
<view class="course-arrangement">课程安排:{{ student.courseType === 'fixed' ? '固定课' : '临时课' }}</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 体验课学员显示 --> |
|
|
|
<view v-if="student.isTrialStudent" class="trial-info"> |
|
|
|
<view class="trial-hours">体验课时:{{ student.trialClassCount }}节</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 付费学员显示 --> |
|
|
|
<view v-else class="paid-student-info"> |
|
|
|
<view class="remaining-hours">剩余课时:{{ student.remainingHours }}节</view> |
|
|
|
<view class="expiry-date">到期时间:{{ student.expiryDate || '未设置' }}</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 课时进度条 --> |
|
|
|
<view class="progress-container"> |
|
|
|
<view class="progress-label"> |
|
|
|
@ -97,8 +97,8 @@ |
|
|
|
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text> |
|
|
|
</view> |
|
|
|
<view class="progress-bar"> |
|
|
|
<view |
|
|
|
class="progress-fill" |
|
|
|
<view |
|
|
|
class="progress-fill" |
|
|
|
:style="{ width: student.course_progress.percentage + '%' }" |
|
|
|
></view> |
|
|
|
</view> |
|
|
|
@ -121,34 +121,34 @@ |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="cards-grid"> |
|
|
|
<view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index" |
|
|
|
<view class="student-card filled waiting-filled" v-for="(student, index) in waitingStudents" :key="index" |
|
|
|
@click="handleStudentClick(student, index)"> |
|
|
|
<!-- 转换提示图标 --> |
|
|
|
<view class="convert-icon">⚡</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 续费提醒徽章 --> |
|
|
|
<view v-if="student.needsRenewal && !student.isTrialStudent" class="renewal-badge">待续费</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 体验课学员标识 --> |
|
|
|
<view v-if="student.isTrialStudent" class="trial-badge">体验课</view> |
|
|
|
|
|
|
|
|
|
|
|
<view class="avatar waiting-avatar">{{ student.name.charAt(0) }}</view> |
|
|
|
<view class="student-info"> |
|
|
|
<view class="student-name">{{ student.name }}</view> |
|
|
|
<view class="student-age">年龄:{{ student.age }}岁</view> |
|
|
|
<view class="course-status">课程状态:{{ student.courseStatus }}</view> |
|
|
|
<view class="course-arrangement">课程安排:等待位</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 体验课学员显示 --> |
|
|
|
<view v-if="student.isTrialStudent" class="trial-info"> |
|
|
|
<view class="trial-hours">体验课时:{{ student.trialClassCount }}节</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 付费学员显示 --> |
|
|
|
<view v-else class="paid-student-info"> |
|
|
|
<view class="remaining-hours">剩余课时:{{ student.remainingHours }}节</view> |
|
|
|
<view class="expiry-date">到期时间:{{ student.expiryDate || '未设置' }}</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 课时进度条 --> |
|
|
|
<view class="progress-container"> |
|
|
|
<view class="progress-label"> |
|
|
|
@ -156,8 +156,8 @@ |
|
|
|
<text class="progress-percentage">{{ student.course_progress.percentage }}%</text> |
|
|
|
</view> |
|
|
|
<view class="progress-bar"> |
|
|
|
<view |
|
|
|
class="progress-fill" |
|
|
|
<view |
|
|
|
class="progress-fill" |
|
|
|
:style="{ width: student.course_progress.percentage + '%' }" |
|
|
|
></view> |
|
|
|
</view> |
|
|
|
@ -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> |
|
|
|
@ -187,7 +191,7 @@ |
|
|
|
<text>重试</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 学员点名底部弹窗 --> |
|
|
|
<fui-modal :show="showAttendanceModal" title="学员点名" @cancel="closeAttendanceModal" :buttons="[]"> |
|
|
|
<view class="attendance-modal" v-if="selectedStudent"> |
|
|
|
@ -198,12 +202,12 @@ |
|
|
|
<view class="student-name-large">{{ selectedStudent.name }}</view> |
|
|
|
<view class="current-status">当前状态:{{ selectedStudent.status_text }}</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<view class="attendance-options"> |
|
|
|
<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"> |
|
|
|
@ -212,7 +216,7 @@ |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</fui-modal> |
|
|
|
|
|
|
|
|
|
|
|
<!-- 升级确认弹窗 --> |
|
|
|
<fui-modal :show="showUpgradeConfirm" title="升级确认" @cancel="cancelUpgrade" :buttons="[]" :zIndex="10000"> |
|
|
|
<view class="upgrade-confirm-modal" v-if="upgradeStudent"> |
|
|
|
@ -225,7 +229,7 @@ |
|
|
|
升级后将占用一个正式位 |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
|
|
|
|
<view class="confirm-buttons"> |
|
|
|
<view class="confirm-btn cancel-btn" @click="cancelUpgrade"> |
|
|
|
<text>取消</text> |
|
|
|
@ -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', |
|
|
|
@ -289,12 +329,12 @@ |
|
|
|
// 计算课程进度百分比 |
|
|
|
progressPercentage() { |
|
|
|
if (!this.scheduleInfo || !this.scheduleInfo.course_info) return 0; |
|
|
|
|
|
|
|
|
|
|
|
const totalHours = this.scheduleInfo.course_info.total_hours || 0; |
|
|
|
const usedHours = this.scheduleInfo.course_info.use_total_hours || 0; |
|
|
|
|
|
|
|
|
|
|
|
if (totalHours <= 0) return 0; |
|
|
|
|
|
|
|
|
|
|
|
const percentage = Math.round((usedHours / totalHours) * 100); |
|
|
|
return Math.min(percentage, 100); // 确保不超过100% |
|
|
|
} |
|
|
|
@ -310,7 +350,10 @@ |
|
|
|
selectedStudentIndex: -1, |
|
|
|
showUpgradeConfirm: false, |
|
|
|
upgradeStudent: null, |
|
|
|
upgradeStudentIndex: -1 |
|
|
|
upgradeStudentIndex: -1, |
|
|
|
showDeleteSchedulesModal: false, |
|
|
|
deleteErrorMessage: '', |
|
|
|
studentToDelete: null |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
@ -334,7 +377,7 @@ |
|
|
|
this.closePopup(); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 获取课程安排详情(使用统一API - 对接admin端) |
|
|
|
async fetchScheduleDetail() { |
|
|
|
if (!this.scheduleId) { |
|
|
|
@ -352,16 +395,16 @@ |
|
|
|
const res = await api.getCourseArrangementDetail({ |
|
|
|
schedule_id: this.scheduleId |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (res.code === 1) { |
|
|
|
// 处理课程安排基本信息 |
|
|
|
const data = res.data; |
|
|
|
|
|
|
|
|
|
|
|
// 使用新的统一API数据结构,与admin端保持一致 |
|
|
|
if (data.schedule_info) { |
|
|
|
// 处理新的统一数据结构(包含schedule_info、formal_students、waiting_students) |
|
|
|
const allStudents = [...(data.formal_students || []), ...(data.waiting_students || [])]; |
|
|
|
|
|
|
|
|
|
|
|
this.scheduleInfo = { |
|
|
|
// 基本课程信息从schedule_info获取 |
|
|
|
id: data.schedule_info.id, |
|
|
|
@ -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, |
|
|
|
// 时间信息 |
|
|
|
@ -465,7 +508,7 @@ |
|
|
|
remaining_capacity: data.remaining_capacity || 0 |
|
|
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('课程安排详情加载成功:', this.scheduleInfo); |
|
|
|
} else { |
|
|
|
uni.showToast({ |
|
|
|
@ -530,7 +573,7 @@ |
|
|
|
this.upgradeStudentIndex = index; |
|
|
|
this.showUpgradeConfirm = true; |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 确认升级等待位学员 |
|
|
|
confirmUpgrade() { |
|
|
|
this.showUpgradeConfirm = false; |
|
|
|
@ -538,7 +581,7 @@ |
|
|
|
this.convertWaitingToFormal(this.upgradeStudent, this.upgradeStudentIndex); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 取消升级等待位学员 |
|
|
|
cancelUpgrade() { |
|
|
|
this.showUpgradeConfirm = false; |
|
|
|
@ -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,8 +633,8 @@ |
|
|
|
}); |
|
|
|
} |
|
|
|
} catch (error) { |
|
|
|
console.error('升级等待位学员失败:', error); |
|
|
|
|
|
|
|
console.error('升级学员失败:', error); |
|
|
|
|
|
|
|
let errorMessage = '升级失败,请重试'; |
|
|
|
if (error.message) { |
|
|
|
if (error.message.includes('timeout')) { |
|
|
|
@ -601,7 +643,7 @@ |
|
|
|
errorMessage = '网络异常,请检查网络连接'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: errorMessage, |
|
|
|
icon: 'none', |
|
|
|
@ -619,15 +661,84 @@ |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
const actionMap = { |
|
|
|
'sign_in': { status: 1, text: '已签到' }, |
|
|
|
'leave': { status: 2, text: '请假' } |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
if (actionMap[action]) { |
|
|
|
try { |
|
|
|
uni.showLoading({ |
|
|
|
@ -654,23 +765,23 @@ |
|
|
|
if (action === 'leave') { |
|
|
|
updateData.reason = '移动端请假操作'; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
console.log('更新学员状态数据:', updateData); |
|
|
|
|
|
|
|
// 调用新的统一状态更新接口 |
|
|
|
const response = await api.updateStudentStatusInArrangement(updateData); |
|
|
|
|
|
|
|
|
|
|
|
if (response.code === 1) { |
|
|
|
// API调用成功,更新前端状态 |
|
|
|
this.selectedStudent.status = actionMap[action].status; |
|
|
|
this.selectedStudent.status_text = actionMap[action].text; |
|
|
|
this.selectedStudent.statusClass = this.getStudentStatusClass(actionMap[action].status); |
|
|
|
|
|
|
|
|
|
|
|
// 更新scheduleInfo中的学员信息 |
|
|
|
if (this.scheduleInfo && this.scheduleInfo.students && this.selectedStudentIndex >= 0) { |
|
|
|
this.$set(this.scheduleInfo.students, this.selectedStudentIndex, this.selectedStudent); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 发射事件给父组件 |
|
|
|
this.$emit('student-attendance', { |
|
|
|
scheduleId: this.scheduleId, |
|
|
|
@ -679,7 +790,7 @@ |
|
|
|
status: actionMap[action].status, |
|
|
|
studentName: this.selectedStudent.name |
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: `${this.selectedStudent.name} ${actionMap[action].text}成功`, |
|
|
|
icon: 'success', |
|
|
|
@ -687,15 +798,21 @@ |
|
|
|
}); |
|
|
|
} 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); |
|
|
|
|
|
|
|
|
|
|
|
let errorMessage = `${actionMap[action].text}失败,请重试`; |
|
|
|
if (error.message) { |
|
|
|
if (error.message.includes('timeout')) { |
|
|
|
@ -704,7 +821,7 @@ |
|
|
|
errorMessage = '网络异常,请检查网络连接'; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: errorMessage, |
|
|
|
icon: 'none', |
|
|
|
@ -714,7 +831,7 @@ |
|
|
|
uni.hideLoading(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.closeAttendanceModal(); |
|
|
|
}, |
|
|
|
|
|
|
|
@ -736,7 +853,18 @@ |
|
|
|
}; |
|
|
|
return statusTextMap[status] || '未知状态'; |
|
|
|
}, |
|
|
|
|
|
|
|
|
|
|
|
// 根据课程状态代码获取状态文本 |
|
|
|
getStatusTextFromCode(statusCode) { |
|
|
|
const statusTextMap = { |
|
|
|
'pending': '待开始', |
|
|
|
'upcoming': '即将开始', |
|
|
|
'ongoing': '进行中', |
|
|
|
'completed': '已结束' |
|
|
|
}; |
|
|
|
return statusTextMap[statusCode] || '待开始'; |
|
|
|
}, |
|
|
|
|
|
|
|
// 批量签到 |
|
|
|
batchCheckIn() { |
|
|
|
uni.navigateTo({ |
|
|
|
@ -762,7 +890,7 @@ |
|
|
|
// 跳转到课程安排详情页面进行学员管理 |
|
|
|
const url = `/pages-market/clue/class_arrangement_detail?schedule_id=${this.scheduleId}`; |
|
|
|
console.log('跳转到学员管理页面:', url); |
|
|
|
|
|
|
|
|
|
|
|
uni.navigateTo({ |
|
|
|
url: url, |
|
|
|
success: () => { |
|
|
|
@ -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, |
|
|
|
@ -1329,7 +1520,7 @@ |
|
|
|
border-radius: 4rpx; |
|
|
|
transition: width 0.3s ease; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* 升级确认弹窗样式 */ |
|
|
|
.upgrade-confirm-modal { |
|
|
|
padding: 40rpx 30rpx; |
|
|
|
@ -1425,4 +1616,46 @@ |
|
|
|
background: linear-gradient(45deg, #7c3aed, #9333ea); |
|
|
|
transform: scale(0.98); |
|
|
|
} |
|
|
|
</style> |
|
|
|
|
|
|
|
/* 删除确认弹窗样式 */ |
|
|
|
.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> |
|
|
|
|