diff --git a/admin/src/app/views/dict/components/dict.vue b/admin/src/app/views/dict/components/dict.vue index 2a04fa9a..f61fe87e 100644 --- a/admin/src/app/views/dict/components/dict.vue +++ b/admin/src/app/views/dict/components/dict.vue @@ -216,7 +216,67 @@ const setFormData = async (row: any = null) => { id.value = row.id name.value = row.name const data = await (await getDictInfo(row.id)).data - tableDate.value = data.dictionary + + // 智能解析dictionary字段 + try { + if (data.dictionary) { + let parsedData = data.dictionary + + // 如果dictionary本身就是数组对象,直接使用 + if (Array.isArray(parsedData)) { + tableDate.value = parsedData + } + // 如果是字符串,需要解析 + else if (typeof parsedData === 'string') { + // 第一次解析 + let firstParse = JSON.parse(parsedData) + + // 检查第一次解析的结果类型 + if (Array.isArray(firstParse)) { + // 如果第一次解析就得到数组,直接使用 + tableDate.value = firstParse + } else if (typeof firstParse === 'string') { + // 如果第一次解析还是字符串,需要第二次解析 + tableDate.value = JSON.parse(firstParse) + } else { + // 其他情况使用空数组 + tableDate.value = [] + } + } + // 其他类型使用空数组 + else { + tableDate.value = [] + } + + // 最终确保结果是数组 + if (!Array.isArray(tableDate.value)) { + tableDate.value = [] + } + + // 调试日志 + console.log('原始dictionary:', data.dictionary) + console.log('dictionary类型:', typeof data.dictionary) + console.log('最终tableDate.value:', tableDate.value) + if (typeof data.dictionary === 'string') { + try { + const firstParse = JSON.parse(data.dictionary) + console.log('第一次解析结果:', firstParse) + console.log('第一次解析结果类型:', typeof firstParse) + if (typeof firstParse === 'string') { + console.log('第二次解析结果:', JSON.parse(firstParse)) + } + } catch (e) { + console.log('解析测试失败:', e) + } + } + } else { + tableDate.value = [] + } + } catch (e) { + console.warn('解析字典数据失败:', e) + tableDate.value = [] + } + loading.value = false } diff --git a/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php b/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php deleted file mode 100644 index f243bb7b..00000000 --- a/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php +++ /dev/null @@ -1,95 +0,0 @@ -executeDistribution(); - return success($result); - } - - /** - * 获取分发统计信息 - * @return \think\Response - */ - public function getDistributionStats() - { - $result = (new ServiceLogsDistributionService())->getDistributionStats(); - return success($result); - } - - /** - * 获取待分发的服务记录列表 - * @return \think\Response - */ - public function getPendingDistributionList() - { - $data = $this->request->params([ - ["distribution_status", ""], - ["date_range", ""] - ]); - - $where = []; - if (!empty($data['distribution_status'])) { - $where['distribution_status'] = $data['distribution_status']; - } - if (!empty($data['date_range'])) { - $where['date_range'] = $data['date_range']; - } - - $result = (new ServiceLogsDistributionService())->getPendingDistributionList($where); - return success($result); - } - - /** - * 重置分发状态 - * @return \think\Response - */ - public function resetDistributionStatus() - { - $data = $this->request->params([ - ["ids", []], - ["type", "both"] - ]); - - if (empty($data['ids'])) { - return error('请选择要重置的记录'); - } - - $result = (new ServiceLogsDistributionService())->resetDistributionStatus($data['ids'], $data['type']); - return success($result); - } - - /** - * 获取教务和教练人员列表 - * @return \think\Response - */ - public function getStaffList() - { - $result = (new ServiceLogsDistributionService())->getStaffList(); - return success($result); - } -} \ No newline at end of file diff --git a/niucloud/app/adminapi/route/service_logs.php b/niucloud/app/adminapi/route/service_logs.php index 790e956f..e4d15e1e 100644 --- a/niucloud/app/adminapi/route/service_logs.php +++ b/niucloud/app/adminapi/route/service_logs.php @@ -23,20 +23,6 @@ Route::group('service_logs', function () { Route::get('service_logs', 'service_logs.ServiceLogs/lists'); - // 服务记录分发管理 - Route::group('distribution', function () { - // 手动执行分发任务 - Route::post('execute', 'service_logs.ServiceLogsDistribution/executeDistribution'); - // 获取分发统计信息 - Route::get('stats', 'service_logs.ServiceLogsDistribution/getDistributionStats'); - // 获取待分发的服务记录列表 - Route::get('pending', 'service_logs.ServiceLogsDistribution/getPendingDistributionList'); - // 重置分发状态 - Route::post('reset', 'service_logs.ServiceLogsDistribution/resetDistributionStatus'); - // 获取教务和教练人员列表 - Route::get('staff', 'service_logs.ServiceLogsDistribution/getStaffList'); - }); - })->middleware([ AdminCheckToken::class, AdminCheckRole::class, diff --git a/niucloud/app/api/controller/personnel/WechatBind.php b/niucloud/app/api/controller/personnel/WechatBind.php new file mode 100644 index 00000000..3005bf98 --- /dev/null +++ b/niucloud/app/api/controller/personnel/WechatBind.php @@ -0,0 +1,91 @@ +request->params([ + ['code', ''], + ['type', 'miniprogram'] // miniprogram: 小程序, official: 公众号 + ]); + + return success('获取成功', (new WechatBindService())->getWechatOpenid($data['code'], $data['type'])); + } + + /** + * 绑定微信openid + * @return \think\Response + */ + public function bindWechatOpenid() + { + $data = $this->request->params([ + ['miniprogram_openid', ''], + ['official_openid', ''], + ['type', 'miniprogram'] // miniprogram: 小程序绑定, official: 公众号绑定, both: 同时绑定 + ]); + + return success('绑定成功', (new WechatBindService())->bindWechatOpenid($data)); + } + + /** + * 微信授权跳转 + * @return \think\Response + */ + public function wechatAuthorize() + { + $data = $this->request->params([ + ['redirect_uri', ''], + ['state', ''] + ]); + + return (new WechatBindService())->wechatAuthorize($data['redirect_uri'], $data['state']); + } + + /** + * 微信授权回调 + * @return \think\Response + */ + public function wechatCallback() + { + $data = $this->request->params([ + ['code', ''], + ['state', ''] + ]); + + return (new WechatBindService())->wechatCallback($data['code'], $data['state']); + } + + /** + * 获取绑定状态 + * @return \think\Response + */ + public function getBindStatus() + { + return success('获取成功', (new WechatBindService())->getBindStatus()); + } + +} \ No newline at end of file diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php index 93c63624..c86c4b68 100644 --- a/niucloud/app/api/route/route.php +++ b/niucloud/app/api/route/route.php @@ -48,6 +48,10 @@ Route::group(function () { //员工端-添加新员工信息 Route::post('personnel/add', 'apiController.Personnel/add'); + + //微信公众号授权相关接口(不需要token验证) + Route::get('personnel/wechatAuthorize', 'personnel.WechatBind/wechatAuthorize'); + Route::get('personnel/wechatCallback', 'personnel.WechatBind/wechatCallback'); }); /** @@ -464,6 +468,11 @@ Route::group(function () { Route::get('personnel/serviceLogDetail', 'apiController.Personnel/serviceLogDetail'); Route::post('personnel/updateServiceRemark', 'apiController.Personnel/updateServiceRemark'); + //微信绑定相关接口(需要人员token验证) + Route::post('personnel/getWechatOpenid', 'personnel.WechatBind/getWechatOpenid'); + Route::post('personnel/bindWechatOpenid', 'personnel.WechatBind/bindWechatOpenid'); + Route::get('personnel/getBindStatus', 'personnel.WechatBind/getBindStatus'); + Route::get('getQrcode', 'pay.Pay/getQrcode'); diff --git a/niucloud/app/dict/schedule/schedule.php b/niucloud/app/dict/schedule/schedule.php index 095207e0..406c98b4 100644 --- a/niucloud/app/dict/schedule/schedule.php +++ b/niucloud/app/dict/schedule/schedule.php @@ -66,4 +66,17 @@ return [ 'class' => 'app\job\schedule\TeachingPersonnelSync', 'function' => 'doJob' ], + [ + 'key' => 'ServiceLogsDistribution', + 'name' => '服务分发', + 'desc' => '检查服务记录是否分配给正确的用户', + 'time' => [ + 'type' => 'day', + 'day' => 1, + 'hour' => 2, + 'min' => 0 + ], + 'class' => 'app\job\schedule\ServiceLogsDistribution', + 'function' => 'doJob' + ], ]; diff --git a/niucloud/app/job/schedule/ServiceLogsDistribution.php b/niucloud/app/job/schedule/ServiceLogsDistribution.php index 01d3b2e4..8830f1f2 100644 --- a/niucloud/app/job/schedule/ServiceLogsDistribution.php +++ b/niucloud/app/job/schedule/ServiceLogsDistribution.php @@ -13,8 +13,10 @@ namespace app\job\schedule; use app\model\service_logs\ServiceLogs; use app\model\personnel\Personnel; +use app\service\admin\service_logs\ServiceLogsDistributionService; use app\service\core\notice\NoticeService; use core\base\BaseJob; +use think\facade\Db; use think\facade\Log; /** @@ -32,8 +34,12 @@ class ServiceLogsDistribution extends BaseJob Log::write('服务记录分发任务开始 ' . date('Y-m-d H:i:s')); try { - $this->distributeToAcademicAffairs(); - $this->distributeToCoaches(); + // 检查并更新人员变更 + $this->checkPersonnelChanges(); + + // 生成新的服务记录 + $this->generateNewServiceLogs(); + Log::write('服务记录分发任务完成 ' . date('Y-m-d H:i:s')); } catch (\Exception $e) { Log::write('服务记录分发任务异常: ' . $e->getMessage()); @@ -44,97 +50,54 @@ class ServiceLogsDistribution extends BaseJob } /** - * 给教务分发服务记录 + * 检查人员变更 */ - private function distributeToAcademicAffairs() + private function checkPersonnelChanges() { - // 获取需要分发的服务记录(最近24小时内完成的记录) - $yesterday = date('Y-m-d H:i:s', strtotime('-1 day')); - $serviceLogs = ServiceLogs::where('status', 1) // 已完成状态 - ->where('created_at', '>=', $yesterday) - ->where('is_distributed_to_academic', 0) // 未分发给教务 - ->with(['service', 'staff']) - ->select(); - - if ($serviceLogs->isEmpty()) { - Log::write('没有需要分发给教务的服务记录'); - return; - } - - // 获取教务人员列表 - $academicStaff = Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教务%') - ->select(); - - if ($academicStaff->isEmpty()) { - Log::write('未找到教务人员'); - return; - } - - $distributionCount = 0; - foreach ($serviceLogs as $serviceLog) { - foreach ($academicStaff as $staff) { - // 发送通知给教务 - $this->sendServiceLogNotice($staff, $serviceLog, 'academic'); - $distributionCount++; - } - - // 标记为已分发给教务 - $serviceLog->is_distributed_to_academic = 1; - $serviceLog->distribution_time = time(); - $serviceLog->save(); - Log::write("已分发给教务,服务记录ID: {$serviceLog->id}"); + $service = new ServiceLogsDistributionService(); + $result = $service->checkAndUpdatePersonnelChanges(); + + if ($result['success']) { + Log::write("人员变更检查完成,更新了 {$result['updated_count']} 条记录"); + } else { + Log::write("人员变更检查失败: {$result['message']}"); } - - Log::write("成功分发给教务 {$distributionCount} 条服务记录通知"); } /** - * 给教练分发服务记录 + * 生成新的服务记录 */ - private function distributeToCoaches() + private function generateNewServiceLogs() { - // 获取需要分发的服务记录(最近24小时内完成的记录) - $yesterday = date('Y-m-d H:i:s', strtotime('-1 day')); - $serviceLogs = ServiceLogs::where('status', 1) // 已完成状态 - ->where('created_at', '>=', $yesterday) - ->where('is_distributed_to_coach', 0) // 未分发给教练 - ->with(['service', 'staff']) - ->select(); - - if ($serviceLogs->isEmpty()) { - Log::write('没有需要分发给教练的服务记录'); - return; - } - - // 获取教练人员列表 - $coaches = Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教练%') - ->select(); - - if ($coaches->isEmpty()) { - Log::write('未找到教练人员'); - return; - } - - $distributionCount = 0; - foreach ($serviceLogs as $serviceLog) { - // 找到对应的教练(根据服务记录中的staff_id) - $coach = $coaches->where('id', $serviceLog->staff_id)->first(); - if ($coach) { - // 发送通知给教练 - $this->sendServiceLogNotice($coach, $serviceLog, 'coach'); - $distributionCount++; + // 获取所有有效的学员课程,但之前没有教练或教务,现在有了的 + $courses = Db::table('school_student_courses') + ->where('status', 1) + ->where(function ($query) { + $query->where('main_coach_id', '>', 0) + ->whereOr('education_id', '>', 0); + }) + ->select() + ->toArray(); + + $service = new ServiceLogsDistributionService(); + $totalGenerated = 0; + + foreach ($courses as $course) { + // 检查是否已经为这个课程生成过服务记录 + $existingLogs = Db::table('school_service_logs') + ->where('course_id', $course['id']) + ->count(); + + // 如果没有服务记录,说明是新设置的教练/教务,需要生成服务记录 + if ($existingLogs == 0) { + $result = $service->generateServiceLogs($course); + if ($result['success']) { + $totalGenerated += $result['generated_count']; + } } - - // 标记为已分发给教练 - $serviceLog->is_distributed_to_coach = 1; - $serviceLog->distribution_time = time(); - $serviceLog->save(); - Log::write("已分发给教练,服务记录ID: {$serviceLog->id}"); } - Log::write("成功分发给教练 {$distributionCount} 条服务记录通知"); + Log::write("本次任务为新课程生成了 {$totalGenerated} 条服务记录"); } /** @@ -145,59 +108,8 @@ class ServiceLogsDistribution extends BaseJob */ private function sendServiceLogNotice($staff, $serviceLog, $type) { - try { - // 准备通知数据 - $noticeData = [ - 'staff_name' => $staff->name, - 'service_name' => $serviceLog->service->service_name ?? '未知服务', - 'service_remark' => $serviceLog->service_remark ?? '', - 'score' => $serviceLog->score ?? 0, - 'feedback' => $serviceLog->feedback ?? '', - 'created_at' => date('Y-m-d H:i:s', $serviceLog->created_at), - 'type' => $type - ]; - - // 根据类型选择不同的通知模板 - $noticeKey = $type === 'academic' ? 'service_log_academic_notice' : 'service_log_coach_notice'; - - // 发送通知 - NoticeService::send($noticeKey, $noticeData); - - Log::write("成功发送服务记录通知给 {$staff->name},服务记录ID: {$serviceLog->id}"); - - } catch (\Exception $e) { - Log::write("发送服务记录通知失败: {$e->getMessage()}"); - } + // 可以在这里实现通知功能 + // 比如发送短信、邮件、站内通知等 } - /** - * 获取分发统计信息 - * @return array - */ - public function getDistributionStats() - { - $yesterday = date('Y-m-d H:i:s', strtotime('-1 day')); - - $stats = [ - 'total_completed' => ServiceLogs::where('status', 1) - ->where('created_at', '>=', $yesterday) - ->count(), - 'distributed_to_academic' => ServiceLogs::where('status', 1) - ->where('created_at', '>=', $yesterday) - ->where('is_distributed_to_academic', 1) - ->count(), - 'distributed_to_coach' => ServiceLogs::where('status', 1) - ->where('created_at', '>=', $yesterday) - ->where('is_distributed_to_coach', 1) - ->count(), - 'academic_staff_count' => Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教务%') - ->count(), - 'coach_count' => Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教练%') - ->count(), - ]; - - return $stats; - } } \ No newline at end of file diff --git a/niucloud/app/job/schedule/TeachingPersonnelSync.php b/niucloud/app/job/schedule/TeachingPersonnelSync.php index f0fdacf0..bfc0fd12 100644 --- a/niucloud/app/job/schedule/TeachingPersonnelSync.php +++ b/niucloud/app/job/schedule/TeachingPersonnelSync.php @@ -59,7 +59,7 @@ class TeachingPersonnelSync ]); } - return $message; + return true; } catch (\Exception $e) { $error = '教研管理人员同步任务执行失败: ' . $e->getMessage(); @@ -69,7 +69,7 @@ class TeachingPersonnelSync 'trace' => $e->getTraceAsString() ]); - return $error; + return false; } } diff --git a/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php b/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php index ec7c9811..a8eef1c2 100644 --- a/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php +++ b/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php @@ -15,6 +15,7 @@ use app\job\schedule\ServiceLogsDistribution; use app\model\service_logs\ServiceLogs; use app\model\personnel\Personnel; use core\base\BaseAdminService; +use think\facade\Db; use think\facade\Log; /** @@ -30,155 +31,321 @@ class ServiceLogsDistributionService extends BaseAdminService } /** - * 手动执行分发任务 + * 为学员课程生成服务记录 + * @param object $studentCourse school_student_courses表的模型对象 * @return array */ - public function executeDistribution() + public function generateServiceLogs($studentCourse) { try { - $job = new ServiceLogsDistribution(); - $result = $job->doJob(); - - if ($result) { - return ['code' => 1, 'msg' => '分发任务执行成功']; - } else { - return ['code' => 0, 'msg' => '分发任务执行失败']; + $result = [ + 'success' => true, + 'generated_count' => 0, + 'message' => '服务记录生成成功' + ]; + + // 获取所有启用的服务 + $services = Db::table('school_service') + ->where('status', 1) + ->where('deleted_at', 0) + ->select() + ->toArray(); + + if (empty($services)) { + $result['message'] = '没有找到启用的服务'; + return $result; + } + + foreach ($services as $service) { + $generatedCount = $this->generateServiceLogsForService($studentCourse, $service); + $result['generated_count'] += $generatedCount; } + + Log::write("为课程ID:{$studentCourse['id']} 学员ID:{$studentCourse['student_id']} 生成了 {$result['generated_count']} 条服务记录"); + + return $result; + } catch (\Exception $e) { - Log::write('手动执行分发任务异常: ' . $e->getMessage()); - return ['code' => 0, 'msg' => '分发任务执行异常:' . $e->getMessage()]; + Log::write('生成服务记录异常: ' . $e->getMessage()); + return [ + 'success' => false, + 'generated_count' => 0, + 'message' => '生成服务记录失败: ' . $e->getMessage() + ]; } } /** - * 获取分发统计信息 - * @return array + * 为特定服务生成服务记录 + * @param object $studentCourse 学员课程对象 + * @param array $service 服务信息 + * @return int 生成的记录数量 */ - public function getDistributionStats() + private function generateServiceLogsForService($studentCourse, $service) { - try { - $job = new ServiceLogsDistribution(); - $stats = $job->getDistributionStats(); - - return [ - 'code' => 1, - 'data' => $stats, - 'msg' => '获取统计信息成功' - ]; - } catch (\Exception $e) { - Log::write('获取分发统计信息异常: ' . $e->getMessage()); - return ['code' => 0, 'msg' => '获取统计信息失败:' . $e->getMessage()]; + $generatedCount = 0; + + // 根据服务类型确定负责人员 + $staffId = null; + if ($service['service_type'] == 1) { // 教务服务 + $staffId = $studentCourse['education_id']; + } elseif ($service['service_type'] == 2) { // 教练服务 + $staffId = $studentCourse['main_coach_id']; } + + // 如果没有对应的人员ID,跳过处理 + if (empty($staffId)) { + return 0; + } + + // 计算需要生成的服务记录时间点 + $serviceDates = $this->calculateServiceDates($studentCourse['start_date'], $studentCourse['end_date'], $service['execution_rules']); + + foreach ($serviceDates as $serviceDate) { + // 检查是否已经存在相同的服务记录 + $existingLog = Db::table('school_service_logs') + ->where('course_id', $studentCourse['id']) + ->where('service_id', $service['id']) + ->where('staff_id', $staffId) + ->where('student_id', $studentCourse['student_id']) + ->where('created_at', $serviceDate) + ->find(); + + if (!$existingLog) { + // 插入新的服务记录 + Db::table('school_service_logs')->insert([ + 'resource_id' => $studentCourse['resource_id'], + 'course_id' => $studentCourse['id'], + 'service_id' => $service['id'], + 'student_id' => $studentCourse['student_id'], + 'staff_id' => $staffId, + 'status' => 0, // 未服务状态 + 'created_at' => $serviceDate, + 'updated_at' => date('Y-m-d H:i:s') + ]); + $generatedCount++; + } + } + + return $generatedCount; } /** - * 获取待分发的服务记录列表 - * @param array $where - * @return array + * 根据执行规则计算服务时间点 + * @param string $startDate 开始日期 + * @param string $endDate 结束日期 + * @param string $executionRule 执行规则 + * @return array 时间点数组 */ - public function getPendingDistributionList(array $where = []) + private function calculateServiceDates($startDate, $endDate, $executionRule) { - $field = 'id,service_id,staff_id,status,service_remark,feedback,score,created_at,updated_at,is_distributed_to_academic,is_distributed_to_coach,distribution_time'; + $dates = []; + $start = strtotime($startDate); + $end = strtotime($endDate); - $search_model = ServiceLogs::alias("a") - ->join(['school_service' => 'b'], 'a.service_id = b.id', 'left') - ->join(['school_personnel' => 'c'], 'a.staff_id = c.id', 'left') - ->where('a.status', 1) // 已完成状态 - ->field("a.{$field},b.service_name,c.name as staff_name") - ->order('a.created_at desc'); - - // 添加筛选条件 - if (isset($where['distribution_status'])) { - switch ($where['distribution_status']) { - case 'pending_academic': - $search_model->where('a.is_distributed_to_academic', 0); - break; - case 'pending_coach': - $search_model->where('a.is_distributed_to_coach', 0); - break; - case 'distributed': - $search_model->where('a.is_distributed_to_academic', 1) - ->where('a.is_distributed_to_coach', 1); - break; - } + // 根据执行规则确定间隔天数 + $intervalDays = $this->getIntervalDaysByRule($executionRule); + + if ($intervalDays === 0) { + return $dates; } - if (isset($where['date_range']) && !empty($where['date_range'])) { - $search_model->whereTime('a.created_at', $where['date_range']); + $currentTime = $start; + while ($currentTime <= $end) { + $dates[] = date('Y-m-d H:i:s', $currentTime); + $currentTime += ($intervalDays * 24 * 60 * 60); // 增加间隔天数 } - $list = $this->pageQuery($search_model); - - return $list; + return $dates; } /** - * 重置分发状态 - * @param array $ids 服务记录ID数组 - * @param string $type 重置类型 (academic|coach|both) + * 根据执行规则获取间隔天数 + * @param string $executionRule 执行规则 + * @return int 间隔天数 + */ + private function getIntervalDaysByRule($executionRule) + { + switch ($executionRule) { + case 'daily_once': + return 1; // 每天一次 + case 'weekly_once': + return 7; // 每周一次 + case 'twice_per_week': + return 3; // 一周两次,约每3-4天一次,这里简化为3天 + case 'biweekly_once': + return 14; // 两周一次 + case 'monthly_once': + return 30; // 每月一次,约30天 + default: + return 0; // 未知规则 + } + } + + /** + * 检查并更新人员变更 * @return array */ - public function resetDistributionStatus(array $ids, string $type = 'both') + public function checkAndUpdatePersonnelChanges() { try { - $updateData = []; + $updatedCount = 0; - if ($type === 'academic' || $type === 'both') { - $updateData['is_distributed_to_academic'] = 0; + // 获取所有未服务的服务记录 + $pendingLogs = Db::table('school_service_logs') + ->alias('sl') + ->leftJoin('school_student_courses sc', 'sl.course_id = sc.id') + ->leftJoin('school_service s', 'sl.service_id = s.id') + ->where('sl.status', 0) // 未服务状态 + ->where('sc.status', 1) // 课程有效 + ->where('s.status', 1) // 服务有效 + ->where('s.deleted_at', 0) + ->field('sl.*, sc.main_coach_id, sc.education_id, s.service_type') + ->select() + ->toArray(); + + foreach ($pendingLogs as $log) { + $newStaffId = null; + + // 根据服务类型确定新的负责人员 + if ($log['service_type'] == 1) { // 教务服务 + $newStaffId = $log['education_id']; + } elseif ($log['service_type'] == 2) { // 教练服务 + $newStaffId = $log['main_coach_id']; + } + + // 如果人员发生变更且新人员不为空 + if ($newStaffId && $log['staff_id'] != $newStaffId) { + Db::table('school_service_logs') + ->where('id', $log['id']) + ->update([ + 'staff_id' => $newStaffId, + 'updated_at' => date('Y-m-d H:i:s') + ]); + $updatedCount++; + } } + + Log::write("检查人员变更完成,更新了 {$updatedCount} 条服务记录"); - if ($type === 'coach' || $type === 'both') { - $updateData['is_distributed_to_coach'] = 0; - } + return [ + 'success' => true, + 'updated_count' => $updatedCount, + 'message' => "人员变更检查完成,更新了 {$updatedCount} 条记录" + ]; - if ($type === 'both') { - $updateData['distribution_time'] = 0; + } catch (\Exception $e) { + Log::write('检查人员变更异常: ' . $e->getMessage()); + return [ + 'success' => false, + 'updated_count' => 0, + 'message' => '检查人员变更失败: ' . $e->getMessage() + ]; + } + } + + /** + * 根据员工ID获取服务记录 + * @param int $staffId 员工ID + * @param int $page 页码 + * @param int $limit 每页数量 + * @param int $status 状态筛选 (null:全部, 0:未服务, 1:已服务) + * @return array + */ + public function getServiceLogsByStaffId($staffId, $page = 1, $limit = 20, $status = null) + { + try { + $query = Db::table('school_service_logs') + ->alias('sl') + ->leftJoin('school_service s', 'sl.service_id = s.id') + ->leftJoin('school_student_courses sc', 'sl.course_id = sc.id') + ->leftJoin('school_student st', 'sl.student_id = st.id') + ->where('sl.staff_id', $staffId) + ->where('s.deleted_at', 0); + + if ($status !== null) { + $query->where('sl.status', $status); } + + $total = $query->count(); - $result = ServiceLogs::whereIn('id', $ids)->update($updateData); + $list = $query->field('sl.*, s.service_name, s.description as service_desc, st.name as student_name, sc.start_date, sc.end_date') + ->order('sl.created_at DESC') + ->page($page, $limit) + ->select() + ->toArray(); + + return [ + 'success' => true, + 'data' => [ + 'list' => $list, + 'total' => $total, + 'page' => $page, + 'limit' => $limit + ], + 'message' => '查询成功' + ]; - if ($result) { - Log::write("重置分发状态成功,记录ID: " . implode(',', $ids) . ",类型: {$type}"); - return ['code' => 1, 'msg' => '重置分发状态成功']; - } else { - return ['code' => 0, 'msg' => '重置分发状态失败']; - } } catch (\Exception $e) { - Log::write('重置分发状态异常: ' . $e->getMessage()); - return ['code' => 0, 'msg' => '重置分发状态异常:' . $e->getMessage()]; + Log::write('查询员工服务记录异常: ' . $e->getMessage()); + return [ + 'success' => false, + 'data' => [], + 'message' => '查询失败: ' . $e->getMessage() + ]; } } /** - * 获取教务和教练人员列表 + * 根据学员ID获取服务记录 + * @param int $studentId 学员ID + * @param int $page 页码 + * @param int $limit 每页数量 + * @param int $status 状态筛选 (null:全部, 0:未服务, 1:已服务) * @return array */ - public function getStaffList() + public function getServiceLogsByStudentId($studentId, $page = 1, $limit = 20, $status = null) { try { - $academicStaff = Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教务%') - ->field('id,name,position,phone') - ->select() - ->toArray(); + $query = Db::table('school_service_logs') + ->alias('sl') + ->leftJoin('school_service s', 'sl.service_id = s.id') + ->leftJoin('school_student_courses sc', 'sl.course_id = sc.id') + ->leftJoin('school_personnel p', 'sl.staff_id = p.id') + ->where('sl.student_id', $studentId) + ->where('s.deleted_at', 0); - $coaches = Personnel::where('status', Personnel::STATUS_NORMAL) - ->where('position', 'like', '%教练%') - ->field('id,name,position,phone') + if ($status !== null) { + $query->where('sl.status', $status); + } + + $total = $query->count(); + + $list = $query->field('sl.*, s.service_name, s.description as service_desc, p.name as staff_name, sc.start_date, sc.end_date') + ->order('sl.created_at DESC') + ->page($page, $limit) ->select() ->toArray(); return [ - 'code' => 1, + 'success' => true, 'data' => [ - 'academic_staff' => $academicStaff, - 'coaches' => $coaches + 'list' => $list, + 'total' => $total, + 'page' => $page, + 'limit' => $limit ], - 'msg' => '获取人员列表成功' + 'message' => '查询成功' ]; + } catch (\Exception $e) { - Log::write('获取人员列表异常: ' . $e->getMessage()); - return ['code' => 0, 'msg' => '获取人员列表失败:' . $e->getMessage()]; + Log::write('查询学员服务记录异常: ' . $e->getMessage()); + return [ + 'success' => false, + 'data' => [], + 'message' => '查询失败: ' . $e->getMessage() + ]; } } + } \ No newline at end of file diff --git a/niucloud/app/service/api/personnel/WechatBindService.php b/niucloud/app/service/api/personnel/WechatBindService.php new file mode 100644 index 00000000..ab04ee58 --- /dev/null +++ b/niucloud/app/service/api/personnel/WechatBindService.php @@ -0,0 +1,512 @@ +getMiniProgramOpenid($code); + } else { + // 公众号获取openid + $openid = $this->getOfficialOpenid($code); + } + + return [ + 'openid' => $openid, + 'type' => $type + ]; + } catch (\Exception $e) { + throw new ApiException('获取openid失败:' . $e->getMessage()); + } + } + + /** + * 绑定微信openid到人员 + * @param array $data + * @return array + */ + public function bindWechatOpenid(array $data) + { + $personnel_id = $this->request->memberId(); // 从token中获取当前登录的人员ID + + if (empty($personnel_id)) { + throw new ApiException('用户未登录'); + } + + // 查找人员记录 + $personnel = SchoolPersonnel::where('id', $personnel_id)->find(); + if (!$personnel) { + throw new ApiException('人员信息不存在'); + } + + $update_data = []; + + // 根据类型绑定不同的openid + if ($data['type'] === 'miniprogram' && !empty($data['miniprogram_openid'])) { + // 检查小程序openid是否已被其他用户绑定 + $exists = SchoolPersonnel::where('wxminiopenid', $data['miniprogram_openid']) + ->where('id', '<>', $personnel_id) + ->find(); + if ($exists) { + throw new ApiException('该微信小程序已绑定其他账号'); + } + $update_data['wxminiopenid'] = $data['miniprogram_openid']; + + } elseif ($data['type'] === 'official' && !empty($data['official_openid'])) { + // 检查公众号openid是否已被其他用户绑定 + $exists = SchoolPersonnel::where('wxgzhopenid', $data['official_openid']) + ->where('id', '<>', $personnel_id) + ->find(); + if ($exists) { + throw new ApiException('该微信公众号已绑定其他账号'); + } + $update_data['wxgzhopenid'] = $data['official_openid']; + + } elseif ($data['type'] === 'both') { + // 同时绑定两个openid + if (!empty($data['miniprogram_openid'])) { + $exists = SchoolPersonnel::where('wxminiopenid', $data['miniprogram_openid']) + ->where('id', '<>', $personnel_id) + ->find(); + if ($exists) { + throw new ApiException('该微信小程序已绑定其他账号'); + } + $update_data['wxminiopenid'] = $data['miniprogram_openid']; + } + + if (!empty($data['official_openid'])) { + $exists = SchoolPersonnel::where('wxgzhopenid', $data['official_openid']) + ->where('id', '<>', $personnel_id) + ->find(); + if ($exists) { + throw new ApiException('该微信公众号已绑定其他账号'); + } + $update_data['wxgzhopenid'] = $data['official_openid']; + } + } + + if (empty($update_data)) { + throw new ApiException('没有可绑定的openid'); + } + + // 更新人员微信信息 + $personnel->save($update_data); + + return [ + 'personnel_id' => $personnel_id, + 'bind_data' => $update_data, + 'message' => '绑定成功' + ]; + } + + /** + * 微信公众号授权跳转 + * @param string $redirect_uri 回调地址 + * @param string $state 状态参数 + * @return Response + */ + public function wechatAuthorize(string $redirect_uri, string $state) + { + if (empty($redirect_uri)) { + throw new ApiException('回调地址不能为空'); + } + + try { + // 使用现有的微信授权服务 + $core_wechat_service = new CoreWechatServeService(); + $callback_url = $core_wechat_service->authorization($redirect_uri . '?state=' . urlencode($state), 'snsapi_userinfo'); + + // 直接跳转到微信授权页面 + return redirect($callback_url); + + } catch (\Exception $e) { + throw new ApiException('生成授权链接失败:' . $e->getMessage()); + } + } + + /** + * 微信授权回调处理 + * @param string $code 授权回调code + * @param string $state 状态参数 + * @return Response + */ + public function wechatCallback(string $code, string $state) + { + try { + if (empty($code)) { + return $this->buildCallbackPage('wechat_bind_cancel', '用户取消授权'); + } + + // 解析state参数 + $state_data = json_decode(urldecode($state), true); + if (!$state_data || !isset($state_data['personnel_id'])) { + return $this->buildCallbackPage('wechat_bind_error', '参数错误'); + } + + // 获取公众号openid + $official_openid = $this->getOfficialOpenid($code); + + // 绑定公众号openid + $bind_result = $this->bindOfficialOpenid($state_data['personnel_id'], $official_openid); + + if ($bind_result) { + // 根据来源返回不同的页面 + if (isset($state_data['from']) && $state_data['from'] === 'h5') { + // H5环境,返回简单的成功页面 + return $this->buildH5SuccessPage('微信绑定成功!'); + } else { + // 小程序webview环境 + return $this->buildCallbackPage('wechat_bind_success', '微信公众号绑定成功'); + } + } else { + return $this->buildCallbackPage('wechat_bind_error', '绑定失败'); + } + + } catch (\Exception $e) { + return $this->buildCallbackPage('wechat_bind_error', '绑定失败:' . $e->getMessage()); + } + } + + /** + * 获取绑定状态 + * @return array + */ + public function getBindStatus() + { + $personnel_id = $this->request->memberId(); + + if (empty($personnel_id)) { + throw new ApiException('用户未登录'); + } + + $personnel = SchoolPersonnel::where('id', $personnel_id)->find(); + if (!$personnel) { + throw new ApiException('人员信息不存在'); + } + + return [ + 'personnel_id' => $personnel_id, + 'miniprogram_bound' => !empty($personnel->wxminiopenid), + 'official_bound' => !empty($personnel->wxgzhopenid), + 'miniprogram_openid' => $personnel->wxminiopenid ?? '', + 'official_openid' => $personnel->wxgzhopenid ?? '' + ]; + } + + /** + * 获取小程序openid + * @param string $code + * @return string + */ + private function getMiniProgramOpenid(string $code) + { + // 获取小程序配置 + $weapp_config = $this->getWeappConfig(); + + // 构建请求URL + $url = "https://api.weixin.qq.com/sns/jscode2session?appid=" . $weapp_config['app_id'] . + "&secret=" . $weapp_config['secret'] . + "&js_code=" . $code . + "&grant_type=authorization_code"; + + // 使用cURL发起HTTP请求,更稳定 + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); + + $response = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($response === false) { + throw new ApiException('获取小程序openid失败:网络请求失败'); + } + + if ($http_code !== 200) { + throw new ApiException('获取小程序openid失败:HTTP状态码' . $http_code); + } + + $result = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw new ApiException('获取小程序openid失败:响应数据格式错误'); + } + + if (isset($result['errcode']) && $result['errcode'] != 0) { + throw new ApiException('获取小程序openid失败:' . ($result['errmsg'] ?? '未知错误') . '(错误码:' . $result['errcode'] . ')'); + } + + if (empty($result['openid'])) { + throw new ApiException('获取小程序openid失败:返回数据中无openid'); + } + + return $result['openid']; + } + + /** + * 获取公众号openid + * @param string $code + * @return string + */ + private function getOfficialOpenid(string $code) + { + // 使用现有的微信服务获取用户信息 + $core_wechat_service = new CoreWechatServeService(); + $user = $core_wechat_service->userFromCode($code); + + if (!$user) { + throw new ApiException('获取公众号openid失败'); + } + + return $user->getId(); // openid + } + + /** + * 绑定公众号openid + * @param int $personnel_id + * @param string $official_openid + * @return bool + */ + private function bindOfficialOpenid(int $personnel_id, string $official_openid) + { + // 检查公众号openid是否已被其他用户绑定 + $exists = SchoolPersonnel::where('wxgzhopenid', $official_openid) + ->where('id', '<>', $personnel_id) + ->find(); + if ($exists) { + throw new ApiException('该微信公众号已绑定其他账号'); + } + + // 更新人员微信公众号openid + $personnel = SchoolPersonnel::where('id', $personnel_id)->find(); + if (!$personnel) { + throw new ApiException('人员信息不存在'); + } + + $personnel->wxgzhopenid = $official_openid; + return $personnel->save(); + } + + /** + * 构建回调页面 + * @param string $type + * @param string $message + * @return Response + */ + private function buildCallbackPage(string $type, string $message) + { + $html = ' + +
+ + +页面将在3秒后自动关闭
+ + +'; + + return Response::create($html, 'html'); + } + + /** + * 构建H5成功页面(用于微信浏览器直接访问) + * @param string $message + * @return Response + */ + private function buildH5SuccessPage(string $message) + { + $html = ' + + + + +