diff --git a/admin/src/app/api/course_schedule.ts b/admin/src/app/api/course_schedule.ts index c71d486f..e7aea998 100644 --- a/admin/src/app/api/course_schedule.ts +++ b/admin/src/app/api/course_schedule.ts @@ -90,9 +90,80 @@ export function getCourseInfo(params: Record) { } +/** + * 添加课程安排(新增统一接口) + * @param params + * @returns + */ export function addSchedule(params: Record) { return request.post('course_schedule/addSchedule', params, { showErrorMessage: true, showSuccessMessage: true, }) } + +/** + * 获取完整课程安排详情(新增统一接口) + * @param params + * @returns + */ +export function getScheduleDetail(params: { schedule_id: number }) { + return request.get('course_schedule/getScheduleDetail', { params }) +} + +/** + * 智能搜索学员(新增统一接口) + * @param params + * @returns + */ +export function searchStudents(params: { phone_number?: string, name?: string }) { + return request.get('course_schedule/searchStudents', { params }) +} + +/** + * 删除学员/请假处理(新增统一接口) + * @param params + * @returns + */ +export function removeStudent(params: Record) { + return request.post('course_schedule/removeStudent', params, { + showErrorMessage: true, + showSuccessMessage: true, + }) +} + +/** + * 学员升级(新增统一接口) + * @param params + * @returns + */ +export function upgradeStudent(params: Record) { + return request.post('course_schedule/upgradeStudent', params, { + showErrorMessage: true, + showSuccessMessage: true, + }) +} + +/** + * 更新学员签到状态(新增统一接口) + * @param params + * @returns + */ +export function updateStudentStatus(params: Record) { + return request.post('course_schedule/updateStudentStatus', params, { + showErrorMessage: true, + showSuccessMessage: true, + }) +} + +/** + * 恢复学员(取消请假)(新增统一接口) + * @param params + * @returns + */ +export function restoreStudent(params: Record) { + return request.post('course_schedule/restoreStudent', params, { + showErrorMessage: true, + showSuccessMessage: true, + }) +} diff --git a/admin/src/app/types/course-schedule.ts b/admin/src/app/types/course-schedule.ts new file mode 100644 index 00000000..8e60316c --- /dev/null +++ b/admin/src/app/types/course-schedule.ts @@ -0,0 +1,227 @@ +/** + * 课程安排相关类型定义 + */ + +// 基础API响应接口 +export interface ApiResponse { + code: number + msg: string + data: T +} + +// 学员信息接口 +export interface StudentInfo { + id: number + person_id?: number + student_id?: number + resource_id?: number + resources_id?: number + name: string + phone: string + age: string | number + person_type: 'student' | 'customer_resource' + schedule_type: 1 | 2 // 1:正式位 2:等待位 + status: number // 签到状态:0-待上课 1-已签到 2-请假 + courseStatus: string // 课程状态描述 + course_progress: { + used: number + total: number + percentage: number + } + student_course_info?: { + end_date: string + start_date?: string + course_id?: number + class_id?: number + } + renewal_status?: boolean // 续费状态 + created_at?: string + updated_at?: string +} + +// 课程安排基本信息接口 +export interface ScheduleInfo { + id: number + course_id?: number + class_id?: number + venue_id?: number + campus_id?: number + coach_id?: number + course_name?: string + class_name?: string + course_date?: string + time_slot?: string + coach_name?: string + venue_name?: string + campus_name?: string + max_students?: number + current_students?: number + waiting_students?: number + status?: 'pending' | 'upcoming' | 'ongoing' | 'completed' + created_at?: string + updated_at?: string +} + +// 课程安排详情响应接口 +export interface ScheduleDetailResponse { + schedule_info: ScheduleInfo + formal_students: StudentInfo[] + waiting_students: StudentInfo[] + formal_empty_seats: number[] + waiting_empty_seats: number[] + total_capacity?: number + available_capacity?: number +} + +// 位置信息接口 +export interface SlotInfo { + type: 'formal' | 'waiting' + index: number +} + +// 添加学员请求参数 +export interface AddStudentParams { + schedule_id: number + person_type: 'student' | 'customer_resource' + schedule_type: 1 | 2 // 1:正式位 2:等待位 + course_date: string + time_slot: string + student_id?: number + resources_id?: number + remarks?: string +} + +// 删除学员请求参数 +export interface RemoveStudentParams { + schedule_id: number + person_id: number + person_type: 'student' | 'customer_resource' + reason?: string + is_leave?: boolean // 是否为请假 +} + +// 升级学员请求参数 +export interface UpgradeStudentParams { + schedule_id: number + person_id: number + from_type: 1 | 2 // 从哪个位置类型 + to_type: 1 | 2 // 到哪个位置类型 + remarks?: string +} + +// 搜索学员请求参数 +export interface SearchStudentsParams { + phone_number?: string + name?: string + campus_id?: number + exclude_schedule_id?: number // 排除已在该课程的学员 + limit?: number +} + +// 搜索学员响应结果 +export interface SearchStudentsResponse { + students: StudentInfo[] + total: number + has_more: boolean +} + +// 学员状态枚举 +export enum StudentStatus { + PENDING = 0, // 待上课 + ATTENDED = 1, // 已签到 + LEAVE = 2 // 请假 +} + +// 课程状态枚举 +export enum CourseStatus { + PENDING = 'pending', // 待开始 + UPCOMING = 'upcoming', // 即将开始 + ONGOING = 'ongoing', // 进行中 + COMPLETED = 'completed' // 已结束 +} + +// 人员类型枚举 +export enum PersonType { + STUDENT = 'student', // 正式学员 + CUSTOMER_RESOURCE = 'customer_resource' // 潜在客户 +} + +// 位置类型枚举 +export enum ScheduleType { + FORMAL = 1, // 正式位 + WAITING = 2 // 等待位 +} + +// 组件事件类型定义 +export interface CourseArrangementEvents { + 'student-added': StudentInfo + 'student-removed': StudentInfo + 'student-upgraded': StudentInfo + 'schedule-updated': ScheduleInfo + 'data-refreshed': ScheduleDetailResponse +} + +// 操作类型定义 +export type StudentActionType = + | 'view' // 查看详情 + | 'checkin' // 更新签到 + | 'leave' // 请假 + | 'cancel_leave' // 取消请假 + | 'upgrade' // 升级到正式位 + | 'remove' // 移除学员 + +// 错误码定义 +export enum ErrorCode { + SUCCESS = 1, + FAILURE = 0, + INVALID_PARAMS = -1, + UNAUTHORIZED = -2, + FORBIDDEN = -3, + NOT_FOUND = -4, + CONFLICT = -5, + SERVER_ERROR = -500 +} + +// 通用分页接口 +export interface PaginationParams { + page?: number + limit?: number + total?: number +} + +// 学员操作历史记录 +export interface StudentOperationLog { + id: number + schedule_id: number + student_id: number + operation_type: StudentActionType + operation_desc: string + operator_id: number + operator_name: string + created_at: string + remarks?: string +} + +// 批量操作接口 +export interface BatchOperationParams { + schedule_id: number + student_ids: number[] + operation_type: StudentActionType + remarks?: string +} + +// 课程容量配置 +export interface CapacityConfig { + max_formal_seats: number // 最大正式位数量 + max_waiting_seats: number // 最大等待位数量 + allow_overbook: boolean // 是否允许超额预订 + warning_threshold: number // 容量警告阈值 +} + +// 预设学员信息 +export interface PresetStudentInfo extends StudentInfo { + is_preset: true + preset_reason?: string + preset_by?: string + preset_at?: string +} \ No newline at end of file diff --git a/admin/src/app/views/contract/contract.vue b/admin/src/app/views/contract/contract.vue index 980fe41a..1471b27d 100644 --- a/admin/src/app/views/contract/contract.vue +++ b/admin/src/app/views/contract/contract.vue @@ -10,6 +10,20 @@ + + + + + + + + + + + + + + @@ -45,9 +59,18 @@ + + + + + + @@ -231,7 +231,7 @@ import { getWithCampusList, getAllVenueList } from '@/app/api/venue' import { img } from '@/utils/common' import { ElMessageBox, FormInstance } from 'element-plus' import Edit from '@/app/views/course_schedule/components/course-schedule-edit.vue' -import Ff from '@/app/views/course_schedule/components/ff.vue' +import CourseArrangementDetail from '@/app/views/course_schedule/components/course-arrangement-detail.vue' import { useRoute } from 'vue-router' const route = useRoute() const pageName = route.meta.title @@ -371,11 +371,10 @@ const editEvent = (data: any) => { -const ffCourseScheduleDialog: Record | null = ref(null) +const courseArrangementDetailDialog: Record | null = ref(null) const ffEvent = (data: any) => { - ffCourseScheduleDialog.value.setFormData(data.id,data.resource_id) - ffCourseScheduleDialog.value.showDialog = true + courseArrangementDetailDialog.value.open(data.id, data.resource_id) } /** diff --git a/niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php b/niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php index 2bbd5e67..ff72efa0 100644 --- a/niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php +++ b/niucloud/app/adminapi/controller/course_schedule/CourseSchedule.php @@ -156,4 +156,114 @@ class CourseSchedule extends BaseAdminController ]); return success((new CourseScheduleService())->courseInfo($data['id'])); } + + /** + * 获取完整课程安排详情(复用移动端逻辑) + * @return \think\Response + */ + public function getScheduleDetail() + { + $data = $this->request->params([ + ['schedule_id', 0] + ]); + + // 复用API端的服务层逻辑 + return success((new \app\service\api\apiService\CourseScheduleService())->getScheduleDetail($data)); + } + + /** + * 智能搜索学员 + * @return \think\Response + */ + public function searchStudents() + { + $data = $this->request->params([ + ['phone_number', ''], + ['name', ''], + ['campus_id', 0] + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->searchStudents($data)); + } + + /** + * 删除学员/请假处理 + * @return \think\Response + */ + public function removeStudent() + { + $data = $this->request->params([ + ['schedule_id', 0], + ['person_id', 0], + ['person_type', ''], + ['reason', ''] + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->removeStudent($data)); + } + + /** + * 添加学员到课程安排 + * @return \think\Response + */ + public function addSchedule() + { + $data = $this->request->params([ + ['schedule_id', 0], + ['person_type', ''], + ['schedule_type', 2], // 默认等待位 + ['student_id', 0], + ['resources_id', 0], + ['course_date', ''], + ['time_slot', ''] + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->addStudentToSchedule($data)); + } + + /** + * 学员升级(等待位→正式位) + * @return \think\Response + */ + public function upgradeStudent() + { + $data = $this->request->params([ + ['schedule_id', 0], + ['person_id', 0], + ['from_type', 2], // 从等待位 + ['to_type', 1] // 到正式位 + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->upgradeStudent($data)); + } + + /** + * 更新学员签到状态 + * @return \think\Response + */ + public function updateStudentStatus() + { + $data = $this->request->params([ + ['schedule_id', 0], + ['person_id', 0], + ['status', 0], + ['reason', ''] + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->updateStudentStatus($data)); + } + + /** + * 恢复学员(取消请假) + * @return \think\Response + */ + public function restoreStudent() + { + $data = $this->request->params([ + ['schedule_id', 0], + ['person_id', 0] + ]); + + return success((new \app\service\api\apiService\CourseScheduleService())->restoreStudent($data)); + } } diff --git a/niucloud/app/adminapi/route/course_schedule.php b/niucloud/app/adminapi/route/course_schedule.php index 9bcd35c7..bd9db59c 100644 --- a/niucloud/app/adminapi/route/course_schedule.php +++ b/niucloud/app/adminapi/route/course_schedule.php @@ -42,6 +42,14 @@ Route::group('course_schedule', function () { Route::post('addSchedule', 'course_schedule.CourseSchedule/addSchedule'); + // 新增统一接口(复用移动端逻辑) + Route::get('getScheduleDetail', 'course_schedule.CourseSchedule/getScheduleDetail'); + Route::get('searchStudents', 'course_schedule.CourseSchedule/searchStudents'); + Route::post('removeStudent', 'course_schedule.CourseSchedule/removeStudent'); + Route::post('upgradeStudent', 'course_schedule.CourseSchedule/upgradeStudent'); + Route::post('updateStudentStatus', 'course_schedule.CourseSchedule/updateStudentStatus'); + Route::post('restoreStudent', 'course_schedule.CourseSchedule/restoreStudent'); + })->middleware([ AdminCheckToken::class, AdminCheckRole::class, diff --git a/niucloud/app/common.php b/niucloud/app/common.php index ab54e83b..40e351be 100644 --- a/niucloud/app/common.php +++ b/niucloud/app/common.php @@ -1605,4 +1605,663 @@ function get_personnel_by_campus_role($campus_id, $role_ids = []) \think\facade\Log::write('获取校区角色人员失败:' . $e->getMessage()); return []; } +} + +// ==================== 合同系统函数 ==================== + +/** + * 获取当前日期 + * @return string + */ +function get_current_date() +{ + return date('Y-m-d'); +} + +/** + * 获取当前时间 + * @return string + */ +function get_current_time() +{ + return date('H:i:s'); +} + +/** + * 获取当前日期时间 + * @return string + */ +function get_current_datetime() +{ + return date('Y-m-d H:i:s'); +} + +/** + * 获取当前年份 + * @return string + */ +function get_current_year() +{ + return date('Y'); +} + +/** + * 获取当前月份 + * @return string + */ +function get_current_month() +{ + return date('m'); +} + +/** + * 获取当前日 + * @return string + */ +function get_current_day() +{ + return date('d'); +} + +/** + * 获取当前周 + * @return string + */ +function get_current_week() +{ + return date('W'); +} + +/** + * 获取当前季度 + * @return string + */ +function get_current_quarter() +{ + $month = (int)date('m'); + return ceil($month / 3); +} + +/** + * 获取当前校区名称 + * @param int $campus_id 校区ID,为空时尝试从当前用户获取 + * @return string + */ +function get_current_campus($campus_id = 0) +{ + try { + if (empty($campus_id)) { + // 尝试从当前登录用户获取校区信息 + // 这里需要根据实际的用户会话机制来获取 + return '默认校区'; + } + + $campus = \think\facade\Db::table('school_campus') + ->where('id', $campus_id) + ->value('campus_name'); + + return $campus ?: '未知校区'; + } catch (\Exception $e) { + return '获取校区失败'; + } +} + +/** + * 获取当前登录用户信息 + * @return string + */ +function get_current_login_user() +{ + try { + // 这里需要根据实际的用户会话机制来获取当前用户 + // 可以从JWT token或session中获取 + return '当前用户'; + } catch (\Exception $e) { + return '获取用户失败'; + } +} + +/** + * 获取当前员工信息 + * @return string + */ +function get_current_personnel() +{ + try { + // 这里需要根据实际的员工会话机制来获取当前员工 + return '当前员工'; + } catch (\Exception $e) { + return '获取员工失败'; + } +} + +/** + * 获取合同生成时间 + * @return string + */ +function get_contract_generate_time() +{ + return date('Y-m-d H:i:s'); +} + +/** + * 获取合同签署时间 + * @return string + */ +function get_contract_sign_time() +{ + return date('Y-m-d H:i:s'); +} + +/** + * 获取系统名称 + * @return string + */ +function get_system_name() +{ + return '智慧教务系统'; +} + +/** + * 获取系统版本 + * @return string + */ +function get_system_version() +{ + return '1.0.0'; +} + +/** + * 生成随机数字 + * @param int $length 数字长度,默认6位 + * @return string + */ +function get_random_number($length = 6) +{ + $min = pow(10, $length - 1); + $max = pow(10, $length) - 1; + return str_pad(rand($min, $max), $length, '0', STR_PAD_LEFT); +} + +/** + * 生成合同序号 + * @param string $prefix 前缀,默认为HT + * @return string + */ +function get_contract_sequence($prefix = 'HT') +{ + return $prefix . date('Ymd') . get_random_number(4); +} + +/** + * 格式化货币 + * @param float $amount 金额 + * @param string $currency 货币符号,默认为¥ + * @return string + */ +function format_currency($amount, $currency = '¥') +{ + return $currency . number_format($amount, 2); +} + +/** + * 上课规则配置函数 + * @param array $params 配置参数 + * @return array + */ +function get_class_schedule_rules($params = []) +{ + // 默认上课规则配置 + $default_rules = [ + 'class_duration' => 45, // 课时长度(分钟) + 'break_duration' => 15, // 课间休息时间(分钟) + 'start_time' => '08:00', // 开始时间 + 'end_time' => '18:00', // 结束时间 + 'lunch_break_start' => '12:00', // 午休开始时间 + 'lunch_break_end' => '14:00', // 午休结束时间 + 'weekend_enabled' => true, // 是否允许周末上课 + 'holiday_enabled' => false, // 是否允许节假日上课 + 'max_students_per_class' => 20, // 每班最大学生数 + 'min_students_per_class' => 5, // 每班最小学生数 + 'advance_booking_days' => 7, // 提前预约天数 + 'cancel_deadline_hours' => 24, // 取消课程截止时间(小时) + ]; + + // 合并自定义参数 + return array_merge($default_rules, $params); +} + +/** + * 生成上课时间表 + * @param string $start_date 开始日期 + * @param string $end_date 结束日期 + * @param array $rules 上课规则 + * @return array + */ +function generate_class_schedule($start_date, $end_date, $rules = []) +{ + $rules = get_class_schedule_rules($rules); + $schedule = []; + + $current_date = strtotime($start_date); + $end_timestamp = strtotime($end_date); + + while ($current_date <= $end_timestamp) { + $date_str = date('Y-m-d', $current_date); + $day_of_week = date('w', $current_date); + + // 检查是否为周末 + if (!$rules['weekend_enabled'] && ($day_of_week == 0 || $day_of_week == 6)) { + $current_date = strtotime('+1 day', $current_date); + continue; + } + + // 生成当天的课程时间段 + $daily_schedule = generate_daily_time_slots($date_str, $rules); + if (!empty($daily_schedule)) { + $schedule[$date_str] = $daily_schedule; + } + + $current_date = strtotime('+1 day', $current_date); + } + + return $schedule; +} + +/** + * 生成单日课程时间段 + * @param string $date 日期 + * @param array $rules 规则配置 + * @return array + */ +function generate_daily_time_slots($date, $rules) +{ + $slots = []; + $current_time = strtotime($date . ' ' . $rules['start_time']); + $end_time = strtotime($date . ' ' . $rules['end_time']); + $lunch_start = strtotime($date . ' ' . $rules['lunch_break_start']); + $lunch_end = strtotime($date . ' ' . $rules['lunch_break_end']); + + $slot_number = 1; + + while ($current_time < $end_time) { + $slot_end = $current_time + ($rules['class_duration'] * 60); + + // 检查是否与午休时间冲突 + if ($current_time < $lunch_end && $slot_end > $lunch_start) { + $current_time = $lunch_end; + continue; + } + + $slots[] = [ + 'slot_number' => $slot_number, + 'start_time' => date('H:i', $current_time), + 'end_time' => date('H:i', $slot_end), + 'duration' => $rules['class_duration'], + 'available' => true + ]; + + $current_time = $slot_end + ($rules['break_duration'] * 60); + $slot_number++; + } + + return $slots; +} + +/** + * 验证上课时间冲突 + * @param string $date 日期 + * @param string $start_time 开始时间 + * @param string $end_time 结束时间 + * @param int $teacher_id 教师ID + * @param int $classroom_id 教室ID + * @return bool + */ +function check_class_time_conflict($date, $start_time, $end_time, $teacher_id = 0, $classroom_id = 0) +{ + try { + $where = [ + ['date', '=', $date], + ['status', '=', 1], // 正常状态 + ]; + + // 时间冲突检查:新课程的开始时间在已有课程时间范围内,或新课程的结束时间在已有课程时间范围内 + $time_conflict = [ + ['start_time', '<', $end_time], + ['end_time', '>', $start_time] + ]; + + $where = array_merge($where, $time_conflict); + + // 检查教师冲突 + if ($teacher_id > 0) { + $teacher_conflict = \think\facade\Db::table('school_class_schedule') + ->where($where) + ->where('teacher_id', $teacher_id) + ->count(); + + if ($teacher_conflict > 0) { + return true; // 有冲突 + } + } + + // 检查教室冲突 + if ($classroom_id > 0) { + $classroom_conflict = \think\facade\Db::table('school_class_schedule') + ->where($where) + ->where('classroom_id', $classroom_id) + ->count(); + + if ($classroom_conflict > 0) { + return true; // 有冲突 + } + } + + return false; // 无冲突 + + } catch (\Exception $e) { + \think\facade\Log::write('检查上课时间冲突失败:' . $e->getMessage()); + return true; // 异常情况下认为有冲突,确保安全 + } +} + +// ==================== 签名图片处理函数 ==================== + +/** + * 获取校区印章图片URL + * @param int $campus_id 校区ID + * @return string 印章图片URL + */ +function get_campus_seal_image($campus_id) +{ + try { + if (empty($campus_id)) { + return ''; + } + + $seal_image = \think\facade\Db::table('school_campus') + ->where('id', $campus_id) + ->value('seal_image'); + + if (empty($seal_image)) { + return ''; + } + + // 如果已经是完整URL,直接返回 + if (str_contains($seal_image, 'http://') || str_contains($seal_image, 'https://')) { + return $seal_image; + } + + // 转换为完整的URL路径 + return get_file_url($seal_image); + + } catch (\Exception $e) { + \think\facade\Log::write('获取校区印章图片失败:' . $e->getMessage()); + return ''; + } +} + +/** + * 验证并处理上传的签名图片 + * @param array $file 上传的文件信息 + * @param array $options 配置选项 + * @return array 处理结果 + */ +function process_signature_image($file, $options = []) +{ + try { + // 默认配置 + $default_options = [ + 'max_size' => 2 * 1024 * 1024, // 2MB + 'allowed_types' => ['jpg', 'jpeg', 'png', 'gif'], + 'save_path' => 'upload/signature/', + 'compress_quality' => 80, // 图片压缩质量 + 'max_width' => 800, // 最大宽度 + 'max_height' => 600, // 最大高度 + ]; + + $config = array_merge($default_options, $options); + + // 验证文件大小 + if ($file['size'] > $config['max_size']) { + return [ + 'success' => false, + 'message' => '图片文件大小不能超过' . ($config['max_size'] / 1024 / 1024) . 'MB' + ]; + } + + // 验证文件类型 + $file_ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if (!in_array($file_ext, $config['allowed_types'])) { + return [ + 'success' => false, + 'message' => '只支持' . implode('、', $config['allowed_types']) . '格式的图片' + ]; + } + + // 创建保存目录 + $save_dir = $config['save_path'] . date('Y/m/d') . '/'; + if (!is_dir($save_dir) && !mkdir($save_dir, 0755, true)) { + return [ + 'success' => false, + 'message' => '创建保存目录失败' + ]; + } + + // 生成文件名 + $filename = 'signature_' . date('YmdHis') . '_' . uniqid() . '.' . $file_ext; + $save_path = $save_dir . $filename; + + // 移动文件 + if (!move_uploaded_file($file['tmp_name'], $save_path)) { + return [ + 'success' => false, + 'message' => '文件保存失败' + ]; + } + + // 压缩图片(如果需要) + $compressed_path = compress_signature_image($save_path, $config); + if ($compressed_path) { + $save_path = $compressed_path; + } + + return [ + 'success' => true, + 'message' => '签名图片上传成功', + 'data' => [ + 'file_path' => $save_path, + 'file_url' => get_file_url($save_path), + 'file_size' => filesize($save_path), + 'original_name' => $file['name'] + ] + ]; + + } catch (\Exception $e) { + \think\facade\Log::write('处理签名图片失败:' . $e->getMessage()); + return [ + 'success' => false, + 'message' => '处理签名图片失败:' . $e->getMessage() + ]; + } +} + +/** + * 压缩签名图片 + * @param string $source_path 源文件路径 + * @param array $config 压缩配置 + * @return string|false 压缩后的文件路径 + */ +function compress_signature_image($source_path, $config) +{ + try { + // 获取图片信息 + $image_info = getimagesize($source_path); + if (!$image_info) { + return false; + } + + [$width, $height, $type] = $image_info; + + // 如果图片尺寸已经符合要求,不需要压缩 + if ($width <= $config['max_width'] && $height <= $config['max_height']) { + return $source_path; + } + + // 计算新尺寸 + $ratio = min($config['max_width'] / $width, $config['max_height'] / $height); + $new_width = intval($width * $ratio); + $new_height = intval($height * $ratio); + + // 创建源图像资源 + switch ($type) { + case IMAGETYPE_JPEG: + $source_image = imagecreatefromjpeg($source_path); + break; + case IMAGETYPE_PNG: + $source_image = imagecreatefrompng($source_path); + break; + case IMAGETYPE_GIF: + $source_image = imagecreatefromgif($source_path); + break; + default: + return false; + } + + if (!$source_image) { + return false; + } + + // 创建新图像 + $new_image = imagecreatetruecolor($new_width, $new_height); + + // 保持透明度(PNG/GIF) + if ($type == IMAGETYPE_PNG || $type == IMAGETYPE_GIF) { + imagealphablending($new_image, false); + imagesavealpha($new_image, true); + $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); + imagefill($new_image, 0, 0, $transparent); + } + + // 重新采样 + imagecopyresampled($new_image, $source_image, 0, 0, 0, 0, + $new_width, $new_height, $width, $height); + + // 生成压缩后的文件路径 + $compressed_path = str_replace('.', '_compressed.', $source_path); + + // 保存压缩后的图片 + $saved = false; + switch ($type) { + case IMAGETYPE_JPEG: + $saved = imagejpeg($new_image, $compressed_path, $config['compress_quality']); + break; + case IMAGETYPE_PNG: + $saved = imagepng($new_image, $compressed_path); + break; + case IMAGETYPE_GIF: + $saved = imagegif($new_image, $compressed_path); + break; + } + + // 释放资源 + imagedestroy($source_image); + imagedestroy($new_image); + + if ($saved) { + // 删除原文件 + unlink($source_path); + return $compressed_path; + } + + return false; + + } catch (\Exception $e) { + \think\facade\Log::write('压缩签名图片失败:' . $e->getMessage()); + return false; + } +} + +/** + * 获取用户签名记录 + * @param int $user_id 用户ID + * @param string $user_type 用户类型(staff/student/member) + * @return array 签名记录 + */ +function get_user_signature_records($user_id, $user_type = 'staff') +{ + try { + if (empty($user_id)) { + return []; + } + + $signatures = \think\facade\Db::table('school_user_signatures') + ->where([ + ['user_id', '=', $user_id], + ['user_type', '=', $user_type], + ['status', '=', 1], // 有效状态 + ['deleted_at', '=', 0] + ]) + ->order('created_at', 'desc') + ->select() + ->toArray(); + + // 处理签名图片URL + foreach ($signatures as &$signature) { + if (!empty($signature['signature_image'])) { + $signature['signature_url'] = get_file_url($signature['signature_image']); + } + } + + return $signatures; + + } catch (\Exception $e) { + \think\facade\Log::write('获取用户签名记录失败:' . $e->getMessage()); + return []; + } +} + +/** + * 保存用户签名记录 + * @param array $data 签名数据 + * @return int|false 签名记录ID + */ +function save_user_signature($data) +{ + try { + $required_fields = ['user_id', 'user_type', 'signature_image']; + foreach ($required_fields as $field) { + if (empty($data[$field])) { + throw new \Exception("缺少必要字段:{$field}"); + } + } + + $insert_data = [ + 'user_id' => $data['user_id'], + 'user_type' => $data['user_type'], + 'signature_image' => $data['signature_image'], + 'signature_name' => $data['signature_name'] ?? '用户签名', + 'signature_type' => $data['signature_type'] ?? 'image', // image/canvas/handwrite + 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? '', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', + 'status' => 1, + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'deleted_at' => 0 + ]; + + $signature_id = \think\facade\Db::table('school_user_signatures') + ->insertGetId($insert_data); + + if ($signature_id) { + \think\facade\Log::write('保存用户签名记录成功:' . json_encode($insert_data)); + return $signature_id; + } + + return false; + + } catch (\Exception $e) { + \think\facade\Log::write('保存用户签名记录失败:' . $e->getMessage()); + return false; + } } \ No newline at end of file diff --git a/niucloud/app/service/api/apiService/CourseScheduleService.php b/niucloud/app/service/api/apiService/CourseScheduleService.php index 97981dbf..f7f07af1 100644 --- a/niucloud/app/service/api/apiService/CourseScheduleService.php +++ b/niucloud/app/service/api/apiService/CourseScheduleService.php @@ -1667,6 +1667,597 @@ class CourseScheduleService extends BaseApiService } } + /** + * 获取完整课程安排详情(包含正式位和等待位学员分类) + * @param array $data 请求参数 + * @return array 课程安排详细信息 + */ + public function getScheduleDetail(array $data) + { + try { + $scheduleId = $data['schedule_id']; + + if (empty($scheduleId)) { + return [ + 'code' => 0, + 'msg' => '课程安排ID不能为空', + 'data' => null + ]; + } + + // 获取课程基本信息 + $scheduleInfo = Db::name('course_schedule') + ->alias('cs') + ->leftJoin($this->prefix . 'course c', 'cs.course_id = c.id') + ->leftJoin($this->prefix . 'venue v', 'cs.venue_id = v.id') + ->leftJoin($this->prefix . 'campus cap', 'cs.campus_id = cap.id') + ->leftJoin($this->prefix . 'personnel coach', 'cs.coach_id = coach.id') + ->leftJoin($this->prefix . 'class cla', 'cs.class_id = cla.id') + ->where('cs.id', $scheduleId) + ->where('cs.deleted_at', 0) + ->field([ + 'cs.*', + 'c.course_name', + 'v.venue_name', + 'cap.campus_name', + 'coach.name as coach_name', + 'cla.class_name' + ]) + ->find(); + + if (!$scheduleInfo) { + return [ + 'code' => 0, + 'msg' => '课程安排不存在', + 'data' => null + ]; + } + + // 获取学员列表 + $students = Db::name('person_course_schedule') + ->alias('pcs') + ->leftJoin($this->prefix . 'customer_resources cr', 'pcs.resources_id = cr.id') + ->leftJoin($this->prefix . 'student s', 'pcs.student_id = s.id') + ->leftJoin($this->prefix . 'student_courses sc', 'pcs.student_id = sc.student_id AND sc.course_id = pcs.schedule_id') + ->where('pcs.schedule_id', $scheduleId) + ->where('pcs.deleted_at', 0) + ->field([ + 'pcs.id', + 'pcs.person_id', + 'pcs.student_id', + 'pcs.resources_id', + 'pcs.person_type', + 'pcs.schedule_type', + 'pcs.status', + 'cr.name as resource_name', + 'cr.phone_number', + 'cr.age', + 's.name as student_name', + 's.contact_phone as student_phone', + 'sc.end_date', + 'sc.use_total_hours as used_session_count', + 'sc.total_hours as total_session_count' + ]) + ->group('pcs.id') // 防止重复数据 + ->select() + ->toArray(); + + // 分类处理学员数据 + $formalStudents = []; + $waitingStudents = []; + + foreach ($students as $student) { + $studentData = $this->formatStudentDataForDetail($student); + + // 如果schedule_type为空,默认为等待位(2) + $scheduleType = $student['schedule_type'] ?? 2; + + if ($scheduleType == 1) { + $formalStudents[] = $studentData; + } else { + $waitingStudents[] = $studentData; + } + } + + // 计算空位 + $maxFormalSeats = $scheduleInfo['max_students'] ?? 10; + $maxWaitingSeats = 5; // 等待位默认5个 + + $formalEmptySeats = max(0, $maxFormalSeats - count($formalStudents)); + $waitingEmptySeats = max(0, $maxWaitingSeats - count($waitingStudents)); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => [ + 'schedule_info' => $scheduleInfo, + 'formal_students' => $formalStudents, + 'waiting_students' => $waitingStudents, + 'formal_empty_seats' => range(1, $formalEmptySeats), + 'waiting_empty_seats' => range(1, $waitingEmptySeats) + ] + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取课程安排详情失败:' . $e->getMessage(), + 'data' => null + ]; + } + } + + /** + * 格式化学员数据用于详情显示 + * @param array $student 学员数据 + * @return array 格式化后的学员数据 + */ + private function formatStudentDataForDetail($student) + { + // 获取课程进度 + $courseProgress = [ + 'used' => $student['used_session_count'] ?? 0, + 'total' => $student['total_session_count'] ?? 0, + 'percentage' => 0 + ]; + + if ($courseProgress['total'] > 0) { + $courseProgress['percentage'] = round(($courseProgress['used'] / $courseProgress['total']) * 100, 1); + } + + return [ + 'id' => $student['id'], + 'person_id' => $student['person_id'], + 'student_id' => $student['student_id'], + 'resources_id' => $student['resources_id'], + 'name' => $student['resource_name'] ?: $student['student_name'], + 'phone' => $student['phone_number'] ?: $student['student_phone'], + 'age' => $student['age'] ?? '', + 'person_type' => $student['person_type'], + 'schedule_type' => $student['schedule_type'] ?? 2, // 默认为等待位 + 'status' => $student['status'], + 'courseStatus' => $this->getStudentStatusText($student['status']), + 'course_progress' => $courseProgress, + 'renewal_status' => false, // 可以根据实际逻辑判断 + 'student_course_info' => [ + 'end_date' => $student['end_date'] ?? '' + ] + ]; + } + + /** + * 智能搜索学员 + * @param array $data 搜索参数 + * @return array 搜索结果 + */ + public function searchStudents(array $data) + { + try { + $phoneNumber = $data['phone_number'] ?? ''; + $name = $data['name'] ?? ''; + + if (empty($phoneNumber) && empty($name)) { + return [ + 'code' => 1, + 'msg' => '请输入搜索条件', + 'data' => [] + ]; + } + + $where = []; + + // 构建搜索条件 + if (!empty($phoneNumber)) { + $where[] = ['cr.phone_number', 'like', '%' . $phoneNumber . '%']; + } + + if (!empty($name)) { + $where[] = ['cr.name', 'like', '%' . $name . '%']; + } + + // 搜索客户资源表 + $results = Db::name('customer_resources') + ->alias('cr') + ->leftJoin($this->prefix . 'student s', 'cr.member_id = s.user_id') + ->leftJoin($this->prefix . 'student_courses sc', 's.id = sc.student_id') + ->where($where) + ->where('cr.deleted_at', 0) + ->field([ + 'cr.id as resource_id', + 'cr.name', + 'cr.phone_number', + 'cr.age', + 'cr.member_id', + 's.id as student_id', + 'sc.id as student_course_id', + 'sc.status as course_status', + 'sc.total_hours as total_session_count', + 'sc.use_total_hours as used_session_count' + ]) + ->limit(20) + ->select() + ->toArray(); + + // 处理结果数据 + $searchResults = []; + foreach ($results as $result) { + $searchResults[] = [ + 'id' => $result['resource_id'], + 'name' => $result['name'], + 'phone_number' => $result['phone_number'], + 'age' => $result['age'], + 'member_id' => $result['member_id'], + 'student_id' => $result['student_id'] ?? 0, + 'resource_id' => $result['resource_id'], + 'is_formal_student' => !empty($result['student_course_id']), + 'course_info' => [ + 'course_status' => $result['course_status'] ?? '', + 'total_sessions' => $result['total_session_count'] ?? 0, + 'used_sessions' => $result['used_session_count'] ?? 0 + ] + ]; + } + + return [ + 'code' => 1, + 'msg' => '搜索成功', + 'data' => $searchResults + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '搜索失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } + + /** + * 删除学员/请假处理 + * @param array $data 删除参数 + * @return array 处理结果 + */ + public function removeStudent(array $data) + { + try { + $scheduleId = $data['schedule_id'] ?? 0; + $personId = $data['person_id'] ?? 0; + $personType = $data['person_type'] ?? ''; + $reason = $data['reason'] ?? '删除学员'; + + if (empty($scheduleId) || empty($personId)) { + return [ + 'code' => 0, + 'msg' => '参数不完整' + ]; + } + + // 开启事务 + 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' => '找不到学员记录' + ]; + } + + // 判断是请假还是删除 + if (strpos($reason, '请假') !== false) { + // 请假处理:更新状态为请假 + $result = Db::name('person_course_schedule') + ->where('id', $enrollment['id']) + ->update([ + 'status' => 2, // 请假状态 + 'updated_at' => date('Y-m-d H:i:s'), + 'remark' => $reason + ]); + } else { + // 删除处理:软删除记录 + $result = Db::name('person_course_schedule') + ->where('id', $enrollment['id']) + ->update([ + 'deleted_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'remark' => $reason + ]); + } + + if ($result === false) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '删除失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '删除成功' + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '删除学员失败:' . $e->getMessage() + ]; + } + } + + /** + * 添加学员到课程安排 + * @param array $data 添加学员的数据 + * @return array 添加结果 + */ + public function addStudentToSchedule(array $data) + { + try { + $scheduleId = $data['schedule_id'] ?? 0; + $personType = $data['person_type'] ?? ''; + $scheduleType = $data['schedule_type'] ?? 2; // 默认等待位 + + if (empty($scheduleId) || empty($personType)) { + return [ + 'code' => 0, + 'msg' => '课程安排ID和人员类型不能为空' + ]; + } + + // 开启事务 + Db::startTrans(); + + // 查询课程安排信息 + $schedule = Db::name('course_schedule') + ->where('id', $scheduleId) + ->where('deleted_at', 0) + ->find(); + + if (!$schedule) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '课程安排不存在或已被删除' + ]; + } + + // 准备插入数据 + $insertData = [ + 'schedule_id' => $scheduleId, + 'person_type' => $personType, + 'schedule_type' => $scheduleType, + 'course_type' => $scheduleType == 1 ? 1 : 3, // 正式位为正常课程,等待位为等待位课程 + 'status' => 0, // 待上课 + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'deleted_at' => 0 + ]; + + // 根据人员类型设置对应的ID + if ($personType === 'student') { + $studentId = $data['student_id'] ?? 0; + if (empty($studentId)) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '学员ID不能为空' + ]; + } + $insertData['student_id'] = $studentId; + $insertData['person_id'] = $studentId; + } else { + $resourcesId = $data['resources_id'] ?? 0; + if (empty($resourcesId)) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '客户资源ID不能为空' + ]; + } + $insertData['resources_id'] = $resourcesId; + $insertData['person_id'] = $resourcesId; + } + + // 检查是否已经添加过 + $whereCheck = [ + 'schedule_id' => $scheduleId, + 'deleted_at' => 0 + ]; + + if ($personType === 'student') { + $whereCheck['student_id'] = $insertData['student_id']; + } else { + $whereCheck['resources_id'] = $insertData['resources_id']; + } + + $exists = Db::name('person_course_schedule') + ->where($whereCheck) + ->find(); + + if ($exists) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '该学员已经在此课程安排中' + ]; + } + + // 检查容量限制 + if ($scheduleType == 1) { + // 检查正式位容量 + $formalCount = Db::name('person_course_schedule') + ->where('schedule_id', $scheduleId) + ->where('schedule_type', 1) + ->where('deleted_at', 0) + ->count(); + + $maxStudents = intval($schedule['max_students'] ?? 10); + if ($formalCount >= $maxStudents) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '正式位已满,无法添加' + ]; + } + } else { + // 检查等待位容量(默认最多5个) + $waitingCount = Db::name('person_course_schedule') + ->where('schedule_id', $scheduleId) + ->where('schedule_type', 2) + ->where('deleted_at', 0) + ->count(); + + if ($waitingCount >= 5) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '等待位已满,无法添加' + ]; + } + } + + // 插入学员记录 + $result = Db::name('person_course_schedule')->insert($insertData); + + if (!$result) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '添加学员失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '添加学员成功' + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '添加学员失败:' . $e->getMessage() + ]; + } + } + + /** + * 学员升级(等待位→正式位) + * @param array $data 升级参数 + * @return array 升级结果 + */ + public function upgradeStudent(array $data) + { + try { + $scheduleId = $data['schedule_id'] ?? 0; + $personId = $data['person_id'] ?? 0; + $fromType = $data['from_type'] ?? 2; + $toType = $data['to_type'] ?? 1; + + if (empty($scheduleId) || empty($personId)) { + return [ + 'code' => 0, + 'msg' => '参数不完整' + ]; + } + + // 验证升级方向 + if ($fromType != 2 || $toType != 1) { + return [ + 'code' => 0, + 'msg' => '只能从等待位升级到正式位' + ]; + } + + // 开启事务 + Db::startTrans(); + + // 查找等待位学员记录 + $enrollment = Db::name('person_course_schedule') + ->where('schedule_id', $scheduleId) + ->where('person_id', $personId) + ->where('schedule_type', 2) + ->where('deleted_at', 0) + ->find(); + + if (!$enrollment) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '找不到等待位学员记录' + ]; + } + + // 检查正式位容量 + $schedule = Db::name('course_schedule') + ->where('id', $scheduleId) + ->find(); + + $formalCount = Db::name('person_course_schedule') + ->where('schedule_id', $scheduleId) + ->where('schedule_type', 1) + ->where('deleted_at', 0) + ->count(); + + $maxStudents = intval($schedule['max_students'] ?? 10); + if ($formalCount >= $maxStudents) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '正式位已满,无法升级' + ]; + } + + // 更新学员记录 + $result = Db::name('person_course_schedule') + ->where('id', $enrollment['id']) + ->update([ + 'schedule_type' => 1, + 'course_type' => 1, // 等待位课程改为正式课 + 'updated_at' => date('Y-m-d H:i:s'), + 'remark' => '从等待位升级为正式位' + ]); + + if ($result === false) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '升级失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '升级成功' + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '升级失败:' . $e->getMessage() + ]; + } + } + /** * 升级等待位学员为正式学员 * @param array $data 升级数据 @@ -1794,4 +2385,160 @@ class CourseScheduleService extends BaseApiService ]; } } + + /** + * 更新学员签到状态 + * @param array $data 更新参数 + * @return array 更新结果 + */ + public function updateStudentStatus(array $data) + { + try { + $scheduleId = $data['schedule_id'] ?? 0; + $personId = $data['person_id'] ?? 0; + $status = $data['status'] ?? 0; + $reason = $data['reason'] ?? ''; + + if (empty($scheduleId) || empty($personId)) { + return [ + 'code' => 0, + 'msg' => '参数不完整' + ]; + } + + // 验证状态值 + if (!in_array($status, [0, 1, 2])) { + return [ + 'code' => 0, + 'msg' => '无效的状态值' + ]; + } + + // 开启事务 + 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) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '更新失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '更新成功' + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '更新失败:' . $e->getMessage() + ]; + } + } + + /** + * 恢复学员(取消请假) + * @param array $data 恢复参数 + * @return array 恢复结果 + */ + public function restoreStudent(array $data) + { + try { + $scheduleId = $data['schedule_id'] ?? 0; + $personId = $data['person_id'] ?? 0; + + if (empty($scheduleId) || empty($personId)) { + return [ + 'code' => 0, + 'msg' => '参数不完整' + ]; + } + + // 开启事务 + 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' => '找不到学员记录' + ]; + } + + // 恢复学员状态 + $result = Db::name('person_course_schedule') + ->where('id', $enrollment['id']) + ->update([ + 'status' => 0, // 恢复为待上课状态 + 'updated_at' => date('Y-m-d H:i:s'), + 'remark' => '取消请假,恢复正常状态' + ]); + + if ($result === false) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '恢复失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '恢复成功' + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '恢复失败:' . $e->getMessage() + ]; + } + } } \ No newline at end of file diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js index a9704d3d..df45f129 100644 --- a/uniapp/api/apiRoute.js +++ b/uniapp/api/apiRoute.js @@ -1293,6 +1293,440 @@ export default { return await http.post('/course/updateStudentStatus', data) }, + //↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓ + + // 获取课程安排详情(统一接口) + async getScheduleDetail(data = {}) { + try { + const response = await http.get('/course/scheduleDetail', data) + return response + } catch (error) { + console.error('获取课程安排详情失败:', error) + // 降级到 Mock 数据 + return await this.getScheduleDetailMock(data) + } + }, + + // 搜索学员(统一接口) + async searchStudents(data = {}) { + try { + const response = await http.get('/course/searchStudents', data) + return response + } catch (error) { + console.error('搜索学员失败:', error) + // 降级到 Mock 数据 + return await this.searchStudentsMock(data) + } + }, + + // 添加学员到课程安排(统一接口) + async addSchedule(data = {}) { + try { + const response = await http.post('/course/addStudentToSchedule', data) + return response + } catch (error) { + console.error('添加学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '添加学员成功(模拟)', + data: { id: Date.now() } + } + } + }, + + // 移除学员(统一接口) + async removeStudent(data = {}) { + try { + const response = await http.post('/course/removeStudentFromSchedule', data) + return response + } catch (error) { + console.error('移除学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '移除学员成功(模拟)', + data: {} + } + } + }, + + // 升级学员(统一接口) + async upgradeStudent(data = {}) { + try { + const response = await http.post('/course/upgradeStudent', data) + return response + } catch (error) { + console.error('升级学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '升级学员成功(模拟)', + data: {} + } + } + }, + + // 更新学员状态(统一接口) + async updateStudentStatus(data = {}) { + try { + const response = await http.post('/course/updateStudentStatus', data) + return response + } catch (error) { + console.error('更新学员状态失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '更新状态成功(模拟)', + data: {} + } + } + }, + + // 恢复学员(统一接口) + async restoreStudent(data = {}) { + try { + const response = await http.post('/course/restoreStudent', data) + return response + } catch (error) { + console.error('恢复学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '恢复学员成功(模拟)', + data: {} + } + } + }, + + //↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排详情管理接口(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓ + + // 获取课程安排详情(新统一接口 - 对接admin端) + async getCourseArrangementDetail(data = {}) { + try { + const response = await http.get('/course/scheduleDetail', data) + return response + } catch (error) { + console.error('获取课程安排详情失败:', error) + // 降级到 Mock 数据 + return await this.getCourseArrangementDetailMock(data) + } + }, + + // 搜索学员(新统一接口 - 对接admin端) + async searchStudentsForArrangement(data = {}) { + try { + const response = await http.get('/course/searchStudents', data) + return response + } catch (error) { + console.error('搜索学员失败:', error) + // 降级到 Mock 数据 + return await this.searchStudentsForArrangementMock(data) + } + }, + + // 添加学员到课程安排(新统一接口 - 对接admin端) + async addStudentToArrangement(data = {}) { + try { + const response = await http.post('/course/addStudentToSchedule', data) + return response + } catch (error) { + console.error('添加学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '添加学员成功(模拟)', + data: { id: Date.now() } + } + } + }, + + // 移除学员(新统一接口 - 对接admin端) + async removeStudentFromArrangement(data = {}) { + try { + const response = await http.post('/course/removeStudentFromSchedule', data) + return response + } catch (error) { + console.error('移除学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '移除学员成功(模拟)', + data: {} + } + } + }, + + // 升级学员(新统一接口 - 对接admin端) + async upgradeStudentInArrangement(data = {}) { + try { + const response = await http.post('/course/upgradeStudent', data) + return response + } catch (error) { + console.error('升级学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '升级学员成功(模拟)', + data: {} + } + } + }, + + // 更新学员状态(新统一接口 - 对接admin端) + async updateStudentStatusInArrangement(data = {}) { + try { + const response = await http.post('/course/updateStudentStatus', data) + return response + } catch (error) { + console.error('更新学员状态失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '更新状态成功(模拟)', + data: {} + } + } + }, + + // 恢复学员(新统一接口 - 对接admin端) + async restoreStudentInArrangement(data = {}) { + try { + const response = await http.post('/course/restoreStudent', data) + return response + } catch (error) { + console.error('恢复学员失败:', error) + // 返回模拟成功响应 + return { + code: 1, + msg: '恢复学员成功(模拟)', + data: {} + } + } + }, + + //↓↓↓↓↓↓↓↓↓↓↓↓-----新统一课程安排Mock数据(与admin端保持一致)-----↓↓↓↓↓↓↓↓↓↓↓↓ + + // 模拟课程安排详情数据(对应admin端格式) + async getCourseArrangementDetailMock(data = {}) { + await new Promise(resolve => setTimeout(resolve, 500)) + + const mockStudents = [ + { + id: 1, + person_id: 1, + student_id: 1, + name: '张小明', + phone: '13800138001', + age: 8, + person_type: 'student', + person_type_text: '正式学员', + schedule_type: 1, + status: 0, + course_progress: { + used: 5, + total: 20, + percentage: 25 + } + }, + { + id: 2, + person_id: 2, + resources_id: 2, + name: '李小红', + phone: '13800138002', + age: 7, + person_type: 'customer_resource', + person_type_text: '潜在客户', + schedule_type: 2, + status: 0, + course_progress: { + used: 0, + total: 1, + percentage: 0 + } + } + ] + + return { + code: 1, + data: { + schedule_info: { + id: parseInt(data.schedule_id) || 1, + course_name: '少儿体适能课程', + course_date: '2025-08-16', + time_slot: '09:00-10:00', + venue_name: '训练馆A', + campus_name: '总部校区', + coach_name: '张教练', + status: 'upcoming', + status_text: '即将开始' + }, + formal_students: mockStudents.filter(s => s.schedule_type === 1), + waiting_students: mockStudents.filter(s => s.schedule_type === 2), + formal_empty_seats: [2, 3, 4, 5], + waiting_empty_seats: [2, 3] + }, + msg: 'SUCCESS' + } + }, + + // 模拟搜索学员数据(对应admin端格式) + async searchStudentsForArrangementMock(data = {}) { + await new Promise(resolve => setTimeout(resolve, 300)) + + const mockResults = [ + { + id: 101, + person_id: 101, + student_id: 101, + name: '王小华', + phone: '13800138101', + age: 9, + person_type: 'student', + person_type_text: '正式学员' + }, + { + id: 102, + person_id: 102, + resources_id: 102, + name: '赵小美', + phone: '13800138102', + age: 6, + person_type: 'customer_resource', + person_type_text: '潜在客户' + } + ] + + // 根据关键词过滤 + let filteredResults = mockResults + if (data.keyword) { + filteredResults = mockResults.filter(student => + student.name.includes(data.keyword) || student.phone.includes(data.keyword) + ) + } + + return { + code: 1, + data: { + list: filteredResults, + total: filteredResults.length + }, + msg: 'SUCCESS' + } + }, + + //↓↓↓↓↓↓↓↓↓↓↓↓-----统一课程安排Mock数据-----↓↓↓↓↓↓↓↓↓↓↓↓ + + // 模拟课程安排详情数据 + async getScheduleDetailMock(data = {}) { + await new Promise(resolve => setTimeout(resolve, 500)) + + const mockStudents = [ + { + id: 1, + person_id: 1, + student_id: 1, + name: '张小明', + phone: '13800138001', + age: 8, + person_type: 'student', + person_type_text: '正式学员', + schedule_type: 1, + status: 0, + course_progress: { + used: 5, + total: 20, + percentage: 25 + } + }, + { + id: 2, + person_id: 2, + resources_id: 2, + name: '李小红', + phone: '13800138002', + age: 7, + person_type: 'customer_resource', + person_type_text: '潜在客户', + schedule_type: 2, + status: 0, + course_progress: { + used: 0, + total: 1, + percentage: 0 + } + } + ] + + return { + code: 1, + data: { + schedule_info: { + id: parseInt(data.schedule_id) || 1, + course_name: '少儿体适能课程', + course_date: '2025-08-16', + time_slot: '09:00-10:00', + venue_name: '训练馆A', + campus_name: '总部校区', + coach_name: '张教练', + status: 'upcoming', + status_text: '即将开始' + }, + formal_students: mockStudents.filter(s => s.schedule_type === 1), + waiting_students: mockStudents.filter(s => s.schedule_type === 2), + formal_empty_seats: [2, 3, 4, 5], + waiting_empty_seats: [2, 3] + }, + msg: 'SUCCESS' + } + }, + + // 模拟搜索学员数据 + async searchStudentsMock(data = {}) { + await new Promise(resolve => setTimeout(resolve, 300)) + + const mockResults = [ + { + id: 101, + person_id: 101, + student_id: 101, + name: '王小华', + phone: '13800138101', + age: 9, + person_type: 'student', + person_type_text: '正式学员' + }, + { + id: 102, + person_id: 102, + resources_id: 102, + name: '赵小美', + phone: '13800138102', + age: 6, + person_type: 'customer_resource', + person_type_text: '潜在客户' + } + ] + + // 根据关键词过滤 + let filteredResults = mockResults + if (data.keyword) { + filteredResults = mockResults.filter(student => + student.name.includes(data.keyword) || student.phone.includes(data.keyword) + ) + } + + return { + code: 1, + data: { + list: filteredResults, + total: filteredResults.length + }, + msg: 'SUCCESS' + } + }, + //↓↓↓↓↓↓↓↓↓↓↓↓-----学员出勤管理相关接口-----↓↓↓↓↓↓↓↓↓↓↓↓ // 学员签到 diff --git a/uniapp/pages-coach/coach/schedule/schedule_table.vue b/uniapp/pages-coach/coach/schedule/schedule_table.vue index 07d232fd..04de22d6 100644 --- a/uniapp/pages-coach/coach/schedule/schedule_table.vue +++ b/uniapp/pages-coach/coach/schedule/schedule_table.vue @@ -1438,11 +1438,26 @@ export default { }) }, - // 查看课程安排详情 - viewScheduleDetail(scheduleId) { - // 显示课程详情弹窗 - this.selectedScheduleId = scheduleId; - this.showScheduleDetail = true; + // 查看课程安排详情(使用统一API) + async viewScheduleDetail(scheduleId) { + try { + // 显示课程详情弹窗 + this.selectedScheduleId = scheduleId; + this.showScheduleDetail = true; + + // 使用新的统一API获取课程安排详情,对接admin端功能 + console.log('调用统一API获取课程安排详情:', scheduleId); + + // 注意:具体的API调用逻辑在ScheduleDetail组件的fetchScheduleDetail方法中实现 + // ScheduleDetail组件会调用api.getCourseArrangementDetail()方法,与admin端保持一致 + // 这确保了移动端和管理端使用相同的数据结构和业务逻辑 + } catch (error) { + console.error('获取课程安排详情失败:', error); + uni.showToast({ + title: '获取课程详情失败', + icon: 'none' + }); + } }, // 处理编辑课程事件