request->get('patient_id'); if (!$patientId) { return $this->fail('患者ID不能为空'); } // 调用逻辑层 $result = DiagnosisLogic::getPatientSignature($patientId); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } /** * @notes 患者挂断视频:后端 DeleteCloudRecording + 结束通话记录(与诊单 patient_id 校验) * @return \think\response\Json */ public function patientHangupVideo() { $params = [ 'diagnosis_id' => (int)$this->request->post('diagnosis_id', 0), 'patient_id' => (int)$this->request->post('patient_id', 0), 'room_id' => trim((string)$this->request->post('room_id', '')), ]; if ($params['patient_id'] <= 0) { return $this->fail('患者ID不能为空'); } if ($params['diagnosis_id'] <= 0 && $params['room_id'] === '') { return $this->fail('诊单ID或房间号须至少传一项'); } $result = DiagnosisLogic::patientHangupVideoCall($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('已结束通话'); } /** * @notes 获取诊单详情(供小程序调用) * @return \think\response\Json */ public function diagnosisDetail() { $params = [ 'id' => $this->request->get('id'), 'user_id' => $this->userId, 'share_user_id' => $this->request->get('share_user_id', 0) ]; if (!$params['id']) { return $this->fail('诊单ID不能为空'); } try { $result = DiagnosisLogic::diagnosisDetailWithRecord($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 确认诊单(供小程序调用) * @return \think\response\Json */ public function confirmDiagnosis() { $params = [ 'id' => $this->request->post('id'), 'user_id' => $this->userId, 'share_user_id' => $this->request->post('share_user', 0) ]; if (!$params['id']) { return $this->fail('诊单ID不能为空'); } try { $result = DiagnosisLogic::confirmDiagnosisRecord($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('确认成功'); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 获取字典数据(供小程序调用) * @return \think\response\Json */ public function getDict() { $type = $this->request->get('type', ''); if (!$type) { return $this->fail('字典类型不能为空'); } try { $data = ConfigLogic::getDictByType($type); return $this->data($data); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 获取就诊卡列表(供小程序调用) * @return \think\response\Json */ public function getCardList() { $user_id =$this->userId; if (!$user_id) { return $this->fail('患者ID不能为空'); } try { $result = DiagnosisLogic::getCardList($user_id); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->data($result); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 新建就诊卡(供小程序调用) * @return \think\response\Json */ public function addCard() { $params = [ 'user_id' => $this->userId, 'patient_id' => $this->request->post('patient_id'), 'patient_name' => $this->request->post('patient_name'), 'gender' => $this->request->post('gender'), 'age' => $this->request->post('age'), 'id_card' => $this->request->post('id_card'), 'height' => $this->request->post('height'), 'weight' => $this->request->post('weight'), 'systolic_pressure' => $this->request->post('systolic_pressure'), 'diastolic_pressure' => $this->request->post('diastolic_pressure'), 'fasting_blood_sugar' => $this->request->post('fasting_blood_sugar'), 'diabetes_discovery_year' => $this->request->post('diabetes_discovery_year'), 'local_hospital_diagnosis' => $this->request->post('local_hospital_diagnosis'), 'local_hospital_name' => $this->request->post('local_hospital_name'), 'local_hospital_visit_date' => $this->request->post('local_hospital_visit_date'), 'diagnosis_date' => $this->request->post('diagnosis_date'), 'diagnosis_type' => $this->request->post('diagnosis_type'), 'syndrome_type' => $this->request->post('syndrome_type'), 'diabetes_type' => $this->request->post('diabetes_type'), 'appetite' => $this->request->post('appetite'), 'water_intake' => $this->request->post('water_intake'), 'diet_condition' => $this->request->post('diet_condition'), 'weight_change' => $this->request->post('weight_change'), 'body_feeling' => $this->request->post('body_feeling'), 'sleep_condition' => $this->request->post('sleep_condition'), 'eye_condition' => $this->request->post('eye_condition'), 'head_feeling' => $this->request->post('head_feeling'), 'sweat_condition' => $this->request->post('sweat_condition'), 'skin_condition' => $this->request->post('skin_condition'), 'urine_condition' => $this->request->post('urine_condition'), 'stool_condition' => $this->request->post('stool_condition'), 'kidney_condition' => $this->request->post('kidney_condition'), 'fatty_liver_degree' => $this->request->post('fatty_liver_degree'), 'past_history' => $this->request->post('past_history'), 'trauma_history' => $this->request->post('trauma_history'), 'surgery_history' => $this->request->post('surgery_history'), 'allergy_history' => $this->request->post('allergy_history'), 'family_history' => $this->request->post('family_history'), 'pregnancy_history' => $this->request->post('pregnancy_history'), 'symptoms' => $this->request->post('symptoms'), 'tongue_coating' => $this->request->post('tongue_coating'), 'tongue_images' => $this->request->post('tongue_images'), 'report_files' => $this->request->post('report_files'), 'pulse' => $this->request->post('pulse'), 'treatment_principle' => $this->request->post('treatment_principle'), 'prescription' => $this->request->post('prescription'), 'doctor_advice' => $this->request->post('doctor_advice'), 'remark' => $this->request->post('remark') ]; // patient_id 可选:不传则创建时自动生成(新患者首张就诊卡) try { $result = DiagnosisLogic::addCard($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('创建成功', $result); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 更新就诊卡(供小程序调用) * @return \think\response\Json */ public function updateCard() { $params = [ 'id' => $this->request->post('id'), 'patient_id' => $this->request->post('patient_id'), 'patient_name' => $this->request->post('patient_name'), 'gender' => $this->request->post('gender'), 'age' => $this->request->post('age'), 'id_card' => $this->request->post('id_card'), // 生命体征 'height' => $this->request->post('height'), 'weight' => $this->request->post('weight'), 'systolic_pressure' => $this->request->post('systolic_pressure'), 'diastolic_pressure' => $this->request->post('diastolic_pressure'), 'fasting_blood_sugar' => $this->request->post('fasting_blood_sugar'), // 主诉 'diabetes_discovery_year' => $this->request->post('diabetes_discovery_year'), 'local_hospital_diagnosis' => $this->request->post('local_hospital_diagnosis'), 'local_hospital_name' => $this->request->post('local_hospital_name'), 'local_hospital_visit_date' => $this->request->post('local_hospital_visit_date'), // 诊断信息 'diagnosis_date' => $this->request->post('diagnosis_date'), 'diagnosis_type' => $this->request->post('diagnosis_type'), 'syndrome_type' => $this->request->post('syndrome_type'), 'diabetes_type' => $this->request->post('diabetes_type'), // 现病史字段 'appetite' => $this->request->post('appetite'), 'water_intake' => $this->request->post('water_intake'), 'diet_condition' => $this->request->post('diet_condition'), 'weight_change' => $this->request->post('weight_change'), 'body_feeling' => $this->request->post('body_feeling'), 'sleep_condition' => $this->request->post('sleep_condition'), 'eye_condition' => $this->request->post('eye_condition'), 'head_feeling' => $this->request->post('head_feeling'), 'sweat_condition' => $this->request->post('sweat_condition'), 'skin_condition' => $this->request->post('skin_condition'), 'urine_condition' => $this->request->post('urine_condition'), 'stool_condition' => $this->request->post('stool_condition'), 'kidney_condition' => $this->request->post('kidney_condition'), 'fatty_liver_degree' => $this->request->post('fatty_liver_degree'), // 既往史字段 'past_history' => $this->request->post('past_history'), 'trauma_history' => $this->request->post('trauma_history'), 'surgery_history' => $this->request->post('surgery_history'), 'allergy_history' => $this->request->post('allergy_history'), 'family_history' => $this->request->post('family_history'), 'pregnancy_history' => $this->request->post('pregnancy_history'), // 临床信息 'symptoms' => $this->request->post('symptoms'), 'tongue_coating' => $this->request->post('tongue_coating'), 'tongue_images' => $this->request->post('tongue_images'), 'report_files' => $this->request->post('report_files'), 'pulse' => $this->request->post('pulse'), 'treatment_principle' => $this->request->post('treatment_principle'), 'prescription' => $this->request->post('prescription'), 'doctor_advice' => $this->request->post('doctor_advice'), 'remark' => $this->request->post('remark') ]; if (!$params['id']) { return $this->fail('诊单ID不能为空'); } if (!$params['patient_id']) { return $this->fail('患者ID不能为空'); } try { $result = DiagnosisLogic::updateCard($params); if ($result === false) { return $this->fail(DiagnosisLogic::getError()); } return $this->success('更新成功'); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 日常记录窗口数据(血糖血压 / 饮食 / 运动)—— 患者端 * * 路由:GET /api/tcm/dailyTrackingWindow?diagnosis_id=:id&patient_id=:pid&start_date=&end_date= * 说明:供小程序「日常记录」页面拉取一段时间内的跟踪数据,复用后台 fetchTrackingWindow。 * * @return \think\response\Json */ public function dailyTrackingWindow() { $diagnosisId = (int) $this->request->get('diagnosis_id', 0); $patientId = (int) $this->request->get('patient_id', 0); $startDate = (string) $this->request->get('start_date', ''); $endDate = (string) $this->request->get('end_date', ''); if ($diagnosisId <= 0) { return $this->fail('诊单ID不能为空'); } try { // 患者端越权校验:诊单必须存在;如传入 patient_id 则须与诊单匹配 $diagnosis = Diagnosis::where('id', $diagnosisId)->find(); if (!$diagnosis) { return $this->fail('诊单不存在'); } if ($patientId > 0 && (int) $diagnosis['patient_id'] !== $patientId) { return $this->fail('无权查看该诊单的日常记录'); } $data = DiagnosisLogic::fetchTrackingWindow($diagnosisId, $startDate, $endDate); // 把诊断的基础信息一并返回,便于小程序展示头部摘要 $data['diagnosis'] = [ 'id' => (int) $diagnosis['id'], 'patient_id' => (int) $diagnosis['patient_id'], 'patient_name' => (string) ($diagnosis['patient_name'] ?? ''), 'age' => (int) ($diagnosis['age'] ?? 0), 'gender' => (int) ($diagnosis['gender'] ?? 0), ]; return $this->data($data); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 日常记录-跟踪备注列表(患者端) * * 路由:GET /api/tcm/dailyTrackingNotes?diagnosis_id=:id&patient_id=:pid * * @return \think\response\Json */ public function dailyTrackingNotes() { $diagnosisId = (int) $this->request->get('diagnosis_id', 0); $patientId = (int) $this->request->get('patient_id', 0); if ($diagnosisId <= 0) { return $this->fail('诊单ID不能为空'); } try { $diagnosis = Diagnosis::where('id', $diagnosisId)->find(); if (!$diagnosis) { return $this->fail('诊单不存在'); } if ($patientId > 0 && (int) $diagnosis['patient_id'] !== $patientId) { return $this->fail('无权查看该诊单的跟踪备注'); } $notes = TrackingNoteLogic::getByDiagnosis($diagnosisId); return $this->data($notes); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 校验当前登录患者是否拥有指定诊单(通过 diagnosis_view_records 关联) * @param int $diagnosisId * @return array{ok:bool,diagnosis?:\app\common\model\tcm\Diagnosis,error?:string} */ private function ensurePatientOwnsDiagnosis(int $diagnosisId): array { if (!$this->userId) { return ['ok' => false, 'error' => '请先登录']; } if ($diagnosisId <= 0) { return ['ok' => false, 'error' => '诊单ID不能为空']; } $diagnosis = Diagnosis::where('id', $diagnosisId)->find(); if (!$diagnosis) { return ['ok' => false, 'error' => '诊单不存在']; } // 通过 diagnosis_view_records 校验当前小程序用户与该诊单的归属关系 // 该表是「就诊卡列表」的来源(参考 DiagnosisLogic::getCardList) $owned = \think\facade\Db::name('diagnosis_view_records') ->where('user_id', $this->userId) ->where('diagnosis_id', $diagnosisId) ->where('delete_time', null) ->find(); if (!$owned) { return ['ok' => false, 'error' => '无权操作该诊单']; } return ['ok' => true, 'diagnosis' => $diagnosis]; } /** * @notes 日常记录-获取今日已录入的血糖血压记录(患者端) * * 路由:GET /api/tcm/dailyTodayBloodRecord?diagnosis_id=:id * * 用于「日常记录」页打开「录入今日」表单时预填数据;只返回 source=1 的当天记录。 * * @return \think\response\Json */ public function dailyTodayBloodRecord() { $diagnosisId = (int) $this->request->get('diagnosis_id', 0); $check = $this->ensurePatientOwnsDiagnosis($diagnosisId); if (!$check['ok']) { return $this->fail($check['error']); } try { $todayStart = strtotime(date('Y-m-d 00:00:00')); $todayEnd = strtotime(date('Y-m-d 23:59:59')); $record = BloodRecord::where('diagnosis_id', $diagnosisId) ->where('source', 1) ->where('record_date', '>=', $todayStart) ->where('record_date', '<=', $todayEnd) ->order('id', 'desc') ->find(); if (!$record) { return $this->data(['record' => null, 'today' => date('Y-m-d')]); } $data = $record->toArray(); $data['record_date'] = date('Y-m-d', (int) $data['record_date']); return $this->data(['record' => $data, 'today' => date('Y-m-d')]); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 日常记录-保存今日的血糖血压记录(患者端) * * 路由:POST /api/tcm/dailySaveBloodRecord * 必填:diagnosis_id * 可选数值:fasting_blood_sugar, postprandial_blood_sugar, other_blood_sugar, * systolic_pressure, diastolic_pressure * 可选字符串:remark * * 语义:同一天同一诊单只有一条 source=1 的记录;存在则 update,不存在则 create。 * 日期强制为「今天」;任何 record_date 入参被忽略,避免补录历史。 * * @return \think\response\Json */ public function dailySaveBloodRecord() { $diagnosisId = (int) $this->request->post('diagnosis_id', 0); $check = $this->ensurePatientOwnsDiagnosis($diagnosisId); if (!$check['ok']) { return $this->fail($check['error']); } $diagnosis = $check['diagnosis']; // 解析并校验数值字段;空字符串当 null 处理 $parseDecimal = function ($v) { if ($v === null || $v === '' || $v === '0' || $v === 0) return null; if (!is_numeric($v)) return null; $f = (float) $v; return $f > 0 ? $f : null; }; $parseInt = function ($v) { if ($v === null || $v === '') return null; if (!is_numeric($v)) return null; $i = (int) $v; return $i > 0 ? $i : null; }; $fasting = $parseDecimal($this->request->post('fasting_blood_sugar')); $postprandial = $parseDecimal($this->request->post('postprandial_blood_sugar')); $other = $parseDecimal($this->request->post('other_blood_sugar')); $systolic = $parseInt($this->request->post('systolic_pressure')); $diastolic = $parseInt($this->request->post('diastolic_pressure')); $remark = trim((string) $this->request->post('remark', '')); // 至少要填一个有效字段 if ($fasting === null && $postprandial === null && $other === null && $systolic === null && $diastolic === null && $remark === '') { return $this->fail('请至少填写一项血糖或血压数据'); } // 边界校验,避免误录天文数字 if ($fasting !== null && ($fasting < 0.1 || $fasting > 50)) return $this->fail('空腹血糖数值异常'); if ($postprandial !== null && ($postprandial < 0.1 || $postprandial > 50)) return $this->fail('餐后血糖数值异常'); if ($other !== null && ($other < 0.1 || $other > 50)) return $this->fail('其他血糖数值异常'); if ($systolic !== null && ($systolic < 40 || $systolic > 300)) return $this->fail('收缩压数值异常'); if ($diastolic !== null && ($diastolic < 20 || $diastolic > 200)) return $this->fail('舒张压数值异常'); try { $todayStart = strtotime(date('Y-m-d 00:00:00')); $todayEnd = strtotime(date('Y-m-d 23:59:59')); $existing = BloodRecord::where('diagnosis_id', $diagnosisId) ->where('source', 1) ->where('record_date', '>=', $todayStart) ->where('record_date', '<=', $todayEnd) ->order('id', 'desc') ->find(); $payload = [ 'diagnosis_id' => $diagnosisId, 'patient_id' => (int) $diagnosis['patient_id'], 'record_date' => $todayStart, 'record_time' => date('H:i'), 'fasting_blood_sugar' => $fasting, 'postprandial_blood_sugar' => $postprandial, 'other_blood_sugar' => $other, 'systolic_pressure' => $systolic, 'diastolic_pressure' => $diastolic, 'remark' => $remark, 'source' => 1, ]; if ($existing) { $payload['id'] = $existing['id']; BloodRecord::update($payload); return $this->success('已更新今日记录', ['id' => $existing['id']]); } $created = BloodRecord::create($payload); return $this->success('录入成功', ['id' => $created->id]); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 日常记录-删除今日的血糖血压记录(患者端) * * 路由:POST /api/tcm/dailyDeleteBloodRecord * 必填:diagnosis_id, id * * 只允许删除当前用户拥有的诊单 + 当天 + source=1 的记录;其他情况一律拒绝。 * * @return \think\response\Json */ public function dailyDeleteBloodRecord() { $diagnosisId = (int) $this->request->post('diagnosis_id', 0); $recordId = (int) $this->request->post('id', 0); $check = $this->ensurePatientOwnsDiagnosis($diagnosisId); if (!$check['ok']) { return $this->fail($check['error']); } if ($recordId <= 0) { return $this->fail('记录ID不能为空'); } try { $todayStart = strtotime(date('Y-m-d 00:00:00')); $todayEnd = strtotime(date('Y-m-d 23:59:59')); $record = BloodRecord::where('id', $recordId) ->where('diagnosis_id', $diagnosisId) ->where('source', 1) ->find(); if (!$record) { return $this->fail('记录不存在或无权删除'); } $ts = (int) $record['record_date']; if ($ts < $todayStart || $ts > $todayEnd) { return $this->fail('只能删除当天的记录'); } BloodRecord::destroy($recordId); return $this->success('已删除'); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } /** * @notes 根据订单号获取订单详情(供小程序支付页调用) * @return \think\response\Json */ public function getOrderByNo() { $orderNo = $this->request->get('order_no', ''); if (empty($orderNo)) { return $this->fail('订单号不能为空'); } try { $order = \app\common\model\Order::where('order_no', $orderNo)->find(); if (!$order) { return $this->fail('订单不存在'); } $typeMap = [1 => '挂号费', 2 => '问诊费', 3 => '药品费用']; $statusMap = [1 => '待支付', 2 => '已支付', 3 => '已取消', 4 => '已退款']; return $this->data([ 'id' => $order['id'], 'order_no' => $order['order_no'], 'order_type' => $order['order_type'], 'order_type_desc' => $typeMap[$order['order_type']] ?? '未知', 'amount' => $order['amount'], 'status' => $order['status'], 'status_desc' => $statusMap[$order['status']] ?? '未知', 'remark' => $order['remark'] ?? '', 'create_time' => $order['create_time'], ]); } catch (\Exception $e) { return $this->fail($e->getMessage()); } } }