From 5b4862ba49ecccd7f1f3c870abda5fb9cb5bc272 Mon Sep 17 00:00:00 2001 From: sa0ChunLuyu Date: Sat, 25 Jul 2026 23:18:42 +0800 Subject: [PATCH] =?UTF-8?q?=E9=A2=84=E7=BA=A6=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/API/His/UserController.php | 25 +++- .../Services/Admin/YeWu/PlanListService.php | 121 ++++++++++++++++ Laravel/app/Services/LogService.php | 27 ++++ Laravel/config/app.php | 2 + Laravel/routes/web.php | 18 ++- .../src/components/Yewu/YuYue202506.vue | 41 ++++-- YiJi-admin/src/views/HisLogin.vue | 131 +++++++++++++----- YiJi-admin/src/views/Index.vue | 4 +- YiJi-admin/src/views/YeWu/MainList.vue | 9 +- 9 files changed, 325 insertions(+), 53 deletions(-) diff --git a/Laravel/app/Http/Controllers/API/His/UserController.php b/Laravel/app/Http/Controllers/API/His/UserController.php index fdeae87..563e84c 100644 --- a/Laravel/app/Http/Controllers/API/His/UserController.php +++ b/Laravel/app/Http/Controllers/API/His/UserController.php @@ -63,8 +63,15 @@ public function GetUserList(){ public function AutoLogin(){ $usercode = request('usercode'); $deptcode = request('deptcode'); - if(empty($usercode) || empty($deptcode)){ - return \Yz::JsonError("参数不全"); + + $missingParams = []; + if (empty($usercode)) $missingParams[] = '医生编码'; + if (empty($deptcode)) $missingParams[] = '科室编码'; + + if (!empty($missingParams)) { + return \Yz::JsonReturn(false, '缺少参数:' . implode('、', $missingParams), [ + 'missing_params' => $missingParams + ]); } $dept=DB::table('s_department')->where(['department_number' => $deptcode])->first(); if(!$dept) return \Yz::JsonError("科室不存在"); @@ -81,6 +88,20 @@ public function AutoLogin(){ $access_token = $jwt->BuildJWT('yz','access',$user->id,$user->group,$accessTimeout); $refresh_token = $jwt->BuildJWT('yz','refresh',$user->id,'',$refreshTimeout); DB::table('users')->where(['id'=>$user->id,'status'=>1])->update(['token'=>md5($refresh_token)]); + + // 记录 HIS 登录日志 + try { + $allParams = request()->all(); + \App\Services\LogService::DoctorLoginLog( + request()->ip(), + http_build_query($allParams), + json_encode($allParams, JSON_UNESCAPED_UNICODE), + 'HIS登录' + ); + } catch (\Exception $e) { + // 日志写入失败不影响主流程 + } + return \Yz::JsonReturn(true,'登录成功',['access_token'=>$access_token,'refresh_token'=>$refresh_token]); } } diff --git a/Laravel/app/Services/Admin/YeWu/PlanListService.php b/Laravel/app/Services/Admin/YeWu/PlanListService.php index 2f3a3a8..6441d34 100644 --- a/Laravel/app/Services/Admin/YeWu/PlanListService.php +++ b/Laravel/app/Services/Admin/YeWu/PlanListService.php @@ -436,6 +436,8 @@ public function GetOptimalPlan($type, $regnum, $entrustids, $episodeid, $appoint if ($type === 'full_day') { return $this->doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids); + } elseif ($type === 'same_day_slot') { + return $this->doSameDaySlotAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids); } else { return $this->doOptimalTimeAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids); } @@ -619,6 +621,125 @@ private function doFullDayAssign($startDate, $endDate, $regnum, $entrustids, $ep return \Yz::Return(true, '获取成功', []); } + // 同一天同一时间段分配:所有 item 必须在同一天且同一时间段完成 + private function doSameDaySlotAssign($startDate, $endDate, $regnum, $entrustids, $episodeid, $appointment_type, $items, $scope, $userDeptId, $occupied_plan_ids = []) + { + // 按科室分组 + $deptGroups = $this->groupItemsByDept($items); + if (empty($deptGroups)) { + return \Yz::Return(true, '获取成功', []); + } + + $currentDate = clone $startDate; + + while ($currentDate <= $endDate) { + $nowdate = $currentDate->format('Y-m-d'); + + // 收集所有科室在该日期的可用号源 + $allDeptPlans = []; + $allAssigned = true; + + foreach ($deptGroups as $deptCode => $group) { + $availablePlans = $this->getAvailablePlansForDate($regnum, $group['entrustids'], $episodeid, $appointment_type, $nowdate, $scope, $userDeptId); + + // 过滤已占用的号源 + if (!empty($occupied_plan_ids)) { + $availablePlans = array_filter($availablePlans, function($plan) use ($occupied_plan_ids) { + return !in_array($plan->id, $occupied_plan_ids); + }); + $availablePlans = array_values($availablePlans); + } + + if (empty($availablePlans)) { + $allAssigned = false; + break; + } + + $allDeptPlans[$deptCode] = $availablePlans; + } + + if (!$allAssigned) { + $currentDate->modify('+1 day'); + continue; + } + + // 按时间段分组:提取所有科室共有的时间段 + $slotGroups = []; + foreach ($allDeptPlans as $deptCode => $plans) { + foreach ($plans as $plan) { + $slotKey = $plan->begin_time . '-' . $plan->end_time; + if (!isset($slotGroups[$slotKey])) { + $slotGroups[$slotKey] = []; + } + if (!isset($slotGroups[$slotKey][$deptCode])) { + $slotGroups[$slotKey][$deptCode] = []; + } + $slotGroups[$slotKey][$deptCode][] = $plan; + } + } + + // 对每个时间段,尝试分配所有项目 + foreach ($slotGroups as $slotKey => $deptPlans) { + $result = []; + $allInSlot = true; + + foreach ($deptGroups as $deptCode => $group) { + if (!isset($deptPlans[$deptCode])) { + $allInSlot = false; + break; + } + + $availablePlans = $deptPlans[$deptCode]; + $usedCapacity = []; + $assignedPlans = []; + + foreach ($group['items'] as $item) { + $assigned = false; + foreach ($availablePlans as $plan) { + $planId = $plan->id; + $remaining = ($plan->count ?? 0) - ($plan->used_count ?? 0); + $used = $usedCapacity[$planId] ?? 0; + + if (($remaining - $used) <= 0) continue; + + // 冲突检查:同一科室组内,不同排班时间不能重叠 + if ($this->isTimeConflict($assignedPlans, $plan)) continue; + + $result[] = [ + 'id' => $item['id'], + 'plan_id' => $planId, + 'department_resources_name' => $plan->department_resources_name, + 'date' => $plan->date, + 'begin_time' => $plan->begin_time, + 'end_time' => $plan->end_time + ]; + $usedCapacity[$planId] = ($usedCapacity[$planId] ?? 0) + 1; + $assignedPlans[] = [ + 'resource_name' => $plan->department_resources_name, + 'begin_time' => $plan->begin_time, + 'end_time' => $plan->end_time + ]; + $assigned = true; + break; + } + if (!$assigned) { + $allInSlot = false; + break 2; + } + } + } + + if ($allInSlot) { + return \Yz::Return(true, '获取成功', $result); + } + } + + $currentDate->modify('+1 day'); + } + + return \Yz::Return(true, '获取成功', []); + } + // 获取指定日期的可用号源(已排序) private function getAvailablePlansForDate($regnum, $entrustids, $episodeid, $appointment_type, $date, $scope, $userDeptId) { diff --git a/Laravel/app/Services/LogService.php b/Laravel/app/Services/LogService.php index 15a2671..48c9da7 100644 --- a/Laravel/app/Services/LogService.php +++ b/Laravel/app/Services/LogService.php @@ -66,6 +66,33 @@ public static function CheckTableName(){ // 查看日志表是否存在,每 // 并发时另一个请求已先创建,忽略此异常 } } + public static function DoctorLoginLog($ip, $queryString, $params, $mark) + { + $tableName = 'zz_doctor_login_' . date('ym'); + if (!Schema::hasTable($tableName)) { + try { + Schema::create($tableName, function (Blueprint $table) { + $table->id(); + $table->string('mark', 50)->nullable(); + $table->string('request_ip', 45)->nullable(); + $table->text('query_string')->nullable(); + $table->text('params')->nullable(); + $table->string('create_time', 30); + $table->timestamps(); + }); + } catch (\Exception $e) { + // 并发时另一个请求已先创建,忽略 + } + } + DB::table($tableName)->insert([ + 'mark' => $mark, + 'request_ip' => $ip, + 'query_string' => $queryString, + 'params' => $params, + 'create_time' => date('Y-m-d H:i:s'), + ]); + } + public static function JsonEncode($data){ //格式化数据,转json $post_data =$data; foreach ($post_data as $key => $post_datum) { diff --git a/Laravel/config/app.php b/Laravel/config/app.php index d239345..3c4010b 100644 --- a/Laravel/config/app.php +++ b/Laravel/config/app.php @@ -3,6 +3,8 @@ return [ 'globals' => [ + 'FrontendUrl'=>env('FRONTEND_URL', 'http://192.168.80.76'), //前端页面地址 + 'YingGuBaseUrl'=>'http://192.168.0.1', //盈谷url diff --git a/Laravel/routes/web.php b/Laravel/routes/web.php index 70baa7b..039f2f0 100644 --- a/Laravel/routes/web.php +++ b/Laravel/routes/web.php @@ -2,6 +2,9 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Schema; +use Illuminate\Database\Schema\Blueprint; /* |-------------------------------------------------------------------------- @@ -32,8 +35,21 @@ //his跳到此路由,再跳转到对应的系统页面 Route::get('/yiji', function (Request $request) { $queryString = $request->server('QUERY_STRING'); // 获取原始查询字符串 + + // 记录医技预约跳转日志 + try { + \App\Services\LogService::DoctorLoginLog( + $request->ip(), + $queryString, + json_encode($request->all(), JSON_UNESCAPED_UNICODE), + '医技预约' + ); + } catch (\Exception $e) { + // 日志写入失败不影响主流程 + } + //return redirect("http://192.168.80.76/jq_page/appointment.html?".$queryString); - return redirect("http://192.168.80.76/admin/#/doctorappointment?".$queryString); + return redirect(config('app.globals.FrontendUrl')."/admin/#/doctorappointment?".$queryString); }); //cas 登录 diff --git a/YiJi-admin/src/components/Yewu/YuYue202506.vue b/YiJi-admin/src/components/Yewu/YuYue202506.vue index 0d72950..c6fd871 100644 --- a/YiJi-admin/src/components/Yewu/YuYue202506.vue +++ b/YiJi-admin/src/components/Yewu/YuYue202506.vue @@ -101,13 +101,13 @@ - 推荐最近时间号源 + 推荐最近时间号源 - 推荐同一天号源 + 推荐同一天号源 - - 推荐同一时段号源 + + 推荐同一天同一时间段号源
@@ -275,6 +275,7 @@ let TanChuangMsgDialogVisible=ref(false) let crossTimeDialogVisible = ref(false) let crossTimeDialogHtml = ref('') + let cachedCrossDialogData = ref(null) // 缓存跨时段 dialog 数据:{ html, idx } let resultDialogVisible = ref(false) let resultDialogHtml = ref('') const getWeekday = (date1) => { @@ -376,6 +377,17 @@ GetEnablePlanFunc(); } } + // 检查缓存中是否有跨时段 dialog 需要弹出 + if (cachedCrossDialogData.value) { + const currentIdx = entrustTableDate.value.findIndex( + r => selectedMianListId.value.includes(r.id) + ) + 1 + if (currentIdx === cachedCrossDialogData.value.idx) { + crossTimeDialogHtml.value = cachedCrossDialogData.value.html + crossTimeDialogVisible.value = true + cachedCrossDialogData.value = null + } + } checkSameSourceRoom() } const checkSameSourceRoom = () => { @@ -732,8 +744,17 @@ crossMsg += '' + p.idx + '' + p.entrust + '' + p.room + '' + p.date + '' + p.timeSlot + '' }) crossMsg += '' - crossTimeDialogHtml.value = crossMsg - crossTimeDialogVisible.value = true + // 首次自动推荐:比对序号,不一致则缓存 + const minIdx = Math.min(...crossParts.map(p => p.idx)) + const selectedIdx = entrustTableDate.value.findIndex( + r => selectedMianListId.value.includes(r.id) + ) + 1 + if (selectedIdx === minIdx) { + crossTimeDialogHtml.value = crossMsg + crossTimeDialogVisible.value = true + } else { + cachedCrossDialogData.value = { html: crossMsg, idx: minIdx } + } } // 更新高亮 @@ -1111,7 +1132,7 @@ } }) } - // 推荐同一时段号源 + // 推荐同一天同一时间段号源 const sameTimeSlotClick = () => { if (selectedEntrustId.value.length === 0) { ElMessage.error('请选择检查项目') @@ -1149,9 +1170,9 @@ .filter(v => !selectedMianListId.value.includes(v.id) && v.list_status === 0 && v.temp_plan_id) .map(v => v.temp_plan_id) - // 调用后端接口(复用同一天号源接口) + // 调用后端接口 GetOptimalPlan({ - type: 'full_day', + type: 'same_day_slot', regnum: props.reg_num, entrustid: selectedEntrustId.value, episodeid: props.episode_id, @@ -1848,7 +1869,7 @@ } .do_button_recommend { - width: 150px; + width: 180px; } .do_button { diff --git a/YiJi-admin/src/views/HisLogin.vue b/YiJi-admin/src/views/HisLogin.vue index 516d66e..5730ddc 100644 --- a/YiJi-admin/src/views/HisLogin.vue +++ b/YiJi-admin/src/views/HisLogin.vue @@ -1,59 +1,118 @@ - \ No newline at end of file diff --git a/YiJi-admin/src/views/Index.vue b/YiJi-admin/src/views/Index.vue index 0d0372b..a0357d1 100644 --- a/YiJi-admin/src/views/Index.vue +++ b/YiJi-admin/src/views/Index.vue @@ -33,7 +33,7 @@
- + {{BaseUserInfo.department_name}} @@ -50,7 +50,7 @@ -
{{BaseUserInfo.department_info[0].department_name}}
diff --git a/YiJi-admin/src/views/YeWu/MainList.vue b/YiJi-admin/src/views/YeWu/MainList.vue index c880ebe..a2ae070 100644 --- a/YiJi-admin/src/views/YeWu/MainList.vue +++ b/YiJi-admin/src/views/YeWu/MainList.vue @@ -299,6 +299,7 @@