技师排班的解决方案.md 29 KB


html: embed_local_images: false embed_svg: true offline: false toc: true

print_background: false export_on_save:

html: true

技师排班解决方案

目录

  1. 数据库设计
  2. 后端实现
  3. 前端实现

设计指标

功能特点

  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. 数据表结构

-- 技师表
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

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('technicians', function (Blueprint $table) {
            $table->id();
            $table->string('name', 50);
            $table->tinyInteger('status')->default(1);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('technicians');
    }
};

3. Model 定义

<?php
// app/Models/Technician.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Technician extends Model
{
    protected $fillable = ['name', 'status'];

    public function workRule(): HasOne
    {
        return $this->hasOne(TechnicianWorkRule::class);
    }

    public function specialDates(): HasMany
    {
        return $this->hasMany(TechnicianSpecialDate::class);
    }

    public function timeSlots(): HasMany
    {
        return $this->hasMany(TechnicianTimeSlot::class);
    }
}
<?php
// app/Models/TechnicianWorkRule.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TechnicianWorkRule extends Model
{
    protected $fillable = [
        'technician_id',
        'work_days',
        'start_time',
        'end_time',
        'is_active'
    ];

    protected $casts = [
        'is_active' => '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
// app/Models/TechnicianSpecialDate.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TechnicianSpecialDate extends Model
{
    protected $fillable = [
        'technician_id',
        'date',
        'is_working',
        'start_time',
        'end_time'
    ];

    protected $casts = [
        'date' => 'date',
        'is_working' => 'boolean',
        'start_time' => 'datetime',
        'end_time' => 'datetime'
    ];

    public function technician(): BelongsTo
    {
        return $this->belongsTo(Technician::class);
    }
}
<?php
// app/Models/TechnicianTimeSlot.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class TechnicianTimeSlot extends Model
{
    protected $fillable = [
        'technician_id',
        'date',
        'start_time',
        'end_time',
        'status'
    ];

    protected $casts = [
        'date' => '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
// app/Services/TechnicianScheduleService.php
namespace App\Services;

use App\Models\TechnicianWorkRule;
use App\Models\TechnicianSpecialDate;
use App\Models\TechnicianTimeSlot;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use DB;
use Exception;

class TechnicianScheduleService
{
    /**
     * 设置技师工作规则
     */
    public function setWorkRule(
        int $technicianId, 
        array $workDays, 
        string $startTime = '08:00', 
        string $endTime = '18:00'
    ): void {
        try {
            DB::beginTransaction();

            TechnicianWorkRule::updateOrCreate(
                ['technician_id' => $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
// app/Http/Controllers/Api/TechnicianScheduleController.php
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Services\TechnicianScheduleService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Exception;

class TechnicianScheduleController extends Controller
{
    private TechnicianScheduleService $scheduleService;

    public function __construct(TechnicianScheduleService $scheduleService)
    {
        $this->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. 路由定义

// 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. 工作规则设置页面

<!-- pages/technician/schedule/index.vue -->
<template>
  <view class="schedule-setting">
    <!-- 工作日设置 -->
    <uni-section title="工作日设置" type="line">
      <uni-list>
        <uni-list-item v-for="day in weekDays" :key="day.value"
          :title="day.label"
          :switchChecked="workDays.includes(day.value)"
          @switch-change="(e) => toggleWorkDay(day.value, e.value)"
          showSwitch
        />
      </uni-list>
    </uni-section>

    <!-- 工作时间设置 -->
    <uni-section title="工作时间设置" type="line">
      <uni-list>
        <uni-list-item title="上班时间" showArrow @click="showStartTimePicker = true">
          <template v-slot:footer>
            <text>{{ startTime }}</text>
          </template>
        </uni-list-item>
        <uni-list-item title="下班时间" showArrow @click="showEndTimePicker = true">
          <template v-slot:footer>
            <text>{{ endTime }}</text>
          </template>
        </uni-list-item>
      </uni-list>
    </uni-section>

    <!-- 保存按钮 -->
    <view class="button-group">
      <button class="primary-button" @click="saveWorkRule">保存设置</button>
    </view>

    <!-- 特殊日期设置 -->
    <uni-section title="特殊日期设置" type="line">
      <button class="secondary-button" @click="showCalendar = true">
        设置休息日/特殊工作日
      </button>
    </uni-section>

    <!-- 时间选择器 -->
    <uni-datetime-picker
      v-model="showStartTimePicker"
      type="time"
      :value="startTime"
      @confirm="onStartTimeChange"
    />
    <uni-datetime-picker
      v-model="showEndTimePicker"
      type="time"
      :value="endTime"
      @confirm="onEndTimeChange"
    />

    <!-- 日历选择器 -->
    <uni-calendar
      v-model="showCalendar"
      :insert="false"
      @confirm="onSelectSpecialDate"
      :start-date="startDate"
      :end-date="endDate"
    />
  </view>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

// 工作日配置
const weekDays = [
  { label: '周一', value: 1 },
  { label: '周二', value: 2 },
  { label: '周三', value: 3 },
  { label: '周四', value: 4 },
  { label: '周五', value: 5 },
  { label: '周六', value: 6 },
  { label: '周日', value: 0 }
]

// 响应式数据
const workDays = ref([1, 2, 3, 4, 5])
const startTime = ref('08:00')
const endTime = ref('18:00')
const showStartTimePicker = ref(false)
const showEndTimePicker = ref(false)
const showCalendar = ref(false)

// 日期范围
const startDate = new Date()
const endDate = new Date(Date.now() + 90 * 24 * 60 * 60 * 1000)

// 获取技师当前设置
const getCurrentSettings = async () => {
  try {
    const res = await uni.request({
      url: '/api/technician/work-rule',
      method: 'GET',
      data: {
        technician_id: getApp().globalData.technicianId
      }
    })
    
    if (res.statusCode === 200) {
      const { work_days, start_time, end_time } = res.data.data
      workDays.value = work_days.split(',').map(Number)
      startTime.value = start_time
      endTime.value = end_time
    }
  } catch (error) {
    uni.showToast({
      title: '获取设置失败',
      icon: 'none'
    })
  }
}

// 切换工作日
const toggleWorkDay = (day, checked) => {
  if (checked) {
    workDays.value.push(day)
  } else {
    const index = workDays.value.indexOf(day)
    if (index > -1) {
      workDays.value.splice(index, 1)
    }
  }
}

// 时间选择回调
const onStartTimeChange = (time) => {
  startTime.value = time
}

const onEndTimeChange = (time) => {
  endTime.value = time
}

// 保存工作规则
const saveWorkRule = async () => {
  try {
    const res = await uni.request({
      url: '/api/technician/work-rule',
      method: 'POST',
      data: {
        technician_id: getApp().globalData.technicianId,
        work_days: workDays.value,
        start_time: startTime.value,
        end_time: endTime.value
      }
    })

    if (res.statusCode === 200) {
      uni.showToast({
        title: '设置成功',
        icon: 'success'
      })
    } else {
      throw new Error(res.data.message)
    }
  } catch (error) {
    uni.showToast({
      title: error.message || '设置失败',
      icon: 'none'
    })
  }
}

// 页面加载时获取当前设置
onLoad(() => {
  getCurrentSettings()
})
</script>

<style lang="scss">
.schedule-setting {
  padding: 20rpx;
  
  .button-group {
    margin: 30rpx 0;
  }
  
  .primary-button {
    background-color: #007AFF;
    color: #fff;
    border-radius: 10rpx;
    padding: 20rpx;
    text-align: center;
  }
  
  .secondary-button {
    background-color: #F8F8F8;
    color: #333;
    border: 1px solid #DDDDDD;
    border-radius: 10rpx;
    padding: 20rpx;
    text-align: center;
    margin-top: 20rpx;
  }
}
</style>

2. 特殊日期设置页面

<!-- pages/technician/schedule/special-date.vue -->
<template>
  <view class="special-date">
    <uni-forms ref="form" :modelValue="formData">
      <uni-forms-item label="日期">
        <text>{{ date }}</text>
      </uni-forms-item>
      
      <uni-forms-item label="是否工作">
        <switch :checked="formData.is_working" @change="onWorkingChange" />
      </uni-forms-item>
      
      <template v-if="formData.is_working">
        <uni-forms-item label="上班时间">
          <view class="time-picker" @click="showStartTimePicker = true">
            {{ formData.start_time || '请选择' }}
          </view>
        </uni-forms-item>
        
        <uni-forms-item label="下班时间">
          <view class="time-picker" @click="showEndTimePicker = true">
            {{ formData.end_time || '请选择' }}
          </view>
        </uni-forms-item>
      </template>
    </uni-forms>
    
    <view class="button-group">
      <button class="primary-button" @click="saveSpecialDate">保存设置</button>
    </view>

    <!-- 时间选择器 -->
    <uni-datetime-picker
      v-model="showStartTimePicker"
      type="time"
      :value="formData.start_time"
      @confirm="onStartTimeChange"
    />
    <uni-datetime-picker
      v-model="showEndTimePicker"
      type="time"
      :value="formData.end_time"
      @confirm="onEndTimeChange"
    />
  </view>
</template>

<script setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'

const date = ref('')
const formData = ref({
  is_working: false,
  start_time: '',
  end_time: ''
})

const showStartTimePicker = ref(false)
const showEndTimePicker = ref(false)

// 获取特殊日期设置
const getSpecialDateSettings = async (date) => {
  try {
    const res = await uni.request({
      url: '/api/technician/special-date',
      method: 'GET',
      data: {
        technician_id: getApp().globalData.technicianId,
        date: date
      }
    })
    
    if (res.statusCode === 200) {
      formData.value = res.data.data || {
        is_working: false,
        start_time: '',
        end_time: ''
      }
    }
  } catch (error) {
    uni.showToast({
      title: '获取设置失败',
      icon: 'none'
    })
  }
}

// 工作状态切换
const onWorkingChange = (e) => {
  formData.value.is_working = e.detail.value
}

// 时间选择回调
const onStartTimeChange = (time) => {
  formData.value.start_time = time
}

const onEndTimeChange = (time) => {
  formData.value.end_time = time
}

// 保存特殊日期设置
const saveSpecialDate = async () => {
  try {
    const res = await uni.request({
      url: '/api/technician/special-date',
      method: 'POST',
      data: {
        technician_id: getApp().globalData.technicianId,
        date: date.value,
        ...formData.value
      }
    })

    if (res.statusCode === 200) {
      uni.showToast({
        title: '设置成功',
        icon: 'success'
      })
      setTimeout(() => {
        uni.navigateBack()
      }, 1500)
    } else {
      throw new Error(res.data.message)
    }
  } catch (error) {
    uni.showToast({
      title: error.message || '设置失败',
      icon: 'none'
    })
  }
}

// 页面加载
onLoad((options) => {
  date.value = options.date
  getSpecialDateSettings(options.date)
})
</script>

<style lang="scss">
.special-date {
  padding: 20rpx;
  
  .time-picker {
    border: 1px solid #DDDDDD;
    padding: 20rpx;
    border-radius: 10rpx;
  }
  
  .button-group {
    margin: 30rpx 0;
  }
  
  .primary-button {
    background-color: #007AFF;
    color: #fff;
    border-radius: 10rpx;
    padding: 20rpx;
    text-align: center;
  }
}
</style>

3. 获取技师可用时间例子

<template>
  <div>
    <van-datetime-picker
      v-model="selectedDate"
      type="date"
      :min-date="minDate"
      :max-date="maxDate"
      @confirm="onDateSelected"
    />
    
    <van-picker
      v-if="timeSlots.length"
      :columns="timeSlots"
      @confirm="onTimeSelected"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import axios from 'axios'
import { showToast } from 'vant'

const selectedDate = ref(new Date())
const minDate = new Date()
const maxDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
const timeSlots = ref([])

const getAvailableTimeSlots = async (date) => {
  try {
    const response = await axios.get('/api/technician/available-time-slots', {
      params: {
        technician_id: 1, // 技师ID
        date: date,
        service_duration: 60 // 服务时长(分钟)
      }
    })
    
    timeSlots.value = response.data.data.map(slot => 
      `${slot.start_time}-${slot.end_time}`
    )
  } catch (error) {
    showToast(error.response?.data?.message || '获取可用时间失败')
  }
}

const onDateSelected = (value) => {
  const formatDate = value.toISOString().split('T')[0]
  getAvailableTimeSlots(formatDate)
}

const onTimeSelected = (value) => {
  // 处理时间段选择
  console.log('Selected time slot:', value)
}
</script>

4. API 请求封装

// 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 接口定义

// 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. 页面配置

// pages.json
{
  "pages": [
    {
      "path": "pages/technician/schedule/index",
      "style": {
        "navigationBarTitleText": "排班设置"
      }
    },
    {
      "path": "pages/technician/schedule/special-date",
      "style": {
        "navigationBarTitleText": "特殊日期设置"
      }
    }
  ]
}