success('中医诊单控制器可以访问', [ 'controller' => 'TcmDiagnosisController', 'namespace' => __NAMESPACE__, 'class' => __CLASS__, 'method' => __METHOD__ ]); } /** * @notes 诊单列表 * @return \think\response\Json */ public function lists() { return $this->dataLists(new DiagnosisLists()); } /** * @notes 医助理诊单统计(按部门、按人) * @return \think\response\Json */ public function assistantDiagnosisStats() { $params = $this->request->get(); $result = DiagnosisLogic::assistantDiagnosisStats($params, (int) $this->adminId, $this->adminInfo); return $this->data($result); } /** * @notes 添加诊单 * @return \think\response\Json */ public function add() { $params = (new DiagnosisValidate())->post()->goCheck('add'); // 诊单创建人(需表 zyt_tcm_diagnosis 已增加 admin_id 列,见 sql 脚本) $params['admin_id'] = (int) $this->adminId; $result = DiagnosisLogic::add($params); if ($result) { return $this->success('添加成功', ['id' => $result], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 编辑诊单 * @return \think\response\Json */ public function edit() { $params = (new DiagnosisValidate())->post()->goCheck('edit'); $result = DiagnosisLogic::edit($params, $this->adminInfo); if ($result) { return $this->success('编辑成功', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 删除诊单 * @return \think\response\Json */ public function delete() { $params = (new DiagnosisValidate())->post()->goCheck('id'); $result = DiagnosisLogic::delete($params); if ($result) { return $this->success('删除成功', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 诊单详情 * @return \think\response\Json */ public function detail() { $params = (new DiagnosisValidate())->goCheck('id'); $result = DiagnosisLogic::detail($params, $this->adminInfo); DiagnosisLogic::markAssignRead((int) ($params['id'] ?? 0), $this->adminId); return $this->data($result); } /** * @notes 二诊只读病例详情(患者信息 + 病例 + 跟踪记录) * * 路由:GET /tcm.diagnosis/readonlyDetail?id=:diagnosisId * 权限:tcm.diagnosis/readonlyDetail * * @return \think\response\Json */ public function readonlyDetail() { $params = (new DiagnosisValidate())->goCheck('readonlyDetail'); $result = DiagnosisLogic::readonlyDetail($params, $this->adminId, $this->adminInfo); if (empty($result)) { return $this->fail(DiagnosisLogic::getError() ?: '诊单不存在或无权访问'); } DiagnosisLogic::markAssignRead((int) ($params['id'] ?? 0), $this->adminId); return $this->data($result); } /** * @notes 跟踪信息(血糖血压 / 饮食 / 运动)—— 按日期区间 lazy load * * 路由:GET /tcm.diagnosis/trackingWindow?id=:diagnosisId&start_date=&end_date= * 权限:tcm.diagnosis/readonlyDetail(与只读病例页同档;接诊台亦复用) * * @return \think\response\Json */ public function trackingWindow() { $params = (new DiagnosisValidate())->goCheck('trackingWindow'); $result = DiagnosisLogic::fetchTrackingWindow( (int) $params['id'], (string) ($params['start_date'] ?? ''), (string) ($params['end_date'] ?? '') ); return $this->data($result); } /** * @notes 新增跟踪备注(按天合并追加,仅文字) * * 路由:POST /tcm.diagnosis/addTrackingNote * 权限:tcm.diagnosis/addTrackingNote * * @return \think\response\Json */ public function addTrackingNote() { $params = (new DiagnosisValidate())->post()->goCheck('addTrackingNote'); $ok = TrackingNoteLogic::addOrAppend([ 'diagnosis_id' => (int) $params['diagnosis_id'], 'admin_id' => (int) $this->adminId, 'content' => (string) $params['tracking_content'], ]); if ($ok === false) { return $this->fail(TrackingNoteLogic::getError() ?: '保存失败'); } return $this->success('保存成功'); } /** * @notes 拉取跟踪备注列表(note_date DESC) * * 路由:GET /tcm.diagnosis/trackingNotes?diagnosis_id=:diagnosisId * 权限:tcm.diagnosis/trackingNotes * * @return \think\response\Json */ public function trackingNotes() { $params = (new DiagnosisValidate())->goCheck('trackingNotes'); return $this->data(TrackingNoteLogic::getByDiagnosis((int) $params['diagnosis_id'])); } /** * @notes 诊单挂号 / 取消挂号 操作日志(谁在何时操作) */ public function guahaoLogList() { $params = (new DiagnosisValidate())->get()->goCheck('guahaoLogList'); return $this->data(DiagnosisLogic::guahaoLogList((int) $params['id'])); } /** * @notes 诊单详情(患者端) * @return \think\response\Json */ public function diagnosisDetail() { $params = $this->request->get(); if (empty($params['id'])) { return $this->fail('诊单ID不能为空'); } if (empty($params['user_id'])) { return $this->fail('用户ID不能为空'); } $result = DiagnosisLogic::diagnosisDetail($params); if ($result) { return $this->data($result); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 检查手机号是否重复 * @return \think\response\Json */ public function checkPhone() { $params = $this->request->post(); $result = DiagnosisLogic::checkPhone($params); return $this->data($result); } /** * @notes 检查身份证号是否重复 * @return \think\response\Json */ public function checkIdCard() { $params = $this->request->post(); $result = DiagnosisLogic::checkIdCard($params); return $this->data($result); } /** * @notes 补全身份证号(自动计算年龄并更新) * @return \think\response\Json */ public function fillIdCard() { $params = (new DiagnosisValidate())->post()->goCheck('fillIdCard'); $result = DiagnosisLogic::fillIdCard($params); if ($result) { return $this->success('补全成功,年龄已自动更新', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 指派医助 * @return \think\response\Json */ public function assign() { $params = $this->request->post(); // 验证参数 if (empty($params['id'])) { return $this->fail('诊单ID不能为空'); } if (!array_key_exists('assistant_id', $params)) { return $this->fail('请选择医助或取消指派'); } $result = DiagnosisLogic::assign($params); if ($result) { $msg = (int) ($params['assistant_id'] ?? -1) === 0 ? '已取消指派' : '指派成功'; return $this->success($msg, [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 指派医助操作记录 * @return \think\response\Json */ public function assignLogList() { $params = (new DiagnosisValidate())->goCheck('id'); $result = DiagnosisLogic::assignLogList((int) $params['id']); return $this->data($result); } /** * @notes 获取通话签名 * @return \think\response\Json */ public function getCallSignature() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } if (empty($params['patient_id'])) { return $this->fail('患者ID不能为空'); } // 传递当前管理员ID $params['admin_id'] = $this->adminId; $result = DiagnosisLogic::getCallSignature($params); if ($result) { return $this->data($result); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 发起通话 * @return \think\response\Json */ public function startCall() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } // 传递当前管理员ID $params['admin_id'] = $this->adminId; $result = DiagnosisLogic::startCall($params); if ($result) { return $this->success('发起通话成功', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 结束通话 * @return \think\response\Json */ public function endCall() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } // 传递当前管理员ID $params['admin_id'] = $this->adminId; $result = DiagnosisLogic::endCall($params); if ($result) { return $this->success('', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 获取通话记录 * @return \think\response\Json */ public function getCallRecords() { $params = $this->request->get(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } $result = DiagnosisLogic::getCallRecords($params); return $this->data($result); } /** * @notes 获取诊单关联的腾讯云 IM 单聊记录(admin_getroammsg) */ public function getImChatMessages() { // 增加执行时间限制到 120 秒 set_time_limit(120); $diagnosisId = (int)$this->request->get('diagnosis_id', 0); if ($diagnosisId <= 0) { return $this->fail('诊单ID不能为空'); } $onlyArchived = (int)$this->request->get('only_archived', 0) === 1; $result = DiagnosisLogic::getImChatMessagesForDiagnosis($diagnosisId, $onlyArchived); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 触发后台异步同步当前诊单的腾讯云 IM 聊天记录到本地归档表 * 请求即返回,真正的同步逻辑在 fastcgi_finish_request 之后执行 */ public function triggerImChatSync() { $diagnosisId = (int)$this->request->post('diagnosis_id', 0); if ($diagnosisId <= 0) { $diagnosisId = (int)$this->request->get('diagnosis_id', 0); } if ($diagnosisId <= 0) { return $this->fail('诊单ID不能为空'); } register_shutdown_function(function () use ($diagnosisId) { try { @set_time_limit(300); ignore_user_abort(true); DiagnosisLogic::syncImChatArchiveForDiagnosis($diagnosisId); } catch (\Throwable $e) { \think\facade\Log::warning('triggerImChatSync failed', [ 'diagnosis_id' => $diagnosisId, 'err' => $e->getMessage(), ]); } }); return $this->success('已发起后台同步,几秒后请重新加载查看', ['queued' => true]); } /** * @notes 绑定 TRTC 房间号到当前诊单通话记录(便于云端录制回调关联) * @return \think\response\Json */ public function bindCallRoom() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } if (empty($params['room_id'])) { return $this->fail('房间号不能为空'); } $params['admin_id'] = (int)$this->adminId; $result = DiagnosisLogic::bindCallRoom($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('', is_array($result) ? $result : [], 1, 0); } /** * @notes 接通后发起腾讯云云端混流录制(需配置 CAM 与云点播) * @return \think\response\Json */ public function startCloudRecording() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } $params['admin_id'] = (int)$this->adminId; $result = DiagnosisLogic::startCloudRecording($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 浏览器本地上传通话录制后,关联到通话记录(合并 recording_urls) * @return \think\response\Json */ public function attachLocalCallRecording() { $params = $this->request->post(); if (empty($params['diagnosis_id'])) { return $this->fail('诊单ID不能为空'); } if (empty($params['file_url'])) { return $this->fail('文件地址不能为空'); } $params['admin_id'] = (int)$this->adminId; $result = DiagnosisLogic::attachLocalCallRecording($params); if ($result) { return $this->success('已关联录制', [], 1, 1); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 手动上传视频前,先创建一条模拟通话记录 * @return \think\response\Json */ public function createManualCallRecord() { $params = (new DiagnosisValidate())->post()->goCheck('trackingNotes'); $result = DiagnosisLogic::createManualCallRecord([ 'diagnosis_id' => (int)$params['diagnosis_id'], 'admin_id' => (int)$this->adminId, ]); if (!$result) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('创建成功', $result); } /** * @notes 医助旁观诊单当前视频通话(TRTC 进房参数,仅拉流) * @return \think\response\Json */ public function watchCall() { $diagnosisId = (int)$this->request->get('diagnosis_id', 0); if ($diagnosisId <= 0) { return $this->fail('诊单ID不能为空'); } $result = DiagnosisLogic::getAssistantWatchRoomParams([ 'diagnosis_id' => $diagnosisId, 'admin_id' => (int)$this->adminId, ]); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 获取患者通话签名(用于测试) * @return \think\response\Json */ public function getDoctorSignature() { $params = $this->request->get(); if (empty($params['patient_id'])) { return $this->fail('医助理ID不能为空'); } $result = DiagnosisLogic::getDoctorSignature((int)$params['patient_id']); if ($result) { return $this->data($result); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 获取患者通话签名(用于测试) * @return \think\response\Json */ public function getPatientSignature() { $params = $this->request->get(); if (empty($params['patient_id'])) { return $this->fail('患者ID不能为空'); } $result = DiagnosisLogic::getPatientSignature((int)$params['patient_id']); if ($result) { return $this->data($result); } return $this->fail(DiagnosisLogic::getError()); } /** * @notes 获取医助列表 * @return \think\response\Json */ public function getAssistants() { $result = DiagnosisLogic::getAssistants((int) $this->adminId, $this->adminInfo); return $this->data($result); } /** * @notes 获取医生列表 * @return \think\response\Json */ public function getDoctors() { $result = DiagnosisLogic::getDoctors(); return $this->data($result); } /** * @notes 生成小程序码 * @return \think\response\Json */ public function generateMiniProgramQrcode() { $params = (new DiagnosisValidate())->post()->goCheck('generateQrcode'); $result = DiagnosisLogic::generateMiniProgramQrcode($params); return $this->data($result); } /** * @notes 生成订单小程序码 * @return \think\response\Json */ public function generateOrderQrcode() { $params = $this->request->post(); $params['share_user_id'] = $this->adminId; $result = DiagnosisLogic::generateOrderQrcode($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } $orderNo = (string)($params['order_no'] ?? ''); if ($orderNo !== '') { $po = Order::where('order_no', $orderNo)->find(); if ($po) { OrderActionLogLogic::record( (int)$po->id, (int)$this->adminId, $this->adminInfo, 'wx_qrcode', '生成订单小程序码' ); } } return $this->data($result); } /** * @notes 获取企业微信聊天记录 */ public function getWechatChatRecords() { $diagnosisId = $this->request->get('diagnosis_id', 0); $patientId = $this->request->get('patient_id', 0); $pageNo = $this->request->get('page_no', 1); $pageSize = $this->request->get('page_size', 20); if (empty($diagnosisId) && empty($patientId)) { return $this->fail('诊单ID或患者ID不能都为空'); } $query = WechatChatRecord::order('chat_time', 'desc'); if ($diagnosisId) { $query->where('diagnosis_id', $diagnosisId); } if ($patientId) { $query->where('patient_id', $patientId); } $count = (clone $query)->count(); $lists = $query->page($pageNo, $pageSize)->select()->toArray(); return $this->data([ 'lists' => $lists, 'count' => $count, 'page_no' => $pageNo, 'page_size' => $pageSize ]); } /** * @notes 添加企业微信聊天记录(手动录入/同步写入) */ public function addWechatChatRecord() { $params = $this->request->post(); if (empty($params['diagnosis_id']) && empty($params['patient_id'])) { return $this->fail('诊单ID或患者ID不能都为空'); } if (empty($params['content'])) { return $this->fail('内容不能为空'); } $adminInfo = $this->adminInfo; $data = [ 'diagnosis_id' => $params['diagnosis_id'] ?? 0, 'patient_id' => $params['patient_id'] ?? 0, 'staff_userid' => $adminInfo['work_wechat_userid'] ?? ($params['staff_userid'] ?? ''), 'staff_name' => $params['staff_name'] ?? ($adminInfo['name'] ?? ''), 'external_userid' => $params['external_userid'] ?? '', 'external_name' => $params['external_name'] ?? '', 'msg_type' => $params['msg_type'] ?? 'note', 'content' => $params['content'], 'media_url' => $params['media_url'] ?? '', 'chat_time' => $params['chat_time'] ?? time(), 'direction' => $params['direction'] ?? 0, 'create_time' => time(), ]; $record = WechatChatRecord::create($data); return $this->success('添加成功', ['id' => $record->id]); } /** * @notes 删除企业微信聊天记录 */ public function deleteWechatChatRecord() { $id = $this->request->post('id', 0); if (empty($id)) { return $this->fail('记录ID不能为空'); } WechatChatRecord::destroy($id); return $this->success('删除成功'); } /** * @notes 同步企业微信外部联系人信息(查找该患者在企业微信中的跟进人) */ public function getWechatExternalContact() { $patientId = $this->request->get('patient_id', 0); if (empty($patientId)) { return $this->fail('患者ID不能为空'); } $result = DiagnosisLogic::getWechatExternalContact($patientId); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 获取会话内容存档开启的成员列表 */ public function getMsgAuditPermitUsers() { $result = DiagnosisLogic::getMsgAuditPermitUsers(); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 搜索患者(用于创建订单等场景) * @return \think\response\Json */ public function searchPatient() { $keyword = $this->request->get('keyword', ''); $page_no = $this->request->get('page_no', 1); $page_size = $this->request->get('page_size', 10); if (empty($keyword)) { return $this->success('', ['lists' => [], 'count' => 0]); } $offset = ($page_no - 1) * $page_size; $lists = \app\common\model\tcm\Diagnosis::where('patient_name|phone|id_card', 'like', '%' . $keyword . '%') ->field(['id', 'patient_name', 'phone', 'id_card', 'gender', 'age']) ->limit($offset, $page_size) ->order('id desc') ->select() ->toArray(); $count = \app\common\model\tcm\Diagnosis::where('patient_name|phone|id_card', 'like', '%' . $keyword . '%') ->count(); return $this->success('', [ 'lists' => $lists, 'count' => $count, 'page_no' => $page_no, 'page_size' => $page_size ]); } }