技術 6 分鐘閱讀
從零開始:用 Laravel + React 建一個訂位系統
前言
最近幫一個客戶做了一個餐廳訂位系統,從需求訪談到上線大約花了六週。這篇分享整個開發過程中的架構設計與踩過的坑。
需求分析
客戶是一間中型餐廳,主要需求:
- 顧客可以線上選擇日期、時段、人數來訂位
- 後台可以管理訂位、查看每日訂位狀況
- 自動發送訂位確認信和提醒通知
- 處理 No-Show 問題
資料庫設計
// 餐廳時段設定
Schema::create('time_slots', function (Blueprint $table) {
$table->id();
$table->time('start_time');
$table->time('end_time');
$table->integer('max_capacity');
$table->boolean('is_active')->default(true);
$table->timestamps();
});
// 訂位記錄
Schema::create('reservations', function (Blueprint $table) {
$table->id();
$table->date('date');
$table->foreignId('time_slot_id')->constrained();
$table->string('customer_name');
$table->string('customer_phone');
$table->string('customer_email')->nullable();
$table->integer('party_size');
$table->enum('status', ['pending', 'confirmed', 'cancelled', 'completed', 'no_show']);
$table->text('note')->nullable();
$table->timestamps();
});
核心邏輯:可用時段計算
class ReservationService
{
public function getAvailableSlots(string $date): Collection
{
$slots = TimeSlot::where('is_active', true)->get();
return $slots->map(function ($slot) use ($date) {
$reserved = Reservation::where('date', $date)
->where('time_slot_id', $slot->id)
->whereNotIn('status', ['cancelled'])
->sum('party_size');
return [
'id' => $slot->id,
'start_time' => $slot->start_time,
'end_time' => $slot->end_time,
'remaining' => $slot->max_capacity - $reserved,
'available' => ($slot->max_capacity - $reserved) > 0,
];
});
}
}
前端:日期選擇器元件
import { useState } from 'react';
import { router } from '@inertiajs/react';
function BookingForm({ availableSlots }: { availableSlots: TimeSlot[] }) {
const [form, setForm] = useState({
date: '',
time_slot_id: '',
party_size: 2,
customer_name: '',
customer_phone: '',
});
const handleSubmit = () => {
router.post('/reservations', form);
};
return (
<div className="mx-auto max-w-lg space-y-4">
<input
type="date"
min={new Date().toISOString().split('T')[0]}
value={form.date}
onChange={e => setForm({ ...form, date: e.target.value })}
className="w-full rounded-md border p-2"
/>
<div className="grid grid-cols-3 gap-2">
{availableSlots.map(slot => (
<button
key={slot.id}
disabled={!slot.available}
onClick={() => setForm({ ...form, time_slot_id: String(slot.id) })}
className={`rounded-md p-2 ${
slot.available ? 'bg-green-100 hover:bg-green-200' : 'bg-gray-100 text-gray-400'
}`}
>
{slot.start_time}
<span className="block text-xs">剩 {slot.remaining} 位</span>
</button>
))}
</div>
<button onClick={handleSubmit} className="w-full rounded-md bg-blue-600 py-3 text-white">
確認訂位
</button>
</div>
);
}
通知功能
使用 Laravel Notification 發送確認信和提醒:
// 訂位確認後排程提醒
$reservation->notify(new ReservationConfirmed());
// 排程:訂位前一天提醒
Schedule::command('reservations:send-reminders')->dailyAt('10:00');
學到的經驗
- 時段計算要考慮「用餐時間」,不能讓兩組客人重疊
- 搭配 Seeder 產生假資料 來測試各種滿桌情境很重要
- 手機號碼驗證要做好,台灣手機格式 09xx-xxx-xxx
如果你需要類似的系統,歡迎聯繫討論。
#Laravel #React #訂位系統