Files
zyt/admin/APPOINTMENT_SYSTEM.md
T
2026-03-04 15:32:30 +08:00

9.0 KiB
Raw Blame History

患者挂号系统实现文档

功能概述

实现了基于医生排班的患者挂号系统,支持15分钟间隔的时间段预约,防止重复预约,并根据医生排班状态控制挂号可用性。

核心功能

1. 时间段管理

  • 每个时间段间隔15分钟
  • 上午时段:08:00 - 12:0016个时间段)
  • 下午时段:14:00 - 18:0016个时间段)
  • 每个时间段只能被一个患者预约

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. 获取可用时间段

GET /doctor.appointment/availableSlots
参数:
{
  doctor_id: number,    // 医生ID
  date: string,         // 日期 YYYY-MM-DD
  period: 'morning' | 'afternoon'  // 时段
}

返回:
{
  slots: [
    {
      time: '08:00',    // 时间
      available: true   // 是否可预约
    },
    ...
  ]
}

2. 创建挂号

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. 取消挂号

POST /doctor.appointment/cancel
参数:
{
  id: number  // 挂号记录ID
}

4. 挂号列表

GET /doctor.appointment/lists
参数:
{
  page_no: number,
  page_size: number,
  patient_id?: number,
  doctor_id?: number,
  date?: string
}

后端实现要点

1. 数据库表设计

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. 获取可用时间段逻辑

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. 创建挂号逻辑

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. 备注输入:可选的备注信息

使用方式

<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>

权限控制

在诊断列表中,挂号按钮使用权限指令:

<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. 评价系统:就诊后患者评价医生