diff --git a/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php b/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php new file mode 100644 index 00000000..f243bb7b --- /dev/null +++ b/niucloud/app/adminapi/controller/service_logs/ServiceLogsDistribution.php @@ -0,0 +1,95 @@ +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/dict/notice/service_logs_notice.php b/niucloud/app/dict/notice/service_logs_notice.php new file mode 100644 index 00000000..d0438c74 --- /dev/null +++ b/niucloud/app/dict/notice/service_logs_notice.php @@ -0,0 +1,45 @@ + [ + 'name' => '服务记录教务通知', + 'key' => 'service_log_academic_notice', + 'sms' => [ + 'content' => '尊敬的{staff_name},有新的服务记录需要您查看。服务名称:{service_name},评分:{score}分,创建时间:{created_at}。' + ], + 'wechat' => [ + 'first' => '您有新的服务记录需要查看', + 'remark' => '请及时处理相关事务' + ], + 'weapp' => [ + 'title' => '服务记录通知', + 'content' => '有新的服务记录需要您查看' + ], + 'async' => true + ], + 'service_log_coach_notice' => [ + 'name' => '服务记录教练通知', + 'key' => 'service_log_coach_notice', + 'sms' => [ + 'content' => '尊敬的{staff_name}教练,您有一项服务记录已完成。服务名称:{service_name},学员评分:{score}分,学员反馈:{feedback},创建时间:{created_at}。' + ], + 'wechat' => [ + 'first' => '您的服务记录已完成', + 'remark' => '请查看学员反馈和评分' + ], + 'weapp' => [ + 'title' => '服务记录完成通知', + 'content' => '您的服务记录已完成,请查看详情' + ], + 'async' => true + ] +]; \ No newline at end of file diff --git a/niucloud/app/job/schedule/ServiceLogsDistribution.php b/niucloud/app/job/schedule/ServiceLogsDistribution.php new file mode 100644 index 00000000..01d3b2e4 --- /dev/null +++ b/niucloud/app/job/schedule/ServiceLogsDistribution.php @@ -0,0 +1,203 @@ +distributeToAcademicAffairs(); + $this->distributeToCoaches(); + Log::write('服务记录分发任务完成 ' . date('Y-m-d H:i:s')); + } catch (\Exception $e) { + Log::write('服务记录分发任务异常: ' . $e->getMessage()); + return false; + } + + return true; + } + + /** + * 给教务分发服务记录 + */ + private function distributeToAcademicAffairs() + { + // 获取需要分发的服务记录(最近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}"); + } + + Log::write("成功分发给教务 {$distributionCount} 条服务记录通知"); + } + + /** + * 给教练分发服务记录 + */ + private function distributeToCoaches() + { + // 获取需要分发的服务记录(最近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++; + } + + // 标记为已分发给教练 + $serviceLog->is_distributed_to_coach = 1; + $serviceLog->distribution_time = time(); + $serviceLog->save(); + Log::write("已分发给教练,服务记录ID: {$serviceLog->id}"); + } + + Log::write("成功分发给教练 {$distributionCount} 条服务记录通知"); + } + + /** + * 发送服务记录通知 + * @param Personnel $staff 接收人员 + * @param ServiceLogs $serviceLog 服务记录 + * @param string $type 通知类型 (academic|coach) + */ + 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/service/admin/service_logs/ServiceLogsDistributionService.php b/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php new file mode 100644 index 00000000..ec7c9811 --- /dev/null +++ b/niucloud/app/service/admin/service_logs/ServiceLogsDistributionService.php @@ -0,0 +1,184 @@ +doJob(); + + if ($result) { + return ['code' => 1, 'msg' => '分发任务执行成功']; + } else { + return ['code' => 0, 'msg' => '分发任务执行失败']; + } + } catch (\Exception $e) { + Log::write('手动执行分发任务异常: ' . $e->getMessage()); + return ['code' => 0, 'msg' => '分发任务执行异常:' . $e->getMessage()]; + } + } + + /** + * 获取分发统计信息 + * @return array + */ + public function getDistributionStats() + { + 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()]; + } + } + + /** + * 获取待分发的服务记录列表 + * @param array $where + * @return array + */ + public function getPendingDistributionList(array $where = []) + { + $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'; + + $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; + } + } + + if (isset($where['date_range']) && !empty($where['date_range'])) { + $search_model->whereTime('a.created_at', $where['date_range']); + } + + $list = $this->pageQuery($search_model); + + return $list; + } + + /** + * 重置分发状态 + * @param array $ids 服务记录ID数组 + * @param string $type 重置类型 (academic|coach|both) + * @return array + */ + public function resetDistributionStatus(array $ids, string $type = 'both') + { + try { + $updateData = []; + + if ($type === 'academic' || $type === 'both') { + $updateData['is_distributed_to_academic'] = 0; + } + + if ($type === 'coach' || $type === 'both') { + $updateData['is_distributed_to_coach'] = 0; + } + + if ($type === 'both') { + $updateData['distribution_time'] = 0; + } + + $result = ServiceLogs::whereIn('id', $ids)->update($updateData); + + 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()]; + } + } + + /** + * 获取教务和教练人员列表 + * @return array + */ + public function getStaffList() + { + try { + $academicStaff = Personnel::where('status', Personnel::STATUS_NORMAL) + ->where('position', 'like', '%教务%') + ->field('id,name,position,phone') + ->select() + ->toArray(); + + $coaches = Personnel::where('status', Personnel::STATUS_NORMAL) + ->where('position', 'like', '%教练%') + ->field('id,name,position,phone') + ->select() + ->toArray(); + + return [ + 'code' => 1, + 'data' => [ + 'academic_staff' => $academicStaff, + 'coaches' => $coaches + ], + 'msg' => '获取人员列表成功' + ]; + } catch (\Exception $e) { + Log::write('获取人员列表异常: ' . $e->getMessage()); + return ['code' => 0, 'msg' => '获取人员列表失败:' . $e->getMessage()]; + } + } +} \ No newline at end of file diff --git a/niucloud/app/upgrade/v151/upgrade.php b/niucloud/app/upgrade/v151/upgrade.php new file mode 100644 index 00000000..0c4e481f --- /dev/null +++ b/niucloud/app/upgrade/v151/upgrade.php @@ -0,0 +1,54 @@ +executeSql(); + + // 记录升级日志 + $this->addUpgradeLog('v151', '服务记录分发功能升级完成'); + + return true; + } catch (\Exception $e) { + $this->addUpgradeLog('v151', '升级失败:' . $e->getMessage()); + return false; + } + } + + /** + * 执行SQL升级 + */ + private function executeSql() + { + $sqlFile = __DIR__ . '/upgrade.sql'; + if (file_exists($sqlFile)) { + $sql = file_get_contents($sqlFile); + $this->execute($sql); + } + } +} \ No newline at end of file diff --git a/niucloud/app/upgrade/v151/upgrade.sql b/niucloud/app/upgrade/v151/upgrade.sql new file mode 100644 index 00000000..69480d9a --- /dev/null +++ b/niucloud/app/upgrade/v151/upgrade.sql @@ -0,0 +1,9 @@ +-- 为服务记录表添加分发状态字段 +ALTER TABLE `service_logs` +ADD COLUMN `is_distributed_to_academic` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已分发给教务 0未分发 1已分发' AFTER `feedback`, +ADD COLUMN `is_distributed_to_coach` tinyint(1) NOT NULL DEFAULT 0 COMMENT '是否已分发给教练 0未分发 1已分发' AFTER `is_distributed_to_academic`, +ADD COLUMN `distribution_time` int(11) NOT NULL DEFAULT 0 COMMENT '分发时间' AFTER `is_distributed_to_coach`; + +-- 为人员表添加职位字段(如果不存在) +ALTER TABLE `personnel` +ADD COLUMN `position` varchar(100) NOT NULL DEFAULT '' COMMENT '职位' AFTER `name`; \ No newline at end of file diff --git a/niucloud/服务记录分发功能说明.md b/niucloud/服务记录分发功能说明.md new file mode 100644 index 00000000..b3c68ada --- /dev/null +++ b/niucloud/服务记录分发功能说明.md @@ -0,0 +1,165 @@ +# 服务记录分发功能说明 + +## 功能概述 + +服务记录分发功能是一个定时任务系统,用于自动将已完成的服务记录分发给教务人员和教练,确保相关人员能够及时了解服务情况。 + +## 功能特性 + +### 1. 自动分发 +- 定时检查最近24小时内完成的服务记录 +- 自动识别教务人员和教练 +- 根据人员角色发送相应的通知 + +### 2. 分发规则 +- **教务人员**:接收所有已完成的服务记录通知 +- **教练**:只接收自己负责的服务记录通知 + +### 3. 通知方式 +- 支持短信通知 +- 支持微信通知 +- 支持小程序通知 + +## 文件结构 + +``` +app/ +├── job/schedule/ +│ └── ServiceLogsDistribution.php # 定时任务类 +├── service/admin/service_logs/ +│ └── ServiceLogsDistributionService.php # 分发管理服务 +├── adminapi/controller/service_logs/ +│ └── ServiceLogsDistribution.php # 分发管理控制器 +├── dict/notice/ +│ └── service_logs_notice.php # 通知模板配置 +└── upgrade/v151/ + ├── upgrade.php # 升级脚本 + └── upgrade.sql # 数据库升级SQL +``` + +## 数据库变更 + +### 服务记录表 (service_logs) +新增字段: +- `is_distributed_to_academic` (tinyint): 是否已分发给教务 +- `is_distributed_to_coach` (tinyint): 是否已分发给教练 +- `distribution_time` (int): 分发时间 + +### 人员表 (personnel) +新增字段: +- `position` (varchar): 职位信息 + +## API接口 + +### 1. 手动执行分发任务 +``` +POST /adminapi/service_logs/distribution/execute +``` + +### 2. 获取分发统计信息 +``` +GET /adminapi/service_logs/distribution/stats +``` + +### 3. 获取待分发的服务记录列表 +``` +GET /adminapi/service_logs/distribution/pending +参数: +- distribution_status: 分发状态筛选 +- date_range: 日期范围筛选 +``` + +### 4. 重置分发状态 +``` +POST /adminapi/service_logs/distribution/reset +参数: +- ids: 服务记录ID数组 +- type: 重置类型 (academic|coach|both) +``` + +### 5. 获取教务和教练人员列表 +``` +GET /adminapi/service_logs/distribution/staff +``` + +## 使用方法 + +### 1. 安装升级 +执行数据库升级脚本: +```sql +-- 执行 app/upgrade/v151/upgrade.sql 中的SQL语句 +``` + +### 2. 配置定时任务 +在系统定时任务中添加: +```php +// 任务类 +app\job\schedule\ServiceLogsDistribution + +// 执行频率:每小时执行一次 +0 * * * * +``` + +### 3. 配置通知模板 +在后台管理系统中配置通知模板: +- 服务记录教务通知 (service_log_academic_notice) +- 服务记录教练通知 (service_log_coach_notice) + +### 4. 设置人员职位 +确保人员表中的职位字段正确设置: +- 教务人员:职位包含"教务" +- 教练:职位包含"教练" + +## 通知模板变量 + +### 教务通知模板变量 +- `{staff_name}`: 教务人员姓名 +- `{service_name}`: 服务名称 +- `{score}`: 评分 +- `{created_at}`: 创建时间 + +### 教练通知模板变量 +- `{staff_name}`: 教练姓名 +- `{service_name}`: 服务名称 +- `{score}`: 学员评分 +- `{feedback}`: 学员反馈 +- `{created_at}`: 创建时间 + +## 日志记录 + +系统会自动记录以下日志: +- 分发任务开始和完成时间 +- 分发成功的记录数量 +- 发送通知的成功和失败情况 +- 异常错误信息 + +## 注意事项 + +1. **数据库升级**:首次使用前必须执行数据库升级脚本 +2. **人员配置**:确保人员表中的职位信息正确设置 +3. **通知配置**:需要在后台配置相应的通知模板 +4. **定时任务**:建议设置为每小时执行一次 +5. **权限控制**:API接口需要相应的权限才能访问 + +## 故障排除 + +### 常见问题 + +1. **没有找到教务/教练人员** + - 检查人员表中的职位字段是否正确设置 + - 确认人员状态是否为正常状态 + +2. **通知发送失败** + - 检查通知模板配置 + - 确认短信/微信配置是否正确 + +3. **分发状态不更新** + - 检查数据库字段是否存在 + - 确认数据库权限是否正确 + +### 调试方法 + +1. 查看系统日志文件 +2. 使用手动执行接口测试 +3. 检查数据库中的分发状态字段 +4. 验证通知模板配置 \ No newline at end of file diff --git a/uniapp/pages/market/clue/clue_info.vue b/uniapp/pages/market/clue/clue_info.vue index 742118c2..7fc7885d 100644 --- a/uniapp/pages/market/clue/clue_info.vue +++ b/uniapp/pages/market/clue/clue_info.vue @@ -64,12 +64,12 @@ 通话记录 - - 体测记录 - - 学习计划 + 体测记录 + + 学习计划 + @@ -122,7 +122,7 @@ + @click="viewCourseDetail(course)"> {{ course.course_name || '未知课程' }} @@ -170,7 +170,7 @@ - 点击修改教练配置 + 点击修改教练配置 @@ -1119,6 +1119,26 @@ closeCourseEdit() { this.$refs.courseEditPopup.close(); }, + + // 查看课程详情 + viewCourseDetail(course) { + // 检查课程信息ID是否存在 + if (!course || !course.id) { + console.error('课程信息ID缺失,无法跳转到详情页面'); + uni.showToast({ + title: '课程信息不完整', + icon: 'none' + }); + return; + } + + console.log('跳转到课程详情页面,课程ID:', course.id); + + // 跳转到课程详情页面,带上课程ID和学生资源ID + this.$navigateTo({ + url: `/pages/market/course/course_detail?id=${course.id}&resource_id=${this.clientInfo.resource_id}&student_name=${encodeURIComponent(this.safeGet(this.clientInfo, 'customerResource.name', '未知学生'))}` + }); + }, // 安全访问对象属性的方法,优化性能 @@ -1236,7 +1256,10 @@ width: 100%; height: 40%; display: flex; - justify-content: space-between; + justify-content: space-around; + flex-wrap: nowrap; + overflow-x: auto; + padding: 0 10rpx; } .course_box_top_top { @@ -1296,12 +1319,18 @@ color: #1CD188; display: flex; align-items: center; + white-space: nowrap; + font-size: 28rpx; + padding: 0 6rpx; } .text { color: #333333; display: flex; align-items: center; + white-space: nowrap; + font-size: 28rpx; + padding: 0 6rpx; } .basic-message {