This commit is contained in:
Your Name
2026-03-23 18:02:16 +08:00
parent 18fee2e8f8
commit 250d173c2f
525 changed files with 10659 additions and 793 deletions
@@ -36,7 +36,7 @@ class UploadController extends BaseAdminController
{
try {
$cid = $this->request->post('cid', 0);
$result = UploadService::image($cid);
$result = UploadService::image($cid, $this->adminId);
return $this->success('上传成功', $result);
} catch (Exception $e) {
return $this->fail($e->getMessage());
@@ -53,7 +53,7 @@ class UploadController extends BaseAdminController
{
try {
$cid = $this->request->post('cid', 0);
$result = UploadService::video($cid);
$result = UploadService::video($cid, $this->adminId);
return $this->success('上传成功', $result);
} catch (Exception $e) {
return $this->fail($e->getMessage());
@@ -70,7 +70,7 @@ class UploadController extends BaseAdminController
{
try {
$cid = $this->request->post('cid', 0);
$result = UploadService::file($cid);
$result = UploadService::file($cid, $this->adminId);
return $this->success('上传成功', $result);
} catch (Exception $e) {
return $this->fail($e->getMessage());
@@ -283,6 +283,44 @@ class DiagnosisController extends BaseAdminController
$result = DiagnosisLogic::getCallRecords($params);
return $this->data($result);
}
/**
* @notes 获取诊单关联的腾讯云 IM 单聊记录(admin_getroammsg
*/
public function getImChatMessages()
{
$diagnosisId = (int)$this->request->get('diagnosis_id', 0);
if ($diagnosisId <= 0) {
return $this->fail('诊单ID不能为空');
}
$result = DiagnosisLogic::getImChatMessagesForDiagnosis($diagnosisId);
if ($result === false) {
return $this->fail(DiagnosisLogic::getError());
}
return $this->data($result);
}
/**
* @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('房间号不能为空');
}
$result = DiagnosisLogic::bindCallRoom($params);
if ($result) {
return $this->success('绑定成功', [], 1, 1);
}
return $this->fail(DiagnosisLogic::getError());
}
/**
* @notes 获取患者通话签名(用于测试)
* @return \think\response\Json
@@ -13,6 +13,14 @@ use app\adminapi\validate\tcm\PrescriptionValidate;
*/
class PrescriptionController extends BaseAdminController
{
/**
* @notes 处方列表
*/
public function lists()
{
return $this->dataLists(new \app\adminapi\lists\tcm\PrescriptionLists());
}
/**
* @notes 添加处方
*/
@@ -27,6 +35,32 @@ class PrescriptionController extends BaseAdminController
return $this->success('保存成功', ['id' => $id]);
}
/**
* @notes 编辑处方
*/
public function edit()
{
$params = (new PrescriptionValidate())->post()->goCheck('edit');
$result = PrescriptionLogic::edit($params, $this->adminId);
if (!$result) {
return $this->fail(PrescriptionLogic::getError());
}
return $this->success('编辑成功');
}
/**
* @notes 删除处方
*/
public function delete()
{
$params = (new PrescriptionValidate())->post()->goCheck('delete');
$result = PrescriptionLogic::delete($params['id']);
if (!$result) {
return $this->fail(PrescriptionLogic::getError());
}
return $this->success('删除成功');
}
/**
* @notes 处方详情
*/
@@ -0,0 +1,77 @@
<?php
declare(strict_types=1);
namespace app\adminapi\controller\tcm;
use app\adminapi\controller\BaseAdminController;
use app\adminapi\logic\tcm\PrescriptionLibraryLogic;
use app\adminapi\validate\tcm\PrescriptionLibraryValidate;
/**
* 处方库控制器
*/
class PrescriptionLibraryController extends BaseAdminController
{
/**
* @notes 处方库列表
*/
public function lists()
{
return $this->dataLists(new \app\adminapi\lists\tcm\PrescriptionLibraryLists());
}
/**
* @notes 添加处方库
*/
public function add()
{
$params = (new PrescriptionLibraryValidate())->post()->goCheck('add');
$params['creator_id'] = $this->adminId;
$params['creator_name'] = $this->adminInfo['name'] ?? '';
$id = PrescriptionLibraryLogic::add($params);
if ($id === null) {
return $this->fail(PrescriptionLibraryLogic::getError());
}
return $this->success('保存成功', ['id' => $id]);
}
/**
* @notes 编辑处方库
*/
public function edit()
{
$params = (new PrescriptionLibraryValidate())->post()->goCheck('edit');
$result = PrescriptionLibraryLogic::edit($params, $this->adminId);
if (!$result) {
return $this->fail(PrescriptionLibraryLogic::getError());
}
return $this->success('编辑成功');
}
/**
* @notes 删除处方库
*/
public function delete()
{
$params = (new PrescriptionLibraryValidate())->post()->goCheck('delete');
$result = PrescriptionLibraryLogic::delete($params['id'], $this->adminId);
if (!$result) {
return $this->fail(PrescriptionLibraryLogic::getError());
}
return $this->success('删除成功');
}
/**
* @notes 处方库详情
*/
public function detail()
{
$params = (new PrescriptionLibraryValidate())->get()->goCheck('detail');
$detail = PrescriptionLibraryLogic::detail((int)$params['id'], $this->adminId);
if (!$detail) {
return $this->fail('处方不存在或无权限查看');
}
return $this->data($detail);
}
}
+6 -3
View File
@@ -38,8 +38,9 @@ class FileLists extends BaseAdminDataLists implements ListsSearchInterface
*/
public function setSearch(): array
{
// 不按 source 搜索:列表固定为「当前管理员后台上传」,避免请求参数覆盖权限条件
return [
'=' => ['type', 'source'],
'=' => ['type'],
'%like%' => ['name']
];
}
@@ -79,7 +80,8 @@ class FileLists extends BaseAdminDataLists implements ListsSearchInterface
->order('id', 'desc')
->where($this->searchWhere)
->where($this->queryWhere())
// ->where('source', FileEnum::SOURCE_ADMIN)
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $this->adminId)
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
@@ -102,7 +104,8 @@ class FileLists extends BaseAdminDataLists implements ListsSearchInterface
{
return (new File())->where($this->searchWhere)
->where($this->queryWhere())
// ->where('source', FileEnum::SOURCE_ADMIN)
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $this->adminId)
->count();
}
}
@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace app\adminapi\lists\tcm;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\model\tcm\PrescriptionLibrary;
use app\common\lists\ListsSearchInterface;
/**
* 处方库列表
*/
class PrescriptionLibraryLists extends BaseAdminDataLists implements ListsSearchInterface
{
/**
* @notes 设置搜索条件
*/
public function setSearch(): array
{
return [
'%like%' => ['prescription_name'],
'=' => ['is_public', 'creator_id']
];
}
/**
* @notes 获取列表
*/
public function lists(): array
{
$field = [
'id', 'prescription_name', 'herbs', 'is_public',
'creator_id', 'creator_name', 'create_time', 'update_time'
];
$lists = PrescriptionLibrary::where($this->searchWhere)
->where(function ($query) {
// 只显示自己创建的或公开的处方
$query->where('creator_id', $this->adminId)
->whereOr('is_public', 1);
})
->field($field)
->limit($this->limitOffset, $this->limitLength)
->order('id', 'desc')
->select()
->toArray();
// 解析药材JSON
foreach ($lists as &$item) {
if (!empty($item['herbs'])) {
$item['herbs'] = json_decode($item['herbs'], true);
} else {
$item['herbs'] = [];
}
}
return $lists;
}
/**
* @notes 获取数量
*/
public function count(): int
{
return PrescriptionLibrary::where($this->searchWhere)
->where(function ($query) {
// 只统计自己创建的或公开的处方
$query->where('creator_id', $this->adminId)
->whereOr('is_public', 1);
})
->count();
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace app\adminapi\lists\tcm;
use app\adminapi\lists\BaseAdminDataLists;
use app\common\lists\ListsSearchInterface;
use app\common\model\tcm\Prescription;
/**
* 处方列表
*/
class PrescriptionLists extends BaseAdminDataLists implements ListsSearchInterface
{
/**
* @notes 搜索条件
*/
public function setSearch(): array
{
return [
'=' => ['is_shared'],
'%like%' => ['prescription_name', 'patient_name', 'sn']
];
}
/**
* @notes 获取列表
*/
public function lists(): array
{
$lists = Prescription::where($this->searchWhere)
->where(function ($query) {
// 如果不是共享的,只能看到自己创建的
$query->whereOr([
['is_shared', '=', 1],
['creator_id', '=', $this->adminId]
]);
})
->whereNull('delete_time')
->order('id', 'desc')
->limit($this->limitOffset, $this->limitLength)
->select()
->toArray();
// 处理字段格式
foreach ($lists as &$item) {
// 设置默认值
$item['usage_time'] = $item['usage_time'] ?? '饭前';
$item['usage_way'] = $item['usage_way'] ?? '温水送服';
$item['usage_notes'] = $item['usage_notes'] ?? '';
$item['usage_days'] = $item['usage_days'] ?? 7;
$item['is_shared'] = $item['is_shared'] ?? 0;
// 将 dietary_taboo 从逗号分隔的字符串转换为数组(前端需要数组格式)
if (!empty($item['dietary_taboo']) && is_string($item['dietary_taboo'])) {
$item['dietary_taboo'] = array_filter(explode(',', $item['dietary_taboo']));
} else {
$item['dietary_taboo'] = [];
}
}
return $lists;
}
/**
* @notes 获取数量
*/
public function count(): int
{
return Prescription::where($this->searchWhere)
->where(function ($query) {
// 如果不是共享的,只能看到自己创建的
$query->whereOr([
['is_shared', '=', 1],
['creator_id', '=', $this->adminId]
]);
})
->whereNull('delete_time')
->count();
}
}
+23 -4
View File
@@ -15,6 +15,7 @@
namespace app\adminapi\logic;
use app\common\enum\FileEnum;
use app\common\logic\BaseLogic;
use app\common\model\file\File;
use app\common\model\file\FileCate;
@@ -36,7 +37,10 @@ class FileLogic extends BaseLogic
*/
public static function move($params)
{
$adminId = (int)(request()->adminId ?? 0);
(new File())->whereIn('id', $params['ids'])
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $adminId)
->update([
'cid' => $params['cid'],
'update_time' => time()
@@ -51,7 +55,10 @@ class FileLogic extends BaseLogic
*/
public static function rename($params)
{
$adminId = (int)(request()->adminId ?? 0);
(new File())->where('id', $params['id'])
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $adminId)
->update([
'name' => $params['name'],
'update_time' => time()
@@ -66,7 +73,15 @@ class FileLogic extends BaseLogic
*/
public static function delete($params)
{
$result = File::whereIn('id', $params['ids'])->select();
$adminId = (int)(request()->adminId ?? 0);
$result = File::whereIn('id', $params['ids'])
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $adminId)
->select();
$ids = $result->column('id');
if (empty($ids)) {
return;
}
$StorageDriver = new StorageDriver([
'default' => ConfigService::get('storage', 'default', 'local'),
'engine' => ConfigService::get('storage') ?? ['local'=>[]],
@@ -74,7 +89,7 @@ class FileLogic extends BaseLogic
foreach ($result as $item) {
$StorageDriver->delete($item['uri']);
}
File::destroy($params['ids']);
File::destroy($ids);
}
/**
@@ -123,8 +138,12 @@ class FileLogic extends BaseLogic
// 删除分类及子分类
$cateModel->whereIn('id', $cateIds)->update(['delete_time' => time()]);
// 删除文件
$fileIds = $fileModel->whereIn('cid', $cateIds)->column('id');
// 删除文件(仅当前管理员在该分类下的素材)
$adminId = (int)(request()->adminId ?? 0);
$fileIds = $fileModel->whereIn('cid', $cateIds)
->where('source', FileEnum::SOURCE_ADMIN)
->where('source_id', $adminId)
->column('id');
if (!empty($fileIds)) {
self::delete(['ids' => $fileIds]);
@@ -591,6 +591,289 @@ class DiagnosisLogic extends BaseLogic
return false;
}
}
/**
* 所有可能以 doctor_{id} 登录 IM 的后台账号(医生 role_id=1、医助 role_id=2),用于合并会话漫游记录
*
* @return array<int, string> 如 ['doctor_1','doctor_2']
*/
private static function collectAllDoctorImPeerAccounts(): array
{
try {
$ids = \app\common\model\auth\Admin::alias('a')
->join('admin_role ar', 'a.id = ar.admin_id')
->whereIn('ar.role_id', [1, 2])
->where('a.disable', 0)
->group('a.id')
->column('a.id');
$accounts = [];
foreach ($ids as $id) {
$accounts[] = 'doctor_' . (int)$id;
}
return array_values(array_unique($accounts));
} catch (\Exception $e) {
\think\facade\Log::error('collectAllDoctorImPeerAccounts: ' . $e->getMessage());
return [];
}
}
/**
* @notes 拉取与本诊单相关的腾讯云 IM 单聊记录(C2C:patient_{患者ID} 与 全部医生/医助账号 doctor_* 分别会话后合并)
*/
public static function getImChatMessagesForDiagnosis(int $diagnosisId)
{
try {
$config = self::getTrtcConfig();
if (!$config) {
self::setError('请先配置腾讯云 TRTC / IM 参数');
return false;
}
$diag = Diagnosis::where('id', $diagnosisId)->where('delete_time', null)->find();
if (!$diag) {
self::setError('诊单不存在');
return false;
}
$patientId = (int)$diag['patient_id'];
if ($patientId <= 0) {
self::setError('诊单缺少患者信息');
return false;
}
$patientImId = 'patient_' . $patientId;
$doctorAccounts = self::collectAllDoctorImPeerAccounts();
$assistantId = isset($diag['assistant_id']) ? (int)$diag['assistant_id'] : 0;
if ($assistantId > 0) {
$doctorAccounts[] = 'doctor_' . $assistantId;
}
$doctorAccounts = array_values(array_unique($doctorAccounts));
if (empty($doctorAccounts)) {
self::setError('未找到医生/医助角色账号,无法拉取 IM 记录');
return false;
}
$imService = new \app\common\service\TencentImService();
$merged = [];
foreach ($doctorAccounts as $docAccount) {
$batch = self::pullAllRoamMessages($imService, $docAccount, $patientImId);
foreach ($batch as $row) {
$merged[] = $row;
}
}
usort($merged, function ($a, $b) {
return ($a['time'] ?? 0) <=> ($b['time'] ?? 0);
});
$seen = [];
$unique = [];
foreach ($merged as $row) {
$k = $row['msg_id'] ?? '';
if ($k !== '' && isset($seen[$k])) {
continue;
}
if ($k !== '') {
$seen[$k] = true;
}
$unique[] = $row;
}
$unique = self::enrichImMessagesWithStaffNames($unique);
return [
'lists' => $unique,
'patient_im_id' => $patientImId,
'patient_name' => $diag['patient_name'] ?? '',
'doctor_accounts_queried' => $doctorAccounts,
];
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 为 from_account = doctor_{id} 的消息补充后台姓名,便于前端区分不同医生
*
* @param array<int, array<string, mixed>> $rows
* @return array<int, array<string, mixed>>
*/
private static function enrichImMessagesWithStaffNames(array $rows): array
{
$ids = [];
foreach ($rows as $r) {
$f = (string)($r['from_account'] ?? '');
if (preg_match('/^doctor_(\d+)$/', $f, $m)) {
$ids[] = (int)$m[1];
}
}
$ids = array_unique($ids);
if (empty($ids)) {
return $rows;
}
$map = \app\common\model\auth\Admin::whereIn('id', $ids)->column('name', 'id');
foreach ($rows as $k => $r) {
$f = (string)($r['from_account'] ?? '');
if (preg_match('/^doctor_(\d+)$/', $f, $m)) {
$aid = (int)$m[1];
$rows[$k]['from_staff_name'] = $map[$aid] ?? '';
}
}
return $rows;
}
/**
* @return array<int, array<string, mixed>>
*/
private static function pullAllRoamMessages(\app\common\service\TencentImService $svc, string $operator, string $peer): array
{
$out = [];
$lastKey = null;
$lastTime = null;
$guard = 0;
do {
$res = $svc->adminGetRoamMsg($operator, $peer, 100, 0, 4294967295, $lastKey, $lastTime);
if (!$res['success']) {
if (!empty($res['error'])) {
\think\facade\Log::warning('IM漫游消息拉取失败', [
'operator' => $operator,
'peer' => $peer,
'error' => $res['error'],
'code' => $res['rawErrorCode'] ?? 0,
]);
}
break;
}
foreach ($res['msgList'] as $raw) {
if (!is_array($raw)) {
continue;
}
$out[] = self::normalizeTimMessage($raw);
}
$complete = (int)$res['complete'];
if ($complete === 1) {
break;
}
$lastKey = $res['lastMsgKey'];
$lastTime = $res['lastMsgTime'];
if ($lastKey === null || $lastKey === '') {
break;
}
$guard++;
if ($guard > 80) {
break;
}
} while (true);
return $out;
}
/**
* @param array<string, mixed> $raw
* @return array<string, mixed>
*/
private static function normalizeTimMessage(array $raw): array
{
$from = (string)($raw['From_Account'] ?? '');
$to = (string)($raw['To_Account'] ?? '');
$time = (int)($raw['MsgTimeStamp'] ?? $raw['MsgTime'] ?? 0);
$seq = $raw['MsgSeq'] ?? '';
$rand = $raw['MsgRandom'] ?? '';
$msgId = $seq . '_' . $rand . '_' . $from;
$isDoctor = strpos($from, 'doctor_') === 0;
$parsed = self::parseTimMsgBody($raw['MsgBody'] ?? []);
return array_merge(
[
'msg_id' => $msgId,
'from_account' => $from,
'to_account' => $to,
'time' => $time,
'is_from_doctor' => $isDoctor,
],
$parsed
);
}
/**
* @param mixed $body
* @return array<string, mixed>
*/
private static function parseTimMsgBody($body): array
{
$out = [
'msg_type' => 'other',
'text' => '',
'image_url' => '',
'file_url' => '',
'file_name' => '',
'raw_elem_type' => '',
];
if (!is_array($body) || !$body) {
return $out;
}
foreach ($body as $elem) {
if (!is_array($elem)) {
continue;
}
$type = (string)($elem['MsgType'] ?? '');
$out['raw_elem_type'] = $type;
$content = $elem['MsgContent'] ?? [];
if (!is_array($content)) {
$content = [];
}
switch ($type) {
case 'TIMTextElem':
$out['msg_type'] = 'text';
$out['text'] = (string)($content['Text'] ?? '');
return $out;
case 'TIMImageElem':
$out['msg_type'] = 'image';
$arr = $content['ImageInfoArray'] ?? [];
if (is_array($arr)) {
foreach ($arr as $info) {
if (is_array($info) && !empty($info['URL'])) {
$out['image_url'] = (string)$info['URL'];
break;
}
}
}
return $out;
case 'TIMFileElem':
$out['msg_type'] = 'file';
$out['file_url'] = (string)($content['Url'] ?? '');
$out['file_name'] = (string)($content['FileName'] ?? '');
return $out;
case 'TIMSoundElem':
$out['msg_type'] = 'sound';
$out['file_url'] = (string)($content['Url'] ?? '');
return $out;
case 'TIMVideoFileElem':
$out['msg_type'] = 'video';
$out['file_url'] = (string)($content['VideoUrl'] ?? '');
return $out;
case 'TIMVideoElem':
$out['msg_type'] = 'video';
$out['file_url'] = (string)($content['VideoUrl'] ?? '');
return $out;
case 'TIMLocationElem':
$out['msg_type'] = 'location';
$out['text'] = (string)($content['Desc'] ?? '');
return $out;
case 'TIMCustomElem':
$out['msg_type'] = 'custom';
$out['text'] = (string)($content['Data'] ?? '');
return $out;
case 'TIMFaceElem':
$out['msg_type'] = 'face';
$out['text'] = (string)($content['Index'] ?? '');
return $out;
default:
break;
}
}
return $out;
}
/**
* @notes 发起通话
@@ -685,6 +968,15 @@ class DiagnosisLogic extends BaseLogic
$record['start_time_text'] = date('Y-m-d H:i:s', $record['start_time']);
$record['end_time_text'] = $record['end_time'] ? date('Y-m-d H:i:s', $record['end_time']) : '';
$record['duration_text'] = self::formatDuration($record['duration']);
$urls = [];
if (!empty($record['recording_urls'])) {
$decoded = json_decode((string)$record['recording_urls'], true);
if (is_array($decoded)) {
$urls = $decoded;
}
}
$record['recording_urls_list'] = $urls;
$record['recording_status_text'] = self::recordingStatusText((int)($record['recording_status'] ?? 0));
}
return $records;
@@ -693,6 +985,58 @@ class DiagnosisLogic extends BaseLogic
return [];
}
}
/**
* @notes 将 TRTC 房间号写入当前诊单最近一次通话记录,便于云端录制回调按 room_id 关联
*/
public static function bindCallRoom(array $params): bool
{
try {
$diagnosisId = (int)($params['diagnosis_id'] ?? 0);
$roomId = trim((string)($params['room_id'] ?? ''));
if ($diagnosisId <= 0 || $roomId === '') {
self::setError('诊单ID或房间号不能为空');
return false;
}
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
->where('status', 1)
->order('id', 'desc')
->find();
if (!$record) {
$record = \app\common\model\tcm\CallRecord::where('diagnosis_id', $diagnosisId)
->where('room_id', '')
->order('id', 'desc')
->find();
}
if (!$record) {
self::setError('未找到可绑定的通话记录');
return false;
}
$record->save([
'room_id' => $roomId,
'update_time' => time(),
]);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
private static function recordingStatusText(int $status): string
{
$map = [
0 => '无录制',
1 => '录制中',
2 => '已生成',
3 => '录制失败',
];
return $map[$status] ?? '未知';
}
/**
* @notes 获取TRTC配置
@@ -1020,8 +1364,9 @@ class DiagnosisLogic extends BaseLogic
throw new \Exception('小程序配置未设置');
}
$scene = "id={$sceneId}&share_user={$shareUserId}&doctor_id={$doctorId}";
// 小程序 onLoad 解析 scene,须与前端 parsePageParams 约定一致(微信 scene 总长勿超过 32 字符)
$scene = "id={$sceneId}&sa={$shareUserId}&did={$doctorId}";
// 调用微信接口生成小程序码
$qrcodeUrl = self::generateWxQrcode($config, $page, $scene);
@@ -1051,13 +1396,20 @@ class DiagnosisLogic extends BaseLogic
throw new \Exception('订单号不能为空');
}
// 查询订单ID(使用ID代替订单号,更短)
$order = \app\common\model\Order::where('order_no', $orderNo)->find();
if (!$order) {
throw new \Exception('订单不存在');
}
$config = self::getMiniProgramConfig();
if (!$config) {
throw new \Exception('小程序配置未设置');
}
$page = 'pages/order/order';
$scene = "order_no={$orderNo}";
// 微信小程序 scene 参数最大长度为32字符,使用订单ID代替订单号
$scene = "id={$order->id}";
$qrcodeUrl = self::generateWxQrcode($config, $page, $scene);
@@ -1128,6 +1480,13 @@ class DiagnosisLogic extends BaseLogic
// 调用微信接口生成小程序码
$url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={$accessToken}";
// 记录 scene 长度
\think\facade\Log::info('Scene参数信息', [
'scene' => $scene,
'scene_length' => strlen($scene),
'scene_urlencode_length' => strlen(urlencode($scene))
]);
$data = [
'scene' => $scene,
'page' => $page,
@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace app\adminapi\logic\tcm;
use app\common\logic\BaseLogic;
use app\common\model\tcm\PrescriptionLibrary;
/**
* 处方库逻辑层
*/
class PrescriptionLibraryLogic extends BaseLogic
{
/**
* @notes 添加处方库
*/
public static function add(array $params): ?int
{
try {
// 处理药材数据
if (isset($params['herbs']) && is_array($params['herbs'])) {
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
}
$model = PrescriptionLibrary::create($params);
return $model->id;
} catch (\Exception $e) {
self::setError($e->getMessage());
return null;
}
}
/**
* @notes 编辑处方库
*/
public static function edit(array $params, int $adminId): bool
{
try {
$model = PrescriptionLibrary::findOrEmpty($params['id']);
if ($model->isEmpty()) {
self::setError('处方不存在');
return false;
}
// 权限检查:只有创建者或公开的处方才能编辑
if ($model->creator_id != $adminId && $model->is_public != 1) {
self::setError('无权限编辑此处方');
return false;
}
// 处理药材数据
if (isset($params['herbs']) && is_array($params['herbs'])) {
$params['herbs'] = json_encode($params['herbs'], JSON_UNESCAPED_UNICODE);
}
$model->save($params);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 删除处方库
*/
public static function delete(int $id, int $adminId): bool
{
try {
$model = PrescriptionLibrary::findOrEmpty($id);
if ($model->isEmpty()) {
self::setError('处方不存在');
return false;
}
// 权限检查:只有创建者才能删除
if ($model->creator_id != $adminId) {
self::setError('无权限删除此处方');
return false;
}
$model->delete();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* @notes 处方库详情
*/
public static function detail(int $id, int $adminId): ?array
{
try {
$model = PrescriptionLibrary::findOrEmpty($id);
if ($model->isEmpty()) {
return null;
}
// 权限检查:只有创建者或公开的处方才能查看
if ($model->creator_id != $adminId && $model->is_public != 1) {
return null;
}
$data = $model->toArray();
// 解析药材JSON
if (!empty($data['herbs'])) {
$data['herbs'] = json_decode($data['herbs'], true);
} else {
$data['herbs'] = [];
}
return $data;
} catch (\Exception $e) {
self::setError($e->getMessage());
return null;
}
}
}
@@ -31,10 +31,13 @@ class PrescriptionLogic
*/
public static function add(array $params, int $adminId): ?int
{
$diagnosis = Diagnosis::find($params['diagnosis_id']);
if (!$diagnosis) {
self::setError('诊单不存在');
return null;
// 如果没有诊单ID,允许直接创建处方模板
if (!empty($params['diagnosis_id'])) {
$diagnosis = Diagnosis::find($params['diagnosis_id']);
if (!$diagnosis) {
self::setError('诊单不存在');
return null;
}
}
$herbs = $params['herbs'] ?? [];
@@ -57,27 +60,37 @@ class PrescriptionLogic
$data = [
'sn' => $sn,
'diagnosis_id' => (int)$params['diagnosis_id'],
'prescription_name' => $params['prescription_name'] ?? '',
'prescription_type' => $params['prescription_type'] ?? '浓缩水丸',
'diagnosis_id' => (int)($params['diagnosis_id'] ?? 0),
'appointment_id' => (int)($params['appointment_id'] ?? 0),
'patient_id' => (int)$diagnosis->patient_id,
'patient_name' => $params['patient_name'] ?? $diagnosis->patient_name,
'gender' => (int)($params['gender'] ?? $diagnosis->gender),
'age' => (int)($params['age'] ?? $diagnosis->age ?? 0),
'phone' => $params['phone'] ?? $diagnosis->phone ?? '',
'patient_id' => (int)($params['patient_id'] ?? 0),
'patient_name' => $params['patient_name'] ?? '',
'gender' => (int)($params['gender'] ?? 1),
'age' => (int)($params['age'] ?? 0),
'phone' => $params['phone'] ?? '',
'visit_no' => $params['visit_no'] ?? $sn,
'prescription_date' => $params['prescription_date'] ?? date('Y-m-d'),
'pulse' => $params['pulse'] ?? $diagnosis->pulse ?? '',
'tongue' => $params['tongue'] ?? $diagnosis->tongue_coating ?? '',
'pulse' => $params['pulse'] ?? '',
'pulse_condition' => $params['pulse_condition'] ?? '',
'tongue' => $params['tongue'] ?? '',
'tongue_image' => $params['tongue_image'] ?? '',
'clinical_diagnosis' => $params['clinical_diagnosis'] ?? '',
'case_record' => $params['case_record'] ?? null,
'herbs' => $herbs,
'dose_count' => (int)($params['dose_count'] ?? 1),
'dose_unit' => $params['dose_unit'] ?? '剂',
'usage_days' => (int)($params['usage_days'] ?? 7),
'usage_instruction' => $params['usage_instruction'] ?? '水煎服一日二次',
'usage_time' => $params['usage_time'] ?? '饭前',
'usage_way' => $params['usage_way'] ?? '温水送服',
'dietary_taboo' => is_array($params['dietary_taboo'] ?? null) ? implode(',', $params['dietary_taboo']) : ($params['dietary_taboo'] ?? ''),
'usage_notes' => $params['usage_notes'] ?? '',
'amount' => (float)($params['amount'] ?? 0),
'doctor_name' => $params['doctor_name'] ?? '',
'doctor_signature' => $params['doctor_signature'] ?? '',
'template_id' => (int)($params['template_id'] ?? 0),
'is_shared' => (int)($params['is_shared'] ?? 0),
'creator_id' => $adminId,
];
@@ -86,6 +99,91 @@ class PrescriptionLogic
return (int)$prescription->id;
}
/**
* 编辑处方
*/
public static function edit(array $params, int $adminId): bool
{
try {
$prescription = Prescription::find($params['id']);
if (!$prescription) {
self::setError('处方不存在');
return false;
}
// 检查权限:只有创建者或共享的处方才能编辑
if ($prescription->creator_id != $adminId && $prescription->is_shared != 1) {
self::setError('无权限编辑此处方');
return false;
}
$herbs = $params['herbs'] ?? [];
if (empty($herbs) || !is_array($herbs)) {
self::setError('请添加中药');
return false;
}
foreach ($herbs as $h) {
if (empty($h['name'])) {
self::setError('中药名称不能为空');
return false;
}
}
$data = [
'prescription_name' => $params['prescription_name'] ?? $prescription->prescription_name,
'prescription_type' => $params['prescription_type'] ?? $prescription->prescription_type,
'patient_name' => $params['patient_name'] ?? $prescription->patient_name,
'gender' => (int)($params['gender'] ?? $prescription->gender),
'age' => (int)($params['age'] ?? $prescription->age),
'visit_no' => $params['visit_no'] ?? $prescription->visit_no,
'prescription_date' => $params['prescription_date'] ?? $prescription->prescription_date,
'tongue' => $params['tongue'] ?? $prescription->tongue,
'tongue_image' => $params['tongue_image'] ?? $prescription->tongue_image,
'pulse' => $params['pulse'] ?? $prescription->pulse,
'pulse_condition' => $params['pulse_condition'] ?? $prescription->pulse_condition,
'clinical_diagnosis' => $params['clinical_diagnosis'] ?? $prescription->clinical_diagnosis,
'herbs' => $herbs,
'dose_count' => (int)($params['dose_count'] ?? $prescription->dose_count),
'dose_unit' => $params['dose_unit'] ?? $prescription->dose_unit,
'usage_days' => (int)($params['usage_days'] ?? $prescription->usage_days),
'usage_instruction' => $params['usage_instruction'] ?? $prescription->usage_instruction,
'usage_time' => $params['usage_time'] ?? $prescription->usage_time,
'usage_way' => $params['usage_way'] ?? $prescription->usage_way,
'dietary_taboo' => is_array($params['dietary_taboo'] ?? null) ? implode(',', $params['dietary_taboo']) : ($params['dietary_taboo'] ?? $prescription->dietary_taboo),
'usage_notes' => $params['usage_notes'] ?? $prescription->usage_notes,
'doctor_name' => $params['doctor_name'] ?? $prescription->doctor_name,
'is_shared' => (int)($params['is_shared'] ?? $prescription->is_shared),
];
$prescription->save($data);
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 删除处方
*/
public static function delete(int $id): bool
{
try {
$prescription = Prescription::find($id);
if (!$prescription) {
self::setError('处方不存在');
return false;
}
$prescription->delete();
return true;
} catch (\Exception $e) {
self::setError($e->getMessage());
return false;
}
}
/**
* 处方详情
*/
@@ -34,6 +34,8 @@ class DiagnosisValidate extends BaseValidate
'diagnosis_date' => 'require',
'diagnosis_type' => 'require',
'status' => 'in:0,1',
'tongue_images' => 'array',
'report_files' => 'array',
];
protected $message = [
@@ -60,7 +62,7 @@ class DiagnosisValidate extends BaseValidate
public function sceneEdit()
{
return $this->only(['id', 'patient_name', 'id_card', 'phone', 'gender', 'age', 'diagnosis_date', 'diagnosis_type', 'syndrome_type', 'marital_status', 'height', 'weight', 'region', 'systolic_pressure', 'diastolic_pressure', 'fasting_blood_sugar', 'diabetes_discovery_year', 'local_hospital_diagnosis', 'local_hospital_name', 'past_history', 'symptoms', 'tongue_coating', 'pulse', 'treatment_principle', 'prescription', 'doctor_advice', 'remark', 'status']);
return $this->only(['id', 'patient_name', 'id_card', 'phone', 'gender', 'age', 'diagnosis_date', 'diagnosis_type', 'syndrome_type', 'marital_status', 'height', 'weight', 'region', 'systolic_pressure', 'diastolic_pressure', 'fasting_blood_sugar', 'diabetes_discovery_year', 'local_hospital_diagnosis', 'local_hospital_name', 'past_history', 'symptoms', 'tongue_coating', 'pulse', 'treatment_principle', 'prescription', 'doctor_advice', 'remark', 'status', 'tongue_images', 'report_files']);
}
public function sceneId()
@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace app\adminapi\validate\tcm;
use app\common\validate\BaseValidate;
/**
* 处方库验证器
*/
class PrescriptionLibraryValidate extends BaseValidate
{
protected $rule = [
'id' => 'require|number',
'prescription_name' => 'require|max:100',
'herbs' => 'require|array',
'is_public' => 'in:0,1'
];
protected $message = [
'id.require' => '处方ID不能为空',
'id.number' => '处方ID必须为数字',
'prescription_name.require' => '处方名称不能为空',
'prescription_name.max' => '处方名称最多100个字符',
'herbs.require' => '药材列表不能为空',
'herbs.array' => '药材列表格式错误',
'is_public.in' => '是否公开参数错误'
];
/**
* @notes 添加场景
*/
public function sceneAdd()
{
return $this->only(['prescription_name', 'herbs', 'is_public']);
}
/**
* @notes 编辑场景
*/
public function sceneEdit()
{
return $this->only(['id', 'prescription_name', 'herbs', 'is_public']);
}
/**
* @notes 删除场景
*/
public function sceneDelete()
{
return $this->only(['id']);
}
/**
* @notes 详情场景
*/
public function sceneDetail()
{
return $this->only(['id']);
}
}
@@ -10,7 +10,7 @@ class PrescriptionValidate extends BaseValidate
{
protected $rule = [
'id' => 'require',
'diagnosis_id' => 'require|number',
'diagnosis_id' => 'number',
'appointment_id' => 'number',
'patient_name' => 'require',
'clinical_diagnosis' => 'require',
@@ -18,7 +18,6 @@ class PrescriptionValidate extends BaseValidate
];
protected $message = [
'diagnosis_id.require' => '诊单ID不能为空',
'patient_name.require' => '患者姓名不能为空',
'clinical_diagnosis.require' => '临床诊断不能为空',
'herbs.require' => '请添加中药',
@@ -26,7 +25,29 @@ class PrescriptionValidate extends BaseValidate
public function sceneAdd()
{
return $this->only(['diagnosis_id', 'patient_name', 'clinical_diagnosis', 'herbs']);
return $this->only([
'prescription_name', 'prescription_type', 'patient_name', 'gender', 'age',
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
'usage_days', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
'usage_notes', 'doctor_name', 'is_shared', 'diagnosis_id', 'appointment_id'
]);
}
public function sceneEdit()
{
return $this->only([
'id', 'prescription_name', 'prescription_type', 'patient_name', 'gender', 'age',
'visit_no', 'prescription_date', 'tongue', 'tongue_image', 'pulse',
'pulse_condition', 'clinical_diagnosis', 'herbs', 'dose_count', 'dose_unit',
'usage_days', 'usage_instruction', 'usage_time', 'usage_way', 'dietary_taboo',
'usage_notes', 'doctor_name', 'is_shared'
]);
}
public function sceneDelete()
{
return $this->only(['id']);
}
public function sceneDetail()