getUserId(); // 通过客户资源表获取user_id $customerResource = (new CustomerResources())->where('id', $customerId)->find(); if (!$customerResource) { throw new CommonException('用户信息不存在'); } // 获取该用户的所有学员 $studentList = (new Student()) ->where('user_id', $customerId) ->where('deleted_at', 0) ->field('id,name,gender,birthday,headimg,created_at') ->order('id desc') ->select() ->toArray(); // 计算年龄和格式化数据,同时获取课时信息 foreach ($studentList as &$student) { $student['age'] = $this->calculateAge($student['birthday']); $student['gender_text'] = $student['gender'] == 1 ? '男' : '女'; $student['headimg'] = $student['headimg'] ? get_image_url($student['headimg']) : ''; // 添加student_id字段,确保前端能正确获取学员ID $student['student_id'] = $student['id']; // 获取学员课时统计信息 $courseStats = $this->getStudentCourseStats($student['id']); $student['course_stats'] = $courseStats; } return [ 'list' => $studentList, 'total' => count($studentList) ]; } /** * 获取学员概览信息(首页用) * @param int $studentId * @return array */ public function getStudentSummary($studentId) { // 验证学员权限 $this->checkStudentPermission($studentId); $student = (new Student()) ->where('id', $studentId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('学员信息不存在'); } // 获取用户基本信息 $member = (new CustomerResources())->where('id', $student['user_id'])->find(); // 获取课程统计信息 $courseStats = $this->getStudentCourseStats($studentId); // 获取预约资格信息 $bookingService = new \app\service\api\student\CourseBookingService(); $qualification = $bookingService->checkStudentQualification($studentId); return [ 'student_id' => $student['id'], 'name' => $student['name'], 'age' => $this->calculateAge($student['birthday']), 'gender' => $student['gender'], 'gender_text' => $student['gender'] == 1 ? '男' : '女', 'headimg' => $student['headimg'] ? get_image_url($student['headimg']) : '', 'member_name' => $member['name'] ?? '', 'created_at' => $student['created_at'], 'create_year_month' => date('Y年m月', strtotime($student['created_at'])), 'week_day' => '星期' . ['日', '一', '二', '三', '四', '五', '六'][date('w')], // 课程统计信息 'course_stats' => $courseStats, // 预约资格信息 'booking_qualification' => [ 'can_book' => $qualification['can_book'], 'has_valid_course' => $qualification['has_valid_course'], 'remaining_courses' => $qualification['total_remaining_hours'] ?? 0, 'trial_class_count' => $qualification['trial_class_count'] ?? 0, 'reason' => $qualification['reason'] ] ]; } /** * 获取学员详细信息 * @param int $studentId * @return array */ public function getStudentInfo($studentId) { // 验证学员权限 $this->checkStudentPermission($studentId); $student = (new Student()) ->where('id', $studentId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('学员信息不存在'); } $studentData = $student->toArray(); // 处理图片URL $studentData['headimg'] = $studentData['headimg'] ? get_image_url($studentData['headimg']) : ''; // 计算年龄 $studentData['age'] = $this->calculateAge($studentData['birthday']); $studentData['gender_text'] = $studentData['gender'] == 1 ? '男' : '女'; return $studentData; } /** * 获取学员详细信息(包含体测信息) * @param int $studentId * @return array */ public function getStudentInfoWithPhysicalTest($studentId) { // 验证学员权限 $this->checkStudentPermission($studentId); // 获取学员基本信息 $student = Db::table('school_student') ->where('id', $studentId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('学员信息不存在'); } // 获取最新的体测信息 $physicalTest = Db::table('school_physical_test') ->where('student_id', $studentId) ->order('created_at desc') ->find(); // 处理学员信息 $studentInfo = [ 'id' => $student['id'], 'name' => $student['name'], 'gender' => $student['gender'], 'gender_text' => $student['gender'] == 1 ? '男' : '女', 'birthday' => $student['birthday'], 'emergency_contact' => $student['emergency_contact'], 'contact_phone' => $student['contact_phone'], 'note' => $student['note'], 'headimg' => $student['headimg'] ? get_image_url($student['headimg']) : '', ]; // 处理体测信息 $physicalTestInfo = []; if ($physicalTest) { $physicalTestInfo = [ 'height' => $physicalTest['height'] ? (string)$physicalTest['height'] : '', 'weight' => $physicalTest['weight'] ? (string)$physicalTest['weight'] : '', 'test_date' => date('Y-m-d', strtotime($physicalTest['created_at'])) ]; } return [ 'student_info' => $studentInfo, 'physical_test_info' => $physicalTestInfo ]; } /** * 更新学员信息 * @param array $data * @return bool */ public function updateStudentInfo($data) { $studentId = $data['student_id']; // 验证学员权限 $this->checkStudentPermission($studentId); // 验证学员是否存在 $student = Db::table('school_student') ->where('id', $studentId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('学员信息不存在'); } // 允许更新的字段 $allowedFields = ['name', 'gender', 'birthday', 'emergency_contact', 'contact_phone', 'note', 'headimg']; $updateData = []; foreach ($allowedFields as $field) { if (isset($data[$field]) && $data[$field] !== '') { $updateData[$field] = $data[$field]; } } if (empty($updateData)) { throw new CommonException('没有需要更新的数据'); } // 如果有生日更新,需要重新计算年龄 if (isset($updateData['birthday'])) { $updateData['age'] = $this->calculateAgeFromBirthday($updateData['birthday']); } $result = Db::table('school_student') ->where('id', $studentId) ->update($updateData); if ($result === false) { throw new CommonException('更新学员信息失败'); } return true; } /** * 上传学员头像 * @param int $studentId * @return array */ public function uploadAvatar($studentId) { // 验证学员权限 $this->checkStudentPermission($studentId); // 处理文件上传 $uploadService = new \app\service\api\upload\UploadService(); $result = $uploadService->avatar(request()->file('image')); if (!$result) { throw new CommonException('头像上传失败'); } // 更新学员头像 $student = (new Student())->where('id', $studentId)->find(); $student->headimg = $result['url']; $student->save(); return [ 'url' => get_image_url($result['url']), 'path' => $result['url'] ]; } /** * 检查学员权限(确保只能操作自己的孩子) * @param int $studentId * @return bool */ private function checkStudentPermission($studentId) { $customerId = $this->getUserId(); // 获取客户资源信息 $customerResource = (new CustomerResources())->where('id', $customerId)->find(); if (!$customerResource) { throw new CommonException('用户信息不存在'); } // 检查学员是否属于当前用户 $student = (new Student()) ->where('id', $studentId) ->where('user_id', $customerId) ->where('deleted_at', 0) ->find(); if (!$student) { throw new CommonException('无权限访问该学员信息'); } return true; } /** * 计算年龄 * @param string $birthday * @return int */ private function calculateAge($birthday) { if (!$birthday) return 0; $birthTime = strtotime($birthday); if (!$birthTime) return 0; $age = date('Y') - date('Y', $birthTime); // 如果还没过生日,年龄减1 if (date('md') < date('md', $birthTime)) { $age--; } return max(0, $age); } /** * 根据生日精确计算年龄(支持小数表示) * @param string $birthday * @return float */ private function calculateAgeFromBirthday($birthday) { if (!$birthday) return 0; $birthTime = strtotime($birthday); if (!$birthTime) return 0; $today = new \DateTime(); $birthDate = new \DateTime($birthday); $interval = $today->diff($birthDate); $years = $interval->y; $months = $interval->m; // 将月份转换为小数,如3岁11个月 = 3.11 return $years + ($months / 100); } /** * 添加孩子信息 * @param array $data * @return array */ public function addChild($data) { $customerId = $this->getUserId(); // 创建学员数据 $studentData = [ 'user_id' => $customerId, 'name' => $data['name'], 'gender' => (int)$data['gender'], 'birthday' => $data['birthday'], 'headimg' => $data['headimg'] ?? '', 'emergency_contact' => $data['emergency_contact'] ?? '', 'contact_phone' => $data['contact_phone'] ?? '', 'note' => $data['note'] ?? '', 'age' => $this->calculateAgeFromBirthday($data['birthday']), 'created_at' => date('Y-m-d H:i:s'), 'updated_at' => date('Y-m-d H:i:s'), 'deleted_at' => 0 ]; try { // 插入学员数据 $studentId = Db::table('school_student')->insertGetId($studentData); if (!$studentId) { throw new CommonException('添加孩子失败'); } return [ 'student_id' => $studentId, 'name' => $data['name'], 'gender_text' => $data['gender'] == 1 ? '男' : '女', 'age' => $this->calculateAge($data['birthday']) ]; } catch (\Exception $e) { throw new CommonException('添加孩子失败:' . $e->getMessage()); } } /** * 获取学员课程安排列表 * @param array $params * @return array */ public function getCourseScheduleList($params) { $studentId = $params['student_id']; // 构建查询条件 $where = [ ['pcs.student_id', '=', $studentId], ['pcs.deleted_at', '=', 0], ['cs.deleted_at', '=', 0] ]; // 日期筛选 if (!empty($params['date'])) { $where[] = ['cs.course_date', '=', $params['date']]; } // 日期范围筛选 if (!empty($params['start_date']) && !empty($params['end_date'])) { $where[] = ['cs.course_date', 'between', [$params['start_date'], $params['end_date']]]; } // 状态筛选 if (isset($params['status']) && $params['status'] !== '') { $where[] = ['pcs.status', '=', $params['status']]; } // 查询课程安排数据,联合两个表 $scheduleList = Db::table('school_person_course_schedule pcs') ->leftJoin('school_course_schedule cs', 'pcs.schedule_id = cs.id') ->leftJoin('school_course c', 'cs.course_id = c.id') ->leftJoin('school_personnel p', 'cs.coach_id = p.id') ->leftJoin('school_venue v', 'cs.venue_id = v.id') ->where($where) ->field('pcs.id, cs.course_date, COALESCE(cs.start_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", 1) ELSE "09:00" END ) as start_time, COALESCE(cs.end_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", -1) ELSE "10:00" END ) as end_time, cs.time_slot, pcs.status, pcs.cancel_reason, c.course_name, c.remarks as course_description, p.name as coach_name, v.venue_name, 60 as duration') ->order('cs.course_date desc, cs.start_time desc') ->select() ->toArray(); // 处理数据格式 foreach ($scheduleList as &$schedule) { // 状态处理 $schedule['status_text'] = $this->getScheduleStatusText($schedule['status']); // 时间处理 if (!$schedule['start_time'] || !$schedule['end_time']) { // 如果没有具体时间,从time_slot中解析 $timeSlot = $schedule['time_slot'] ?? '09:00-10:00'; $times = explode('-', $timeSlot); $schedule['start_time'] = $times[0] ?? '09:00'; $schedule['end_time'] = $times[1] ?? '10:00'; } $schedule['time_slot'] = $schedule['start_time'] . '-' . $schedule['end_time']; $schedule['duration'] = $schedule['duration'] ?: 60; // 准备事项(模拟数据,实际可从课程信息中获取) $schedule['preparation_items'] = $this->getCoursePreparationItems($schedule['course_name']); } return [ 'list' => $scheduleList, 'total' => count($scheduleList) ]; } /** * 获取课程安排详情 * @param int $scheduleId * @return array */ public function getCourseScheduleDetail($scheduleId) { // 查询课程安排详情 - 通过schedule_id关联到course_schedule表获取详细信息 $schedule = Db::table('school_person_course_schedule pcs') ->leftJoin('school_course_schedule cs', 'pcs.schedule_id = cs.id') ->leftJoin('school_course c', 'cs.course_id = c.id') ->leftJoin('school_personnel p', 'cs.coach_id = p.id') ->leftJoin('school_venue v', 'cs.venue_id = v.id') ->leftJoin('school_student s', 'pcs.student_id = s.id') ->where('pcs.id', $scheduleId) ->where('pcs.deleted_at', 0) ->field(' pcs.id, pcs.student_id, pcs.course_date, COALESCE(cs.start_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", 1) ELSE "09:00" END ) as start_time, COALESCE(cs.end_time, CASE WHEN pcs.time_slot REGEXP "^[0-9]{2}:[0-9]{2}-[0-9]{2}:[0-9]{2}$" THEN SUBSTRING_INDEX(pcs.time_slot, "-", -1) ELSE "10:00" END ) as end_time, pcs.status, pcs.cancel_reason, c.course_name, c.remarks as course_description, p.name as coach_name, v.venue_name, s.user_id, TIMESTAMPDIFF(MINUTE, cs.start_time, cs.end_time) as duration ') ->find(); if (!$schedule) { throw new CommonException('课程安排不存在'); } // 验证权限 $this->checkStudentPermission($schedule['student_id']); // 处理数据格式 $schedule['status_text'] = $this->getScheduleStatusText($schedule['status']); $schedule['time_slot'] = $schedule['start_time'] . '-' . $schedule['end_time']; $schedule['duration'] = $schedule['duration'] ?: 60; $schedule['preparation_items'] = $this->getCoursePreparationItems($schedule['course_name']); return $schedule; } /** * 申请课程请假 * @param array $data * @return bool */ public function requestCourseLeave($data) { $scheduleId = $data['schedule_id']; $reason = $data['reason'] ?? ''; // 查询课程安排 $schedule = Db::table('school_person_course_schedule') ->where('id', $scheduleId) ->where('deleted_at', 0) ->find(); if (!$schedule) { throw new CommonException('课程安排不存在'); } // 验证权限 $this->checkStudentPermission($schedule['student_id']); // 检查课程状态 if ($schedule['status'] != 0) { // 0-待上课 throw new CommonException('当前课程状态不允许请假'); } // 获取对应的课程安排信息来检查时间 $courseSchedule = Db::table('school_course_schedule') ->where('id', $schedule['schedule_id']) ->find(); if ($courseSchedule) { // 检查请假时间限制(上课前6小时) $courseDateTime = $courseSchedule['course_date'] . ' ' . $courseSchedule['start_time']; $courseTimestamp = strtotime($courseDateTime); $currentTimestamp = time(); if ($courseTimestamp - $currentTimestamp < 6 * 3600) { throw new CommonException('上课前6小时内不允许请假'); } } // 更新状态为请假 $result = Db::table('school_person_course_schedule') ->where('id', $scheduleId) ->update([ 'status' => 2, // 2-请假 'cancel_reason' => $reason, 'updated_at' => date('Y-m-d H:i:s') ]); if ($result === false) { throw new CommonException('请假申请失败'); } // TODO: 发送消息通知相关人员 return true; } /** * 获取课程状态文本 * @param int $status * @return string */ private function getScheduleStatusText($status) { $statusMap = [ 0 => '待上课', 1 => '已完成', 2 => '请假', 3 => '取消' ]; return $statusMap[$status] ?? '未知状态'; } /** * 获取课程准备事项 * @param string $courseName * @return array */ private function getCoursePreparationItems($courseName) { // 根据课程名称返回相应的准备事项 $preparationMap = [ '基础体能训练' => ['运动服装', '运动鞋', '毛巾', '水杯'], '专项技能训练' => ['专项器材', '护具', '运动服装'], '体适能评估' => ['轻便服装', '测试表格'], '少儿形体课' => ['舞蹈服装', '舞蹈鞋', '毛巾'], '成人瑜伽' => ['瑜伽垫', '舒适服装', '毛巾'], '私教训练' => ['运动服装', '运动鞋', '水杯'], '儿童游泳' => ['泳衣', '泳帽', '游泳镜', '毛巾'], '暑季特训营' => ['运动服装', '防晒用品', '充足水分', '能量补充食品'] ]; return $preparationMap[$courseName] ?? ['运动服装', '运动鞋', '毛巾', '水杯']; } /** * 获取学员课时统计信息 * @param int $studentId * @return array */ private function getStudentCourseStats($studentId) { // 查询学员的所有课程记录 $courseStats = Db::table('school_student_courses') ->where('student_id', $studentId) ->field(' SUM(total_hours) as total_hours, SUM(gift_hours) as gift_hours, SUM(use_total_hours) as use_total_hours, SUM(use_gift_hours) as use_gift_hours ') ->find(); // 如果没有课程记录,返回默认值 if (!$courseStats || is_null($courseStats['total_hours'])) { return [ 'remaining_hours' => 0, 'total_valid_hours' => 0, 'used_hours' => 0 ]; } // 计算课时统计 $totalHours = (int)$courseStats['total_hours']; $giftHours = (int)$courseStats['gift_hours']; $useTotalHours = (int)$courseStats['use_total_hours']; $useGiftHours = (int)$courseStats['use_gift_hours']; // 有效课程 = total_hours + gift_hours $totalValidHours = $totalHours + $giftHours; // 已使用 = use_total_hours + use_gift_hours $usedHours = $useTotalHours + $useGiftHours; // 剩余课时数 = total_hours + gift_hours - use_total_hours - use_gift_hours $remainingHours = $totalValidHours - $usedHours; return [ 'remaining_hours' => max(0, $remainingHours), // 确保不为负数 'total_valid_hours' => $totalValidHours, 'used_hours' => $usedHours ]; } /** * 获取当前登录用户ID * @return int */ private function getUserId() { // 从request中获取memberId(由ApiCheckToken中间件设置) $memberId = request()->memberId(); if ($memberId) { return $memberId; } // 如果没有中间件设置,尝试解析token $token = request()->header('token'); if ($token) { try { $loginService = new \app\service\api\login\LoginService(); $tokenInfo = $loginService->parseToken($token); if (!empty($tokenInfo) && isset($tokenInfo['member_id'])) { return $tokenInfo['member_id']; } } catch (\Exception $e) { // token解析失败,抛出异常 throw new CommonException('用户未登录或token无效'); } } // 如果都没有,抛出异常 throw new CommonException('用户未登录'); } /** * 学员标签 * @return array * @throws \think\db\exception\DataNotFoundException * @throws \think\db\exception\DbException * @throws \think\db\exception\ModelNotFoundException */ public function getStudentTagList() { return (new StudentLabel())->order('sort desc,create_time desc')->select()->toArray(); } /** * 获取学员课程统计 * @param array $params * @return array */ public function getCourseStatistics($params) { $studentId = $params['student_id']; $startDate = $params['start_date']; $endDate = $params['end_date']; // 构建查询条件 $where = [ ['pcs.student_id', '=', $studentId], ['pcs.deleted_at', '=', 0], ['cs.deleted_at', '=', 0] ]; // 日期筛选 if (!empty($startDate) && !empty($endDate)) { $where[] = ['cs.course_date', 'between', [$startDate, $endDate]]; } // 查询课程安排数据 $scheduleList = Db::table('school_person_course_schedule pcs') ->leftJoin('school_course_schedule cs', 'pcs.schedule_id = cs.id') ->where($where) ->field('pcs.status') ->select() ->toArray(); // 统计各状态数量 $totalCourses = count($scheduleList); $completedCourses = 0; $scheduledCourses = 0; $cancelledCourses = 0; foreach ($scheduleList as $schedule) { switch ($schedule['status']) { case 1: // 已完成 $completedCourses++; break; case 0: // 待上课 $scheduledCourses++; break; case 2: // 请假 case 3: // 取消 $cancelledCourses++; break; } } return [ 'total_courses' => $totalCourses, 'completed_courses' => $completedCourses, 'scheduled_courses' => $scheduledCourses, 'cancelled_courses' => $cancelledCourses ]; } public function checkMemberOldPwd(string $old_passowrd) { $res = [ 'code' => 0, 'msg' => '操作失败', 'data' => [] ]; $customerResources = new CustomerResources(); $personnel_id = $this->getUserId(); $member = new Member(); $phone = $customerResources->where('id', $personnel_id)->value('phone_number'); $password = $customerResources->where('id', $personnel_id)->value('password'); if (!check_password($old_passowrd, $password)) { $res['msg'] = '旧密码错误'; return $res; } $res['code'] = 1; $res['msg'] = '密码正确'; $res['data'] = [ 'key_value' => $this->setEditPasswordKey($phone) ]; return $res; } public function updatePassword($new_password){ $customerResources = new CustomerResources(); $personnel_id = $this->getUserId(); $update = $customerResources->where('id', $personnel_id)->update([ 'password' => create_password($new_password) ]); if (!$update) { $res = [ 'code' => 0, 'msg' => '操作失败', 'data' => [] ]; } else { $res = [ 'code' => 1, 'msg' => '操作成功', 'data' => [] ]; } return $res; } public function setEditPasswordKey(string $phone) { $key_name = 'edit_password_' . $phone; //生成字符串,存入cache中 //check_password()//验证 //create_password()//创建 $key_value = create_password($key_name); // 缓存在3600秒之后过期 Cache::set($key_name, $key_value, 3600); return $key_value; } }