21 changed files with 2253 additions and 469 deletions
@ -0,0 +1,172 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\adminapi\controller\sys; |
|||
|
|||
use app\service\admin\sys\SysMenuService; |
|||
use core\base\BaseAdminController; |
|||
use think\Response; |
|||
|
|||
/** |
|||
* 系统菜单管理控制器 |
|||
*/ |
|||
class SysMenu extends BaseAdminController |
|||
{ |
|||
/** |
|||
* 获取菜单列表 |
|||
* @return Response |
|||
*/ |
|||
public function lists() |
|||
{ |
|||
$data = $this->request->params([ |
|||
['keyword', ''], |
|||
['status', ''], |
|||
['page', 1], |
|||
['limit', 20] |
|||
]); |
|||
|
|||
$service = new SysMenuService(); |
|||
return success($service->getPage($data)); |
|||
} |
|||
|
|||
/** |
|||
* 获取菜单详情 |
|||
* @param int $id |
|||
* @return Response |
|||
*/ |
|||
public function info(int $id) |
|||
{ |
|||
$service = new SysMenuService(); |
|||
return success($service->getInfo($id)); |
|||
} |
|||
|
|||
/** |
|||
* 添加菜单 |
|||
* @return Response |
|||
*/ |
|||
public function add() |
|||
{ |
|||
$data = $this->request->params([ |
|||
['menu_key', ''], |
|||
['menu_name', ''], |
|||
['menu_icon', ''], |
|||
['menu_path', ''], |
|||
['menu_params', ''], |
|||
['sort_order', 0], |
|||
['status', 1], |
|||
['description', ''] |
|||
]); |
|||
|
|||
$this->validate($data, [ |
|||
'menu_key' => 'require|unique:sys_menus', |
|||
'menu_name' => 'require', |
|||
'menu_path' => 'require' |
|||
]); |
|||
|
|||
$service = new SysMenuService(); |
|||
$id = $service->add($data); |
|||
return success(['id' => $id], '添加成功'); |
|||
} |
|||
|
|||
/** |
|||
* 编辑菜单 |
|||
* @param int $id |
|||
* @return Response |
|||
*/ |
|||
public function edit(int $id) |
|||
{ |
|||
$data = $this->request->params([ |
|||
['menu_key', ''], |
|||
['menu_name', ''], |
|||
['menu_icon', ''], |
|||
['menu_path', ''], |
|||
['menu_params', ''], |
|||
['sort_order', 0], |
|||
['status', 1], |
|||
['description', ''] |
|||
]); |
|||
|
|||
$this->validate($data, [ |
|||
'menu_key' => 'require', |
|||
'menu_name' => 'require', |
|||
'menu_path' => 'require' |
|||
]); |
|||
|
|||
$service = new SysMenuService(); |
|||
$service->edit($id, $data); |
|||
return success([], '编辑成功'); |
|||
} |
|||
|
|||
/** |
|||
* 删除菜单 |
|||
* @param int $id |
|||
* @return Response |
|||
*/ |
|||
public function del(int $id) |
|||
{ |
|||
$service = new SysMenuService(); |
|||
$service->del($id); |
|||
return success([], '删除成功'); |
|||
} |
|||
|
|||
/** |
|||
* 修改菜单状态 |
|||
* @param int $id |
|||
* @return Response |
|||
*/ |
|||
public function modifyStatus(int $id) |
|||
{ |
|||
$data = $this->request->params([ |
|||
['status', 1] |
|||
]); |
|||
|
|||
$service = new SysMenuService(); |
|||
$service->modifyStatus($id, $data['status']); |
|||
return success([], '操作成功'); |
|||
} |
|||
|
|||
/** |
|||
* 获取所有菜单(用于角色权限配置) |
|||
* @return Response |
|||
*/ |
|||
public function getAllMenus() |
|||
{ |
|||
$service = new SysMenuService(); |
|||
return success($service->getAllMenus()); |
|||
} |
|||
|
|||
/** |
|||
* 获取角色菜单权限 |
|||
* @param int $roleId |
|||
* @return Response |
|||
*/ |
|||
public function getRoleMenus(int $roleId) |
|||
{ |
|||
$service = new SysMenuService(); |
|||
return success($service->getRoleMenus($roleId)); |
|||
} |
|||
|
|||
/** |
|||
* 设置角色菜单权限 |
|||
* @param int $roleId |
|||
* @return Response |
|||
*/ |
|||
public function setRoleMenus(int $roleId) |
|||
{ |
|||
$data = $this->request->params([ |
|||
['menu_ids', []] |
|||
]); |
|||
|
|||
$service = new SysMenuService(); |
|||
$service->setRoleMenus($roleId, $data['menu_ids']); |
|||
return success([], '设置成功'); |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\adminapi\controller\uniapp; |
|||
|
|||
use core\base\BaseAdminController; |
|||
use app\service\admin\uniapp\UniappAuthService; |
|||
|
|||
/** |
|||
* UniApp权限管理控制器 |
|||
* Class UniappAuth |
|||
* @package app\adminapi\controller\uniapp |
|||
*/ |
|||
class UniappAuth extends BaseAdminController |
|||
{ |
|||
/** |
|||
* 获取所有UniApp菜单(用于权限配置) |
|||
*/ |
|||
public function getMenus() |
|||
{ |
|||
return success((new UniappAuthService())->getAllMenus()); |
|||
} |
|||
|
|||
/** |
|||
* 获取角色的UniApp菜单权限 |
|||
* @param int $role_id |
|||
* @return \think\Response |
|||
*/ |
|||
public function getRoleMenus(int $role_id) |
|||
{ |
|||
return success((new UniappAuthService())->getRoleMenus($role_id)); |
|||
} |
|||
|
|||
/** |
|||
* 设置角色的UniApp菜单权限 |
|||
* @param int $role_id |
|||
* @return \think\Response |
|||
*/ |
|||
public function setRoleMenus(int $role_id) |
|||
{ |
|||
$data = $this->request->params([ |
|||
['menu_ids', []] |
|||
]); |
|||
(new UniappAuthService())->setRoleMenus($role_id, $data['menu_ids']); |
|||
return success([], '设置成功'); |
|||
} |
|||
} |
|||
@ -0,0 +1,50 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
use think\facade\Route; |
|||
|
|||
/** |
|||
* 系统菜单管理路由 |
|||
*/ |
|||
Route::group('sys_menu', function () { |
|||
|
|||
// 菜单列表 |
|||
Route::get('lists', 'app\adminapi\controller\sys\SysMenu@lists'); |
|||
|
|||
// 菜单详情 |
|||
Route::get('info/:id', 'app\adminapi\controller\sys\SysMenu@info'); |
|||
|
|||
// 添加菜单 |
|||
Route::post('add', 'app\adminapi\controller\sys\SysMenu@add'); |
|||
|
|||
// 编辑菜单 |
|||
Route::put('edit/:id', 'app\adminapi\controller\sys\SysMenu@edit'); |
|||
|
|||
// 删除菜单 |
|||
Route::delete('del/:id', 'app\adminapi\controller\sys\SysMenu@del'); |
|||
|
|||
// 修改菜单状态 |
|||
Route::put('modify_status/:id', 'app\adminapi\controller\sys\SysMenu@modifyStatus'); |
|||
|
|||
// 获取所有菜单(用于角色权限配置) |
|||
Route::get('all_menus', 'app\adminapi\controller\sys\SysMenu@getAllMenus'); |
|||
|
|||
// 获取角色菜单权限 |
|||
Route::get('role_menus/:role_id', 'app\adminapi\controller\sys\SysMenu@getRoleMenus'); |
|||
|
|||
// 设置角色菜单权限 |
|||
Route::post('set_role_menus/:role_id', 'app\adminapi\controller\sys\SysMenu@setRoleMenus'); |
|||
|
|||
})->middleware([ |
|||
app\adminapi\middleware\AdminCheckToken::class, |
|||
app\adminapi\middleware\AdminCheckRole::class, |
|||
app\adminapi\middleware\AdminLog::class |
|||
]); |
|||
@ -0,0 +1,32 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
use think\facade\Route; |
|||
|
|||
/** |
|||
* UniApp权限管理路由 |
|||
*/ |
|||
Route::group('uniapp_auth', function () { |
|||
|
|||
// 获取所有UniApp菜单(用于权限配置) |
|||
Route::get('menus', 'app\adminapi\controller\uniapp\UniappAuth@getMenus'); |
|||
|
|||
// 获取角色的UniApp菜单权限 |
|||
Route::get('role_menus/:role_id', 'app\adminapi\controller\uniapp\UniappAuth@getRoleMenus'); |
|||
|
|||
// 设置角色的UniApp菜单权限 |
|||
Route::post('set_role_menus/:role_id', 'app\adminapi\controller\uniapp\UniappAuth@setRoleMenus'); |
|||
|
|||
})->middleware([ |
|||
app\adminapi\middleware\AdminCheckToken::class, |
|||
app\adminapi\middleware\AdminCheckRole::class, |
|||
app\adminapi\middleware\AdminLog::class |
|||
]); |
|||
@ -0,0 +1,272 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\job\transfer\schedule; |
|||
|
|||
use app\model\course\Course; |
|||
use app\model\course_booking\CourseBooking; |
|||
use think\facade\Db; |
|||
use core\base\BaseJob; |
|||
use think\facade\Log; |
|||
|
|||
/** |
|||
* 定时处理课程有效状态任务 |
|||
* 检查课程的有效期,更新过期课程状态 |
|||
*/ |
|||
class CourseValidityJob extends BaseJob |
|||
{ |
|||
/** |
|||
* 执行定时任务 |
|||
* @param mixed ...$data 任务参数 |
|||
* @return bool 处理结果 |
|||
*/ |
|||
public function doJob(...$data) |
|||
{ |
|||
Log::write('开始执行课程有效状态检查任务'); |
|||
|
|||
try { |
|||
// 处理过期课程 |
|||
$expiredResult = $this->processExpiredCourses(); |
|||
|
|||
// 处理孩子的固定课 |
|||
$warningResult = $this->processStudentCourses(); |
|||
|
|||
Log::write('课程有效状态检查任务执行完成-' . print_r([ |
|||
'expired_courses' => $expiredResult['count'], |
|||
'warning_courses' => $warningResult['count'], |
|||
], true)); |
|||
|
|||
return true; |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('课程有效状态检查任务执行失败:' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 处理课程的状态 |
|||
* 根据school_course_schedule表的course_date和time_slot更新课程状态 |
|||
* @return array 处理结果 |
|||
*/ |
|||
private function processExpiredCourses() |
|||
{ |
|||
$currentDate = date('Y-m-d'); |
|||
$currentTime = date('H:i:s'); |
|||
$currentDateTime = date('Y-m-d H:i:s'); |
|||
|
|||
$count = 0; |
|||
|
|||
try { |
|||
// 1. 更新已结束的课程 - course_date小于当前日期 |
|||
$completedCount = Db::name('school_course_schedule') |
|||
->where('course_date', '<', $currentDate) |
|||
->where('status', '<>', 'completed') |
|||
->update([ |
|||
'status' => 'completed', |
|||
'updated_at' => $currentDateTime |
|||
]); |
|||
|
|||
Log::write("更新已结束课程数量: {$completedCount}"); |
|||
$count += $completedCount; |
|||
|
|||
// 2. 处理今天的课程,根据time_slot判断状态 |
|||
$todaySchedules = Db::name('school_course_schedule') |
|||
->where('course_date', $currentDate) |
|||
->where('status', '<>', 'completed') |
|||
->select(); |
|||
|
|||
foreach ($todaySchedules as $schedule) { |
|||
$newStatus = $this->calculateCourseStatus($schedule['time_slot'], $currentTime); |
|||
|
|||
if ($newStatus && $newStatus != $schedule['status']) { |
|||
Db::name('school_course_schedule') |
|||
->where('id', $schedule['id']) |
|||
->update([ |
|||
'status' => $newStatus, |
|||
'updated_at' => $currentDateTime |
|||
]); |
|||
|
|||
$count++; |
|||
Log::write("更新课程状态: ID {$schedule['id']}, 时间段 {$schedule['time_slot']}, 状态 {$schedule['status']} -> {$newStatus}"); |
|||
} |
|||
} |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('处理课程状态失败: ' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 处理学员固定课的安排 |
|||
* 查看school_person_course_schedule表中schedule_type=2且person_type=student的课程 |
|||
* 自动为下周相同时间新增上课记录 |
|||
* @return array 处理结果 |
|||
*/ |
|||
private function processStudentCourses() |
|||
{ |
|||
$currentDate = date('Y-m-d'); |
|||
$currentDateTime = date('Y-m-d H:i:s'); |
|||
$count = 0; |
|||
|
|||
try { |
|||
// 查找学员的固定课程安排 |
|||
$studentFixedCourses = Db::name('school_person_course_schedule') |
|||
->where('schedule_type', 2) |
|||
->where('person_type', 'student') |
|||
->where('status', 1) // 假设1为有效状态 |
|||
->select(); |
|||
|
|||
foreach ($studentFixedCourses as $studentCourse) { |
|||
// 通过schedule_id获取课程安排信息 |
|||
$courseSchedule = Db::name('school_course_schedule') |
|||
->where('id', $studentCourse['schedule_id']) |
|||
->find(); |
|||
|
|||
if (!$courseSchedule) { |
|||
continue; |
|||
} |
|||
|
|||
// 计算下周同一天的日期 |
|||
$courseDate = $courseSchedule['course_date']; |
|||
$dayOfWeek = date('w', strtotime($courseDate)); // 0(周日)到6(周六) |
|||
$nextWeekDate = $this->getNextWeekSameDay($courseDate); |
|||
|
|||
// 检查下周那天是否有相同时间段的课程安排 |
|||
$nextWeekSchedule = Db::name('school_course_schedule') |
|||
->where('course_date', $nextWeekDate) |
|||
->where('time_slot', $courseSchedule['time_slot']) |
|||
->where('venue_id', $courseSchedule['venue_id']) // 同一场地 |
|||
->find(); |
|||
|
|||
if ($nextWeekSchedule) { |
|||
// 检查该学员是否已经有下周的上课记录 |
|||
$existingRecord = Db::name('school_person_course_schedule') |
|||
->where('person_id', $studentCourse['person_id']) |
|||
->where('schedule_id', $nextWeekSchedule['id']) |
|||
->where('person_type', 'student') |
|||
->count(); |
|||
|
|||
if ($existingRecord == 0) { |
|||
// 为学员新增下周的上课记录 |
|||
$newRecord = [ |
|||
'person_id' => $studentCourse['person_id'], |
|||
'schedule_id' => $nextWeekSchedule['id'], |
|||
'person_type' => 'student', |
|||
'schedule_type' => 2, // 固定课 |
|||
'status' => 1, |
|||
'created_at' => $currentDateTime, |
|||
'updated_at' => $currentDateTime |
|||
]; |
|||
|
|||
// 复制其他可能的字段 |
|||
if (isset($studentCourse['course_id'])) { |
|||
$newRecord['course_id'] = $studentCourse['course_id']; |
|||
} |
|||
if (isset($studentCourse['venue_id'])) { |
|||
$newRecord['venue_id'] = $studentCourse['venue_id']; |
|||
} |
|||
|
|||
Db::name('school_person_course_schedule')->insert($newRecord); |
|||
|
|||
$count++; |
|||
Log::write("为学员新增下周固定课: 学员ID {$studentCourse['person_id']}, 日期 {$nextWeekDate}, 时间段 {$courseSchedule['time_slot']}"); |
|||
} |
|||
} else { |
|||
Log::write("下周无对应课程安排,跳过: 日期 {$nextWeekDate}, 时间段 {$courseSchedule['time_slot']}"); |
|||
} |
|||
} |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('处理学员固定课安排失败: ' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 根据时间段和当前时间计算课程状态 |
|||
* @param string $timeSlot 时间段,格式如 "11:30-12:30" |
|||
* @param string $currentTime 当前时间,格式如 "14:30:00" |
|||
* @return string|null 课程状态:upcoming, ongoing, completed 或 null(无需更新) |
|||
*/ |
|||
private function calculateCourseStatus($timeSlot, $currentTime) |
|||
{ |
|||
if (empty($timeSlot)) { |
|||
return null; |
|||
} |
|||
|
|||
// 解析时间段,格式如 "11:30-12:30" |
|||
$timeParts = explode('-', $timeSlot); |
|||
if (count($timeParts) != 2) { |
|||
Log::write("时间段格式错误: {$timeSlot}"); |
|||
return null; |
|||
} |
|||
|
|||
$startTime = trim($timeParts[0]) . ':00'; // 转换为 "11:30:00" |
|||
$endTime = trim($timeParts[1]) . ':00'; // 转换为 "12:30:00" |
|||
|
|||
$currentTimestamp = strtotime($currentTime); |
|||
$startTimestamp = strtotime($startTime); |
|||
$endTimestamp = strtotime($endTime); |
|||
$sixHoursBeforeStart = $startTimestamp - (6 * 3600); // 开始时间前6小时 |
|||
|
|||
if ($currentTimestamp <= $sixHoursBeforeStart) { |
|||
// 当前时间距离开始时间超过6小时,不需要更新状态 |
|||
return null; |
|||
} elseif ($currentTimestamp <= $startTimestamp) { |
|||
// 当前时间在开始前6小时内,状态为即将开始 |
|||
return 'upcoming'; |
|||
} elseif ($currentTimestamp >= $startTimestamp && $currentTimestamp <= $endTimestamp) { |
|||
// 当前时间在课程时间范围内,状态为进行中 |
|||
return 'ongoing'; |
|||
} elseif ($currentTimestamp > $endTimestamp) { |
|||
// 当前时间已过结束时间,状态为已结束 |
|||
return 'completed'; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/** |
|||
* 计算下周同一天的日期 |
|||
* @param string $courseDate 课程日期,格式如 "2025-01-08" |
|||
* @return string 下周同一天的日期 |
|||
*/ |
|||
private function getNextWeekSameDay($courseDate) |
|||
{ |
|||
$timestamp = strtotime($courseDate); |
|||
$nextWeekTimestamp = $timestamp + (7 * 24 * 3600); // 加7天 |
|||
return date('Y-m-d', $nextWeekTimestamp); |
|||
} |
|||
|
|||
/** |
|||
* 发送课程即将过期的警告通知 |
|||
* @param Course $course 课程对象 |
|||
*/ |
|||
protected function sendExpiryWarning($course) |
|||
{ |
|||
// 这里可以实现具体的通知逻辑 |
|||
// 比如发送邮件、短信、系统内通知等 |
|||
Log::write("发送过期提醒通知:课程 {$course->course_name} 将在 {$course->valid_until} 过期"); |
|||
|
|||
// 示例:可以调用通知服务 |
|||
// NotificationService::send([ |
|||
// 'type' => 'course_expiry_warning', |
|||
// 'course_id' => $course->id, |
|||
// 'message' => "您的课程《{$course->course_name}》将在{$course->valid_until}过期" |
|||
// ]); |
|||
} |
|||
} |
|||
@ -0,0 +1,277 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\job\transfer\schedule; |
|||
|
|||
use app\model\staff\Staff; |
|||
use app\model\staff\StaffAttendance; |
|||
use core\base\BaseJob; |
|||
use think\facade\Log; |
|||
|
|||
/** |
|||
* 定时处理员工考勤状态任务 |
|||
* 自动生成考勤记录、处理迟到早退、计算工作时长等 |
|||
*/ |
|||
class StaffAttendanceJob extends BaseJob |
|||
{ |
|||
/** |
|||
* 执行定时任务 |
|||
* @param mixed ...$data 任务参数 |
|||
* @return bool 处理结果 |
|||
*/ |
|||
public function doJob(...$data) |
|||
{ |
|||
Log::write('开始执行员工考勤状态处理任务'); |
|||
|
|||
try { |
|||
$currentDate = date('Y-m-d'); |
|||
$currentTime = date('H:i:s'); |
|||
|
|||
// 处理当日考勤记录生成 |
|||
$generateResult = $this->generateDailyAttendance($currentDate); |
|||
|
|||
// 处理迟到状态更新 |
|||
$lateResult = $this->processLateArrivals($currentDate, $currentTime); |
|||
|
|||
// 处理早退状态更新 |
|||
$earlyResult = $this->processEarlyDepartures($currentDate, $currentTime); |
|||
|
|||
// 处理缺勤状态更新 |
|||
$absentResult = $this->processAbsences($currentDate); |
|||
|
|||
// 计算工作时长 |
|||
$workHoursResult = $this->calculateWorkHours($currentDate); |
|||
|
|||
Log::write('员工考勤状态处理任务执行完成', [ |
|||
'generated_records' => $generateResult['count'], |
|||
'late_arrivals' => $lateResult['count'], |
|||
'early_departures' => $earlyResult['count'], |
|||
'absences' => $absentResult['count'], |
|||
'calculated_hours' => $workHoursResult['count'] |
|||
]); |
|||
|
|||
return true; |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('员工考勤状态处理任务执行失败:' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 生成当日考勤记录 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function generateDailyAttendance($date) |
|||
{ |
|||
// 获取所有在职员工 |
|||
$activeStaff = Staff::where('status', 1)->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($activeStaff as $staff) { |
|||
// 检查是否已有当日考勤记录 |
|||
$exists = StaffAttendance::where('staff_id', $staff->id) |
|||
->where('attendance_date', $date) |
|||
->count(); |
|||
|
|||
if ($exists == 0) { |
|||
// 创建考勤记录 |
|||
$attendance = new StaffAttendance(); |
|||
$attendance->staff_id = $staff->id; |
|||
$attendance->attendance_date = $date; |
|||
$attendance->status = 'pending'; // 待考勤状态 |
|||
$attendance->work_schedule_start = '09:00:00'; // 默认上班时间 |
|||
$attendance->work_schedule_end = '18:00:00'; // 默认下班时间 |
|||
$attendance->created_at = date('Y-m-d H:i:s'); |
|||
$attendance->save(); |
|||
|
|||
$count++; |
|||
Log::write("为员工生成考勤记录:员工ID {$staff->id}, 日期: {$date}"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 处理迟到状态 |
|||
* @param string $date 日期 |
|||
* @param string $currentTime 当前时间 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function processLateArrivals($date, $currentTime) |
|||
{ |
|||
// 查找今日还未签到但已过上班时间的员工 |
|||
$lateThreshold = '09:30:00'; // 迟到阈值:9:30后算迟到 |
|||
|
|||
$lateAttendances = StaffAttendance::where('attendance_date', $date) |
|||
->where('check_in_time', null) |
|||
->where('work_schedule_start', '<', $currentTime) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($lateAttendances as $attendance) { |
|||
if ($currentTime > $lateThreshold) { |
|||
$attendance->status = 'late'; |
|||
$attendance->late_minutes = $this->calculateLateMinutes($attendance->work_schedule_start, $currentTime); |
|||
$attendance->save(); |
|||
|
|||
$count++; |
|||
Log::write("员工迟到:员工ID {$attendance->staff_id}, 迟到 {$attendance->late_minutes} 分钟"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 处理早退状态 |
|||
* @param string $date 日期 |
|||
* @param string $currentTime 当前时间 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function processEarlyDepartures($date, $currentTime) |
|||
{ |
|||
// 查找今日已签到但提前签退的员工 |
|||
$earlyAttendances = StaffAttendance::where('attendance_date', $date) |
|||
->where('check_in_time', '<>', null) |
|||
->where('check_out_time', '<>', null) |
|||
->where('check_out_time', '<', 'work_schedule_end') |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($earlyAttendances as $attendance) { |
|||
$attendance->status = 'early_departure'; |
|||
$attendance->early_minutes = $this->calculateEarlyMinutes($attendance->check_out_time, $attendance->work_schedule_end); |
|||
$attendance->save(); |
|||
|
|||
$count++; |
|||
Log::write("员工早退:员工ID {$attendance->staff_id}, 早退 {$attendance->early_minutes} 分钟"); |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 处理缺勤状态 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function processAbsences($date) |
|||
{ |
|||
$currentTime = date('H:i:s'); |
|||
$absentThreshold = '18:30:00'; // 18:30后还未签到算缺勤 |
|||
|
|||
if ($currentTime < $absentThreshold) { |
|||
return ['count' => 0]; // 还没到判断缺勤的时间 |
|||
} |
|||
|
|||
// 查找今日全天未签到的员工 |
|||
$absentAttendances = StaffAttendance::where('attendance_date', $date) |
|||
->where('check_in_time', null) |
|||
->where('status', '!=', 'leave') // 排除请假的员工 |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($absentAttendances as $attendance) { |
|||
$attendance->status = 'absent'; |
|||
$attendance->save(); |
|||
|
|||
$count++; |
|||
Log::write("员工缺勤:员工ID {$attendance->staff_id}, 日期: {$date}"); |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 计算工作时长 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function calculateWorkHours($date) |
|||
{ |
|||
// 查找当日已完成签到签退的考勤记录 |
|||
$completedAttendances = StaffAttendance::where('attendance_date', $date) |
|||
->where('check_in_time', '<>', null) |
|||
->where('check_out_time', '<>', null) |
|||
->where('work_hours', null) // 还未计算工作时长的 |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($completedAttendances as $attendance) { |
|||
$workHours = $this->calculateHoursBetween($attendance->check_in_time, $attendance->check_out_time); |
|||
|
|||
$attendance->work_hours = $workHours; |
|||
$attendance->save(); |
|||
|
|||
$count++; |
|||
Log::write("计算员工工作时长:员工ID {$attendance->staff_id}, 工作时长: {$workHours} 小时"); |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 计算迟到分钟数 |
|||
* @param string $scheduleTime 计划时间 |
|||
* @param string $actualTime 实际时间 |
|||
* @return int 迟到分钟数 |
|||
*/ |
|||
protected function calculateLateMinutes($scheduleTime, $actualTime) |
|||
{ |
|||
$schedule = strtotime($scheduleTime); |
|||
$actual = strtotime($actualTime); |
|||
|
|||
if ($actual > $schedule) { |
|||
return intval(($actual - $schedule) / 60); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/** |
|||
* 计算早退分钟数 |
|||
* @param string $actualTime 实际时间 |
|||
* @param string $scheduleTime 计划时间 |
|||
* @return int 早退分钟数 |
|||
*/ |
|||
protected function calculateEarlyMinutes($actualTime, $scheduleTime) |
|||
{ |
|||
$actual = strtotime($actualTime); |
|||
$schedule = strtotime($scheduleTime); |
|||
|
|||
if ($schedule > $actual) { |
|||
return intval(($schedule - $actual) / 60); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
/** |
|||
* 计算两个时间之间的小时数 |
|||
* @param string $startTime 开始时间 |
|||
* @param string $endTime 结束时间 |
|||
* @return float 小时数 |
|||
*/ |
|||
protected function calculateHoursBetween($startTime, $endTime) |
|||
{ |
|||
$start = strtotime($startTime); |
|||
$end = strtotime($endTime); |
|||
|
|||
if ($end > $start) { |
|||
return round(($end - $start) / 3600, 2); |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
} |
|||
@ -0,0 +1,372 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\job\transfer\schedule; |
|||
|
|||
use app\model\member\Member; |
|||
use app\model\course\Course; |
|||
use app\model\course_booking\CourseBooking; |
|||
use app\model\notification\Notification; |
|||
use core\base\BaseJob; |
|||
use think\facade\Log; |
|||
|
|||
/** |
|||
* 定时检查学员课程有效期和课时数量发送提醒任务 |
|||
* 监控学员课程状态,及时发送相关提醒通知 |
|||
*/ |
|||
class StudentCourseReminderJob extends BaseJob |
|||
{ |
|||
/** |
|||
* 执行定时任务 |
|||
* @param mixed ...$data 任务参数 |
|||
* @return bool 处理结果 |
|||
*/ |
|||
public function doJob(...$data) |
|||
{ |
|||
Log::write('开始执行学员课程提醒任务'); |
|||
|
|||
try { |
|||
// 检查课程有效期提醒 |
|||
$expiryResult = $this->checkCourseExpiryReminders(); |
|||
|
|||
// 检查课时余量提醒 |
|||
$hoursResult = $this->checkRemainingHoursReminders(); |
|||
|
|||
// 检查预约课程提醒 |
|||
$bookingResult = $this->checkUpcomingBookingReminders(); |
|||
|
|||
// 检查长期未上课提醒 |
|||
$inactiveResult = $this->checkInactiveStudentReminders(); |
|||
|
|||
// 检查课程完成祝贺 |
|||
$completionResult = $this->checkCourseCompletionCongratulations(); |
|||
|
|||
Log::write('学员课程提醒任务执行完成', [ |
|||
'expiry_reminders' => $expiryResult['count'], |
|||
'hours_reminders' => $hoursResult['count'], |
|||
'booking_reminders' => $bookingResult['count'], |
|||
'inactive_reminders' => $inactiveResult['count'], |
|||
'completion_congratulations' => $completionResult['count'] |
|||
]); |
|||
|
|||
return true; |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('学员课程提醒任务执行失败:' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 检查课程有效期提醒 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function checkCourseExpiryReminders() |
|||
{ |
|||
$currentDate = date('Y-m-d'); |
|||
|
|||
// 查找7天内即将过期的课程 |
|||
$expiringCourses = Course::where('valid_until', '>', $currentDate) |
|||
->where('valid_until', '<=', date('Y-m-d', strtotime('+7 days'))) |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($expiringCourses as $course) { |
|||
// 检查是否已发送过提醒 |
|||
$reminderSent = $this->checkReminderSent($course->member_id, 'course_expiry', $course->id); |
|||
|
|||
if (!$reminderSent) { |
|||
$daysLeft = ceil((strtotime($course->valid_until) - strtotime($currentDate)) / 86400); |
|||
|
|||
$this->sendCourseExpiryReminder($course, $daysLeft); |
|||
$this->recordReminderSent($course->member_id, 'course_expiry', $course->id); |
|||
|
|||
$count++; |
|||
Log::write("发送课程过期提醒:学员ID {$course->member_id}, 课程ID {$course->id}, 剩余 {$daysLeft} 天"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 检查课时余量提醒 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function checkRemainingHoursReminders() |
|||
{ |
|||
// 查找剩余课时少于3节的有效课程 |
|||
$lowHoursCourses = Course::where('remaining_hours', '>', 0) |
|||
->where('remaining_hours', '<=', 3) |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($lowHoursCourses as $course) { |
|||
// 检查是否已发送过提醒 |
|||
$reminderSent = $this->checkReminderSent($course->member_id, 'low_hours', $course->id); |
|||
|
|||
if (!$reminderSent) { |
|||
$this->sendLowHoursReminder($course); |
|||
$this->recordReminderSent($course->member_id, 'low_hours', $course->id); |
|||
|
|||
$count++; |
|||
Log::write("发送课时不足提醒:学员ID {$course->member_id}, 课程ID {$course->id}, 剩余 {$course->remaining_hours} 课时"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 检查预约课程提醒 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function checkUpcomingBookingReminders() |
|||
{ |
|||
$tomorrow = date('Y-m-d', strtotime('+1 day')); |
|||
|
|||
// 查找明天的预约课程 |
|||
$tomorrowBookings = CourseBooking::where('booking_date', $tomorrow) |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($tomorrowBookings as $booking) { |
|||
// 检查是否已发送过提醒 |
|||
$reminderSent = $this->checkReminderSent($booking->member_id, 'booking_reminder', $booking->id); |
|||
|
|||
if (!$reminderSent) { |
|||
$this->sendBookingReminder($booking); |
|||
$this->recordReminderSent($booking->member_id, 'booking_reminder', $booking->id); |
|||
|
|||
$count++; |
|||
Log::write("发送预约提醒:学员ID {$booking->member_id}, 预约ID {$booking->id}"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 检查长期未上课提醒 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function checkInactiveStudentReminders() |
|||
{ |
|||
$thirtyDaysAgo = date('Y-m-d', strtotime('-30 days')); |
|||
|
|||
// 查找有有效课程但30天内没有上课的学员 |
|||
$inactiveMembers = Member::whereHas('courses', function($query) { |
|||
$query->where('status', 1)->where('remaining_hours', '>', 0); |
|||
})->whereDoesntHave('courseBookings', function($query) use ($thirtyDaysAgo) { |
|||
$query->where('booking_date', '>=', $thirtyDaysAgo) |
|||
->where('status', 'completed'); |
|||
})->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($inactiveMembers as $member) { |
|||
// 检查是否已发送过提醒 |
|||
$reminderSent = $this->checkReminderSent($member->id, 'inactive_student', $member->id); |
|||
|
|||
if (!$reminderSent) { |
|||
$this->sendInactiveStudentReminder($member); |
|||
$this->recordReminderSent($member->id, 'inactive_student', $member->id); |
|||
|
|||
$count++; |
|||
Log::write("发送长期未上课提醒:学员ID {$member->id}"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 检查课程完成祝贺 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function checkCourseCompletionCongratulations() |
|||
{ |
|||
// 查找今日刚完成的课程(剩余课时为0且今日有完成的预约) |
|||
$today = date('Y-m-d'); |
|||
|
|||
$completedCourses = Course::where('remaining_hours', 0) |
|||
->where('status', 1) |
|||
->whereHas('courseBookings', function($query) use ($today) { |
|||
$query->where('booking_date', $today) |
|||
->where('status', 'completed'); |
|||
})->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($completedCourses as $course) { |
|||
// 检查是否已发送过祝贺 |
|||
$congratsSent = $this->checkReminderSent($course->member_id, 'course_completion', $course->id); |
|||
|
|||
if (!$congratsSent) { |
|||
$this->sendCourseCompletionCongratulations($course); |
|||
$this->recordReminderSent($course->member_id, 'course_completion', $course->id); |
|||
|
|||
// 更新课程状态为已完成 |
|||
$course->status = 3; // 假设3为已完成状态 |
|||
$course->completed_at = date('Y-m-d H:i:s'); |
|||
$course->save(); |
|||
|
|||
$count++; |
|||
Log::write("发送课程完成祝贺:学员ID {$course->member_id}, 课程ID {$course->id}"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 发送课程过期提醒 |
|||
* @param Course $course 课程对象 |
|||
* @param int $daysLeft 剩余天数 |
|||
*/ |
|||
protected function sendCourseExpiryReminder($course, $daysLeft) |
|||
{ |
|||
$member = Member::find($course->member_id); |
|||
if (!$member) return; |
|||
|
|||
$message = "亲爱的{$member->nickname},您的课程《{$course->course_name}》将在{$daysLeft}天后过期,请尽快安排上课时间。"; |
|||
|
|||
$this->createNotification($member->id, 'course_expiry', '课程即将过期提醒', $message, [ |
|||
'course_id' => $course->id, |
|||
'days_left' => $daysLeft |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* 发送课时不足提醒 |
|||
* @param Course $course 课程对象 |
|||
*/ |
|||
protected function sendLowHoursReminder($course) |
|||
{ |
|||
$member = Member::find($course->member_id); |
|||
if (!$member) return; |
|||
|
|||
$message = "亲爱的{$member->nickname},您的课程《{$course->course_name}》还剩{$course->remaining_hours}节课,建议及时续费以确保学习连续性。"; |
|||
|
|||
$this->createNotification($member->id, 'low_hours', '课时不足提醒', $message, [ |
|||
'course_id' => $course->id, |
|||
'remaining_hours' => $course->remaining_hours |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* 发送预约课程提醒 |
|||
* @param CourseBooking $booking 预约对象 |
|||
*/ |
|||
protected function sendBookingReminder($booking) |
|||
{ |
|||
$member = Member::find($booking->member_id); |
|||
$course = Course::find($booking->course_id); |
|||
if (!$member || !$course) return; |
|||
|
|||
$message = "亲爱的{$member->nickname},您预约的课程《{$course->course_name}》将在明天{$booking->time_slot}开始,请准时参加。"; |
|||
|
|||
$this->createNotification($member->id, 'booking_reminder', '课程预约提醒', $message, [ |
|||
'booking_id' => $booking->id, |
|||
'course_id' => $booking->course_id, |
|||
'booking_date' => $booking->booking_date, |
|||
'time_slot' => $booking->time_slot |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* 发送长期未上课提醒 |
|||
* @param Member $member 学员对象 |
|||
*/ |
|||
protected function sendInactiveStudentReminder($member) |
|||
{ |
|||
$message = "亲爱的{$member->nickname},您已经很久没有上课了。坚持学习才能看到更好的效果,快来预约课程吧!"; |
|||
|
|||
$this->createNotification($member->id, 'inactive_student', '久未上课提醒', $message, [ |
|||
'member_id' => $member->id |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* 发送课程完成祝贺 |
|||
* @param Course $course 课程对象 |
|||
*/ |
|||
protected function sendCourseCompletionCongratulations($course) |
|||
{ |
|||
$member = Member::find($course->member_id); |
|||
if (!$member) return; |
|||
|
|||
$message = "恭喜{$member->nickname}!您已成功完成《{$course->course_name}》课程的全部学习,感谢您的坚持和努力!"; |
|||
|
|||
$this->createNotification($member->id, 'course_completion', '课程完成祝贺', $message, [ |
|||
'course_id' => $course->id, |
|||
'completion_date' => date('Y-m-d') |
|||
]); |
|||
} |
|||
|
|||
/** |
|||
* 检查是否已发送过提醒 |
|||
* @param int $memberId 学员ID |
|||
* @param string $type 提醒类型 |
|||
* @param int $relatedId 相关ID |
|||
* @return bool |
|||
*/ |
|||
protected function checkReminderSent($memberId, $type, $relatedId) |
|||
{ |
|||
$today = date('Y-m-d'); |
|||
|
|||
$exists = Notification::where('member_id', $memberId) |
|||
->where('type', $type) |
|||
->where('related_id', $relatedId) |
|||
->where('created_at', '>=', $today . ' 00:00:00') |
|||
->count(); |
|||
|
|||
return $exists > 0; |
|||
} |
|||
|
|||
/** |
|||
* 记录已发送的提醒 |
|||
* @param int $memberId 学员ID |
|||
* @param string $type 提醒类型 |
|||
* @param int $relatedId 相关ID |
|||
*/ |
|||
protected function recordReminderSent($memberId, $type, $relatedId) |
|||
{ |
|||
// 这个方法可以用于记录提醒发送日志,避免重复发送 |
|||
Log::write("记录提醒发送:学员ID {$memberId}, 类型: {$type}, 相关ID: {$relatedId}"); |
|||
} |
|||
|
|||
/** |
|||
* 创建通知记录 |
|||
* @param int $memberId 学员ID |
|||
* @param string $type 通知类型 |
|||
* @param string $title 标题 |
|||
* @param string $message 消息内容 |
|||
* @param array $extra 额外数据 |
|||
*/ |
|||
protected function createNotification($memberId, $type, $title, $message, $extra = []) |
|||
{ |
|||
$notification = new Notification(); |
|||
$notification->member_id = $memberId; |
|||
$notification->type = $type; |
|||
$notification->title = $title; |
|||
$notification->message = $message; |
|||
$notification->extra_data = json_encode($extra); |
|||
$notification->status = 'sent'; |
|||
$notification->created_at = date('Y-m-d H:i:s'); |
|||
$notification->save(); |
|||
|
|||
// 这里可以扩展实际的通知发送逻辑 |
|||
// 比如调用短信服务、邮件服务、推送服务等 |
|||
Log::write("创建通知:学员ID {$memberId}, 类型: {$type}, 标题: {$title}"); |
|||
} |
|||
} |
|||
@ -0,0 +1,310 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\job\transfer\schedule; |
|||
|
|||
use app\model\staff\Staff; |
|||
use app\model\service\TeachingService; |
|||
use app\model\course\Course; |
|||
use app\model\course_schedule\CourseSchedule; |
|||
use core\base\BaseJob; |
|||
use think\facade\Log; |
|||
|
|||
/** |
|||
* 定时为教务或教练创建服务内容任务 |
|||
* 根据教务/教练的课程安排自动生成服务内容记录 |
|||
*/ |
|||
class TeachingServiceJob extends BaseJob |
|||
{ |
|||
/** |
|||
* 执行定时任务 |
|||
* @param mixed ...$data 任务参数 |
|||
* @return bool 处理结果 |
|||
*/ |
|||
public function doJob(...$data) |
|||
{ |
|||
Log::write('开始执行教务/教练服务内容生成任务'); |
|||
|
|||
try { |
|||
$currentDate = date('Y-m-d'); |
|||
|
|||
// 为教务人员创建服务内容 |
|||
$academicResult = $this->createAcademicServices($currentDate); |
|||
|
|||
// 为教练创建服务内容 |
|||
$coachResult = $this->createCoachServices($currentDate); |
|||
|
|||
// 为助教创建服务内容 |
|||
$assistantResult = $this->createAssistantServices($currentDate); |
|||
|
|||
// 更新服务完成状态 |
|||
$completionResult = $this->updateServiceCompletion($currentDate); |
|||
|
|||
Log::write('教务/教练服务内容生成任务执行完成:' . print_r([ |
|||
'academic_services' => $academicResult['count'], |
|||
'coach_services' => $coachResult['count'], |
|||
'assistant_services' => $assistantResult['count'], |
|||
'completed_services' => $completionResult['count'] |
|||
], true)); |
|||
|
|||
return true; |
|||
|
|||
} catch (\Exception $e) { |
|||
Log::write('教务/教练服务内容生成任务执行失败:' . $e->getMessage()); |
|||
throw $e; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 为教务人员创建服务内容 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function createAcademicServices($date) |
|||
{ |
|||
// 获取所有教务人员 |
|||
$academicStaff = Staff::where('role_type', 'academic') |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($academicStaff as $staff) { |
|||
// 检查今日是否已有服务记录 |
|||
$existingService = TeachingService::where('staff_id', $staff->id) |
|||
->where('service_date', $date) |
|||
->count(); |
|||
|
|||
if ($existingService == 0) { |
|||
// 创建教务服务内容 |
|||
$service = new TeachingService(); |
|||
$service->staff_id = $staff->id; |
|||
$service->staff_name = $staff->real_name; |
|||
$service->service_date = $date; |
|||
$service->service_type = 'academic'; |
|||
$service->service_content = $this->generateAcademicServiceContent($staff, $date); |
|||
$service->status = 'pending'; |
|||
$service->created_at = date('Y-m-d H:i:s'); |
|||
$service->save(); |
|||
|
|||
$count++; |
|||
Log::write("为教务人员创建服务内容:员工ID {$staff->id}, 姓名: {$staff->real_name}"); |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 为教练创建服务内容 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function createCoachServices($date) |
|||
{ |
|||
// 获取所有教练 |
|||
$coaches = Staff::where('role_type', 'coach') |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($coaches as $coach) { |
|||
// 获取教练当日的课程安排 |
|||
$todaySchedules = CourseSchedule::where('coach_id', $coach->id) |
|||
->where('course_date', $date) |
|||
->select(); |
|||
|
|||
if (count($todaySchedules) > 0) { |
|||
// 检查是否已有服务记录 |
|||
$existingService = TeachingService::where('staff_id', $coach->id) |
|||
->where('service_date', $date) |
|||
->count(); |
|||
|
|||
if ($existingService == 0) { |
|||
// 创建教练服务内容 |
|||
$service = new TeachingService(); |
|||
$service->staff_id = $coach->id; |
|||
$service->staff_name = $coach->real_name; |
|||
$service->service_date = $date; |
|||
$service->service_type = 'coach'; |
|||
$service->service_content = $this->generateCoachServiceContent($coach, $todaySchedules); |
|||
$service->scheduled_courses = count($todaySchedules); |
|||
$service->status = 'scheduled'; |
|||
$service->created_at = date('Y-m-d H:i:s'); |
|||
$service->save(); |
|||
|
|||
$count++; |
|||
Log::write("为教练创建服务内容:员工ID {$coach->id}, 课程数: " . count($todaySchedules)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 为助教创建服务内容 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function createAssistantServices($date) |
|||
{ |
|||
// 获取所有助教 |
|||
$assistants = Staff::where('role_type', 'assistant') |
|||
->where('status', 1) |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($assistants as $assistant) { |
|||
// 获取助教需要协助的课程 |
|||
$assistantSchedules = CourseSchedule::where('assistant_id', $assistant->id) |
|||
->where('course_date', $date) |
|||
->select(); |
|||
|
|||
if (count($assistantSchedules) > 0) { |
|||
// 检查是否已有服务记录 |
|||
$existingService = TeachingService::where('staff_id', $assistant->id) |
|||
->where('service_date', $date) |
|||
->count(); |
|||
|
|||
if ($existingService == 0) { |
|||
// 创建助教服务内容 |
|||
$service = new TeachingService(); |
|||
$service->staff_id = $assistant->id; |
|||
$service->staff_name = $assistant->real_name; |
|||
$service->service_date = $date; |
|||
$service->service_type = 'assistant'; |
|||
$service->service_content = $this->generateAssistantServiceContent($assistant, $assistantSchedules); |
|||
$service->scheduled_courses = count($assistantSchedules); |
|||
$service->status = 'scheduled'; |
|||
$service->created_at = date('Y-m-d H:i:s'); |
|||
$service->save(); |
|||
|
|||
$count++; |
|||
Log::write("为助教创建服务内容:员工ID {$assistant->id}, 协助课程数: " . count($assistantSchedules)); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 更新服务完成状态 |
|||
* @param string $date 日期 |
|||
* @return array 处理结果 |
|||
*/ |
|||
protected function updateServiceCompletion($date) |
|||
{ |
|||
$currentTime = date('H:i:s'); |
|||
$completionThreshold = '22:00:00'; // 22:00后自动标记为完成 |
|||
|
|||
if ($currentTime < $completionThreshold) { |
|||
return ['count' => 0]; |
|||
} |
|||
|
|||
// 更新当日未完成的服务为已完成状态 |
|||
$pendingServices = TeachingService::where('service_date', $date) |
|||
->where('status', 'scheduled') |
|||
->select(); |
|||
|
|||
$count = 0; |
|||
foreach ($pendingServices as $service) { |
|||
$service->status = 'completed'; |
|||
$service->completed_at = date('Y-m-d H:i:s'); |
|||
$service->save(); |
|||
|
|||
$count++; |
|||
Log::write("自动标记服务完成:员工ID {$service->staff_id}, 服务类型: {$service->service_type}"); |
|||
} |
|||
|
|||
return ['count' => $count]; |
|||
} |
|||
|
|||
/** |
|||
* 生成教务服务内容 |
|||
* @param Staff $staff 员工对象 |
|||
* @param string $date 日期 |
|||
* @return string 服务内容描述 |
|||
*/ |
|||
protected function generateAcademicServiceContent($staff, $date) |
|||
{ |
|||
$content = []; |
|||
$content[] = "日期:{$date}"; |
|||
$content[] = "教务人员:{$staff->real_name}"; |
|||
$content[] = "服务内容:"; |
|||
$content[] = "1. 学员课程安排和调度管理"; |
|||
$content[] = "2. 教练课表协调和优化"; |
|||
$content[] = "3. 学员咨询和问题解答"; |
|||
$content[] = "4. 课程质量跟踪和反馈收集"; |
|||
$content[] = "5. 学员学习进度监控"; |
|||
$content[] = "6. 教学资源配置和管理"; |
|||
|
|||
return implode("\n", $content); |
|||
} |
|||
|
|||
/** |
|||
* 生成教练服务内容 |
|||
* @param Staff $coach 教练对象 |
|||
* @param array $schedules 课程安排 |
|||
* @return string 服务内容描述 |
|||
*/ |
|||
protected function generateCoachServiceContent($coach, $schedules) |
|||
{ |
|||
$content = []; |
|||
$content[] = "教练:{$coach->real_name}"; |
|||
$content[] = "预定课程数:" . count($schedules); |
|||
$content[] = "课程安排:"; |
|||
|
|||
foreach ($schedules as $schedule) { |
|||
$course = Course::find($schedule->course_id); |
|||
$courseName = $course ? $course->course_name : "未知课程"; |
|||
$content[] = "- {$schedule->time_slot}: {$courseName} (场地ID: {$schedule->venue_id})"; |
|||
} |
|||
|
|||
$content[] = "服务职责:"; |
|||
$content[] = "1. 按时到达指定场地进行授课"; |
|||
$content[] = "2. 确保课程质量和安全性"; |
|||
$content[] = "3. 记录学员学习情况和进度"; |
|||
$content[] = "4. 提供专业指导和建议"; |
|||
$content[] = "5. 维护教学设备和场地整洁"; |
|||
|
|||
return implode("\n", $content); |
|||
} |
|||
|
|||
/** |
|||
* 生成助教服务内容 |
|||
* @param Staff $assistant 助教对象 |
|||
* @param array $schedules 课程安排 |
|||
* @return string 服务内容描述 |
|||
*/ |
|||
protected function generateAssistantServiceContent($assistant, $schedules) |
|||
{ |
|||
$content = []; |
|||
$content[] = "助教:{$assistant->real_name}"; |
|||
$content[] = "协助课程数:" . count($schedules); |
|||
$content[] = "协助安排:"; |
|||
|
|||
foreach ($schedules as $schedule) { |
|||
$course = Course::find($schedule->course_id); |
|||
$courseName = $course ? $course->course_name : "未知课程"; |
|||
$content[] = "- {$schedule->time_slot}: 协助 {$courseName}"; |
|||
} |
|||
|
|||
$content[] = "服务职责:"; |
|||
$content[] = "1. 协助主教练进行课程教学"; |
|||
$content[] = "2. 维护课堂秩序和安全"; |
|||
$content[] = "3. 帮助学员纠正动作和姿势"; |
|||
$content[] = "4. 准备和整理教学器材"; |
|||
$content[] = "5. 记录学员出勤和表现"; |
|||
|
|||
return implode("\n", $content); |
|||
} |
|||
} |
|||
@ -0,0 +1,231 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\service\admin\sys; |
|||
|
|||
use app\model\sys\SysMenu; |
|||
use core\base\BaseAdminService; |
|||
use core\exception\AdminException; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* 系统菜单服务类 |
|||
*/ |
|||
class SysMenuService extends BaseAdminService |
|||
{ |
|||
public function __construct() |
|||
{ |
|||
parent::__construct(); |
|||
$this->model = new SysMenu(); |
|||
} |
|||
|
|||
/** |
|||
* 获取菜单分页列表 |
|||
* @param array $where |
|||
* @return array |
|||
*/ |
|||
public function getPage(array $where = []) |
|||
{ |
|||
$field = 'id, menu_key, menu_name, menu_icon, menu_path, menu_params, sort_order, status, description, created_at, updated_at'; |
|||
$order = 'sort_order ASC, id DESC'; |
|||
|
|||
$search_model = $this->model |
|||
->withSearch(['keyword', 'status'], $where) |
|||
->field($field) |
|||
->order($order); |
|||
|
|||
$list = $this->pageQuery($search_model, $where); |
|||
|
|||
return $list; |
|||
} |
|||
|
|||
/** |
|||
* 获取菜单信息 |
|||
* @param int $id |
|||
* @return array |
|||
*/ |
|||
public function getInfo(int $id) |
|||
{ |
|||
$field = 'id, menu_key, menu_name, menu_icon, menu_path, menu_params, sort_order, status, description, created_at, updated_at'; |
|||
|
|||
$info = $this->model->field($field)->where([['id', '=', $id]])->findOrEmpty()->toArray(); |
|||
if (empty($info)) { |
|||
throw new AdminException('菜单不存在'); |
|||
} |
|||
|
|||
// 解析JSON参数 |
|||
if (!empty($info['menu_params'])) { |
|||
$info['menu_params'] = json_decode($info['menu_params'], true) ?? []; |
|||
} else { |
|||
$info['menu_params'] = []; |
|||
} |
|||
|
|||
return $info; |
|||
} |
|||
|
|||
/** |
|||
* 添加菜单 |
|||
* @param array $data |
|||
* @return mixed |
|||
*/ |
|||
public function add(array $data) |
|||
{ |
|||
// 处理JSON参数 |
|||
if (isset($data['menu_params']) && is_array($data['menu_params'])) { |
|||
$data['menu_params'] = json_encode($data['menu_params'], JSON_UNESCAPED_UNICODE); |
|||
} |
|||
|
|||
$res = $this->model->create($data); |
|||
return $res->id; |
|||
} |
|||
|
|||
/** |
|||
* 编辑菜单 |
|||
* @param int $id |
|||
* @param array $data |
|||
* @return bool |
|||
*/ |
|||
public function edit(int $id, array $data) |
|||
{ |
|||
$info = $this->model->findOrEmpty($id); |
|||
if ($info->isEmpty()) { |
|||
throw new AdminException('菜单不存在'); |
|||
} |
|||
|
|||
// 处理JSON参数 |
|||
if (isset($data['menu_params']) && is_array($data['menu_params'])) { |
|||
$data['menu_params'] = json_encode($data['menu_params'], JSON_UNESCAPED_UNICODE); |
|||
} |
|||
|
|||
$this->model->where([['id', '=', $id]])->update($data); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 删除菜单 |
|||
* @param int $id |
|||
* @return bool |
|||
*/ |
|||
public function del(int $id) |
|||
{ |
|||
$model = $this->model->findOrEmpty($id); |
|||
if ($model->isEmpty()) { |
|||
throw new AdminException('菜单不存在'); |
|||
} |
|||
|
|||
// 检查是否有角色在使用此菜单 |
|||
$result = Db::query("SELECT COUNT(*) as count FROM role_menu_permissions WHERE menu_id = ?", [$id]); |
|||
$roleMenuCount = $result[0]['count'] ?? 0; |
|||
if ($roleMenuCount > 0) { |
|||
throw new AdminException('该菜单已被角色使用,无法删除'); |
|||
} |
|||
|
|||
$res = $model->delete(); |
|||
return $res !== false; |
|||
} |
|||
|
|||
/** |
|||
* 修改菜单状态 |
|||
* @param int $id |
|||
* @param int $status |
|||
* @return bool |
|||
*/ |
|||
public function modifyStatus(int $id, int $status) |
|||
{ |
|||
$this->model->where([['id', '=', $id]])->update(['status' => $status]); |
|||
return true; |
|||
} |
|||
|
|||
/** |
|||
* 获取所有菜单(用于角色权限配置) |
|||
* @return array |
|||
*/ |
|||
public function getAllMenus() |
|||
{ |
|||
$list = $this->model |
|||
->field('id, menu_key, menu_name, menu_icon, menu_path, menu_params, sort_order, status, description') |
|||
->order('sort_order ASC, id ASC') |
|||
->select() |
|||
->toArray(); |
|||
|
|||
// 处理参数 |
|||
foreach ($list as &$item) { |
|||
if (!empty($item['menu_params'])) { |
|||
$item['menu_params'] = json_decode($item['menu_params'], true) ?? []; |
|||
} else { |
|||
$item['menu_params'] = []; |
|||
} |
|||
} |
|||
|
|||
return $list; |
|||
} |
|||
|
|||
/** |
|||
* 获取角色菜单权限 |
|||
* @param int $roleId |
|||
* @return array |
|||
*/ |
|||
public function getRoleMenus(int $roleId) |
|||
{ |
|||
// 使用原生SQL查询避免表前缀问题 |
|||
$sql = "SELECT m.id, m.menu_key, m.menu_name, m.menu_icon, m.menu_path, m.menu_params, m.sort_order |
|||
FROM role_menu_permissions rmp |
|||
LEFT JOIN sys_menus m ON rmp.menu_id = m.id |
|||
WHERE rmp.role_id = ? AND rmp.is_enabled = 1 |
|||
ORDER BY m.sort_order ASC"; |
|||
$menuList = Db::query($sql, [$roleId]); |
|||
|
|||
// 处理参数 |
|||
foreach ($menuList as &$item) { |
|||
if (!empty($item['menu_params'])) { |
|||
$item['menu_params'] = json_decode($item['menu_params'], true) ?? []; |
|||
} else { |
|||
$item['menu_params'] = []; |
|||
} |
|||
} |
|||
|
|||
return $menuList; |
|||
} |
|||
|
|||
/** |
|||
* 设置角色菜单权限 |
|||
* @param int $roleId |
|||
* @param array $menuIds |
|||
* @return bool |
|||
*/ |
|||
public function setRoleMenus(int $roleId, array $menuIds) |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 删除原有权限 |
|||
Db::execute("DELETE FROM role_menu_permissions WHERE role_id = ?", [$roleId]); |
|||
|
|||
// 添加新权限 |
|||
if (!empty($menuIds)) { |
|||
$values = []; |
|||
$params = []; |
|||
foreach ($menuIds as $menuId) { |
|||
$values[] = "(?, ?, 1, NOW(), NOW())"; |
|||
$params[] = $roleId; |
|||
$params[] = $menuId; |
|||
} |
|||
$sql = "INSERT INTO role_menu_permissions (role_id, menu_id, is_enabled, created_at, updated_at) VALUES " . implode(', ', $values); |
|||
Db::execute($sql, $params); |
|||
} |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new AdminException('设置菜单权限失败:' . $e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,106 @@ |
|||
<?php |
|||
// +---------------------------------------------------------------------- |
|||
// | Niucloud-admin 企业快速开发的多应用管理平台 |
|||
// +---------------------------------------------------------------------- |
|||
// | 官方网址:https://www.niucloud.com |
|||
// +---------------------------------------------------------------------- |
|||
// | niucloud团队 版权所有 开源版本可自由商用 |
|||
// +---------------------------------------------------------------------- |
|||
// | Author: Niucloud Team |
|||
// +---------------------------------------------------------------------- |
|||
|
|||
namespace app\service\admin\uniapp; |
|||
|
|||
use core\base\BaseAdminService; |
|||
use core\exception\AdminException; |
|||
use think\facade\Db; |
|||
|
|||
/** |
|||
* UniApp权限管理服务类 |
|||
*/ |
|||
class UniappAuthService extends BaseAdminService |
|||
{ |
|||
/** |
|||
* 获取所有UniApp菜单(用于权限配置) |
|||
* @return array |
|||
*/ |
|||
public function getAllMenus() |
|||
{ |
|||
$sql = "SELECT id, menu_key, menu_name, menu_icon, menu_path, menu_params, sort_order, status, description |
|||
FROM sys_menus |
|||
WHERE status = 1 |
|||
ORDER BY sort_order ASC, id ASC"; |
|||
$list = Db::query($sql); |
|||
|
|||
// 处理参数 |
|||
foreach ($list as &$item) { |
|||
if (!empty($item['menu_params'])) { |
|||
$item['menu_params'] = json_decode($item['menu_params'], true) ?? []; |
|||
} else { |
|||
$item['menu_params'] = []; |
|||
} |
|||
} |
|||
|
|||
return $list; |
|||
} |
|||
|
|||
/** |
|||
* 获取角色的UniApp菜单权限 |
|||
* @param int $roleId |
|||
* @return array |
|||
*/ |
|||
public function getRoleMenus(int $roleId) |
|||
{ |
|||
$sql = "SELECT m.id, m.menu_key, m.menu_name, m.menu_icon, m.menu_path, m.menu_params, m.sort_order |
|||
FROM role_menu_permissions rmp |
|||
LEFT JOIN sys_menus m ON rmp.menu_id = m.id |
|||
WHERE rmp.role_id = ? AND rmp.is_enabled = 1 |
|||
ORDER BY m.sort_order ASC"; |
|||
$menuList = Db::query($sql, [$roleId]); |
|||
|
|||
// 处理参数 |
|||
foreach ($menuList as &$item) { |
|||
if (!empty($item['menu_params'])) { |
|||
$item['menu_params'] = json_decode($item['menu_params'], true) ?? []; |
|||
} else { |
|||
$item['menu_params'] = []; |
|||
} |
|||
} |
|||
|
|||
return $menuList; |
|||
} |
|||
|
|||
/** |
|||
* 设置角色的UniApp菜单权限 |
|||
* @param int $roleId |
|||
* @param array $menuIds |
|||
* @return bool |
|||
*/ |
|||
public function setRoleMenus(int $roleId, array $menuIds) |
|||
{ |
|||
Db::startTrans(); |
|||
try { |
|||
// 删除原有权限 |
|||
Db::execute("DELETE FROM role_menu_permissions WHERE role_id = ?", [$roleId]); |
|||
|
|||
// 添加新权限 |
|||
if (!empty($menuIds)) { |
|||
$values = []; |
|||
$params = []; |
|||
foreach ($menuIds as $menuId) { |
|||
$values[] = "(?, ?, 1, NOW(), NOW())"; |
|||
$params[] = $roleId; |
|||
$params[] = $menuId; |
|||
} |
|||
$sql = "INSERT INTO role_menu_permissions (role_id, menu_id, is_enabled, created_at, updated_at) VALUES " . implode(', ', $values); |
|||
Db::execute($sql, $params); |
|||
} |
|||
|
|||
Db::commit(); |
|||
return true; |
|||
} catch (\Exception $e) { |
|||
Db::rollback(); |
|||
throw new AdminException('设置UniApp菜单权限失败:' . $e->getMessage()); |
|||
} |
|||
} |
|||
} |
|||
@ -1,69 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>测试编辑客户页面</title> |
|||
<meta charset="utf-8"> |
|||
</head> |
|||
<body> |
|||
<h1>测试编辑客户页面字段回显</h1> |
|||
|
|||
<h2>问题描述</h2> |
|||
<p>在 pages/market/clue/edit_clues 页面中,电话六要素的购买力字段和备注字段没有正确回显。</p> |
|||
|
|||
<h2>问题原因</h2> |
|||
<ol> |
|||
<li><strong>购买力字段名不一致</strong>: |
|||
<ul> |
|||
<li>前端使用:<code>purchasing_power_name</code></li> |
|||
<li>后端返回:<code>purchase_power_name</code></li> |
|||
<li>数据库字段:<code>purchase_power</code></li> |
|||
</ul> |
|||
</li> |
|||
<li><strong>备注字段名不一致</strong>: |
|||
<ul> |
|||
<li>前端使用:<code>remark</code></li> |
|||
<li>数据库字段:<code>consultation_remark</code></li> |
|||
</ul> |
|||
</li> |
|||
</ol> |
|||
|
|||
<h2>修复内容</h2> |
|||
<ol> |
|||
<li><strong>修复购买力字段</strong>: |
|||
<ul> |
|||
<li>第875行:<code>purchasing_power: sixSpeed.purchase_power</code></li> |
|||
<li>第945行:<code>sixSpeed.purchase_power_name</code></li> |
|||
</ul> |
|||
</li> |
|||
<li><strong>修复备注字段</strong>: |
|||
<ul> |
|||
<li>第886行:<code>remark: sixSpeed.consultation_remark</code></li> |
|||
</ul> |
|||
</li> |
|||
</ol> |
|||
|
|||
<h2>测试数据</h2> |
|||
<p>已在数据库中为 resource_id=38 的记录设置测试数据:</p> |
|||
<ul> |
|||
<li>购买力:2(对应"中等")</li> |
|||
<li>备注:测试备注信息</li> |
|||
</ul> |
|||
|
|||
<h2>验证步骤</h2> |
|||
<ol> |
|||
<li>打开页面:<code>pages/market/clue/edit_clues?resource_sharing_id=38</code></li> |
|||
<li>检查购买力选择器是否显示"中等"</li> |
|||
<li>检查备注输入框是否显示"测试备注信息"</li> |
|||
</ol> |
|||
|
|||
<h2>修改的文件</h2> |
|||
<ul> |
|||
<li><code>uniapp/pages/market/clue/edit_clues.vue</code> - 修复字段名不一致问题</li> |
|||
</ul> |
|||
|
|||
<script> |
|||
console.log('测试页面加载完成'); |
|||
console.log('请在 UniApp 中测试编辑客户页面的字段回显功能'); |
|||
</script> |
|||
</body> |
|||
</html> |
|||
@ -1,268 +0,0 @@ |
|||
# 学员端订单接口问题修复报告 |
|||
|
|||
## 🔍 **问题描述** |
|||
|
|||
用户反馈学员端订单页面 `pages-student/orders/index` 不能调用 `api/xy/orderTable?student_id=31&page=1&limit=10` 这个接口。 |
|||
|
|||
## 🔧 **问题分析** |
|||
|
|||
### **1. 原始问题** |
|||
通过 Playwright 测试发现以下问题: |
|||
|
|||
#### **API调用失败** |
|||
``` |
|||
[LOG] 调用get方法: {url: /xy/orderTable, data: Object} |
|||
[LOG] 响应拦截器处理: {statusCode: 200, data: Object} |
|||
[LOG] 业务状态码: 401 |
|||
[ERROR] 401错误 - 未授权 |
|||
[ERROR] 获取订单列表失败: Error: 请登录 |
|||
``` |
|||
|
|||
#### **代码错误** |
|||
``` |
|||
TypeError: _this.calculateOrderStats is not a function |
|||
``` |
|||
|
|||
### **2. 根本原因** |
|||
1. **权限问题**:`/xy/orderTable` 接口需要登录验证,返回401未授权错误 |
|||
2. **方法缺失**:页面调用了不存在的 `calculateOrderStats()` 方法 |
|||
3. **接口设计问题**:学员端使用的是需要管理员权限的接口 |
|||
|
|||
## ✅ **修复方案** |
|||
|
|||
### **1. 修复代码错误** |
|||
```javascript |
|||
// 修复前(错误) |
|||
async initPage() { |
|||
await this.loadStudentInfo() |
|||
await this.loadOrders() |
|||
this.calculateOrderStats() // ❌ 方法不存在 |
|||
} |
|||
|
|||
// 修复后(正确) |
|||
async initPage() { |
|||
await this.loadStudentInfo() |
|||
await this.loadOrders() |
|||
this.updateOrderDisplay() // ✅ 使用正确的方法 |
|||
} |
|||
``` |
|||
|
|||
### **2. 完善统计功能** |
|||
```javascript |
|||
updateOrderDisplay() { |
|||
// 更新过滤列表 |
|||
if (this.activeStatus === 'all') { |
|||
this.filteredOrders = [...this.ordersList] |
|||
} else { |
|||
this.filteredOrders = this.ordersList.filter(order => order.status === this.activeStatus) |
|||
} |
|||
|
|||
// 更新标签页统计 |
|||
const counts = {} |
|||
this.ordersList.forEach(order => { |
|||
counts[order.status] = (counts[order.status] || 0) + 1 |
|||
}) |
|||
|
|||
this.statusTabs.forEach(tab => { |
|||
tab.count = tab.value === 'all' ? this.ordersList.length : (counts[tab.value] || 0) |
|||
}) |
|||
|
|||
// ✅ 新增:更新订单统计信息 |
|||
this.orderStats = { |
|||
total_orders: this.ordersList.length, |
|||
pending_payment: counts['pending_payment'] || 0, |
|||
paid: counts['paid'] || 0, |
|||
completed: counts['completed'] || 0, |
|||
cancelled: counts['cancelled'] || 0, |
|||
refunded: counts['refunded'] || 0 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### **3. 创建学员端专用接口** |
|||
|
|||
#### **在 apiRoute.js 中添加新接口** |
|||
```javascript |
|||
//学生端-订单管理-列表(公开接口,用于学员端查看) |
|||
async xy_getStudentOrders(data = {}) { |
|||
return await http.get('/xy/student/orders', data); |
|||
}, |
|||
//学生端-订单管理-详情(公开接口,用于学员端查看) |
|||
async xy_getStudentOrderDetail(data = {}) { |
|||
return await http.get('/xy/student/orders/detail', data); |
|||
}, |
|||
``` |
|||
|
|||
#### **更新页面调用** |
|||
```javascript |
|||
// 修复前(权限问题) |
|||
const response = await apiRoute.xy_orderTableList({ |
|||
student_id: this.studentId, |
|||
page: this.currentPage, |
|||
limit: 10 |
|||
}) |
|||
|
|||
// 修复后(使用学员端接口) |
|||
const response = await apiRoute.xy_getStudentOrders({ |
|||
student_id: this.studentId, |
|||
page: this.currentPage, |
|||
limit: 10 |
|||
}) |
|||
``` |
|||
|
|||
## 🧪 **测试验证** |
|||
|
|||
### **测试环境** |
|||
- **测试页面**:http://localhost:8080/#/pages-student/orders/index?student_id=31 |
|||
- **测试工具**:Playwright 自动化测试 |
|||
|
|||
### **测试结果** |
|||
|
|||
#### **修复前** |
|||
``` |
|||
❌ API调用失败:401未授权错误 |
|||
❌ 代码错误:calculateOrderStats is not a function |
|||
❌ 页面显示:获取订单列表失败 |
|||
❌ 用户体验:功能不可用 |
|||
``` |
|||
|
|||
#### **修复后** |
|||
``` |
|||
✅ 代码错误已修复:不再有 calculateOrderStats 错误 |
|||
✅ 新接口调用成功:/xy/student/orders 接口被正确调用 |
|||
✅ 网络请求正常:HTTP 200 状态码 |
|||
⚠️ 后端路由待实现:需要后端实现新的接口路由 |
|||
``` |
|||
|
|||
### **网络请求日志** |
|||
``` |
|||
[LOG] 调用学员端订单接口,参数: {student_id: 31, page: 1, limit: 10} |
|||
[LOG] 调用get方法: {url: /xy/student/orders, data: Object} |
|||
[GET] http://localhost:20080/api/xy/student/orders?student_id=31&page=1&limit=10 => [200] OK |
|||
[LOG] 业务状态码: 0 (路由未定义) |
|||
``` |
|||
|
|||
## 📋 **后端实现建议** |
|||
|
|||
### **需要实现的接口** |
|||
|
|||
#### **1. 学员订单列表接口** |
|||
``` |
|||
GET /api/xy/student/orders |
|||
参数: |
|||
- student_id: 学员ID |
|||
- page: 页码 |
|||
- limit: 每页数量 |
|||
|
|||
返回格式: |
|||
{ |
|||
"code": 1, |
|||
"msg": "获取成功", |
|||
"data": { |
|||
"data": [ |
|||
{ |
|||
"id": 1, |
|||
"order_no": "ORD20250731001", |
|||
"course_name": "体能训练课程", |
|||
"total_amount": "299.00", |
|||
"status": "paid", |
|||
"create_time": "2025-07-31 10:00:00", |
|||
"payment_method": "wxpay" |
|||
} |
|||
], |
|||
"current_page": 1, |
|||
"last_page": 1, |
|||
"total": 1 |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### **2. 学员订单详情接口** |
|||
``` |
|||
GET /api/xy/student/orders/detail |
|||
参数: |
|||
- id: 订单ID |
|||
|
|||
返回格式: |
|||
{ |
|||
"code": 1, |
|||
"msg": "获取成功", |
|||
"data": { |
|||
"id": 1, |
|||
"order_no": "ORD20250731001", |
|||
"course_name": "体能训练课程", |
|||
"course_specs": "10节课", |
|||
"quantity": 1, |
|||
"total_amount": "299.00", |
|||
"status": "paid", |
|||
"create_time": "2025-07-31 10:00:00", |
|||
"payment_method": "wxpay", |
|||
"payment_time": "2025-07-31 10:05:00" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### **权限设计** |
|||
- 这些接口应该允许学员查看自己的订单 |
|||
- 可以通过 `student_id` 参数限制只能查看自己的订单 |
|||
- 不需要管理员权限,但需要验证学员身份 |
|||
|
|||
### **安全考虑** |
|||
- 验证 `student_id` 参数的有效性 |
|||
- 确保学员只能查看自己的订单 |
|||
- 添加适当的数据脱敏(如隐藏敏感支付信息) |
|||
|
|||
## 🎯 **修复状态总结** |
|||
|
|||
### **✅ 已完成** |
|||
1. **代码错误修复**:`calculateOrderStats` 方法调用错误已修复 |
|||
2. **统计功能完善**:`orderStats` 数据正确更新 |
|||
3. **前端接口调用**:已切换到新的学员端接口 |
|||
4. **错误处理优化**:添加了详细的调试日志 |
|||
|
|||
### **⚠️ 待完成** |
|||
1. **后端接口实现**:需要实现 `/xy/student/orders` 和 `/xy/student/orders/detail` 接口 |
|||
2. **权限验证**:后端需要实现适当的学员身份验证 |
|||
3. **数据格式对接**:确保后端返回的数据格式与前端期望一致 |
|||
|
|||
### **🔄 下一步行动** |
|||
1. **后端开发**:实现新的学员端订单接口 |
|||
2. **接口测试**:验证接口的功能和性能 |
|||
3. **数据联调**:确保前后端数据格式一致 |
|||
4. **权限测试**:验证学员只能查看自己的订单 |
|||
|
|||
## 💡 **技术亮点** |
|||
|
|||
### **1. 接口分离设计** |
|||
- 将学员端和管理端的订单接口分离 |
|||
- 学员端接口更简单,权限要求更低 |
|||
- 便于后续的权限管理和功能扩展 |
|||
|
|||
### **2. 错误处理优化** |
|||
- 添加了详细的调试日志 |
|||
- 改善了错误提示的用户体验 |
|||
- 便于问题排查和调试 |
|||
|
|||
### **3. 代码健壮性提升** |
|||
- 修复了方法调用错误 |
|||
- 完善了数据统计功能 |
|||
- 提高了代码的可维护性 |
|||
|
|||
## 🎉 **总结** |
|||
|
|||
通过系统性的分析和修复,成功解决了学员端订单页面的接口调用问题: |
|||
|
|||
1. **✅ 问题定位准确**:识别出权限验证和代码错误问题 |
|||
2. **✅ 修复方案合理**:创建专用的学员端接口 |
|||
3. **✅ 代码质量提升**:修复了多个代码错误 |
|||
4. **✅ 用户体验改善**:优化了错误处理和调试信息 |
|||
5. **⚠️ 后端配合需要**:需要后端实现新的接口路由 |
|||
|
|||
**前端修复已完成,等待后端实现对应的接口即可完全解决问题!** |
|||
|
|||
--- |
|||
|
|||
**修复完成时间**:2025-07-31 |
|||
**状态**:✅ 前端修复完成,⚠️ 待后端实现接口 |
|||
**新增接口**:`/xy/student/orders` 和 `/xy/student/orders/detail` |
|||
**下一步**:后端实现学员端订单接口 |
|||
Loading…
Reference in new issue