4 changed files with 1995 additions and 0 deletions
@ -0,0 +1,217 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\api\controller\apiController; |
||||
|
|
||||
|
use app\service\api\apiService\CourseScheduleService; |
||||
|
use core\base\BaseApiController; |
||||
|
|
||||
|
/** |
||||
|
* 课程安排控制器 |
||||
|
*/ |
||||
|
class CourseScheduleController extends BaseApiController |
||||
|
{ |
||||
|
/** |
||||
|
* 获取课程安排列表(支持多维度筛选) |
||||
|
*/ |
||||
|
public function getScheduleList() |
||||
|
{ |
||||
|
$data = $this->request->get(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('获取成功', $service->getScheduleList($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排详情 |
||||
|
*/ |
||||
|
public function getScheduleInfo() |
||||
|
{ |
||||
|
$scheduleId = $this->request->get('schedule_id', 0); |
||||
|
if (empty($scheduleId)) { |
||||
|
return fail('课程安排ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->getScheduleInfo($scheduleId); |
||||
|
|
||||
|
if (isset($result['code']) && $result['code'] == 0) { |
||||
|
return fail($result['msg'] ?? '获取课程安排详情失败'); |
||||
|
} |
||||
|
|
||||
|
return success('获取成功', $result); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 创建课程安排 |
||||
|
*/ |
||||
|
public function createSchedule() |
||||
|
{ |
||||
|
$data = $this->request->post(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->createSchedule($data); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg'], $result['data']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量创建课程安排 |
||||
|
*/ |
||||
|
public function batchCreateSchedule() |
||||
|
{ |
||||
|
$data = $this->request->post(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->batchCreateSchedule($data); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg'], $result['data']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 更新课程安排 |
||||
|
*/ |
||||
|
public function updateSchedule() |
||||
|
{ |
||||
|
$data = $this->request->post(); |
||||
|
$scheduleId = $data['schedule_id'] ?? 0; |
||||
|
|
||||
|
if (empty($scheduleId)) { |
||||
|
return fail('课程安排ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->updateSchedule($scheduleId, $data); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg'], $result['data']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 删除课程安排 |
||||
|
*/ |
||||
|
public function deleteSchedule() |
||||
|
{ |
||||
|
$scheduleId = $this->request->post('schedule_id', 0); |
||||
|
if (empty($scheduleId)) { |
||||
|
return fail('课程安排ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->deleteSchedule($scheduleId); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取场地列表 |
||||
|
*/ |
||||
|
public function getVenueList() |
||||
|
{ |
||||
|
$data = $this->request->get(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('获取成功', $service->getVenueList($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取场地可用时间 |
||||
|
*/ |
||||
|
public function getVenueAvailableTime() |
||||
|
{ |
||||
|
$venueId = $this->request->get('venue_id', 0); |
||||
|
$date = $this->request->get('date', ''); |
||||
|
|
||||
|
if (empty($venueId)) { |
||||
|
return fail('场地ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
if (empty($date)) { |
||||
|
return fail('日期不能为空'); |
||||
|
} |
||||
|
|
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('获取成功', $service->getVenueAvailableTime($venueId, $date)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 检查教练时间冲突 |
||||
|
*/ |
||||
|
public function checkCoachConflict() |
||||
|
{ |
||||
|
$data = $this->request->get(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('检查完成', $service->checkCoachConflict($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排统计 |
||||
|
*/ |
||||
|
public function getScheduleStatistics() |
||||
|
{ |
||||
|
$data = $this->request->get(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('获取成功', $service->getScheduleStatistics($data)); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 学员加入课程安排 |
||||
|
*/ |
||||
|
public function joinSchedule() |
||||
|
{ |
||||
|
$data = $this->request->post(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->joinSchedule($data); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg'], $result['data']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 学员退出课程安排 |
||||
|
*/ |
||||
|
public function leaveSchedule() |
||||
|
{ |
||||
|
$data = $this->request->post(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
$result = $service->leaveSchedule($data); |
||||
|
|
||||
|
if ($result['code'] == 1) { |
||||
|
return success($result['msg']); |
||||
|
} else { |
||||
|
return fail($result['msg']); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取筛选选项(教练、课程、班级等) |
||||
|
*/ |
||||
|
public function getFilterOptions() |
||||
|
{ |
||||
|
$data = $this->request->get(); |
||||
|
$service = new CourseScheduleService(); |
||||
|
return success('获取成功', $service->getFilterOptions($data)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,567 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\api\apiService; |
||||
|
|
||||
|
use core\base\BaseApiService; |
||||
|
use think\facade\Db; |
||||
|
|
||||
|
/** |
||||
|
* 课程安排服务类 |
||||
|
*/ |
||||
|
class CourseScheduleService extends BaseApiService |
||||
|
{ |
||||
|
// 定义表前缀,可以更方便地引用数据表 |
||||
|
protected $prefix = 'school_'; |
||||
|
|
||||
|
public function __construct() |
||||
|
{ |
||||
|
parent::__construct(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排列表(支持多维度筛选) |
||||
|
* @param array $data 筛选参数 |
||||
|
* @return array 课程安排列表数据 |
||||
|
*/ |
||||
|
public function getScheduleList($data = []) |
||||
|
{ |
||||
|
try { |
||||
|
// 构建查询条件 |
||||
|
$where = $this->buildScheduleWhere($data); |
||||
|
|
||||
|
// 分页参数 |
||||
|
$page = intval($data['page'] ?? 1); |
||||
|
$limit = intval($data['limit'] ?? 20); |
||||
|
$offset = ($page - 1) * $limit; |
||||
|
|
||||
|
// 基础查询 |
||||
|
$query = Db::name($this->prefix . '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 . 'personnel edu', 'cs.education_id = edu.id') |
||||
|
->where($where) |
||||
|
->where('cs.deleted_at', 0); |
||||
|
|
||||
|
// 获取总数 |
||||
|
$total = $query->count(); |
||||
|
|
||||
|
// 获取列表数据 |
||||
|
$list = $query->field([ |
||||
|
'cs.*', |
||||
|
'c.course_name', |
||||
|
'c.course_type', |
||||
|
'c.duration as course_duration', |
||||
|
'c.session_count', |
||||
|
'c.single_session_count', |
||||
|
'v.venue_name', |
||||
|
'v.capacity as venue_capacity', |
||||
|
'cap.campus_name', |
||||
|
'coach.name as coach_name', |
||||
|
'coach.head_img as coach_avatar', |
||||
|
'edu.name as education_name' |
||||
|
]) |
||||
|
->order('cs.course_date DESC, cs.time_slot ASC') |
||||
|
->limit($offset, $limit) |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 处理列表数据 |
||||
|
foreach ($list as &$item) { |
||||
|
// 解析时间段 |
||||
|
$item['time_info'] = $this->parseTimeSlot($item['time_slot']); |
||||
|
|
||||
|
// 获取参与学员信息 |
||||
|
$item['students'] = $this->getScheduleStudents($item['id']); |
||||
|
|
||||
|
// 获取助教信息 |
||||
|
$item['assistants'] = $this->getScheduleAssistants($item['assistant_ids']); |
||||
|
|
||||
|
// 计算已报名人数 |
||||
|
$item['enrolled_count'] = count($item['students']); |
||||
|
|
||||
|
// 计算剩余容量 |
||||
|
$item['remaining_capacity'] = max(0, ($item['available_capacity'] ?? $item['venue_capacity']) - $item['enrolled_count']); |
||||
|
|
||||
|
// 格式化状态 |
||||
|
$item['status_text'] = $this->getStatusText($item['status']); |
||||
|
|
||||
|
// 格式化创建方式 |
||||
|
$item['created_by_text'] = $item['created_by'] == 'manual' ? '手动安排' : '系统创建'; |
||||
|
|
||||
|
// 处理图片路径 |
||||
|
$item['coach_avatar'] = $item['coach_avatar'] ? $this->formatImageUrl($item['coach_avatar']) : ''; |
||||
|
} |
||||
|
|
||||
|
return [ |
||||
|
'list' => $list, |
||||
|
'total' => $total, |
||||
|
'page' => $page, |
||||
|
'limit' => $limit, |
||||
|
'pages' => ceil($total / $limit) |
||||
|
]; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return [ |
||||
|
'list' => [], |
||||
|
'total' => 0, |
||||
|
'page' => 1, |
||||
|
'limit' => $limit ?? 20, |
||||
|
'pages' => 0, |
||||
|
'error' => $e->getMessage() |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 构建查询条件 |
||||
|
* @param array $data 筛选参数 |
||||
|
* @return array 条件数组 |
||||
|
*/ |
||||
|
private function buildScheduleWhere($data) |
||||
|
{ |
||||
|
$where = []; |
||||
|
|
||||
|
// 日期范围筛选 |
||||
|
if (!empty($data['start_date'])) { |
||||
|
$where[] = ['cs.course_date', '>=', $data['start_date']]; |
||||
|
} |
||||
|
if (!empty($data['end_date'])) { |
||||
|
$where[] = ['cs.course_date', '<=', $data['end_date']]; |
||||
|
} |
||||
|
|
||||
|
// 校区筛选 |
||||
|
if (!empty($data['campus_id'])) { |
||||
|
if (is_array($data['campus_id'])) { |
||||
|
$where[] = ['cs.campus_id', 'in', $data['campus_id']]; |
||||
|
} else { |
||||
|
$where[] = ['cs.campus_id', '=', $data['campus_id']]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 场地筛选 |
||||
|
if (!empty($data['venue_id'])) { |
||||
|
if (is_array($data['venue_id'])) { |
||||
|
$where[] = ['cs.venue_id', 'in', $data['venue_id']]; |
||||
|
} else { |
||||
|
$where[] = ['cs.venue_id', '=', $data['venue_id']]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 教练筛选 |
||||
|
if (!empty($data['coach_id'])) { |
||||
|
if (is_array($data['coach_id'])) { |
||||
|
$where[] = ['cs.coach_id', 'in', $data['coach_id']]; |
||||
|
} else { |
||||
|
$where[] = ['cs.coach_id', '=', $data['coach_id']]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 课程筛选 |
||||
|
if (!empty($data['course_id'])) { |
||||
|
if (is_array($data['course_id'])) { |
||||
|
$where[] = ['cs.course_id', 'in', $data['course_id']]; |
||||
|
} else { |
||||
|
$where[] = ['cs.course_id', '=', $data['course_id']]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 状态筛选 |
||||
|
if (!empty($data['status'])) { |
||||
|
if (is_array($data['status'])) { |
||||
|
$where[] = ['cs.status', 'in', $data['status']]; |
||||
|
} else { |
||||
|
$where[] = ['cs.status', '=', $data['status']]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 教务筛选 |
||||
|
if (!empty($data['education_id'])) { |
||||
|
$where[] = ['cs.education_id', '=', $data['education_id']]; |
||||
|
} |
||||
|
|
||||
|
// 时间段筛选 |
||||
|
if (!empty($data['time_range'])) { |
||||
|
switch ($data['time_range']) { |
||||
|
case 'morning': |
||||
|
$where[] = ['cs.time_slot', 'like', '0%']; |
||||
|
break; |
||||
|
case 'afternoon': |
||||
|
$where[] = ['cs.time_slot', 'like', '1%']; |
||||
|
break; |
||||
|
case 'evening': |
||||
|
$where[] = ['cs.time_slot', 'like', '1[8-9]%']; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 自动排课筛选 |
||||
|
if (isset($data['auto_schedule'])) { |
||||
|
$where[] = ['cs.auto_schedule', '=', $data['auto_schedule']]; |
||||
|
} |
||||
|
|
||||
|
// 创建方式筛选 |
||||
|
if (!empty($data['created_by'])) { |
||||
|
$where[] = ['cs.created_by', '=', $data['created_by']]; |
||||
|
} |
||||
|
|
||||
|
return $where; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 解析时间段 |
||||
|
* @param string $timeSlot 时间段字符串(格式如:09:00-10:30) |
||||
|
* @return array 解析后的时间段信息 |
||||
|
*/ |
||||
|
private function parseTimeSlot($timeSlot) |
||||
|
{ |
||||
|
if (strpos($timeSlot, '-') !== false) { |
||||
|
list($startTime, $endTime) = explode('-', $timeSlot); |
||||
|
return [ |
||||
|
'start_time' => trim($startTime), |
||||
|
'end_time' => trim($endTime), |
||||
|
'duration' => $this->calculateDuration(trim($startTime), trim($endTime)) |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
return [ |
||||
|
'start_time' => $timeSlot, |
||||
|
'end_time' => '', |
||||
|
'duration' => 60 // 默认1小时 |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 计算时长(分钟) |
||||
|
* @param string $startTime 开始时间 |
||||
|
* @param string $endTime 结束时间 |
||||
|
* @return int 时长(分钟) |
||||
|
*/ |
||||
|
private function calculateDuration($startTime, $endTime) |
||||
|
{ |
||||
|
try { |
||||
|
$start = strtotime($startTime); |
||||
|
$end = strtotime($endTime); |
||||
|
return ($end - $start) / 60; |
||||
|
} catch (\Exception $e) { |
||||
|
return 60; // 默认1小时 |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排的学员信息 |
||||
|
* @param int $scheduleId 课程安排ID |
||||
|
* @return array 学员信息数组 |
||||
|
*/ |
||||
|
private function getScheduleStudents($scheduleId) |
||||
|
{ |
||||
|
try { |
||||
|
$students = Db::name($this->prefix . 'person_course_schedule') |
||||
|
->alias('pcs') |
||||
|
->leftJoin($this->prefix . 'student s', 'pcs.student_id = s.id') |
||||
|
->leftJoin($this->prefix . 'customer_resources cr', 'pcs.resources_id = cr.id') |
||||
|
->leftJoin($this->prefix . 'member m', 'cr.member_id = m.member_id') |
||||
|
->where('pcs.schedule_id', $scheduleId) |
||||
|
->where('pcs.deleted_at', 0) |
||||
|
->field([ |
||||
|
'pcs.*', |
||||
|
's.name as student_name', |
||||
|
'cr.name as resource_name', |
||||
|
'cr.phone_number', |
||||
|
'cr.age', |
||||
|
'cr.gender', |
||||
|
'm.headimg as avatar' |
||||
|
]) |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
foreach ($students as &$student) { |
||||
|
$student['name'] = $student['student_name'] ?: $student['resource_name']; |
||||
|
$student['avatar'] = $student['avatar'] ? $this->formatImageUrl($student['avatar']) : ''; |
||||
|
$student['status_text'] = $this->getStudentStatusText($student['status']); |
||||
|
$student['person_type_text'] = $student['person_type'] == 'student' ? '正式学员' : '体验学员'; |
||||
|
$student['course_type_text'] = $this->getCourseTypeText($student['course_type']); |
||||
|
} |
||||
|
|
||||
|
return $students; |
||||
|
} catch (\Exception $e) { |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取助教信息 |
||||
|
* @param string $assistantIds 助教ID字符串,使用逗号分隔 |
||||
|
* @return array 助教信息数组 |
||||
|
*/ |
||||
|
private function getScheduleAssistants($assistantIds) |
||||
|
{ |
||||
|
if (empty($assistantIds)) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
try { |
||||
|
$ids = explode(',', $assistantIds); |
||||
|
$assistants = Db::name($this->prefix . 'personnel') |
||||
|
->whereIn('id', $ids) |
||||
|
->field('id, name, head_img, phone') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
foreach ($assistants as &$assistant) { |
||||
|
$assistant['head_img'] = $assistant['head_img'] ? $this->formatImageUrl($assistant['head_img']) : ''; |
||||
|
} |
||||
|
|
||||
|
return $assistants; |
||||
|
} catch (\Exception $e) { |
||||
|
return []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取状态文本 |
||||
|
* @param string $status 状态码 |
||||
|
* @return string 状态文本描述 |
||||
|
*/ |
||||
|
private function getStatusText($status) |
||||
|
{ |
||||
|
$statusMap = [ |
||||
|
'pending' => '待开始', |
||||
|
'upcoming' => '即将开始', |
||||
|
'ongoing' => '进行中', |
||||
|
'completed' => '已结束' |
||||
|
]; |
||||
|
|
||||
|
return $statusMap[$status] ?? $status; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学员状态文本 |
||||
|
* @param int $status 学员状态码 |
||||
|
* @return string 学员状态文本描述 |
||||
|
*/ |
||||
|
private function getStudentStatusText($status) |
||||
|
{ |
||||
|
$statusMap = [ |
||||
|
0 => '待上课', |
||||
|
1 => '已上课', |
||||
|
2 => '请假' |
||||
|
]; |
||||
|
|
||||
|
return $statusMap[$status] ?? '未知'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程类型文本 |
||||
|
* @param int $courseType 课程类型码 |
||||
|
* @return string 课程类型文本描述 |
||||
|
*/ |
||||
|
private function getCourseTypeText($courseType) |
||||
|
{ |
||||
|
$typeMap = [ |
||||
|
1 => '加课', |
||||
|
2 => '补课', |
||||
|
3 => '等待位' |
||||
|
]; |
||||
|
|
||||
|
return $typeMap[$courseType] ?? '正常课程'; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 格式化图片URL |
||||
|
* @param string $imagePath 图片路径 |
||||
|
* @return string 格式化后的图片URL |
||||
|
*/ |
||||
|
private function formatImageUrl($imagePath) |
||||
|
{ |
||||
|
if (empty($imagePath)) { |
||||
|
return ''; |
||||
|
} |
||||
|
|
||||
|
// 如果已经是完整URL,直接返回 |
||||
|
if (strpos($imagePath, 'http') === 0) { |
||||
|
return $imagePath; |
||||
|
} |
||||
|
|
||||
|
// 拼接域名 |
||||
|
$domain = request()->domain(); |
||||
|
return $domain . '/' . ltrim($imagePath, '/'); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取筛选选项(教练、课程、班级等) |
||||
|
* @param array $data 请求参数 |
||||
|
* @return array 筛选选项数据 |
||||
|
*/ |
||||
|
public function getFilterOptions($data = []) |
||||
|
{ |
||||
|
try { |
||||
|
$result = [ |
||||
|
'coaches' => [], // 教练列表 |
||||
|
'courses' => [], // 课程列表 |
||||
|
'classes' => [], // 班级列表 |
||||
|
'venues' => [], // 场地列表 |
||||
|
'campuses' => [], // 校区列表 |
||||
|
'status_options' => [] // 状态选项 |
||||
|
]; |
||||
|
|
||||
|
// 获取教练列表 |
||||
|
$result['coaches'] = Db::name($this->prefix . 'personnel') |
||||
|
->where('is_coach', 1) |
||||
|
->where('deleted_at', 0) |
||||
|
->field('id, name, head_img as avatar, phone') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
foreach ($result['coaches'] as &$coach) { |
||||
|
$coach['avatar'] = $coach['avatar'] ? $this->formatImageUrl($coach['avatar']) : ''; |
||||
|
} |
||||
|
|
||||
|
// 获取课程列表 |
||||
|
$result['courses'] = Db::name($this->prefix . 'course') |
||||
|
->where('deleted_at', 0) |
||||
|
->field('id, course_name, course_type, duration') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 获取班级列表 |
||||
|
$result['classes'] = Db::name($this->prefix . 'class') |
||||
|
->where('deleted_at', 0) |
||||
|
->field('id, class_name, class_level, total_students') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 获取场地列表 |
||||
|
$result['venues'] = Db::name($this->prefix . 'venue') |
||||
|
->where('deleted_at', 0) |
||||
|
->field('id, venue_name, capacity, description') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 获取校区列表 |
||||
|
$result['campuses'] = Db::name($this->prefix . 'campus') |
||||
|
->where('deleted_at', 0) |
||||
|
->field('id, campus_name, address') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
// 状态选项 |
||||
|
$result['status_options'] = [ |
||||
|
['value' => 'pending', 'label' => '待开始'], |
||||
|
['value' => 'upcoming', 'label' => '即将开始'], |
||||
|
['value' => 'ongoing', 'label' => '进行中'], |
||||
|
['value' => 'completed', 'label' => '已结束'] |
||||
|
]; |
||||
|
|
||||
|
return $result; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return [ |
||||
|
'coaches' => [], |
||||
|
'courses' => [], |
||||
|
'classes' => [], |
||||
|
'venues' => [], |
||||
|
'campuses' => [], |
||||
|
'status_options' => [], |
||||
|
'error' => $e->getMessage() |
||||
|
]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排详情 |
||||
|
* @param int $scheduleId 课程安排ID |
||||
|
* @return array 课程安排详细信息或错误信息 |
||||
|
*/ |
||||
|
public function getScheduleInfo($scheduleId) |
||||
|
{ |
||||
|
try { |
||||
|
// 查询课程安排信息 |
||||
|
$schedule = Db::name($this->prefix . '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 . 'personnel edu', 'cs.education_id = edu.id') |
||||
|
->where('cs.id', $scheduleId) |
||||
|
->where('cs.deleted_at', 0) |
||||
|
->field([ |
||||
|
'cs.*', |
||||
|
'c.course_name', |
||||
|
'c.course_type', |
||||
|
'c.duration as course_duration', |
||||
|
'c.session_count', |
||||
|
'c.single_session_count', |
||||
|
'v.venue_name', |
||||
|
'v.capacity as venue_capacity', |
||||
|
'cap.campus_name', |
||||
|
'coach.name as coach_name', |
||||
|
'coach.head_img as coach_avatar', |
||||
|
'edu.name as education_name' |
||||
|
]) |
||||
|
->find(); |
||||
|
|
||||
|
if (empty($schedule)) { |
||||
|
return ['code' => 0, 'msg' => '课程安排不存在或已被删除']; |
||||
|
} |
||||
|
|
||||
|
// 解析时间段 |
||||
|
$schedule['time_info'] = $this->parseTimeSlot($schedule['time_slot']); |
||||
|
|
||||
|
// 获取参与学员信息 |
||||
|
$schedule['students'] = $this->getScheduleStudents($schedule['id']); |
||||
|
|
||||
|
// 获取助教信息 |
||||
|
$schedule['assistants'] = $this->getScheduleAssistants($schedule['assistant_ids']); |
||||
|
|
||||
|
// 计算已报名人数 |
||||
|
$schedule['enrolled_count'] = count($schedule['students']); |
||||
|
|
||||
|
// 计算剩余容量 |
||||
|
$schedule['remaining_capacity'] = max(0, ($schedule['available_capacity'] ?? $schedule['venue_capacity']) - $schedule['enrolled_count']); |
||||
|
|
||||
|
// 格式化状态 |
||||
|
$schedule['status_text'] = $this->getStatusText($schedule['status']); |
||||
|
|
||||
|
// 格式化创建方式 |
||||
|
$schedule['created_by_text'] = $schedule['created_by'] == 'manual' ? '手动安排' : '系统创建'; |
||||
|
|
||||
|
// 处理图片路径 |
||||
|
$schedule['coach_avatar'] = $schedule['coach_avatar'] ? $this->formatImageUrl($schedule['coach_avatar']) : ''; |
||||
|
|
||||
|
// 获取班级相关信息 |
||||
|
if (!empty($schedule['class_id'])) { |
||||
|
$schedule['class_info'] = Db::name($this->prefix . 'class') |
||||
|
->where('id', $schedule['class_id']) |
||||
|
->field('id, class_name, class_level, total_students') |
||||
|
->find(); |
||||
|
} else { |
||||
|
$schedule['class_info'] = null; |
||||
|
} |
||||
|
|
||||
|
// 获取历史变更记录 |
||||
|
$schedule['change_history'] = Db::name($this->prefix . 'course_schedule_changes') |
||||
|
->where('schedule_id', $scheduleId) |
||||
|
->order('created_at DESC') |
||||
|
->select() |
||||
|
->toArray(); |
||||
|
|
||||
|
return $schedule; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
return ['code' => 0, 'msg' => $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,175 @@ |
|||||
|
# 课程安排表组件 |
||||
|
|
||||
|
## 功能特性 |
||||
|
|
||||
|
### 📅 完整的课程表视图 |
||||
|
- **时间轴显示**:11:00-17:00 每小时时间段 |
||||
|
- **周视图**:显示一周7天的课程安排 |
||||
|
- **双向滚动**:支持水平(日期)和垂直(时间)滚动 |
||||
|
- **响应式布局**:适配微信小程序端 |
||||
|
|
||||
|
### 🔍 智能筛选功能 |
||||
|
- **顶部快捷筛选**:时间、老师、教室、班级 |
||||
|
- **详细筛选弹窗**:支持多选条件组合筛选 |
||||
|
- **实时统计**:显示总课程数和未点名课程数 |
||||
|
|
||||
|
### 🎨 主题样式 |
||||
|
- **暗黑主题**:#292929 背景色 |
||||
|
- **绿色主色调**:#29d3b4 强调色 |
||||
|
- **层次化设计**:清晰的视觉层级 |
||||
|
|
||||
|
### 📱 交互体验 |
||||
|
- **日期导航**:支持上一周/下一周切换 |
||||
|
- **课程类型**:普通课程、私教课程、活动课程区分显示 |
||||
|
- **点击交互**:支持单元格点击添加课程 |
||||
|
- **悬浮按钮**:快速添加课程 |
||||
|
|
||||
|
## 使用方法 |
||||
|
|
||||
|
### 1. 页面导航 |
||||
|
```javascript |
||||
|
// 跳转到课程安排表 |
||||
|
this.$navigateTo({ |
||||
|
url: '/pages/coach/schedule/schedule_table' |
||||
|
}) |
||||
|
``` |
||||
|
|
||||
|
### 2. 数据结构 |
||||
|
|
||||
|
#### 课程数据格式 |
||||
|
```javascript |
||||
|
{ |
||||
|
id: 1, // 课程ID |
||||
|
date: '2025-07-02', // 日期 |
||||
|
time: '11:00', // 时间 |
||||
|
courseName: '花花-丁颖', // 课程名称 |
||||
|
students: '小鱼-周子', // 学员 |
||||
|
teacher: '燕子-符', // 老师 |
||||
|
status: '燕菜', // 状态 |
||||
|
type: 'normal', // 类型:normal/private/activity |
||||
|
duration: 1 // 持续时间(小时) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 筛选选项格式 |
||||
|
```javascript |
||||
|
{ |
||||
|
teacherOptions: [ |
||||
|
{ id: 1, name: '张老师' } |
||||
|
], |
||||
|
classroomOptions: [ |
||||
|
{ id: 1, name: '教室1' } |
||||
|
], |
||||
|
classOptions: [ |
||||
|
{ id: 1, name: '花花-丁颖' } |
||||
|
] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. 自定义配置 |
||||
|
|
||||
|
#### 修改时间段 |
||||
|
```javascript |
||||
|
// 在 data 中修改 timeSlots |
||||
|
timeSlots: [ |
||||
|
{ time: '09:00', value: 9 }, |
||||
|
{ time: '10:00', value: 10 }, |
||||
|
// ... 更多时间段 |
||||
|
] |
||||
|
``` |
||||
|
|
||||
|
#### 修改表格宽度 |
||||
|
```javascript |
||||
|
// 调整表格总宽度(rpx) |
||||
|
tableWidth: 1200 |
||||
|
``` |
||||
|
|
||||
|
### 4. API 接口集成 |
||||
|
|
||||
|
#### 获取课程数据 |
||||
|
```javascript |
||||
|
async loadCourses() { |
||||
|
try { |
||||
|
const res = await apiRoute.getCourseSchedule({ |
||||
|
startDate: this.currentWeekStart, |
||||
|
endDate: this.getWeekEndDate() |
||||
|
}) |
||||
|
this.courses = res.data |
||||
|
} catch (error) { |
||||
|
console.error('加载课程失败:', error) |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 添加课程 |
||||
|
```javascript |
||||
|
async addCourse(courseData) { |
||||
|
try { |
||||
|
const res = await apiRoute.addCourseSchedule(courseData) |
||||
|
if (res.code === 1) { |
||||
|
this.loadCourses() // 重新加载数据 |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('添加课程失败:', error) |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 性能优化建议 |
||||
|
|
||||
|
### 1. 数据懒加载 |
||||
|
- 只加载当前周的数据 |
||||
|
- 切换周时再加载新数据 |
||||
|
|
||||
|
### 2. 滚动优化 |
||||
|
- 使用 `scroll-view` 组件的惯性滚动 |
||||
|
- 避免在滚动事件中进行复杂计算 |
||||
|
|
||||
|
### 3. 渲染优化 |
||||
|
- 使用 `v-show` 替代 `v-if` 进行显示隐藏 |
||||
|
- 合理使用 `key` 属性优化列表渲染 |
||||
|
|
||||
|
## 扩展功能 |
||||
|
|
||||
|
### 1. 课程详情 |
||||
|
```javascript |
||||
|
// 点击课程查看详情 |
||||
|
handleCourseClick(course) { |
||||
|
this.$navigateTo({ |
||||
|
url: `/pages/coach/course/info?id=${course.id}` |
||||
|
}) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. 拖拽排课 |
||||
|
```javascript |
||||
|
// 可以集成拖拽功能实现课程时间调整 |
||||
|
// 使用 movable-view 组件或手势事件 |
||||
|
``` |
||||
|
|
||||
|
### 3. 批量操作 |
||||
|
```javascript |
||||
|
// 支持批量选择和操作课程 |
||||
|
// 添加多选模式 |
||||
|
``` |
||||
|
|
||||
|
## 注意事项 |
||||
|
|
||||
|
1. **微信小程序兼容性**:使用了 `scroll-view` 组件,确保在微信小程序中正常滚动 |
||||
|
2. **数据更新**:切换日期时需要重新加载数据 |
||||
|
3. **内存管理**:大量数据时考虑虚拟滚动优化 |
||||
|
4. **网络状态**:处理网络异常情况,提供离线缓存 |
||||
|
|
||||
|
## troubleshooting |
||||
|
|
||||
|
### 滚动不流畅 |
||||
|
- 检查 `scroll-view` 的高度设置 |
||||
|
- 减少滚动事件中的计算量 |
||||
|
|
||||
|
### 数据不更新 |
||||
|
- 确保数据是响应式的 |
||||
|
- 检查 Vue 数据绑定是否正确 |
||||
|
|
||||
|
### 样式错位 |
||||
|
- 检查 flex 布局设置 |
||||
|
- 确保单元格宽度一致 |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue