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