---
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
toggleWorkDay(day.value, e.value)"
showSwitch
/>
{{ startTime }}
{{ endTime }}
```
### 2. 特殊日期设置页面
```html
{{ date }}
{{ formData.start_time || '请选择' }}
{{ formData.end_time || '请选择' }}
```
### 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": "特殊日期设置"
}
}
]
}
```