# 患者挂号系统实现文档
## 功能概述
实现了基于医生排班的患者挂号系统,支持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
```
## 权限控制
在诊断列表中,挂号按钮使用权限指令:
```vue
挂号
```
需要在后端权限系统中添加对应的权限节点。
## 注意事项
1. **时区处理**:确保前后端时间格式一致
2. **并发控制**:使用数据库唯一索引防止重复预约
3. **事务处理**:创建挂号时使用事务确保数据一致性
4. **缓存策略**:可以对排班数据进行缓存提高性能
5. **通知机制**:挂号成功后可以发送短信/消息通知
## 扩展功能建议
1. **预约提醒**:在预约时间前发送提醒
2. **候补机制**:号源满时支持候补排队
3. **预约改期**:支持修改预约时间
4. **统计报表**:医生预约统计、患者预约历史
5. **评价系统**:就诊后患者评价医生