--- html: embed_local_images: false embed_svg: true offline: false toc: true print_background: false export_on_save: html: true --- # 技师排班解决方案 ## 目录 1. [数据库设计](#数据库设计) 2. [后端实现](#后端实现) - [Model 定义](#model-定义) - [Service 层](#service-层) - [Controller 层](#controller-层) 3. [前端实现](#前端实现) - [工作规则设置页面](#工作规则设置页面) - [特殊日期设置页面](#特殊日期设置页面) - [API 封装](#api-封装) ## 设计指标 ### 功能特点 1. 技师可以设置默认工作规则 2. 支持设置特殊日期(休假或特殊工作日) 3. 自动生成30分钟时间槽 4. 预约后自动预留间隔时间 5. 完整的事务处理和并发控制 ### 可扩展功能 1. 节假日自动识别 2. 批量设置特殊日期 3. 工作规则模板 4. 临时调整工作时间 5. 规则生效时间设置 6. 规则复制功能 ### 技术特点 1. 使用 Laravel + UniApp (Vue3) 技术栈 2. TypeScript 类型支持 3. 完整的错误处理 4. 统一的请求封装 5. 模块化的 API 管理 6. 响应式界面设计 ### 性能优化 1. 数据库索引优化 2. 事务处理 3. 并发控制 4. 缓存支持 5. 批量操作优化 ## 数据库设计 ### 1. 数据表结构 ```sql -- 技师表 CREATE TABLE technicians ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, status TINYINT DEFAULT 1 COMMENT '1:正常,0:停用', created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL ); -- 技师工作规则表 CREATE TABLE technician_work_rules ( id INT PRIMARY KEY AUTO_INCREMENT, technician_id INT NOT NULL, work_days VARCHAR(50) NOT NULL COMMENT '工作日 例如:1,2,3,4,5 表示周一到周五', start_time TIME NOT NULL DEFAULT '08:00' COMMENT '默认上班时间', end_time TIME NOT NULL DEFAULT '18:00' COMMENT '默认下班时间', is_active TINYINT DEFAULT 1 COMMENT '是否启用', created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, UNIQUE KEY idx_technician (technician_id) ); -- 技师特殊日期表(休假或特殊工作日) CREATE TABLE technician_special_dates ( id INT PRIMARY KEY AUTO_INCREMENT, technician_id INT NOT NULL, date DATE NOT NULL, is_working TINYINT DEFAULT 0 COMMENT '1:特殊工作日,0:休息日', start_time TIME NULL COMMENT '特殊工作日的上班时间', end_time TIME NULL COMMENT '特殊工作日的下班时间', created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, UNIQUE KEY idx_technician_date (technician_id, date) ); -- 技师时间槽表 CREATE TABLE technician_time_slots ( id INT PRIMARY KEY AUTO_INCREMENT, technician_id INT NOT NULL, date DATE NOT NULL, start_time TIME NOT NULL, end_time TIME NOT NULL, status TINYINT DEFAULT 1 COMMENT '1:可预约,0:已预约,2:预留间隔', created_at TIMESTAMP NULL, updated_at TIMESTAMP NULL, INDEX idx_technician_date (technician_id, date, status) ); ``` ### 2. Laravel Migration 文件 ```php id(); $table->string('name', 50); $table->tinyInteger('status')->default(1); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('technicians'); } }; ``` ### 3. Model 定义 ```php hasOne(TechnicianWorkRule::class); } public function specialDates(): HasMany { return $this->hasMany(TechnicianSpecialDate::class); } public function timeSlots(): HasMany { return $this->hasMany(TechnicianTimeSlot::class); } } ``` ```php 'boolean', 'start_time' => 'datetime', 'end_time' => 'datetime' ]; public function technician(): BelongsTo { return $this->belongsTo(Technician::class); } public function getWorkDaysArrayAttribute(): array { return array_map('intval', explode(',', $this->work_days)); } } ``` ```php 'date', 'is_working' => 'boolean', 'start_time' => 'datetime', 'end_time' => 'datetime' ]; public function technician(): BelongsTo { return $this->belongsTo(Technician::class); } } ``` ```php 'date', 'start_time' => 'datetime', 'end_time' => 'datetime' ]; public function technician(): BelongsTo { return $this->belongsTo(Technician::class); } } class TechnicianSchedule extends Model { protected $casts = [ 'work_time' => 'array', 'work_date' => 'date', ]; public function technician() { return $this->belongsTo(Technician::class); } public function workPlans() { return $this->hasMany(TechnicianWorkPlan::class, 'technician_id', 'technician_id') ->where('work_date', $this->work_date); } } ``` ## 后端实现 ### 1. Service 层实现 ```php $technicianId], [ 'work_days' => implode(',', $workDays), 'start_time' => $startTime, 'end_time' => $endTime, 'is_active' => true ] ); // 生成未来30天的时间槽 $this->generateFutureTimeSlots($technicianId); DB::commit(); } catch (Exception $e) { DB::rollBack(); throw $e; } } /** * 生成时间槽 */ public function generateTimeSlots(int $technicianId, string $date): void { $dateObj = Carbon::parse($date); $dayOfWeek = $dateObj->dayOfWeek; // 获取工作规则 $workRule = TechnicianWorkRule::where('technician_id', $technicianId) ->where('is_active', true) ->first(); // 获取特殊日期设置 $specialDate = TechnicianSpecialDate::where('technician_id', $technicianId) ->where('date', $date) ->first(); // 判断是否工作日 $isWorkDay = false; $startTime = null; $endTime = null; if ($specialDate) { // 特殊日期优先 $isWorkDay = $specialDate->is_working; $startTime = $specialDate->start_time; $endTime = $specialDate->end_time; } elseif ($workRule && in_array($dayOfWeek, $workRule->work_days_array)) { // 普通工作日 $isWorkDay = true; $startTime = $workRule->start_time; $endTime = $workRule->end_time; } if (!$isWorkDay) { return; } try { DB::beginTransaction(); // 删除原有时间槽 TechnicianTimeSlot::where('technician_id', $technicianId) ->where('date', $date) ->delete(); // 生成30分钟时间槽 $currentTime = Carbon::parse($startTime); $endDateTime = Carbon::parse($endTime); while ($currentTime->copy()->addMinutes(30) <= $endDateTime) { TechnicianTimeSlot::create([ 'technician_id' => $technicianId, 'date' => $date, 'start_time' => $currentTime->format('H:i:s'), 'end_time' => $currentTime->copy()->addMinutes(30)->format('H:i:s'), 'status' => 1 ]); $currentTime->addMinutes(30); } DB::commit(); } catch (Exception $e) { DB::rollBack(); throw $e; } } } ``` ### 2. Controller 层实现 ```php scheduleService = $scheduleService; } /** * 设置工作规则 */ public function setWorkRule(Request $request): JsonResponse { $request->validate([ 'technician_id' => 'required|exists:technicians,id', 'work_days' => 'required|array', 'work_days.*' => 'integer|between:0,6', 'start_time' => 'required|date_format:H:i', 'end_time' => 'required|date_format:H:i|after:start_time' ]); try { $this->scheduleService->setWorkRule( $request->technician_id, $request->work_days, $request->start_time, $request->end_time ); return response()->json([ 'message' => '工作规则设置成功' ]); } catch (Exception $e) { return response()->json([ 'message' => '工作规则设置失败:' . $e->getMessage() ], 500); } } /** * 设置特殊日期 */ public function setSpecialDate(Request $request): JsonResponse { $request->validate([ 'technician_id' => 'required|exists:technicians,id', 'date' => 'required|date|after:today', 'is_working' => 'required|boolean', 'start_time' => 'required_if:is_working,true|date_format:H:i|nullable', 'end_time' => 'required_if:is_working,true|date_format:H:i|after:start_time|nullable' ]); try { $this->scheduleService->setSpecialDate( $request->technician_id, $request->date, $request->is_working, $request->start_time, $request->end_time ); return response()->json([ 'message' => '特殊日期设置成功' ]); } catch (Exception $e) { return response()->json([ 'message' => '特殊日期设置失败:' . $e->getMessage() ], 500); } } /** * 获取技师可预约时间段 * @param Request $request * @return JsonResponse */ public function getAvailableTimeSlots(Request $request): JsonResponse { $request->validate([ 'technician_id' => 'required|exists:technicians,id', 'date' => 'required|date|after_or_equal:today', 'service_duration' => 'required|integer|min:30', // 服务时长(分钟) ]); try { // 获取技师当天排班 $schedule = TechnicianSchedule::where('technician_id', $request->technician_id) ->where('work_date', $request->date) ->first(); if (!$schedule) { return response()->json([ 'message' => '技师当天未排班', 'data' => [] ]); } // 获取已预约的时间段 $bookedTimeSlots = TechnicianWorkPlan::where('technician_id', $request->technician_id) ->where('date', $request->date) ->where('status', '!=', 'cancelled') ->get(['plan_start_time', 'plan_end_time']) ->toArray(); // 计算可用时间段 $availableTimeSlots = $this->calculateAvailableTimeSlots( $schedule->work_time, $bookedTimeSlots, $request->service_duration ); return response()->json([ 'message' => 'success', 'data' => $availableTimeSlots ]); } catch (\Exception $e) { return response()->json([ 'message' => $e->getMessage(), 'data' => [] ], 500); } } /** * 计算可用时间段 * @param array $workTime 工作时间 [{"start":"09:00","end":"18:00"}] * @param array $bookedTimeSlots 已预约时间段 * @param int $serviceDuration 服务时长(分钟) * @return array */ private function calculateAvailableTimeSlots(array $workTime, array $bookedTimeSlots, int $serviceDuration): array { $availableSlots = []; foreach ($workTime as $period) { $startTime = strtotime($period['start']); $endTime = strtotime($period['end']); // 按30分钟间隔切分时间段 $currentSlot = $startTime; while ($currentSlot + ($serviceDuration * 60) <= $endTime) { $slotEnd = $currentSlot + ($serviceDuration * 60); // 检查是否与已预约时间段冲突 $isAvailable = true; foreach ($bookedTimeSlots as $bookedSlot) { $bookedStart = strtotime($bookedSlot['plan_start_time']); $bookedEnd = strtotime($bookedSlot['plan_end_time']); if ($currentSlot < $bookedEnd && $slotEnd > $bookedStart) { $isAvailable = false; break; } } if ($isAvailable) { $availableSlots[] = [ 'start_time' => date('H:i', $currentSlot), 'end_time' => date('H:i', $slotEnd) ]; } $currentSlot += 1800; // 30分钟 } } return $availableSlots; } } ``` ### 3. 路由定义 ```php // routes/api.php Route::prefix('technician')->group(function () { // 技师工作规则设置 Route::post('work-rule', [TechnicianScheduleController::class, 'setWorkRule']); // 特殊日期设置 Route::post('special-date', [TechnicianScheduleController::class, 'setSpecialDate']); // 获取可用时间槽 Route::get('time-slots', [TechnicianScheduleController::class, 'getAvailableTimeSlots']); // 预约时间槽 Route::post('book', [TechnicianScheduleController::class, 'bookTimeSlot']); }); ``` ## 前端实现 (UniApp Vue3) ### 1. 工作规则设置页面 ```html ``` ### 2. 特殊日期设置页面 ```html ``` ### 3. 获取技师可用时间例子 ```html ``` ### 4. API 请求封装 ```typescript // utils/request.ts import { baseURL } from '@/config' interface RequestOptions extends UniApp.RequestOptions { loading?: boolean; } const request = (options: RequestOptions) => { const { loading = true } = options if (loading) { uni.showLoading({ title: '加载中' }) } return new Promise((resolve, reject) => { uni.request({ ...options, url: baseURL + options.url, header: { ...options.header, 'Authorization': `Bearer ${uni.getStorageSync('token')}` }, success: (res) => { if (res.statusCode === 200) { resolve(res.data) } else { reject(res.data) } }, fail: (err) => { reject(err) }, complete: () => { if (loading) { uni.hideLoading() } } }) }) } export default request ``` ### 5. API 接口定义 ```typescript // api/technician.ts import request from '@/utils/request' // 获取工作规则 export const getWorkRule = (technicianId: number) => { return request({ url: '/technician/work-rule', method: 'GET', data: { technician_id: technicianId } }) } // 设置工作规则 export const setWorkRule = (data: { technician_id: number; work_days: number[]; start_time: string; end_time: string; }) => { return request({ url: '/technician/work-rule', method: 'POST', data }) } // 获取特殊日期设置 export const getSpecialDate = (technicianId: number, date: string) => { return request({ url: '/technician/special-date', method: 'GET', data: { technician_id: technicianId, date } }) } // 设置特殊日期 export const setSpecialDate = (data: { technician_id: number; date: string; is_working: boolean; start_time?: string; end_time?: string; }) => { return request({ url: '/technician/special-date', method: 'POST', data }) } // 获取可用时间槽 export const getTimeSlots = (technicianId: number, date: string) => { return request({ url: '/technician/time-slots', method: 'GET', data: { technician_id: technicianId, date } }) } ``` ### 6. 页面配置 ```json // pages.json { "pages": [ { "path": "pages/technician/schedule/index", "style": { "navigationBarTitleText": "排班设置" } }, { "path": "pages/technician/schedule/special-date", "style": { "navigationBarTitleText": "特殊日期设置" } } ] } ```