343 lines
9.0 KiB
Markdown
343 lines
9.0 KiB
Markdown
# 患者挂号系统实现文档
|
||
|
||
## 功能概述
|
||
|
||
实现了基于医生排班的患者挂号系统,支持15分钟间隔的时间段预约,防止重复预约,并根据医生排班状态控制挂号可用性。
|
||
|
||
## 核心功能
|
||
|
||
### 1. 时间段管理
|
||
- 每个时间段间隔15分钟
|
||
- 上午时段:08:00 - 12:00(16个时间段)
|
||
- 下午时段:14:00 - 18:00(16个时间段)
|
||
- 每个时间段只能被一个患者预约
|
||
|
||
### 2. 排班状态控制
|
||
- 出诊(status=1):允许挂号
|
||
- 停诊(status=2):不允许挂号
|
||
- 休息(status=3):不允许挂号
|
||
- 请假(status=4):不允许挂号
|
||
|
||
### 3. 号源管理
|
||
- 医生排班时设置号源数(quota)
|
||
- 已预约数量不能超过号源数
|
||
- 实时显示可用/已占用状态
|
||
|
||
## 文件结构
|
||
|
||
```
|
||
admin/
|
||
├── src/
|
||
│ ├── api/
|
||
│ │ └── doctor.ts # 新增挂号相关API
|
||
│ └── views/
|
||
│ ├── doctor/
|
||
│ │ └── roster.vue # 医生排班管理(已存在)
|
||
│ └── tcm/
|
||
│ └── diagnosis/
|
||
│ ├── index.vue # 诊断列表(已更新)
|
||
│ └── appointment.vue # 挂号弹窗组件(新增)
|
||
```
|
||
|
||
## API接口
|
||
|
||
### 1. 获取可用时间段
|
||
```typescript
|
||
GET /doctor.appointment/availableSlots
|
||
参数:
|
||
{
|
||
doctor_id: number, // 医生ID
|
||
date: string, // 日期 YYYY-MM-DD
|
||
period: 'morning' | 'afternoon' // 时段
|
||
}
|
||
|
||
返回:
|
||
{
|
||
slots: [
|
||
{
|
||
time: '08:00', // 时间
|
||
available: true // 是否可预约
|
||
},
|
||
...
|
||
]
|
||
}
|
||
```
|
||
|
||
### 2. 创建挂号
|
||
```typescript
|
||
POST /doctor.appointment/create
|
||
参数:
|
||
{
|
||
patient_id: number, // 患者ID
|
||
doctor_id: number, // 医生ID
|
||
appointment_date: string, // 预约日期 YYYY-MM-DD
|
||
period: 'morning' | 'afternoon', // 时段
|
||
appointment_time: string, // 预约时间 HH:mm
|
||
remark: string // 备注(可选)
|
||
}
|
||
```
|
||
|
||
### 3. 取消挂号
|
||
```typescript
|
||
POST /doctor.appointment/cancel
|
||
参数:
|
||
{
|
||
id: number // 挂号记录ID
|
||
}
|
||
```
|
||
|
||
### 4. 挂号列表
|
||
```typescript
|
||
GET /doctor.appointment/lists
|
||
参数:
|
||
{
|
||
page_no: number,
|
||
page_size: number,
|
||
patient_id?: number,
|
||
doctor_id?: number,
|
||
date?: string
|
||
}
|
||
```
|
||
|
||
## 后端实现要点
|
||
|
||
### 1. 数据库表设计
|
||
|
||
```sql
|
||
CREATE TABLE `doctor_appointment` (
|
||
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||
`patient_id` int(11) NOT NULL COMMENT '患者ID',
|
||
`doctor_id` int(11) NOT NULL COMMENT '医生ID',
|
||
`roster_id` int(11) NOT NULL COMMENT '排班ID',
|
||
`appointment_date` date NOT NULL COMMENT '预约日期',
|
||
`period` enum('morning','afternoon') NOT NULL COMMENT '时段',
|
||
`appointment_time` time NOT NULL COMMENT '预约时间',
|
||
`status` tinyint(1) DEFAULT '1' COMMENT '状态:1=已预约,2=已取消,3=已完成',
|
||
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
|
||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
|
||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `unique_appointment` (`doctor_id`,`appointment_date`,`appointment_time`,`status`),
|
||
KEY `idx_patient` (`patient_id`),
|
||
KEY `idx_doctor_date` (`doctor_id`,`appointment_date`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医生挂号表';
|
||
```
|
||
|
||
### 2. 获取可用时间段逻辑
|
||
|
||
```php
|
||
public function availableSlots()
|
||
{
|
||
$doctorId = $this->request->get('doctor_id');
|
||
$date = $this->request->get('date');
|
||
$period = $this->request->get('period');
|
||
|
||
// 1. 检查医生排班
|
||
$roster = DoctorRoster::where([
|
||
'doctor_id' => $doctorId,
|
||
'date' => $date,
|
||
'period' => $period
|
||
])->find();
|
||
|
||
// 如果没有排班或状态不是出诊,返回空数组
|
||
if (!$roster || $roster->status != 1) {
|
||
return $this->success(['slots' => []]);
|
||
}
|
||
|
||
// 2. 生成时间段
|
||
$slots = [];
|
||
if ($period == 'morning') {
|
||
$startHour = 8;
|
||
$endHour = 12;
|
||
} else {
|
||
$startHour = 14;
|
||
$endHour = 18;
|
||
}
|
||
|
||
for ($hour = $startHour; $hour < $endHour; $hour++) {
|
||
for ($minute = 0; $minute < 60; $minute += 15) {
|
||
$time = sprintf('%02d:%02d', $hour, $minute);
|
||
$slots[] = [
|
||
'time' => $time,
|
||
'available' => true
|
||
];
|
||
}
|
||
}
|
||
|
||
// 3. 查询已预约的时间段
|
||
$appointments = DoctorAppointment::where([
|
||
'doctor_id' => $doctorId,
|
||
'appointment_date' => $date,
|
||
'period' => $period,
|
||
'status' => 1 // 只查询有效预约
|
||
])->column('appointment_time');
|
||
|
||
// 4. 标记已占用的时间段
|
||
foreach ($slots as &$slot) {
|
||
if (in_array($slot['time'], $appointments)) {
|
||
$slot['available'] = false;
|
||
}
|
||
}
|
||
|
||
// 5. 检查号源限制
|
||
$appointmentCount = count($appointments);
|
||
if ($appointmentCount >= $roster->quota) {
|
||
// 如果已达到号源上限,所有未预约的时间段也标记为不可用
|
||
foreach ($slots as &$slot) {
|
||
if ($slot['available']) {
|
||
$slot['available'] = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $this->success(['slots' => $slots]);
|
||
}
|
||
```
|
||
|
||
### 3. 创建挂号逻辑
|
||
|
||
```php
|
||
public function create()
|
||
{
|
||
$data = $this->request->post();
|
||
|
||
// 1. 验证参数
|
||
$validate = Validate::rule([
|
||
'patient_id|患者ID' => 'require|integer',
|
||
'doctor_id|医生ID' => 'require|integer',
|
||
'appointment_date|预约日期' => 'require|date',
|
||
'period|时段' => 'require|in:morning,afternoon',
|
||
'appointment_time|预约时间' => 'require'
|
||
]);
|
||
|
||
if (!$validate->check($data)) {
|
||
return $this->fail($validate->getError());
|
||
}
|
||
|
||
// 2. 检查医生排班
|
||
$roster = DoctorRoster::where([
|
||
'doctor_id' => $data['doctor_id'],
|
||
'date' => $data['appointment_date'],
|
||
'period' => $data['period']
|
||
])->find();
|
||
|
||
if (!$roster) {
|
||
return $this->fail('该医生当天未排班');
|
||
}
|
||
|
||
if ($roster->status != 1) {
|
||
return $this->fail('该医生当天不出诊');
|
||
}
|
||
|
||
// 3. 检查时间段是否已被预约
|
||
$exists = DoctorAppointment::where([
|
||
'doctor_id' => $data['doctor_id'],
|
||
'appointment_date' => $data['appointment_date'],
|
||
'appointment_time' => $data['appointment_time'],
|
||
'status' => 1
|
||
])->find();
|
||
|
||
if ($exists) {
|
||
return $this->fail('该时间段已被预约');
|
||
}
|
||
|
||
// 4. 检查号源是否已满
|
||
$appointmentCount = DoctorAppointment::where([
|
||
'doctor_id' => $data['doctor_id'],
|
||
'appointment_date' => $data['appointment_date'],
|
||
'period' => $data['period'],
|
||
'status' => 1
|
||
])->count();
|
||
|
||
if ($appointmentCount >= $roster->quota) {
|
||
return $this->fail('该时段号源已满');
|
||
}
|
||
|
||
// 5. 创建挂号记录
|
||
$appointment = DoctorAppointment::create([
|
||
'patient_id' => $data['patient_id'],
|
||
'doctor_id' => $data['doctor_id'],
|
||
'roster_id' => $roster->id,
|
||
'appointment_date' => $data['appointment_date'],
|
||
'period' => $data['period'],
|
||
'appointment_time' => $data['appointment_time'],
|
||
'remark' => $data['remark'] ?? '',
|
||
'status' => 1
|
||
]);
|
||
|
||
return $this->success('挂号成功', $appointment);
|
||
}
|
||
```
|
||
|
||
## 前端组件说明
|
||
|
||
### appointment.vue 组件
|
||
|
||
挂号弹窗组件,包含以下功能:
|
||
|
||
1. **医生选择**:从医生列表中选择
|
||
2. **日期选择**:不能选择过去的日期
|
||
3. **时段选择**:上午/下午
|
||
4. **时间段选择**:
|
||
- 网格布局显示所有时间段
|
||
- 可用时间段:蓝色,可点击
|
||
- 已占用时间段:灰色,不可点击
|
||
- 已选择时间段:深蓝色高亮
|
||
5. **备注输入**:可选的备注信息
|
||
|
||
### 使用方式
|
||
|
||
```vue
|
||
<template>
|
||
<appointment-popup ref="appointmentRef" @success="handleSuccess" />
|
||
</template>
|
||
|
||
<script setup>
|
||
import AppointmentPopup from './appointment.vue'
|
||
|
||
const appointmentRef = ref()
|
||
|
||
const handleAppointment = (patient) => {
|
||
appointmentRef.value?.open(patient)
|
||
}
|
||
|
||
const handleSuccess = () => {
|
||
// 挂号成功后的处理
|
||
console.log('挂号成功')
|
||
}
|
||
</script>
|
||
```
|
||
|
||
## 权限控制
|
||
|
||
在诊断列表中,挂号按钮使用权限指令:
|
||
|
||
```vue
|
||
<el-button
|
||
v-perms="['tcm.diagnosis/guahao']"
|
||
type="success"
|
||
link
|
||
@click="handleAppointment(row)"
|
||
>
|
||
挂号
|
||
</el-button>
|
||
```
|
||
|
||
需要在后端权限系统中添加对应的权限节点。
|
||
|
||
## 注意事项
|
||
|
||
1. **时区处理**:确保前后端时间格式一致
|
||
2. **并发控制**:使用数据库唯一索引防止重复预约
|
||
3. **事务处理**:创建挂号时使用事务确保数据一致性
|
||
4. **缓存策略**:可以对排班数据进行缓存提高性能
|
||
5. **通知机制**:挂号成功后可以发送短信/消息通知
|
||
|
||
## 扩展功能建议
|
||
|
||
1. **预约提醒**:在预约时间前发送提醒
|
||
2. **候补机制**:号源满时支持候补排队
|
||
3. **预约改期**:支持修改预约时间
|
||
4. **统计报表**:医生预约统计、患者预约历史
|
||
5. **评价系统**:就诊后患者评价医生
|