The missing calendar management for Laravel
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
| 5 个月前 | ||
| 11 个月前 | ||
| 5 个月前 | ||
| 8 个月前 | ||
| 6 个月前 | ||
| 2 个月前 | ||
| 2 个月前 | ||
| 11 个月前 | ||
| 11 个月前 | ||
| 1 年前 | ||
| 11 个月前 | ||
| 3 个月前 | ||
| 6 个月前 | ||
| 1 年前 | ||
| 8 个月前 |
目录
🎯 What is Zap?
Zap 是一款适用于 Laravel 的日历和日程安排包。为任何资源(医生、房间、员工等)定义可用时间、预约、屏蔽时间和自定义日程。
应用场景: 预约预订、医疗资源管理、员工排班、共享空间预订。
📦 Installation
系统要求: PHP ≥8.5 • Laravel ≥13.0
composer require laraveljutsu/zap
php artisan vendor:publish --provider="Zap\ZapServiceProvider"
UUID/ULID: 如果您的应用使用非整数主键,请在迁移之前阅读自定义模型支持。您可能需要修改迁移和配置。
php artisan migrate
让模型支持日程安排: 添加 HasSchedules trait。
use Zap\Models\Concerns\HasSchedules;
class Doctor extends Model
{
use HasSchedules;
}
🧩 核心概念
| 类型 | 用途 | 重叠规则 |
|---|---|---|
| Availability | 资源可被预订的时间 | 允许重叠 |
| Appointment | 已预订 / 已安排的事件 | 互斥 |
| Blocked | 禁止预订的时间 | 互斥 |
| Custom | 自定义规则(重叠等) | 自定义 |
🚀 快速开始
use Zap\Facades\Zap;
// 1. Working hours
Zap::for($doctor)
->named('Office Hours')
->availability()
->forYear(2025)
->addPeriod('09:00', '12:00')
->addPeriod('14:00', '17:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
->save();
// 2. Block lunch
Zap::for($doctor)
->named('Lunch Break')
->blocked()
->forYear(2025)
->addPeriod('12:00', '13:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])
->save();
// 3. Create an appointment
Zap::for($doctor)
->named('Patient A - Consultation')
->appointment()
->from('2025-01-15')
->addPeriod('10:00', '11:00')
->withMetadata(['patient_id' => 1, 'type' => 'consultation'])
->save();
// 4. Get bookable slots (60 min, 15 min buffer)
$slots = $doctor->getBookableSlots('2025-01-15', 60, 15);
// 5. Next available slot
$nextSlot = $doctor->getNextBookableSlot('2025-01-15', 60, 15);
💡 如果您偏好,可以使用
zap()辅助函数替代门面:zap()->for($doctor)->...
📅 日程模式
重复周期概览
| 模式 | 方法 / 示例 |
|---|---|
| 每日 | daily() |
| 每周(指定星期几) | weekly(['monday', 'friday']) |
| 每周 + 时间段 | weekDays(['monday', 'friday'], '09:00', '17:00') |
| 单/双周 | weeklyOdd()、weeklyEven()(+ weekOddDays / weekEvenDays) |
| 每两周 | biweekly(['tuesday'], $startsOn?) |
| 每月(指定日期) | monthly(['days_of_month' => [1, 15]]) |
| 每两月 / 每季度 / 每半年 / 每年 | bimonthly()、quarterly()、semiannually()、annually() + 配置 |
| 指定序数的星期几 | firstWednesdayOfMonth()、secondFridayOfMonth()、lastMondayOfMonth() |
| 每 N 周 | everyThreeWeeks(),… everyFiftyTwoWeeks() |
| 每 N 月 | everyFourMonths(),… everyElevenMonths() |
重复周期示例
每日和每周
$schedule->daily()->from('2025-01-01')->to('2025-12-31');
$schedule->weekly(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->weekDays(['monday', 'wednesday', 'friday'], '09:00', '17:00')->forYear(2025);
$schedule->weeklyOdd(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->weeklyEven(['monday', 'wednesday', 'friday'])->forYear(2025);
$schedule->biweekly(['tuesday', 'thursday'], '2025-01-07')->from('2025-01-07')->to('2025-03-31');
每月(按每月的日期)
$schedule->monthly(['days_of_month' => [1, 15]])->forYear(2025);
$schedule->bimonthly(['days_of_month' => [5, 20], 'start_month' => 2])->from('2025-01-05')->to('2025-06-30');
$schedule->quarterly(['days_of_month' => [7, 21], 'start_month' => 2])->from('2025-02-15')->to('2025-11-15');
$schedule->semiannually(['days_of_month' => [10], 'start_month' => 3])->from('2025-03-10')->to('2025-12-10');
$schedule->annually(['days_of_month' => [1, 15], 'start_month' => 4])->from('2025-04-01')->to('2026-04-01');
每月序数周几(每月的第 1、第 2、第 3、第 4 个或最后一个周几)
$schedule->firstWednesdayOfMonth()->forYear(2025); // Every 1st Wednesday
$schedule->secondFridayOfMonth()->forYear(2025); // Every 2nd Friday
$schedule->lastMondayOfMonth()->forYear(2025); // Every last Monday
// Also: thirdTuesdayOfMonth(), fourthSaturdayOfMonth(), lastSundayOfMonth(), etc.
动态间隔
$schedule->everyThreeWeeks(['monday', 'friday'])->from('2025-01-06')->to('2025-12-31');
$schedule->everyFourWeeks(['tuesday'], '2025-01-06')->from('2025-01-13');
$schedule->everyFourMonths(['day_of_month' => 15])->forYear(2025);
$schedule->everyFiveMonths(['days_of_month' => [1, 15], 'start_month' => 2])->forYear(2025);
日期范围
$schedule->from('2025-01-15'); // Start
$schedule->on('2025-01-15'); // Alias for from()
$schedule->from('2025-01-01')->to('2025-12-31'); // Range
$schedule->between('2025-01-01', '2025-12-31'); // Same
$schedule->forYear(2025); // Full year
时间段
$schedule->addPeriod('09:00', '17:00');
$schedule->addPeriod('09:00', '12:00');
$schedule->addPeriod('14:00', '17:00');
🔍 查询与可用性检查
| 需求 | 方法 |
|---|---|
| 今天是否有可预订时段? | $model->isBookableAt('2025-01-15', 60) |
| 时间段是否可预订? | $model->isBookableAtTime('2025-01-15', '09:00', '09:30') |
| 时间段是否可预订(自定义时段)? | $model->isBookableAtTime('2025-01-15', '09:30', '10:00', null, 30) |
| 时间段是否可预订(自定义时段 + 缓冲时间)? | $model->isBookableAtTime('2025-01-15', '09:45', '10:15', null, 30, 15) |
| 列出可预订时段 | $model->getBookableSlots('2025-01-15', 60, 15) |
| 下一个可预订时段 | $model->getNextBookableSlot('2025-01-15', 60, 15) |
| 日程冲突查询 | Zap::findConflicts($schedule) / Zap::hasConflicts($schedule) |
| 指定日期的日程 | $model->schedulesForDate('2025-01-15')->get() |
| 日期范围内的日程 | $model->schedulesForDateRange('2025-01-01', '2025-01-31')->get() |
| 按类型筛选 | $model->appointmentSchedules(), availabilitySchedules(), blockedSchedules() |
| 日程类型检查 | $schedule->isAvailability(), isAppointment(), isBlocked() |
⚠️
isAvailableAt()已弃用。建议使用isBookableAt()、isBookableAtTime()和getBookableSlots()。
💼 实际应用示例
医生
Zap::for($doctor)->named('Office Hours')->availability()->forYear(2025)
->addPeriod('09:00', '12:00')->addPeriod('14:00', '17:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])->save();
Zap::for($doctor)->named('Lunch Break')->blocked()->forYear(2025)
->addPeriod('12:00', '13:00')
->weekly(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'])->save();
Zap::for($doctor)->named('Patient A - Checkup')->appointment()
->from('2025-01-15')->addPeriod('10:00', '11:00')->withMetadata(['patient_id' => 1])->save();
$slots = $doctor->getBookableSlots('2025-01-15', 60, 15);
会议室
Zap::for($room)->named('Conference Room A')->availability()
->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '08:00', '18:00')
->forYear(2025)->save();
Zap::for($room)->named('Board Meeting')->appointment()
->from('2025-03-15')->addPeriod('09:00', '11:00')
->withMetadata(['organizer' => 'john@company.com'])->save();
员工(含休假)
Zap::for($employee)->named('Regular Shift')->availability()
->weekDays(['monday', 'tuesday', 'wednesday', 'thursday', 'friday'], '09:00', '17:00')
->forYear(2025)->save();
Zap::for($employee)->named('Vacation Leave')->blocked()
->between('2025-06-01', '2025-06-15')->addPeriod('00:00', '23:59')->save();
⚙️ 配置
发布资源:
php artisan vendor:publish --tag=zap-migrations
php artisan vendor:publish --tag=zap-config
config/zap.php 中的重要键:time_slots.buffer_minutes、default_rules.no_overlap、conflict_detection、validation。
🛡️ 高级功能
自定义日程安排和规则
Zap::for($user)->named('Custom Event')->custom()
->from('2025-01-15')->addPeriod('15:00', '16:00')->noOverlap()->save();
元数据
->withMetadata(['patient_id' => 1, 'type' => 'consultation', 'notes' => 'Follow-up'])
验证规则: noOverlap()、allowOverlap()、workingHoursOnly('09:00', '17:00')、maxDuration(120)、noWeekends()。
自定义模型支持(UUIDs)
如果您的应用使用UUIDs/ULIDs作为主键:
- 模型 — 扩展
Zap\Models\Schedule和Zap\Models\SchedulePeriod,添加 Laravel 的HasUuidstrait。同时,将HasUuids添加到您的可调度模型(例如Doctor)中。 - 配置 — 在
config/zap.php中,将models.schedule和models.schedule_period设置为您扩展的类。 - 迁移 — 发布迁移后,在 schedules 和 schedule_periods 表中,将
id()改为uuid('id')->primary(),将morphs('schedulable')改为uuidMorphs('schedulable'),并将foreignId('schedule_id')改为foreignUuid('schedule_id')。
请在运行迁移之前执行此操作。
🧠 AI 代理支持
Zap 提供 Laravel Boost 2.0 技能。安装 Boost 后,代理将获得有关 API 的准确知识。
| 技能 | 内容 |
|---|---|
zap-schedules |
类型、构建器 API、验证、冲突检测 |
zap-availability |
可预订时段、可用性检查、查询 |
zap-recurrence |
所有重复模式(每日、每周、单双周、每月、指定周几、动态) |
无需额外配置。
🤝 贡献
欢迎贡献。请遵循 PSR-12 标准并添加测试。
git clone https://github.com/ludoguenet/laravel-zap.git
cd laravel-zap
composer install
composer pest
📄 许可证
🔒 安全
请将问题报告发送至 ludo@epekta.com(请勿使用公共问题跟踪器)。
由 Ludovic Guénet 满怀 💛 为 Laravel 社区打造