diff --git a/niucloud/app/api/controller/apiController/CourseSchedule.php b/niucloud/app/api/controller/apiController/CourseSchedule.php index d16337ce..15378776 100644 --- a/niucloud/app/api/controller/apiController/CourseSchedule.php +++ b/niucloud/app/api/controller/apiController/CourseSchedule.php @@ -280,25 +280,25 @@ class CourseSchedule extends BaseApiService $data = $this->request->params([ ["venue_id", 0] ]); - + if (empty($data['venue_id'])) { return fail('场地ID不能为空'); } - + try { // 获取场地信息 $venue = \think\facade\Db::name('venue') ->where('id', $data['venue_id']) ->where('deleted_at', 0) ->find(); - + if (empty($venue)) { return fail('场地不存在'); } - + // 生成时间选项 $timeOptions = (new CourseScheduleService())->generateVenueTimeOptions($venue); - + return success('获取成功', [ 'time_options' => $timeOptions, 'venue_capacity' => $venue['capacity'] @@ -307,4 +307,80 @@ class CourseSchedule extends BaseApiService return fail('获取场地时间选项失败:' . $e->getMessage()); } } + + /** + * 课程批量签到 + * @param Request $request + * @return \think\Response + */ + public function batchSignIn(Request $request) + { + try { + $data = $this->request->params([ + ["schedule_id", 0], + ["students", []], + ["remark", ""], + ["class_photo", ""] + ]); + + if (empty($data['schedule_id'])) { + return fail('课程安排ID不能为空'); + } + + if (empty($data['students'])) { + return fail('学员列表不能为空'); + } + + $result = (new CourseScheduleService())->batchSignIn($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success($result['msg'] ?? '签到成功', $result['data'] ?? []); + } catch (\Exception $e) { + return fail('批量签到失败:' . $e->getMessage()); + } + } + + /** + * 单个学员签到 + * @param Request $request + * @return \think\Response + */ + public function updateStudentStatus(Request $request) + { + try { + $data = $this->request->params([ + ["schedule_id", 0], + ["person_id", 0], + ["person_type", ""], // student 或 customer_resource + ["status", 1], // 签到状态:0-未到,1-已到,2-请假 + ["reason", ""], // 签到备注 + ["resources_id", 0] // 资源ID(可选,用于customer_resource类型) + ]); + + // 验证必填参数 + if (empty($data['schedule_id'])) { + return fail('课程安排ID不能为空'); + } + + if (empty($data['person_id']) && empty($data['resources_id'])) { + return fail('学员ID或资源ID不能为空'); + } + + // 验证签到状态 + if (!in_array($data['status'], [0, 1, 2])) { + return fail('无效的签到状态'); + } + + $result = (new CourseScheduleService())->updateStudentStatus($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success($result['msg'] ?? '签到成功', $result['data'] ?? []); + } catch (\Exception $e) { + return fail('签到失败:' . $e->getMessage()); + } + } } \ No newline at end of file diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php index 8c86fb58..5eb96530 100644 --- a/niucloud/app/api/route/route.php +++ b/niucloud/app/api/route/route.php @@ -350,6 +350,10 @@ Route::group(function () { Route::get('courseSchedule/filterOptions', 'apiController.CourseSchedule/getFilterOptions'); //员工端-获取场地时间选项 Route::get('courseSchedule/venueTimeOptions', 'apiController.CourseSchedule/getVenueTimeOptions'); + //员工端-课程批量签到 + Route::post('courseSchedule/batchSignIn', 'apiController.CourseSchedule/batchSignIn'); + //员工端-单个学员签到 + Route::post('courseSchedule/updateStudentStatus', 'apiController.CourseSchedule/updateStudentStatus'); // 课程安排统一选项接口(新增-支持校区过滤) //获取所有排课选项(统一接口-支持校区过滤) diff --git a/niucloud/app/service/api/apiService/CourseScheduleService.php b/niucloud/app/service/api/apiService/CourseScheduleService.php index e47d1708..72fe7c20 100644 --- a/niucloud/app/service/api/apiService/CourseScheduleService.php +++ b/niucloud/app/service/api/apiService/CourseScheduleService.php @@ -11,6 +11,7 @@ namespace app\service\api\apiService; +use app\model\student\Student; use core\base\BaseApiService; use think\facade\Db; @@ -2433,45 +2434,18 @@ class CourseScheduleService extends BaseApiService // 开启事务 Db::startTrans(); - - // 查找学员记录 - $enrollment = Db::name('person_course_schedule') - ->where('schedule_id', $scheduleId) - ->where('person_id', $personId) - ->where('deleted_at', 0) - ->find(); - - if (!$enrollment) { - Db::rollback(); - return [ - 'code' => 0, - 'msg' => '找不到学员记录' - ]; - } - - // 准备更新数据 - $updateData = [ - 'status' => $status, - 'updated_at' => date('Y-m-d H:i:s') - ]; - - if ($reason) { - $updateData['remark'] = $reason; - } - - // 更新学员记录 - $result = Db::name('person_course_schedule') - ->where('id', $enrollment['id']) - ->update($updateData); - - if ($result === false) { + + // 使用内部处理方法 + $result = $this->processSingleStudentSignIn($scheduleId, $personId, 0, $status, $reason); + + if (!$result['success']) { Db::rollback(); return [ 'code' => 0, - 'msg' => '更新失败' + 'msg' => $result['error'] ]; } - + // 提交事务 Db::commit(); @@ -2558,4 +2532,311 @@ class CourseScheduleService extends BaseApiService ]; } } + + /** + * 批量签到 + * @param array $data 批量签到数据 + * @return array 签到结果 + */ + public function batchSignIn(array $data) + { + try { + // 验证必填字段 + if (empty($data['schedule_id'])) { + return [ + 'code' => 0, + 'msg' => '课程安排ID不能为空' + ]; + } + + if (empty($data['students']) || !is_array($data['students'])) { + return [ + 'code' => 0, + 'msg' => '学员签到数据不能为空' + ]; + } + + // 开启事务 + Db::startTrans(); + + $scheduleId = $data['schedule_id']; + $students = $data['students']; + $remark = $data['remark'] ?? ''; + $classPhoto = $data['class_photo'] ?? ''; + + // 验证课程安排是否存在 + $schedule = Db::name('course_schedule') + ->where('id', $scheduleId) + ->where('deleted_at', 0) + ->find(); + + if (empty($schedule)) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '课程安排不存在或已被删除' + ]; + } + + $successCount = 0; + $failedCount = 0; + $errors = []; + + // 批量更新学员签到状态 - 优雅复用单个签到逻辑 + foreach ($students as $index => $student) { + $studentId = $student['student_id'] ?? 0; + $resourceId = $student['resource_id'] ?? 0; + $status = $student['status'] ?? 1; // 默认为已到 + + try { + // 验证学员数据 + if (empty($studentId) && empty($resourceId)) { + $failedCount++; + $errors[] = "第" . ($index + 1) . "条记录:学员ID或资源ID不能为空"; + continue; + } + + // 使用内部签到处理方法,避免嵌套事务 + $result = $this->processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $remark); + + if ($result['success']) { + $successCount++; + } else { + $failedCount++; + $errors[] = "第" . ($index + 1) . "条记录:" . $result['error']; + } + + } catch (\Exception $e) { + $failedCount++; + $errors[] = "第" . ($index + 1) . "条记录:处理异常 - " . $e->getMessage(); + } + } + + // 如果有课堂照片,保存到课程安排记录 + if (!empty($classPhoto)) { + // 处理图片上传或保存逻辑 + $updateScheduleData = [ + 'class_photo' => $classPhoto, + 'updated_at' => date('Y-m-d H:i:s') + ]; + + // 如果有签到备注,也保存到课程安排 + if (!empty($remark)) { + $updateScheduleData['sign_in_remark'] = $remark; + } + + Db::name('course_schedule') + ->where('id', $scheduleId) + ->update($updateScheduleData); + } + + // 记录签到历史(可选) + $this->recordSignInHistory($scheduleId, [ + 'total_students' => count($students), + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'remark' => $remark, + 'class_photo' => $classPhoto, + 'sign_in_time' => date('Y-m-d H:i:s'), + 'operator_id' => $this->user_id ?? 0 + ]); + + // 检查是否有失败记录 + if ($failedCount > 0) { + // 如果有失败记录,但也有成功记录,提示部分成功 + if ($successCount > 0) { + Db::commit(); + return [ + 'code' => 1, + 'msg' => "批量签到部分成功,成功:{$successCount}人,失败:{$failedCount}人", + 'data' => [ + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'errors' => $errors + ] + ]; + } else { + // 全部失败 + Db::rollback(); + return [ + 'code' => 0, + 'msg' => "批量签到失败,失败:{$failedCount}人", + 'data' => [ + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'errors' => $errors + ] + ]; + } + } + + // 全部成功 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => "批量签到成功,共签到{$successCount}人", + 'data' => [ + 'success_count' => $successCount, + 'failed_count' => $failedCount, + 'schedule_id' => $scheduleId + ] + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '批量签到失败:' . $e->getMessage() + ]; + } + } + + /** + * 记录签到历史 + * @param int $scheduleId 课程安排ID + * @param array $signInData 签到数据 + * @return void + */ + private function recordSignInHistory($scheduleId, $signInData) + { + try { + // 这里可以记录签到历史到专门的表中 + // 如果没有专门的签到历史表,可以记录到日志或其他地方 + $historyData = [ + 'schedule_id' => $scheduleId, + 'sign_in_data' => json_encode($signInData), + 'created_at' => date('Y-m-d H:i:s') + ]; + + // 假设有一个签到历史表 sign_in_history + // Db::name('sign_in_history')->insert($historyData); + + // 或者记录到系统日志 + trace('Batch Sign In: ' . json_encode($historyData)); + } catch (\Exception $e) { + // 记录日志失败不影响主流程 + trace('Record sign in history failed: ' . $e->getMessage()); + } + } + + /** + * 处理试听课签到逻辑 + * @param Student $student 学员模型实例 + * @throws \Exception + */ + private function handleTrialClassCheckin($student) + { + try { + $currentTrialCount = $student->trial_class_count; + + // 如果试听课次数为0,不进行任何更新 + if ($currentTrialCount <= 0) { + return; + } + + $updateData = []; + $newTrialCount = $currentTrialCount - 1; + $updateData['trial_class_count'] = max(0, $newTrialCount); // 确保最小值为0 + + // 根据当前试听次数设置对应的签到时间 + if ($currentTrialCount === 2) { + // 第一次试听课签到 + $updateData['first_come'] = date('Y-m-d H:i:s'); + } elseif ($currentTrialCount === 1) { + // 第二次试听课签到 + $updateData['second_come'] = date('Y-m-d H:i:s'); + } + + // 更新学员试听信息 + $result = Student::where('id', $student->id)->update($updateData); + + if ($result === false) { + throw new \Exception('更新学员试听信息失败'); + } + + // 记录日志 + trace('Trial class checkin processed', [ + 'student_id' => $student->id, + 'old_trial_count' => $currentTrialCount, + 'new_trial_count' => $updateData['trial_class_count'], + 'update_fields' => array_keys($updateData) + ]); + + } catch (\Exception $e) { + // 抛出异常以便外层事务回滚 + throw new \Exception('处理试听课签到失败:' . $e->getMessage()); + } + } + + /** + * 处理单个学员签到(内部方法,不管理事务) + * @param int $scheduleId 课程安排ID + * @param int $studentId 学员ID + * @param int $resourceId 资源ID + * @param int $status 签到状态 + * @param string $reason 备注 + * @return array 处理结果 + */ + private function processSingleStudentSignIn($scheduleId, $studentId, $resourceId, $status, $reason = '') + { + try { + // 查找学员课程安排记录 + $whereCondition = [ + 'schedule_id' => $scheduleId, + 'deleted_at' => 0 + ]; + + if (!empty($studentId)) { + $whereCondition['student_id'] = $studentId; + } elseif (!empty($resourceId)) { + $whereCondition['resources_id'] = $resourceId; + } else { + return ['success' => false, 'error' => '学员ID或资源ID不能为空']; + } + + $enrollment = Db::name('person_course_schedule') + ->where($whereCondition) + ->find(); + + if (!$enrollment) { + return ['success' => false, 'error' => '找不到学员记录']; + } + + // 获取学员信息(用于试听课处理) + $student = Student::where('id', $enrollment['student_id'])->find(); + if (!$student) { + return ['success' => false, 'error' => '找不到学员信息']; + } + + // 准备更新数据 + $updateData = [ + 'status' => $status, + 'updated_at' => date('Y-m-d H:i:s') + ]; + + if ($reason) { + $updateData['remark'] = $reason; + } + + // 更新学员签到记录 + $result = Db::name('person_course_schedule') + ->where('id', $enrollment['id']) + ->update($updateData); + + if ($result === false) { + return ['success' => false, 'error' => '更新签到状态失败']; + } + + // 处理试听课签到逻辑(仅在签到成功且学员未付费时处理) + if ($status === 1 && $student->pay_status != 1) { + $this->handleTrialClassCheckin($student); + } + + return ['success' => true, 'error' => '']; + + } catch (\Exception $e) { + return ['success' => false, 'error' => $e->getMessage()]; + } + } } \ No newline at end of file diff --git a/niucloud/app/service/api/apiService/OrderTableService.php b/niucloud/app/service/api/apiService/OrderTableService.php index 54072e55..c5ba0b93 100644 --- a/niucloud/app/service/api/apiService/OrderTableService.php +++ b/niucloud/app/service/api/apiService/OrderTableService.php @@ -266,7 +266,7 @@ class OrderTableService extends BaseApiService return false; } $course = $course->toArray(); - Student::where('id', $student_id)->update(['status' => 1]); + Student::where('id', $student_id)->update(['status' => 1, 'pay_status' => 1]); // 检查学员是否已有该课程记录 $existingCourse = Db::table('school_student_courses') ->where('student_id', $student_id) diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js index 2af84deb..db0aa5f7 100644 --- a/uniapp/api/apiRoute.js +++ b/uniapp/api/apiRoute.js @@ -1290,7 +1290,7 @@ export default { // 更新学员课程状态(请假等) async updateStudentStatus(data = {}) { - return await http.post('/course/updateStudentStatus', data) + return await http.post('/courseSchedule/updateStudentStatus', data) }, @@ -1418,7 +1418,7 @@ export default { // 更新学员状态(新统一接口 - 对接admin端) async updateStudentStatusInArrangement(data = {}) { try { - const response = await http.post('/course/updateStudentStatus', data) + const response = await http.post('/courseSchedule/updateStudentStatus', data) return response } catch (error) { console.error('更新学员状态失败:', error) diff --git a/uniapp/components/schedule/ScheduleDetail.vue b/uniapp/components/schedule/ScheduleDetail.vue index ce4718cb..a7c77d20 100644 --- a/uniapp/components/schedule/ScheduleDetail.vue +++ b/uniapp/components/schedule/ScheduleDetail.vue @@ -53,9 +53,15 @@ 正式学员 ({{ formalStudents.length }}人) - - - 安排学员 + + + + 批量签到 + + + + 安排学员 + @@ -731,6 +737,25 @@ return statusTextMap[status] || '未知状态'; }, + // 批量签到 + batchCheckIn() { + uni.navigateTo({ + url: `/pages-coach/coach/schedule/sign_in?id=${this.scheduleId}`, + success: () => { + console.log('跳转到批量签到页面成功'); + // 关闭当前弹窗 + this.closePopup(); + }, + fail: (error) => { + console.error('跳转到批量签到页面失败:', error); + uni.showToast({ + title: '跳转失败,请重试', + icon: 'none' + }); + } + }); + }, + // 处理安排学员按钮点击(支持统一学员管理功能) handleArrangeStudent() { try { @@ -841,29 +866,44 @@ flex: 1; } - .arrange-student-btn { + .header-actions { + display: flex; + gap: 12rpx; + } + + .action-btn { display: flex; align-items: center; padding: 8rpx 16rpx; - background-color: #29d3b4; border-radius: 6rpx; cursor: pointer; transition: background-color 0.3s; - + &:hover { - background-color: #22a68b; + opacity: 0.8; } - - &:active { - background-color: #1e9680; + + .btn-text { + margin-left: 6rpx; + font-size: 24rpx; + color: #fff; } } - .btn-text { - margin-left: 8rpx; - font-size: 24rpx; - color: #fff; - font-weight: 500; + .batch-checkin-btn { + background-color: #007bff; + + &:hover { + background-color: #0056b3; + } + } + + .arrange-student-btn { + background-color: #29d3b4; + + &:hover { + background-color: #22a68b; + } } .waiting-tip { diff --git a/uniapp/pages-coach/coach/schedule/sign_in.vue b/uniapp/pages-coach/coach/schedule/sign_in.vue index 91b1b1e1..a9d0ca3c 100644 --- a/uniapp/pages-coach/coach/schedule/sign_in.vue +++ b/uniapp/pages-coach/coach/schedule/sign_in.vue @@ -1,8 +1,5 @@