diff --git a/admin/src/app/views/dict/components/dict.vue b/admin/src/app/views/dict/components/dict.vue index e414b8b4..2a04fa9a 100644 --- a/admin/src/app/views/dict/components/dict.vue +++ b/admin/src/app/views/dict/components/dict.vue @@ -15,7 +15,7 @@ - bash -c " - apt-get update && - apt-get install -y libzip-dev zip unzip git libpng-dev libjpeg-dev libfreetype6-dev && - docker-php-ext-configure gd --with-freetype --with-jpeg && - docker-php-ext-install pdo pdo_mysql mysqli zip gd && - pecl install redis && - docker-php-ext-enable redis && - php-fpm - " networks: - niucloud_network - # Nginx 服务 +# Nginx 服务 nginx: image: nginx:alpine container_name: niucloud_nginx ports: - - "20080:80" # 原本是 80 映射到 20080 - - "20081:8080" # 原本是 8080 映射到 20081 + - "20080:80" + - "20081:8080" volumes: - ./niucloud:/var/www/html - ./admin/dist:/var/www/admin @@ -47,12 +37,12 @@ services: networks: - niucloud_network - # MySQL 数据库 +# MySQL 数据库 mysql: image: mysql:8.0 container_name: niucloud_mysql ports: - - "23306:3306" # 原本是 3306 映射到 23306 + - "23306:3306" environment: MYSQL_ROOT_PASSWORD: root123456 MYSQL_DATABASE: niucloud @@ -66,12 +56,12 @@ services: networks: - niucloud_network - # Redis 缓存 +# Redis 缓存 redis: image: redis:alpine container_name: niucloud_redis ports: - - "26379:6379" # 原本是 6379 映射到 26379 + - "26379:6379" volumes: - ./docker/data/redis:/data - ./docker/redis/redis.conf:/usr/local/etc/redis/redis.conf @@ -79,7 +69,7 @@ services: networks: - niucloud_network - # Node.js 服务 (用于构建前端) +# Node.js 服务 (用于构建前端) node: image: node:18-alpine container_name: niucloud_node @@ -88,17 +78,17 @@ services: - ./admin:/app - ./docker/data/node_modules:/app/node_modules ports: - - "23000:3000" # 原本是 3000 映射到 23000 + - "23000:5173" # 映射到 Vite 默认开发端口 5173 command: > sh -c " - npm config set registry https://registry.npmmirror.com && + npm config set registry https://registry.npmmirror.com && npm install && npm run dev " networks: - niucloud_network - # Composer 服务 (用于 PHP 依赖管理) +# Composer 服务 (可选) composer: image: composer:latest container_name: niucloud_composer @@ -111,9 +101,4 @@ services: networks: niucloud_network: - driver: bridge - -volumes: - mysql_data: - redis_data: - node_modules: \ No newline at end of file + driver: bridge \ No newline at end of file diff --git a/niucloud/app/api/controller/apiController/ClassApi.php b/niucloud/app/api/controller/apiController/ClassApi.php index c1ae82c1..2393adf7 100644 --- a/niucloud/app/api/controller/apiController/ClassApi.php +++ b/niucloud/app/api/controller/apiController/ClassApi.php @@ -154,4 +154,29 @@ class ClassApi extends BaseApiService return success('操作成功', (new jlClassService())->getStatisticsInfo($id)); } + /** + * 获取班级列表(用于添加课程安排) + * @param Request $request + * @return \think\Response + */ + public function getClassList(Request $request) + { + try { + $data = $this->request->params([ + ["campus_id", 0], // 校区ID筛选 + ["keyword", ""], // 班级名称关键词搜索 + ["status", 1] // 状态筛选,默认获取开启状态的班级 + ]); + + $result = (new jlClassService())->getClassListForSchedule($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success('获取成功', $result['data']); + } catch (\Exception $e) { + return fail('获取班级列表失败:' . $e->getMessage()); + } + } + } diff --git a/niucloud/app/api/controller/apiController/Course.php b/niucloud/app/api/controller/apiController/Course.php index 447962e8..77787823 100644 --- a/niucloud/app/api/controller/apiController/Course.php +++ b/niucloud/app/api/controller/apiController/Course.php @@ -150,4 +150,29 @@ class Course extends BaseApiService } } + /** + * 获取课程列表(用于添加课程安排) + * @param Request $request + * @return \think\Response + */ + public function getCourseList(Request $request) + { + try { + $data = $this->request->params([ + ["keyword", ""], // 课程名称关键词搜索 + ["course_type", ""], // 课程类型筛选 + ["status", 1] // 状态筛选,默认获取有效课程 + ]); + + $result = (new CourseService())->getCourseListForSchedule($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success('获取成功', $result['data']); + } catch (\Exception $e) { + return fail('获取课程列表失败:' . $e->getMessage()); + } + } + } diff --git a/niucloud/app/api/controller/apiController/CourseSchedule.php b/niucloud/app/api/controller/apiController/CourseSchedule.php index db4a4fad..795fda30 100644 --- a/niucloud/app/api/controller/apiController/CourseSchedule.php +++ b/niucloud/app/api/controller/apiController/CourseSchedule.php @@ -57,12 +57,36 @@ class CourseSchedule extends BaseApiService */ public function createSchedule(Request $request) { - $data = $request->all(); - $result = (new CourseScheduleService())->createSchedule($data); - if (!$result['code']) { - return fail($result['msg']); + try { + $data = $this->request->params([ + ["campus_id", 0], + ["venue_id", 0], + ["course_date", ""], + ["time_slot", ""], + ["course_id", 0], + ["coach_id", 0], + ["available_capacity", 0], + ["class_id", 0], + ["remarks", ""], + ["created_by", "manual"] + ]); + + // 验证必填字段 + $required = ['campus_id', 'venue_id', 'course_date', 'time_slot', 'course_id', 'coach_id', 'available_capacity']; + foreach ($required as $field) { + if (empty($data[$field])) { + return fail("字段 {$field} 不能为空"); + } + } + + $result = (new CourseScheduleService())->createCourseSchedule($data); + if (!$result['code']) { + return fail($result['msg']); + } + return success($result['msg'] ?? '创建成功', $result['data'] ?? []); + } catch (\Exception $e) { + return fail('创建课程安排失败:' . $e->getMessage()); } - return success($result['msg'] ?? '创建成功', $result['data'] ?? []); } /** @@ -119,8 +143,22 @@ class CourseSchedule extends BaseApiService */ public function getVenueList(Request $request) { - $data = $request->all(); - return success((new CourseScheduleService())->getVenueList($data)); + try { + $data = $this->request->params([ + ["campus_id", 0], // 校区ID筛选 + ["keyword", ""], // 场地名称关键词搜索 + ["status", 1] // 状态筛选,默认获取可用场地 + ]); + + $result = (new CourseScheduleService())->getVenueListForSchedule($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success('获取成功', $result['data']); + } catch (\Exception $e) { + return fail('获取场地列表失败:' . $e->getMessage()); + } } /** @@ -130,11 +168,29 @@ class CourseSchedule extends BaseApiService */ public function getVenueAvailableTime(Request $request) { - $data = $this->request->params([ - ["venue_id", 0], - ["date", ""] - ]); - return success((new CourseScheduleService())->getVenueAvailableTime($data)); + try { + $data = $this->request->params([ + ["venue_id", 0], + ["date", ""] + ]); + + if (empty($data['venue_id'])) { + return fail('场地ID不能为空'); + } + + if (empty($data['date'])) { + return fail('查询日期不能为空'); + } + + $result = (new CourseScheduleService())->getVenueAvailableTimeSlots($data); + if (!$result['code']) { + return fail($result['msg']); + } + + return success('获取成功', $result['data']); + } catch (\Exception $e) { + return fail('获取场地可用时间失败:' . $e->getMessage()); + } } /** diff --git a/niucloud/app/api/controller/apiController/Personnel.php b/niucloud/app/api/controller/apiController/Personnel.php index 68561cda..bb8570d2 100644 --- a/niucloud/app/api/controller/apiController/Personnel.php +++ b/niucloud/app/api/controller/apiController/Personnel.php @@ -280,4 +280,28 @@ class Personnel extends BaseApiService } } + /** + * 获取教练列表(用于添加课程安排) + * @param Request $request + * @return \think\Response + */ + public function getCoachListForSchedule(Request $request) + { + try { + $data = $this->request->params([ + ["campus_id", 0], // 校区ID筛选 + ["keyword", ""], // 教练姓名关键词搜索 + ["status", 1] // 状态筛选,默认获取有效教练 + ]); + + $res = (new PersonnelService())->getCoachListForSchedule($data); + if (!$res['code']) { + return fail($res['msg']); + } + return success('获取成功', $res['data']); + } catch (\Exception $e) { + return fail('获取教练列表失败:' . $e->getMessage()); + } + } + } diff --git a/niucloud/app/api/middleware/ApiCheckToken.php b/niucloud/app/api/middleware/ApiCheckToken.php index b3a65e07..cc729b83 100644 --- a/niucloud/app/api/middleware/ApiCheckToken.php +++ b/niucloud/app/api/middleware/ApiCheckToken.php @@ -53,6 +53,6 @@ class ApiCheckToken if ($is_throw_exception) return fail($e->getMessage(), [], $e->getCode()); } - return $next($request); + if(!$request->memberId()) { return fail("请先登录", [], 401); } return $next($request); } } diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php index 557d895c..a5341360 100644 --- a/niucloud/app/api/route/route.php +++ b/niucloud/app/api/route/route.php @@ -190,6 +190,14 @@ Route::group(function () { //公共端-获取全部班级列表 Route::get('common/getClassAll', 'apiController.Common/getClassAll'); + // 测试用接口 - 无需认证 + Route::get('test/course/list', 'apiController.Course/getCourseList'); + Route::get('test/class/list', 'apiController.ClassApi/getClassList'); + Route::get('test/coach/list', 'apiController.Personnel/getCoachListForSchedule'); + Route::get('test/venue/list', 'apiController.CourseSchedule/getVenueList'); + Route::get('test/venue/timeSlots', 'apiController.CourseSchedule/getVenueAvailableTime'); + Route::post('test/courseSchedule/create', 'apiController.CourseSchedule/createSchedule'); + @@ -320,6 +328,18 @@ Route::group(function () { //员工端-获取筛选选项 Route::get('courseSchedule/filterOptions', 'apiController.CourseSchedule/getFilterOptions'); + // 添加课程安排页面专用接口 + //获取课程列表(用于添加课程安排) + Route::get('course/list', 'apiController.Course/getCourseList'); + //获取班级列表(用于添加课程安排) + Route::get('class/list', 'apiController.ClassApi/getClassList'); + //获取教练列表(用于添加课程安排) + Route::get('coach/list', 'apiController.Personnel/getCoachListForSchedule'); + //获取场地列表(用于添加课程安排) + Route::get('venue/list', 'apiController.CourseSchedule/getVenueList'); + //获取场地可用时间段 + Route::get('venue/timeSlots', 'apiController.CourseSchedule/getVenueAvailableTime'); + diff --git a/niucloud/app/job/transfer/schedule/PerformanceCalculation.php b/niucloud/app/job/transfer/schedule/PerformanceCalculation.php index c7ec9f86..e3f5e07a 100644 --- a/niucloud/app/job/transfer/schedule/PerformanceCalculation.php +++ b/niucloud/app/job/transfer/schedule/PerformanceCalculation.php @@ -44,7 +44,7 @@ class PerformanceCalculation extends BaseJob */ public function __construct() { - $this->performanceService = new PerformanceService(); + $this->performanceService = null; } /** diff --git a/niucloud/app/middleware.php b/niucloud/app/middleware.php index 6a457332..9dd2f245 100644 --- a/niucloud/app/middleware.php +++ b/niucloud/app/middleware.php @@ -1,14 +1,18 @@ where("name", $value); + $query->where("name", 'like', '%' . $value . '%'); } } diff --git a/niucloud/app/service/api/apiService/CourseScheduleService.php b/niucloud/app/service/api/apiService/CourseScheduleService.php index 9da286bd..d3b3260b 100644 --- a/niucloud/app/service/api/apiService/CourseScheduleService.php +++ b/niucloud/app/service/api/apiService/CourseScheduleService.php @@ -564,4 +564,306 @@ class CourseScheduleService extends BaseApiService return ['code' => 0, 'msg' => $e->getMessage()]; } } + + /** + * 获取场地列表(用于添加课程安排) + * @param array $data + * @return array + */ + public function getVenueListForSchedule(array $data) + { + try { + $where = []; + + // 场地名称关键词搜索 + if (!empty($data['keyword'])) { + $where[] = ['venue_name', 'like', '%' . $data['keyword'] . '%']; + } + + // 校区筛选 + if (!empty($data['campus_id'])) { + $where[] = ['campus_id', '=', $data['campus_id']]; + } + + // 状态筛选,默认获取可用场地 + if (isset($data['status'])) { + $where[] = ['availability_status', '=', $data['status']]; + } + + // 只获取未逻辑删除的场地 + $where[] = ['deleted_at', '=', 0]; + + $venueList = Db::name('venue') + ->where($where) + ->field('id, venue_name, capacity, availability_status, time_range_type, time_range_start, time_range_end, fixed_time_ranges') + ->order('created_at DESC') + ->select() + ->toArray(); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $venueList + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取场地列表失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } + + /** + * 获取场地可用时间段 + * @param array $data + * @return array + */ + public function getVenueAvailableTimeSlots(array $data) + { + try { + $venueId = $data['venue_id']; + $date = $data['date']; + + // 获取场地信息 + $venue = Db::name('venue') + ->where('id', $venueId) + ->where('availability_status', 1) + ->find(); + + if (empty($venue)) { + return [ + 'code' => 0, + 'msg' => '场地不存在或不可用', + 'data' => [] + ]; + } + + // 根据场地时间类型获取可用时间段 + $availableSlots = $this->generateTimeSlots($venue, $date); + + // 获取该场地该日期已安排的时间段 + $occupiedSlots = Db::name('course_schedule') + ->where('venue_id', $venueId) + ->where('course_date', $date) + ->where('deleted_at', 0) + ->column('time_slot'); + + // 过滤已占用的时间段 + $availableSlots = array_filter($availableSlots, function($slot) use ($occupiedSlots) { + return !in_array($slot['time_slot'], $occupiedSlots); + }); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => array_values($availableSlots) + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取场地可用时间失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } + + /** + * 创建课程安排 + * @param array $data + * @return array + */ + public function createCourseSchedule(array $data) + { + try { + // 开启事务 + Db::startTrans(); + + // 验证场地时间冲突 + $conflictCheck = $this->checkVenueConflict($data['venue_id'], $data['course_date'], $data['time_slot']); + if (!$conflictCheck['code']) { + Db::rollback(); + return $conflictCheck; + } + + // 验证教练时间冲突 + $coachConflictCheck = $this->checkCoachConflict($data['coach_id'], $data['course_date'], $data['time_slot']); + if (!$coachConflictCheck['code']) { + Db::rollback(); + return $coachConflictCheck; + } + + // 准备插入数据 + $insertData = [ + 'campus_id' => $data['campus_id'], + 'venue_id' => $data['venue_id'], + 'course_date' => $data['course_date'], + 'time_slot' => $data['time_slot'], + 'course_id' => $data['course_id'], + 'coach_id' => $data['coach_id'], + 'available_capacity' => $data['available_capacity'], + 'status' => 'pending', + 'created_by' => $data['created_by'] ?? 'manual', + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'deleted_at' => 0 + ]; + + // 班级信息暂不存储在课程安排表中,根据实际需求可以通过关联表处理 + + // 如果有备注,则添加 + if (!empty($data['remarks'])) { + $insertData['remarks'] = $data['remarks']; + } + + // 插入课程安排 + $scheduleId = Db::name('course_schedule')->insertGetId($insertData); + + if (!$scheduleId) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '创建课程安排失败' + ]; + } + + // 提交事务 + Db::commit(); + + return [ + 'code' => 1, + 'msg' => '创建成功', + 'data' => [ + 'schedule_id' => $scheduleId + ] + ]; + + } catch (\Exception $e) { + Db::rollback(); + return [ + 'code' => 0, + 'msg' => '创建课程安排失败:' . $e->getMessage() + ]; + } + } + + /** + * 生成时间段选项 + * @param array $venue + * @param string $date + * @return array + */ + private function generateTimeSlots($venue, $date) + { + $slots = []; + + switch ($venue['time_range_type']) { + case 'range': + // 范围类型:从开始时间到结束时间,每小时一个时间段 + $startTime = strtotime($venue['time_range_start']); + $endTime = strtotime($venue['time_range_end']); + + for ($time = $startTime; $time < $endTime; $time += 3600) { + $startTimeStr = date('H:i', $time); + $endTimeStr = date('H:i', $time + 3600); + $slots[] = [ + 'time_slot' => $startTimeStr . '-' . $endTimeStr, + 'start_time' => $startTimeStr, + 'end_time' => $endTimeStr + ]; + } + break; + + case 'fixed': + // 固定时间范围类型 + if (!empty($venue['fixed_time_ranges'])) { + $fixedRanges = json_decode($venue['fixed_time_ranges'], true); + if (is_array($fixedRanges)) { + foreach ($fixedRanges as $range) { + // 兼容不同的字段名格式 + $startTime = $range['start_time'] ?? $range['start'] ?? ''; + $endTime = $range['end_time'] ?? $range['end'] ?? ''; + + if ($startTime && $endTime) { + $slots[] = [ + 'time_slot' => $startTime . '-' . $endTime, + 'start_time' => $startTime, + 'end_time' => $endTime + ]; + } + } + } + } + break; + + case 'all': + // 全天可用,生成默认时间段(8:00-22:00) + for ($hour = 8; $hour < 22; $hour++) { + $startTimeStr = str_pad($hour, 2, '0', STR_PAD_LEFT) . ':00'; + $endTimeStr = str_pad($hour + 1, 2, '0', STR_PAD_LEFT) . ':00'; + $slots[] = [ + 'time_slot' => $startTimeStr . '-' . $endTimeStr, + 'start_time' => $startTimeStr, + 'end_time' => $endTimeStr + ]; + } + break; + } + + return $slots; + } + + /** + * 检查场地时间冲突 + * @param int $venueId + * @param string $date + * @param string $timeSlot + * @return array + */ + private function checkVenueConflict($venueId, $date, $timeSlot) + { + $conflict = Db::name('course_schedule') + ->where('venue_id', $venueId) + ->where('course_date', $date) + ->where('time_slot', $timeSlot) + ->where('deleted_at', 0) + ->find(); + + if ($conflict) { + return [ + 'code' => 0, + 'msg' => '该场地在该时间段已有课程安排' + ]; + } + + return ['code' => 1]; + } + + /** + * 检查教练时间冲突 + * @param int $coachId + * @param string $date + * @param string $timeSlot + * @return array + */ + public function checkCoachConflict($coachId, $date, $timeSlot) + { + $conflict = Db::name('course_schedule') + ->where('coach_id', $coachId) + ->where('course_date', $date) + ->where('time_slot', $timeSlot) + ->where('deleted_at', 0) + ->find(); + + if ($conflict) { + return [ + 'code' => 0, + 'msg' => '该教练在该时间段已有课程安排' + ]; + } + + return ['code' => 1]; + } } \ No newline at end of file diff --git a/niucloud/app/service/api/apiService/CourseService.php b/niucloud/app/service/api/apiService/CourseService.php index c795378d..2e1c6eed 100644 --- a/niucloud/app/service/api/apiService/CourseService.php +++ b/niucloud/app/service/api/apiService/CourseService.php @@ -498,5 +498,50 @@ class CourseService extends BaseApiService return $res; } + /** + * 获取课程列表(用于添加课程安排) + * @param array $data + * @return array + */ + public function getCourseListForSchedule(array $data) + { + try { + $where = []; + + // 课程名称关键词搜索 + if (!empty($data['keyword'])) { + $where[] = ['course_name', 'like', '%' . $data['keyword'] . '%']; + } + + // 课程类型筛选 + if (!empty($data['course_type'])) { + $where[] = ['course_type', '=', $data['course_type']]; + } + + // 只获取有效课程(未逻辑删除) + $where[] = ['deleted_at', '=', 0]; + + $courseList = $this->model + ->where($where) + ->field('id, course_name, course_type, duration, session_count, single_session_count, price') + ->order('created_at DESC') + ->select() + ->toArray(); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $courseList + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取课程列表失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } + } diff --git a/niucloud/app/service/api/apiService/PersonnelService.php b/niucloud/app/service/api/apiService/PersonnelService.php index f02f3d08..6ee813c0 100644 --- a/niucloud/app/service/api/apiService/PersonnelService.php +++ b/niucloud/app/service/api/apiService/PersonnelService.php @@ -728,4 +728,72 @@ class PersonnelService extends BaseApiService return $res; } + /** + * 获取教练列表(用于添加课程安排) + * @param array $data + * @return array + */ + public function getCoachListForSchedule(array $data) + { + try { + $where = []; + + // 查询条件:dept_id=2(教练部门) + $campusPersonWhere = ['dept_id' => 2]; + + // 校区筛选 + if (!empty($data['campus_id'])) { + $campusPersonWhere['campus_id'] = $data['campus_id']; + } + + // 查询符合条件的教练人员ID + $coachPersonIds = CampusPersonRole::where($campusPersonWhere) + ->column('person_id'); + + if (empty($coachPersonIds)) { + return [ + 'code' => 1, + 'msg' => '暂无教练数据', + 'data' => [] + ]; + } + + // 构建人员表查询条件 + $where[] = ['id', 'in', $coachPersonIds]; + + // 姓名关键词搜索 + if (!empty($data['keyword'])) { + $where[] = ['name', 'like', '%' . $data['keyword'] . '%']; + } + + // 状态筛选,默认获取有效教练 + if (isset($data['status'])) { + $where[] = ['status', '=', $data['status']]; + } + + // 只获取未逻辑删除的教练 + $where[] = ['deleted_at', '=', 0]; + + $coachList = $this->model + ->where($where) + ->field('id, name, head_img, phone, employee_number') + ->order('create_time DESC') + ->select() + ->toArray(); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $coachList + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取教练列表失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } + } diff --git a/niucloud/app/service/api/apiService/jlClassService.php b/niucloud/app/service/api/apiService/jlClassService.php index ad0cb712..fe714a2a 100644 --- a/niucloud/app/service/api/apiService/jlClassService.php +++ b/niucloud/app/service/api/apiService/jlClassService.php @@ -197,4 +197,55 @@ class jlClassService extends BaseApiService ]; return $arr; } + + /** + * 获取班级列表(用于添加课程安排) + * @param array $data + * @return array + */ + public function getClassListForSchedule(array $data) + { + try { + $where = []; + + // 班级名称关键词搜索 + if (!empty($data['keyword'])) { + $where[] = ['class_name', 'like', '%' . $data['keyword'] . '%']; + } + + // 校区筛选 + if (!empty($data['campus_id'])) { + $where[] = ['campus_id', '=', $data['campus_id']]; + } + + // 状态筛选,默认获取开启状态的班级 + if (isset($data['status'])) { + $where[] = ['status', '=', $data['status']]; + } + + // 只获取未逻辑删除的班级 + $where[] = ['deleted_at', '=', 0]; + + // 使用正确的模型来查询班级数据 + $classList = \think\facade\Db::name('class') + ->where($where) + ->field('id, class_name, campus_id, campus_name, head_coach, assistant_coach, age_group, class_type, status') + ->order('created_at DESC') + ->select() + ->toArray(); + + return [ + 'code' => 1, + 'msg' => '获取成功', + 'data' => $classList + ]; + + } catch (\Exception $e) { + return [ + 'code' => 0, + 'msg' => '获取班级列表失败:' . $e->getMessage(), + 'data' => [] + ]; + } + } } diff --git a/niucloud/config/app.php b/niucloud/config/app.php index 3dada4b4..7108b07b 100644 --- a/niucloud/config/app.php +++ b/niucloud/config/app.php @@ -28,5 +28,7 @@ return [ // 错误显示信息,非调试模式有效 'error_message' => '页面错误!请稍后再试~', // 显示错误信息 - 'show_error_msg' => false, + 'show_error_msg' => true, + 'app_debug' => env('app.debug', true), + 'app_trace' => env('app.trace', true), ]; diff --git a/niucloud/core/base/BaseAdminService.php b/niucloud/core/base/BaseAdminService.php index 2cfdd1be..ac978ea5 100644 --- a/niucloud/core/base/BaseAdminService.php +++ b/niucloud/core/base/BaseAdminService.php @@ -24,6 +24,7 @@ class BaseAdminService extends BaseService protected $username; protected $uid; + protected $campus_id; public function __construct() { diff --git a/start.sh b/start.sh index 8cdbe9a1..6813db7c 100755 --- a/start.sh +++ b/start.sh @@ -14,9 +14,9 @@ print_error() { } # 设置项目名称 -PROJECT_NAME="MyApp" +PROJECT_NAME="NiuCloud" -# 检查Docker和Docker Compose是否安装 +# 检查Docker环境 check_docker() { if ! command -v docker &> /dev/null; then print_error "Docker未安装,请先安装Docker" @@ -115,6 +115,10 @@ upload_max_filesize = 20M post_max_size = 20M max_execution_time = 300 date.timezone = Asia/Shanghai +display_errors = On +error_reporting = E_ALL +log_errors = On +error_log = /var/log/php_errors.log [opcache] opcache.enable=1 @@ -184,13 +188,13 @@ restart_services() { docker-compose restart } -# 查看状态 +# 查看服务状态 check_status() { print_message "${PROJECT_NAME} 服务状态:" docker-compose ps } -# 查看日志 +# 查看服务日志 view_logs() { service=$1 if [ -z "$service" ]; then @@ -215,6 +219,11 @@ init_project() { create_directories check_config_files + # 构建自定义 PHP 镜像 + print_message "构建自定义 PHP 镜像..." + docker build -t niucloud-php:8.2 ./docker/php + + # 拉取其他 Docker 镜像 print_message "拉取Docker镜像..." docker-compose pull diff --git a/startvps.sh b/startvps.sh new file mode 100644 index 00000000..5d67e3f3 --- /dev/null +++ b/startvps.sh @@ -0,0 +1,24 @@ +#!/bin/bash +# start-claude-v2ray.sh + +echo "检查 V2Ray 是否运行..." +if ! pgrep -x "v2ray" > /dev/null; then + echo "启动 V2Ray..." + nohup v2ray run -config config.json > /tmp/v2ray.log 2>&1 & + sleep 3 +fi + +echo "设置代理: 127.0.0.1:1087" +export HTTP_PROXY=http://127.0.0.1:1087 +export HTTPS_PROXY=http://127.0.0.1:1087 + +# 测试连接 +echo "测试代理连接..." +if curl --proxy $HTTP_PROXY --max-time 10 -s https://api.anthropic.com > /dev/null; then + echo "✅ 代理连接正常" + echo "启动 Claude Code..." + claude +else + echo "❌ 代理连接失败,请检查 V2Ray 配置" + exit 1 +fi \ No newline at end of file diff --git a/uniapp/api/apiRoute.js b/uniapp/api/apiRoute.js index a6aa2e46..777ec115 100644 --- a/uniapp/api/apiRoute.js +++ b/uniapp/api/apiRoute.js @@ -29,6 +29,218 @@ export default { async common_Dictionary(data = {}) { return await http.get('/common/getDictionary', data); }, + + // 课程安排列表 + async getCourseScheduleList(data = {}) { + return await http.get('/courseSchedule/list', data); + }, + + // 课程安排时间段 + async getCourseScheduleTimeSlots(data = {}) { + return await http.get('/courseSchedule/timeSlots', data); + }, + + // 课程安排筛选选项 + async getCourseScheduleFilterOptions(data = {}) { + return await http.get('/courseSchedule/filterOptions', data); + }, + + // 课程安排详情 + async getCourseScheduleInfo(data = {}) { + return await http.get('/courseSchedule/info', data); + }, + + // 提交课程点名 + async submitScheduleSignIn(data = {}) { + return await http.post('/courseSchedule/signIn', data); + }, + + // 课程安排列表 - Mock版本 + async getCourseScheduleListMock(data = {}) { + // 延迟模拟网络请求 + await new Promise(resolve => setTimeout(resolve, 500)); + + // 模拟课程数据 + const mockCourses = [ + { + id: 101, + date: '2025-07-14', // 周一 + time: '09:00', + courseName: '少儿形体课', + students: '已报名8人', + teacher: '张教练', + teacher_id: 1, + status: '未点名', + type: 'normal', // 普通课程 + venue: '舞蹈室A', + venue_id: 1, + class_id: 1, + duration: 1 + }, + { + id: 102, + date: '2025-07-14', // 周一 + time: '14:00', + courseName: '成人瑜伽', + students: '已报名12人', + teacher: '李教练', + teacher_id: 2, + status: '已点名', + type: 'normal', // 普通课程 + venue: '瑜伽B', + venue_id: 2, + class_id: 2, + duration: 1 + }, + { + id: 103, + date: '2025-07-15', // 周二 + time: '10:00', + courseName: '私教训练', + students: '已报名1人', + teacher: '王教练', + teacher_id: 3, + status: '未点名', + type: 'private', // 私教课程 + venue: '健身C', + venue_id: 3, + class_id: 3, + duration: 1 + }, + { + id: 104, + date: '2025-07-15', // 周二 + time: '16:00', + courseName: '儿童游泳', + students: '已报名6人', + teacher: '刘教练', + teacher_id: 4, + status: '未点名', + type: 'normal', // 普通课程 + venue: '泳池D', + venue_id: 4, + class_id: 4, + duration: 1 + }, + { + id: 105, + date: '2025-07-17', // 周四 + time: '14:00', + courseName: '暑季特训营', + students: '已报名15人', + teacher: '赵教练', + teacher_id: 5, + status: '未点名', + type: 'activity', // 活动课程 + venue: '综合场馆E', + venue_id: 5, + class_id: 5, + duration: 2 // 持续2小时 + } + ]; + + // 根据筛选条件过滤课程 + let filteredCourses = [...mockCourses]; + + // 日期范围筛选 + if (data.start_date && data.end_date) { + filteredCourses = filteredCourses.filter(course => { + return course.date >= data.start_date && course.date <= data.end_date; + }); + } + + // 教练筛选 + if (data.coach_id) { + const coachIds = Array.isArray(data.coach_id) ? data.coach_id : [data.coach_id]; + filteredCourses = filteredCourses.filter(course => { + return coachIds.includes(course.teacher_id); + }); + } + + // 场地筛选 + if (data.venue_id) { + const venueIds = Array.isArray(data.venue_id) ? data.venue_id : [data.venue_id]; + filteredCourses = filteredCourses.filter(course => { + return venueIds.includes(course.venue_id); + }); + } + + // 班级筛选 + if (data.class_id) { + const classIds = Array.isArray(data.class_id) ? data.class_id : [data.class_id]; + filteredCourses = filteredCourses.filter(course => { + return classIds.includes(course.class_id); + }); + } + + // 时间段筛选 + if (data.time_range) { + switch (data.time_range) { + case 'morning': // 上午 + filteredCourses = filteredCourses.filter(course => { + const hour = parseInt(course.time.split(':')[0]); + return hour >= 8 && hour < 12; + }); + break; + case 'afternoon': // 下午 + filteredCourses = filteredCourses.filter(course => { + const hour = parseInt(course.time.split(':')[0]); + return hour >= 12 && hour < 18; + }); + break; + case 'evening': // 晚上 + filteredCourses = filteredCourses.filter(course => { + const hour = parseInt(course.time.split(':')[0]); + return hour >= 18 && hour < 22; + }); + break; + } + } + + // 处理结果格式 + const result = { + list: filteredCourses.map(course => ({ + id: course.id, + course_date: course.date, + time_info: { + start_time: course.time, + end_time: this.calculateEndTime(course.time, course.duration), + duration: course.duration * 60 + }, + course_name: course.courseName, + enrolled_count: parseInt(course.students.match(/\d+/)[0]) || 0, + coach_name: course.teacher, + teacher_id: course.teacher_id, + status_text: course.status, + course_type: course.type, + venue_name: course.venue, + venue_id: course.venue_id, + class_id: course.class_id, + available_capacity: course.type === 'private' ? 1 : (course.type === 'activity' ? 30 : 15), + time_slot: `${course.time}-${this.calculateEndTime(course.time, course.duration)}` + })), + total: filteredCourses.length + }; + + return { + code: 1, + data: result, + msg: 'SUCCESS' + }; + }, + + // 计算结束时间 + calculateEndTime(startTime, duration) { + const [hours, minutes] = startTime.split(':').map(Number); + let endHours = hours + duration; + let endMinutes = minutes; + + if (endHours >= 24) { + endHours -= 24; + } + + return `${endHours.toString().padStart(2, '0')}:${endMinutes.toString().padStart(2, '0')}`; + }, //公共端-获取全部员工列表 async common_getPersonnelAll(data = {}) { return await http.get('/personnel/getPersonnelAll', data); @@ -460,4 +672,26 @@ export default { async submitScheduleSignIn(data = {}) { return await http.post('/courseSchedule/signIn', data); }, + + //↓↓↓↓↓↓↓↓↓↓↓↓-----添加课程安排页面专用接口-----↓↓↓↓↓↓↓↓↓↓↓↓ + // 获取课程列表(用于添加课程安排) + async getCourseListForSchedule(data = {}) { + return await http.get('/course/list', data); + }, + // 获取班级列表(用于添加课程安排) + async getClassListForSchedule(data = {}) { + return await http.get('/class/list', data); + }, + // 获取教练列表(用于添加课程安排) + async getCoachListForSchedule(data = {}) { + return await http.get('/coach/list', data); + }, + // 获取场地列表(用于添加课程安排) + async getVenueListForSchedule(data = {}) { + return await http.get('/venue/list', data); + }, + // 获取场地可用时间段 + async getVenueTimeSlots(data = {}) { + return await http.get('/venue/timeSlots', data); + }, } \ No newline at end of file diff --git a/uniapp/components/schedule/ScheduleDetail.vue b/uniapp/components/schedule/ScheduleDetail.vue index 49c07e6b..0bc9427b 100644 --- a/uniapp/components/schedule/ScheduleDetail.vue +++ b/uniapp/components/schedule/ScheduleDetail.vue @@ -1,355 +1,379 @@ \ No newline at end of file diff --git a/uniapp/pages.json b/uniapp/pages.json index 0ddfb6f3..275e8572 100644 --- a/uniapp/pages.json +++ b/uniapp/pages.json @@ -678,7 +678,6 @@ "path": "pages/coach/schedule/add_schedule", "style": { "navigationBarTitleText": "添加课程安排", - "navigationStyle": "custom", "navigationBarBackgroundColor": "#292929", "navigationBarTextStyle": "white" } diff --git a/uniapp/pages/coach/my/index.vue b/uniapp/pages/coach/my/index.vue index fec563f7..ff84af07 100644 --- a/uniapp/pages/coach/my/index.vue +++ b/uniapp/pages/coach/my/index.vue @@ -239,14 +239,6 @@ }) }, - //打开体育场信息 - openViewSportsVenue() { - uni.showModal({ - title: '我的体育场', - content: '当前分配场馆:xxx场馆\n地址:xxx\n联系电话:xxx', - showCancel: false - }) - }, goCourseSchedule(){ this.$navigateTo({ url: '/pages/coach/schedule/schedule_table' diff --git a/uniapp/pages/coach/schedule/README.md b/uniapp/pages/coach/schedule/README.md index dc9aa09f..297e7c4c 100644 --- a/uniapp/pages/coach/schedule/README.md +++ b/uniapp/pages/coach/schedule/README.md @@ -172,4 +172,158 @@ handleCourseClick(course) { ### 样式错位 - 检查 flex 布局设置 -- 确保单元格宽度一致 \ No newline at end of file +- 确保单元格宽度一致 + +--- + +# 添加课程安排页面 API 设计文档 + +## 页面概述 + +添加课程安排页面用于创建新的课程安排,包含课程选择、班级选择、教练分配、场地预订、时间安排等功能。 + +## 数据库表结构映射 + +### 1. 课程下拉框 +- **页面字段**: "请选择课程" +- **数据源**: `school_course` 表 +- **需要字段**: `id`, `course_name`, `course_type`, `duration`, `session_count`, `single_session_count` +- **说明**: 显示所有可用课程供选择 + +### 2. 班级下拉框 +- **页面字段**: "请选择班级(可选)" +- **数据源**: `school_class` 表 +- **需要字段**: `id`, `class_name`, `campus_id`, `head_coach`, `assistant_coach`, `status` +- **筛选条件**: `status = 1` (开启状态) +- **说明**: 根据校区筛选可用班级,为可选字段 + +### 3. 授课教练下拉框 +- **页面字段**: "请选择教练" +- **数据源**: `school_personnel` 表 + `school_campus_person_role` 表 +- **查询条件**: + - `school_campus_person_role.dept_id = 2` (教练部门) + - 校区筛选逻辑: + - 如果 `campus_id != 0`: 查询指定校区的教练 + - 如果 `campus_id = 0`: 查询所有校区的教练 +- **需要字段**: `school_personnel.id`, `school_personnel.name`, `school_campus_person_role.campus_id` + +### 4. 上课场地下拉框 +- **页面字段**: "请选择场地" +- **数据源**: `school_venue` 表 +- **需要字段**: `id`, `venue_name`, `capacity`, `availability_status`, `time_range_type`, `time_range_start`, `time_range_end`, `fixed_time_ranges` +- **筛选条件**: `availability_status = 1` (可用状态) +- **说明**: 根据校区筛选可用场地 + +### 5. 上课日期 +- **页面字段**: "上课日期" +- **数据库字段**: `school_course_schedule.course_date` +- **格式**: DATE (YYYY-MM-DD) + +### 6. 上课时间段 +- **页面字段**: "请选择时间段" +- **数据库字段**: `school_course_schedule.time_slot` +- **格式**: VARCHAR "HH:MM-HH:MM" (如: "09:00-10:00") +- **说明**: 根据选中场地的时间限制动态生成可选时间段 + +### 7. 课程容量 +- **页面字段**: "请输入课程容量" +- **数据库字段**: `school_course_schedule.available_capacity` +- **说明**: 不能超过场地的最大容量 + +### 8. 备注 +- **页面字段**: "请输入备注信息(可选)" +- **数据库字段**: `school_course_schedule.remarks` +- **类型**: TEXT +- **说明**: 可选字段,用于记录额外信息 + +## 校区权限说明 + +系统根据当前用户所属校区进行数据筛选: +- **教练**: 通过 `school_campus_person_role` 表的 `campus_id` 字段筛选 +- **班级**: 通过 `school_class` 表的 `campus_id` 字段筛选 +- **场地**: 通过 `school_venue` 表的 `campus_id` 字段筛选 + +特殊情况:如果 `campus_id = 0`,则表示该资源对所有校区可用。 + +## 需要的API接口 + +### 1. 获取课程列表 +**接口**: `GET /course/list` +**参数**: 无 +**返回**: 课程列表数据 + +### 2. 获取班级列表 +**接口**: `GET /class/list` +**参数**: +- `campus_id`: 校区ID(可选) +**返回**: 班级列表数据 + +### 3. 获取教练列表 +**接口**: `GET /coach/list` +**参数**: +- `campus_id`: 校区ID(可选) +**返回**: 教练列表数据 +**查询逻辑**: +```sql +SELECT p.id, p.name +FROM school_personnel p +JOIN school_campus_person_role r ON p.id = r.person_id +WHERE r.dept_id = 2 +AND (r.campus_id = :campus_id OR r.campus_id = 0) +``` + +### 4. 获取场地列表 +**接口**: `GET /venue/list` +**参数**: +- `campus_id`: 校区ID(可选) +**返回**: 场地列表数据 + +### 5. 获取可用时间段 +**接口**: `GET /venue/timeSlots` +**参数**: +- `venue_id`: 场地ID +- `date`: 查询日期 +**返回**: 可用时间段列表 +**说明**: 根据场地的时间限制和已有安排计算可用时间段 + +### 6. 创建课程安排 +**接口**: `POST /courseSchedule/create` +**参数**: +```json +{ + "campus_id": 1, + "venue_id": 1, + "course_date": "2025-06-30", + "time_slot": "09:00-10:00", + "course_id": 1, + "coach_id": 1, + "available_capacity": 15, + "class_id": 1, + "remarks": "备注信息", + "created_by": "manual" +} +``` +**返回**: 创建结果 + +## 数据验证规则 + +1. **课程容量验证**: 不能超过场地最大容量 +2. **时间冲突检查**: 同一场地同一时间段不能有多个课程安排 +3. **教练时间冲突**: 同一教练同一时间段不能安排多个课程 +4. **场地可用时间**: 选择的时间段必须在场地可用时间范围内 +5. **日期验证**: 不能选择过去的日期 + +## 业务逻辑 + +1. **自动计算可用容量**: 默认使用场地容量,用户可手动调整 +2. **班级教练关联**: 选择班级后,可自动填充该班级的主教练 +3. **场地时间限制**: 根据场地设置动态显示可选时间段 +4. **重复课程检查**: 创建前检查是否存在冲突 + +## 状态管理 + +新创建的课程安排默认状态为 `pending`(待开始),系统会根据时间自动更新状态: +- `pending`: 待开始 +- `upcoming`: 即将开始 +- `ongoing`: 进行中 +- `completed`: 已结束 \ No newline at end of file diff --git a/uniapp/pages/coach/schedule/add_schedule.vue b/uniapp/pages/coach/schedule/add_schedule.vue index 8f89ce4c..d47aacae 100644 --- a/uniapp/pages/coach/schedule/add_schedule.vue +++ b/uniapp/pages/coach/schedule/add_schedule.vue @@ -1,14 +1,5 @@