Files
zyt/server/app/api/controller/TcmController.php
T
2026-05-26 18:05:48 +08:00

669 lines
27 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | likeadmin快速开发前后端分离管理后台(PHP版)
// +----------------------------------------------------------------------
// | 欢迎阅读学习系统程序代码,建议反馈是我们前进的动力
// | 开源版本可自由商用,可去除界面版权logo
// | gitee下载:https://gitee.com/likeshop_gitee/likeadmin
// | github下载:https://github.com/likeshop-github/likeadmin
// | 访问官网:https://www.likeadmin.cn
// | likeadmin团队 版权所有 拥有最终解释权
// +----------------------------------------------------------------------
// | author: likeadminTeam
// +----------------------------------------------------------------------
namespace app\api\controller;
use app\adminapi\logic\tcm\DiagnosisLogic;
use app\adminapi\logic\tcm\TrackingNoteLogic;
use app\adminapi\logic\ConfigLogic;
use app\common\model\tcm\Diagnosis;
use app\common\model\tcm\BloodRecord;
/**
* 中医诊断控制器(供小程序调用)
* Class TcmController
* @package app\api\controller
*/
class TcmController extends BaseApiController
{
/**
* @notes 不需要登录的方法
* @var array
*/
public array $notNeedLogin = ['getPatientSignature', 'diagnosisDetail', 'getDict', 'confirmDiagnosis', 'getCardList', 'getOrderByNo', 'patientHangupVideo', 'dailyTrackingWindow', 'dailyTrackingNotes'];
/**
* @notes 获取患者签名(供小程序调用)
* @return \think\response\Json
*/
public function getPatientSignature()
{
$patientId = $this->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());
}
}
}