Browse Source

feat:技师-获取抢单列表

刘学玺 4 months ago
parent
commit
988e13da74

+ 79 - 0
app/Admin/Controllers/CoachRealRecordController.php

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\CoachRealRecordService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 技师实名认证
+ *
+ * @property CoachRealRecordService $service
+ */
+class CoachRealRecordController extends AdminController
+{
+	protected string $serviceName = CoachRealRecordService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('coach_id', '技师编号'),
+				amis()->TableColumn('real_name', '姓名'),
+				amis()->TableColumn('id_card', '身份证号'),
+				amis()->TableColumn('id_card_front_photo', '身份证正面照片'),
+				amis()->TableColumn('id_card_back_photo', '身份证反面照片'),
+				amis()->TableColumn('id_card_hand_photo', '手持身份证照片'),
+				amis()->TableColumn('auditor', '审核人'),
+				amis()->TableColumn('audit_time', '审核时间'),
+				amis()->TableColumn('audit_remark', '审核回执'),
+				amis()->TableColumn('state', '状态')->sortable(),
+				amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+				amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+				$this->rowActions('dialog')
+			]);
+
+		return $this->baseList($crud);
+	}
+
+	public function form($isEdit = false)
+	{
+		return $this->baseForm()->body([
+			amis()->TextControl('coach_id', '技师编号'),
+			amis()->TextControl('real_name', '姓名'),
+			amis()->TextControl('id_card', '身份证号'),
+			amis()->TextControl('id_card_front_photo', '身份证正面照片'),
+			amis()->TextControl('id_card_back_photo', '身份证反面照片'),
+			amis()->TextControl('id_card_hand_photo', '手持身份证照片'),
+			amis()->TextControl('auditor', '审核人'),
+			amis()->TextControl('audit_time', '审核时间'),
+			amis()->TextControl('audit_remark', '审核回执'),
+			amis()->TextControl('state', '状态'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('coach_id', '技师编号')->static(),
+			amis()->TextControl('real_name', '姓名')->static(),
+			amis()->TextControl('id_card', '身份证号')->static(),
+			amis()->TextControl('id_card_front_photo', '身份证正面照片')->static(),
+			amis()->TextControl('id_card_back_photo', '身份证反面照片')->static(),
+			amis()->TextControl('id_card_hand_photo', '手持身份证照片')->static(),
+			amis()->TextControl('auditor', '审核人')->static(),
+			amis()->TextControl('audit_time', '审核时间')->static(),
+			amis()->TextControl('audit_remark', '审核回执')->static(),
+			amis()->TextControl('state', '状态')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

+ 2 - 2
app/Admin/Controllers/ProjectController.php

@@ -34,8 +34,8 @@ class ProjectController extends AdminController
 				amis()->TableColumn('duration', '服务时长')->sortable(),
 				amis()->TableColumn('project_desc', '项目介绍'),
 				amis()->TableColumn('service_desc', '服务说明'),
-				amis()->TableColumn('type', '服务类型'),
-				amis()->TableColumn('state', '状态'),
+				amis()->TableColumn('type', '服务类型')->sortable(),
+				amis()->TableColumn('state', '状态')->sortable(),
 				amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
 				amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
 				$this->rowActions('dialog')

+ 174 - 0
app/Admin/Controllers/SettingGroupController.php

@@ -0,0 +1,174 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\SettingGroupService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * @group 后台
+ *
+ * [后台]设置分组管理
+ *
+ * @property SettingGroupService $service
+ */
+class SettingGroupController extends AdminController
+{
+    protected string $serviceName = SettingGroupService::class;
+
+    public function list()
+    {
+        $crud = $this->baseCRUD()
+            ->filterTogglable(false)
+            ->headerToolbar([
+                $this->createButton('dialog'),
+                ...$this->baseHeaderToolBar(),
+            ])
+            ->columns([
+                amis()->TableColumn('id', 'ID')->sortable(),
+                amis()->TableColumn('code', 'Code'),
+                amis()->TableColumn('name', 'Name'),
+                amis()->TableColumn('description', 'Description'),
+                amis()->TableColumn('sort', 'Sort')->sortable(),
+                amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+                amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+                $this->rowActions('dialog'),
+            ]);
+
+        return $this->baseList($crud);
+    }
+
+    public function form($isEdit = false)
+    {
+        return $this->baseForm()->body([
+            amis()->TextControl('code', 'Code'),
+            amis()->TextControl('name', 'Name'),
+            amis()->TextControl('description', 'Description'),
+            amis()->TextControl('sort', 'Sort'),
+        ]);
+    }
+
+    public function detail()
+    {
+        return $this->baseDetail()->body([
+            amis()->TextControl('id', 'ID')->static(),
+            amis()->TextControl('code', 'Code')->static(),
+            amis()->TextControl('name', 'Name')->static(),
+            amis()->TextControl('description', 'Description')->static(),
+            amis()->TextControl('sort', 'Sort')->static(),
+            amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+            amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+        ]);
+    }
+
+    /**
+     * 获取设置分组列表
+     *
+     * @description 获取所有设置分组列表
+     *
+     * @param string keyword 关键词搜索
+     * @param int perPage 每页数量
+     *
+     * @response {
+     *  "data": [
+     *    {
+     *      "id": 1,
+     *      "code": "basic",
+     *      "name": "基础设置",
+     *      "description": "系统基础配置",
+     *      "sort": 0,
+     *      "created_at": "2023-01-01 00:00:00",
+     *      "updated_at": "2023-01-01 00:00:00"
+     *    }
+     *  ]
+     * }
+     */
+    public function getList()
+    {
+        return $this->service->getList(request()->all());
+    }
+
+    /**
+     * 创建设置分组
+     *
+     * @description 创建新的设置分组
+     *
+     * @param string code 分组代码
+     * @param string name 分组名称
+     * @param string description 分组描述
+     * @param int sort 排序值
+     *
+     * @response {
+     *   "code": "basic",
+     *   "name": "基础设置",
+     *   "description": "系统基础配置",
+     *   "sort": 0
+     * }
+     */
+    public function createGroup()
+    {
+        return $this->response()->success(
+            $this->service->createGroup(request()->all())
+        );
+    }
+
+    /**
+     * 更新设置分组
+     *
+     * @description 更新指定ID的设置分组
+     *
+     * @param int id 分组ID
+     * @param string code 分组代码
+     * @param string name 分组名称
+     * @param string description 分组描述
+     * @param int sort 排序值
+     *
+     * @response true
+     */
+    public function updateGroup($id)
+    {
+        return $this->response()->success(
+            $this->service->updateGroup($id, request()->all())
+        );
+    }
+
+    /**
+     * 删除设置分组
+     *
+     * @description 删除指定ID的设置分组
+     *
+     * @param int id 分组ID
+     *
+     * @response true
+     */
+    public function deleteGroup($id)
+    {
+        return $this->response()->success(
+            $this->service->deleteGroup($id)
+        );
+    }
+
+    /**
+     * 获取设置分组详情
+     *
+     * @description 获取指定ID的设置分组详情
+     *
+     * @param int id 分组ID
+     *
+     * @response {
+     *   "id": 1,
+     *   "code": "basic",
+     *   "name": "基础设置",
+     *   "description": "系统基础配置",
+     *   "sort": 0,
+     *   "created_at": "2023-01-01 00:00:00",
+     *   "updated_at": "2023-01-01 00:00:00"
+     * }
+     */
+    public function groupDetail($id)
+    {
+        return $this->response()->success(
+            $this->service->groupDetail($id)
+        );
+    }
+}

+ 221 - 0
app/Admin/Controllers/SettingItemController.php

@@ -0,0 +1,221 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\SettingItemService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * @group 后台
+ *
+ * 设置项
+ *
+ * @property SettingItemService $service
+ */
+class SettingItemController extends AdminController
+{
+    protected string $serviceName = SettingItemService::class;
+
+    public function list()
+    {
+        $crud = $this->baseCRUD()
+            ->filterTogglable(false)
+            ->headerToolbar([
+                $this->createButton('dialog'),
+                ...$this->baseHeaderToolBar(),
+            ])
+            ->columns([
+                amis()->TableColumn('id', 'ID')->sortable(),
+                amis()->TableColumn('group_id', 'GroupId'),
+                amis()->TableColumn('code', 'Code'),
+                amis()->TableColumn('name', 'Name'),
+                amis()->TableColumn('description', 'Description'),
+                amis()->TableColumn('value_type', 'ValueType'),
+                amis()->TableColumn('default_value', 'DefaultValue'),
+                amis()->TableColumn('min_value', 'MinValue'),
+                amis()->TableColumn('max_value', 'MaxValue'),
+                amis()->TableColumn('options', 'Options'),
+                amis()->TableColumn('sort', 'Sort')->sortable(),
+                amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+                amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+                $this->rowActions('dialog'),
+            ]);
+
+        return $this->baseList($crud);
+    }
+
+    public function form($isEdit = false)
+    {
+        return $this->baseForm()->body([
+            amis()->TextControl('group_id', 'GroupId'),
+            amis()->TextControl('code', 'Code'),
+            amis()->TextControl('name', 'Name'),
+            amis()->TextControl('description', 'Description'),
+            amis()->TextControl('value_type', 'ValueType'),
+            amis()->TextControl('default_value', 'DefaultValue'),
+            amis()->TextControl('min_value', 'MinValue'),
+            amis()->TextControl('max_value', 'MaxValue'),
+            amis()->TextControl('options', 'Options'),
+            amis()->TextControl('sort', 'Sort'),
+        ]);
+    }
+
+    public function detail()
+    {
+        return $this->baseDetail()->body([
+            amis()->TextControl('id', 'ID')->static(),
+            amis()->TextControl('group_id', 'GroupId')->static(),
+            amis()->TextControl('code', 'Code')->static(),
+            amis()->TextControl('name', 'Name')->static(),
+            amis()->TextControl('description', 'Description')->static(),
+            amis()->TextControl('value_type', 'ValueType')->static(),
+            amis()->TextControl('default_value', 'DefaultValue')->static(),
+            amis()->TextControl('min_value', 'MinValue')->static(),
+            amis()->TextControl('max_value', 'MaxValue')->static(),
+            amis()->TextControl('options', 'Options')->static(),
+            amis()->TextControl('sort', 'Sort')->static(),
+            amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+            amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+        ]);
+    }
+
+    /**
+     * 获取设置项列表
+     *
+     * @description 获取所有设置项列表
+     *
+     * @param string keyword 关键词搜索
+     * @param int group_id 分组ID
+     *
+     * @response {
+     *  "data": [
+     *    {
+     *      "id": 1,
+     *      "group_id": 1,
+     *      "code": "site_name",
+     *      "name": "网站名称",
+     *      "description": "网站的名称",
+     *      "value_type": "string",
+     *      "default_value": "示例网站",
+     *      "min_value": null,
+     *      "max_value": null,
+     *      "options": null,
+     *      "sort": 0,
+     *      "created_at": "2023-01-01 00:00:00",
+     *      "updated_at": "2023-01-01 00:00:00"
+     *    }
+     *  ]
+     * }
+     */
+    public function getItemList()
+    {
+        return $this->response()->success(
+            $this->service->getItemList(request()->all())
+        );
+    }
+
+    /**
+     * 创建设置项
+     *
+     * @description 创建新的设置项
+     *
+     * @param int group_id 分组ID
+     * @param string code 设置项代码
+     * @param string name 设置项名称
+     * @param string description 设置项描述
+     * @param string value_type 值类型
+     * @param string default_value 默认值
+     * @param string min_value 最小值
+     * @param string max_value 最大值
+     * @param string options 选项
+     * @param int sort 排序值
+     *
+     * @response {
+     *   "group_id": 1,
+     *   "code": "site_name",
+     *   "name": "网站名称",
+     *   "description": "网站的名称",
+     *   "value_type": "string",
+     *   "default_value": "示例网站",
+     *   "sort": 0
+     * }
+     */
+    public function createItem()
+    {
+        return $this->response()->success(
+            $this->service->createItem(request()->all())
+        );
+    }
+
+    /**
+     * 更新设置项
+     *
+     * @description 更新指定ID的设置项
+     *
+     * @param int id 设置项ID
+     * @param int group_id 分组ID
+     * @param string code 设置项代码
+     * @param string name 设置项名称
+     * @param string description 设置项描述
+     * @param string value_type 值类型
+     * @param string default_value 默认值
+     * @param string min_value 最小值
+     * @param string max_value 最大值
+     * @param string options 选项
+     * @param int sort 排序值
+     *
+     * @response true
+     */
+    public function updateItem($id)
+    {
+        return $this->response()->success(
+            $this->service->updateItem($id, request()->all())
+        );
+    }
+
+    /**
+     * 删除设置项
+     *
+     * @description 删除指定ID的设置项
+     *
+     * @param int id 设置项ID
+     *
+     * @response true
+     */
+    public function deleteItem($id)
+    {
+        return $this->response()->success(
+            $this->service->deleteItem($id)
+        );
+    }
+
+    /**
+     * 获取设置项详情
+     *
+     * @description 获取指定ID或code的设置项详情
+     *
+     * @param string id_or_code 设置项ID或代码
+     *
+     * @response {
+     *   "id": 1,
+     *   "group_id": 1,
+     *   "code": "site_name",
+     *   "name": "网站名称",
+     *   "description": "网站的名称",
+     *   "value_type": "string",
+     *   "default_value": "示例网站",
+     *   "min_value": null,
+     *   "max_value": null,
+     *   "options": null,
+     *   "sort": 0,
+     *   "created_at": "2023-01-01 00:00:00",
+     *   "updated_at": "2023-01-01 00:00:00"
+     * }
+     */
+    public function getItemDetail($id_or_code)
+    {
+        return $this->response()->success(
+            $this->service->getItemDetail($id_or_code)
+        );
+    }
+}

+ 67 - 0
app/Admin/Controllers/SettingPermissionController.php

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\SettingPermissionService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 设置权限管理
+ *
+ * @property SettingPermissionService $service
+ */
+class SettingPermissionController extends AdminController
+{
+	protected string $serviceName = SettingPermissionService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('item_id', '设置项ID'),
+				amis()->TableColumn('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH'),
+				amis()->TableColumn('can_edit', '是否可编辑'),
+				amis()->TableColumn('min_value', '最小值限制'),
+				amis()->TableColumn('max_value', '最大值限制'),
+				amis()->TableColumn('options', '可选值限制'),
+				amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+				amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+				$this->rowActions('dialog')
+			]);
+
+		return $this->baseList($crud);
+	}
+
+	public function form($isEdit = false)
+	{
+		return $this->baseForm()->body([
+			amis()->TextControl('item_id', '设置项ID'),
+			amis()->TextControl('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH'),
+			amis()->TextControl('can_edit', '是否可编辑'),
+			amis()->TextControl('min_value', '最小值限制'),
+			amis()->TextControl('max_value', '最大值限制'),
+			amis()->TextControl('options', '可选值限制'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('item_id', '设置项ID')->static(),
+			amis()->TextControl('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH')->static(),
+			amis()->TextControl('can_edit', '是否可编辑')->static(),
+			amis()->TextControl('min_value', '最小值限制')->static(),
+			amis()->TextControl('max_value', '最大值限制')->static(),
+			amis()->TextControl('options', '可选值限制')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

+ 61 - 0
app/Admin/Controllers/SettingValueController.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\SettingValueService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 设置值管理
+ *
+ * @property SettingValueService $service
+ */
+class SettingValueController extends AdminController
+{
+	protected string $serviceName = SettingValueService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('item_id', '设置项ID'),
+				amis()->TableColumn('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH'),
+				amis()->TableColumn('object_id', '业务对象ID'),
+				amis()->TableColumn('value', '设置值'),
+				amis()->TableColumn('created_at', admin_trans('admin.created_at'))->type('datetime')->sortable(),
+				amis()->TableColumn('updated_at', admin_trans('admin.updated_at'))->type('datetime')->sortable(),
+				$this->rowActions('dialog')
+			]);
+
+		return $this->baseList($crud);
+	}
+
+	public function form($isEdit = false)
+	{
+		return $this->baseForm()->body([
+			amis()->TextControl('item_id', '设置项ID'),
+			amis()->TextControl('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH'),
+			amis()->TextControl('object_id', '业务对象ID'),
+			amis()->TextControl('value', '设置值'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('item_id', '设置项ID')->static(),
+			amis()->TextControl('object_type', '业务对象类型:PLATFORM,AGENT,SHOP,COACH')->static(),
+			amis()->TextControl('object_id', '业务对象ID')->static(),
+			amis()->TextControl('value', '设置值')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

+ 10 - 10
app/Http/Controllers/Client/CoachLocationController.php

@@ -55,13 +55,13 @@ class CoachLocationController extends Controller
      *
      * @authenticated
      *
-     * @bodyParam type string required 类型. Example: home
-     * @bodyParam latitude float required 纬度. Example: 34.0522
-     * @bodyParam longitude float required 经度. Example: -118.2437
-     * @bodyParam city string required 市. Example: Los Angeles
-     * @bodyParam district string required 区. Example: Downtown
-     * @bodyParam location string required 详细地址. Example: 123 Main St
-     * @bodyParam area_code string required 区域编码. Example: 90001
+     * @bodyparam type int required 类型. example: 1
+     * @bodyparam latitude float required 纬度. example: 39.904989
+     * @bodyparam longitude float required 经度. example: 116.405285
+     * @bodyparam city string required 市. example: los angeles
+     * @bodyparam district string required 区. example: downtown
+     * @bodyparam location string required 详细地址. example: 123 main st
+     * @bodyparam area_code string required 区域编码. example: 90001
      *
      * @response {
      *   "code": 200,
@@ -71,9 +71,9 @@ class CoachLocationController extends Controller
      *     "type": "home",
      *     "latitude": 34.0522,
      *     "longitude": -118.2437,
-     *     "city": "Los Angeles",
-     *     "district": "Downtown",
-     *     "location": "123 Main St",
+     *     "city": "los angeles",
+     *     "district": "downtown",
+     *     "location": "123 main st",
      *     "area_code": "90001"
      *   }
      * }

+ 3 - 1
app/Http/Controllers/Client/OrderController.php

@@ -60,6 +60,7 @@ class OrderController extends Controller
      * @bodyParam service_time datetime required 服务时间. Example: 2024-01-01 10:00:00
      * @bodyParam order_id int 订单ID. Example: null
      * @bodyParam payment_type string required 支付类型. Example: balance
+     * @bodyParam order_type int required 订单类型. Example: 1
      *
      * @response {
      *   "status": "success",
@@ -68,7 +69,8 @@ class OrderController extends Controller
      */
     public function create(Request $request)
     {
-        $data = $request->only(['project_id', 'address_id', 'coach_id', 'use_balance', 'order_id', 'service_time', 'payment_type']);
+
+        $data = $request->only(['project_id', 'address_id', 'coach_id', 'use_balance', 'order_id', 'service_time', 'payment_type', 'order_type']);
 
         return $this->service->createOrder(Auth::user()->id, $data);
     }

+ 14 - 14
app/Http/Controllers/Coach/OrderController → app/Http/Controllers/Coach/OrderController.php

@@ -1,9 +1,9 @@
 <?php
 
-namespace App\Http\Controllers\Client;
+namespace App\Http\Controllers\Coach;
 
 use App\Http\Controllers\Controller;
-use App\Services\Client\OrderService;
+use App\Services\Coach\OrderService;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 
@@ -14,33 +14,33 @@ use Illuminate\Support\Facades\Auth;
  */
 class OrderController extends Controller
 {
-    // protected OrderService $service;
+    protected OrderService $service;
 
-    // public function __construct(OrderService $service)
-    // {
-    //     $this->service = $service;
-    // }
+    public function __construct(OrderService $service)
+    {
+        $this->service = $service;
+    }
 
     /**
-     * [订单]获取可抢订单列表
+     * [技师端-订单]获取可抢订单列表
      *
-     * 获取可抢订单列表
+     * @description 获取当前技师40公里范围内的可抢订单列表,包含订单基本信息和距离
      *
      * @authenticated
      *
-     * @queryParam area_code string required 区划代码. Example: 370602
-     * @queryParam page int 页码. Example: 1
-     * @queryParam per_page int 每页数量. Example: 10
+     * @queryParam area_code string required 区划代码 Example: 370602
+     * @queryParam page int 页码 Example: 1
+     * @queryParam per_page int 每页数量 Example: 10
      *
      * @response {
      *   "data": [
      *     {
      *       "id": 1,
      *       "order_no": "202403210001",
-     *       "project_name": "项目名称",
+     *       "project_name": "精油推拿",
      *       "project_duration": 60,
      *       "project_price": "188.00",
-     *       "address": "山东省烟台市芝罘区",
+     *       "address": "山东省烟台市芝罘区幸福小区1号楼",
      *       "distance": 2.5,
      *       "service_time": "2024-03-21 10:00:00",
      *       "created_at": "2024-03-21 09:30:00"

+ 16 - 0
app/Models/CoachRealRecord.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 技师实名认证
+ */
+class CoachRealRecord extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'coach_real_records';
+}

+ 1 - 1
app/Models/CoachUser.php

@@ -133,7 +133,7 @@ class CoachUser extends Model
      */
     public function real()
     {
-        return $this->hasOne('App\Models\CoachRealAuthRecord', 'id', 'real_auth_record_id');
+        return $this->hasOne('App\Models\CoachRealRecord', 'id', 'real_auth_record_id');
     }
 
     /**

+ 0 - 18
app/Models/Project.php

@@ -13,22 +13,4 @@ class Project extends Model
 	use SoftDeletes;
 
 	protected $table = 'project';
-
-    /**
-     * @Author FelixYin
-     * @description 项目服务所属分类
-     */
-    public function cate()
-    {
-        return $this->belongsTo('App\Models\ProjectCate', 'cate_id');
-    }
-
-    /**
-     * @Author FelixYin
-     * @description 项目服务关联店铺服务
-     */
-    public function shopServices()
-    {
-        return $this->hasMany('App\Models\ShopService', 'service_id', 'id');
-    }
 }

+ 16 - 0
app/Models/SettingGroup.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 设置分组管理
+ */
+class SettingGroup extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'setting_groups';
+}

+ 16 - 0
app/Models/SettingItem.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 设置项
+ */
+class SettingItem extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'setting_items';
+}

+ 16 - 0
app/Models/SettingPermission.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 设置权限管理
+ */
+class SettingPermission extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'setting_permissions';
+}

+ 16 - 0
app/Models/SettingValue.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 设置值管理
+ */
+class SettingValue extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'setting_values';
+}

+ 18 - 14
app/Services/Client/CoachService.php

@@ -2,6 +2,10 @@
 
 namespace App\Services\Client;
 
+use App\Enums\TechnicianAuthStatus;
+use App\Enums\TechnicianLocationType;
+use App\Enums\TechnicianStatus;
+use App\Enums\UserStatus;
 use App\Models\CoachUser;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\Log;
@@ -107,19 +111,19 @@ class CoachService
             throw new \Exception('用户未登录');
         }
 
-        if ($user->state !== 'enable') {
+        if ($user->state !== UserStatus::OPEN->value) {
             throw new \Exception('用户状态异常');
         }
         // 获取技师信息
-        $coach = CoachUser::where('state', 'enable')
+        $coach = CoachUser::where('state', TechnicianStatus::ACTIVE->value)
             ->whereHas('info', function ($query) {
-                $query->where('state', 'approved');
+                $query->where('state', TechnicianAuthStatus::PASSED->value);
             })
             ->whereHas('real', function ($query) {
-                $query->where('state', 'approved');
+                $query->where('state', TechnicianAuthStatus::PASSED->value);
             })
             ->whereHas('qual', function ($query) {
-                $query->where('state', 'approved');
+                $query->where('state', TechnicianAuthStatus::PASSED->value);
             })
             ->with(['info:id,nickname,avatar,gender'])
             ->find($coachId);
@@ -129,8 +133,8 @@ class CoachService
         }
 
         // 从 Redis 获取技师的 id_home 和 id_work 的经纬度
-        $homeLocation = Redis::geopos('coach_locations', $coachId.'_home');
-        $workLocation = Redis::geopos('coach_locations', $coachId.'_work');
+        $homeLocation = Redis::geopos('coach_locations', $coachId.'_'.TechnicianLocationType::COMMON->value);
+        $workLocation = Redis::geopos('coach_locations', $coachId.'_'.TechnicianLocationType::CURRENT->value);
 
         // 检查输入的经纬度是否有效
         if (! is_numeric($latitude) || ! is_numeric($longitude)) {
@@ -147,17 +151,17 @@ class CoachService
         $distanceWork = null;
 
         if ($homeLocation && ! empty($homeLocation[0])) {
-            $distanceHome = Redis::geodist('coach_locations', $tempKey, $coachId.'_home', 'km');
+            $distanceHome = Redis::geodist('coach_locations', $tempKey, $coachId.'_'.TechnicianLocationType::COMMON->value, 'km');
             Log::info('Home distance calculation:', [
                 'from' => $tempKey,
-                'to' => $coachId.'_home',
+                'to' => $coachId.'_'.TechnicianLocationType::COMMON->value,
                 'distance' => $distanceHome,
                 'home_location' => $homeLocation[0],
             ]);
         }
 
         if ($workLocation && ! empty($workLocation[0])) {
-            $distanceWork = Redis::geodist('coach_locations', $tempKey, $coachId.'_work', 'km');
+            $distanceWork = Redis::geodist('coach_locations', $tempKey, $coachId.'_'.TechnicianLocationType::CURRENT->value, 'km');
         }
 
         // 删除临时位置点
@@ -176,12 +180,12 @@ class CoachService
      * @param  int  $coachId  技师ID
      * @param  float  $latitude  纬度
      * @param  float  $longitude  经度
-     * @param  string  $type  位置类型 (home|work)
+     * @param  int  $type  位置类型 (current|common)
      * @return bool
      *
      * @throws \Exception
      */
-    public function setCoachLocation($coachId, $latitude, $longitude, $type = 'home')
+    public function setCoachLocation($coachId, $latitude, $longitude, $type = TechnicianLocationType::COMMON->value)
     {
         if (! is_numeric($latitude) || ! is_numeric($longitude)) {
             Log::error('Invalid coordinates in setCoachLocation:', [
@@ -192,8 +196,8 @@ class CoachService
             throw new \Exception('无效的经纬度坐标');
         }
 
-        if (! in_array($type, ['home', 'work'])) {
-            throw new \Exception('无效的位置类型,必须是 home 或 work');
+        if (! in_array($type, [TechnicianLocationType::CURRENT->value, TechnicianLocationType::COMMON->value])) {
+            throw new \Exception('无效的位置类型,必须是 current 或 common');
         }
 
         $key = $coachId.'_'.$type;

+ 71 - 35
app/Services/Client/OrderService.php

@@ -2,7 +2,14 @@
 
 namespace App\Services\Client;
 
+use App\Enums\OrderSource;
+use App\Enums\OrderStatus;
+use App\Enums\OrderType;
+use App\Enums\PaymentMethod;
 use App\Enums\ProjectStatus;
+use App\Enums\TechnicianAuthStatus;
+use App\Enums\TechnicianStatus;
+use App\Enums\UserStatus;
 use App\Models\AgentConfig;
 use App\Models\AgentInfo;
 use App\Models\CoachConfig;
@@ -20,6 +27,7 @@ use Exception;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
 use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
 
 class OrderService
 {
@@ -107,10 +115,11 @@ class OrderService
      */
     public function createOrder(int $userId, array $data): array
     {
+
         return DB::transaction(function () use ($userId, $data) {
             // 1. 参数校验
             $user = MemberUser::where('id', $userId)
-                ->where('state', 'enable')
+                ->where('state', UserStatus::OPEN->value())
                 ->firstOrFail();
 
             $project = Project::where('id', $data['project_id'])
@@ -118,18 +127,30 @@ class OrderService
                 ->firstOrFail();
 
             // 2. 订单类型判断
-            $orderType = isset($data['order_id']) ? 'add_time' : 'normal';
+            $orderType = $data['order_type'];
 
             // 关键操作:验证必要参数
             abort_if(empty($data['project_id']), 400, '项目ID不能为空');
-            abort_if($orderType == 'normal' && empty($data['coach_id']), 400, '技师ID不能为空');
-            abort_if($orderType == 'normal' && empty($data['address_id']), 400, '地址ID不能为空');
+            // 上门订单必须指定技师和地址
+            abort_if($orderType == OrderType::VISIT->value && empty($data['coach_id']), 400, '技师ID不能为空');
+            abort_if($orderType == OrderType::VISIT->value && empty($data['address_id']), 400, '地址ID不能为空');
+            // 抢单订单必须指定地址
+            abort_if($orderType == OrderType::GRAB->value && empty($data['address_id']), 400, '地址ID不能为空');
+            // 加钟订单必须指定原订单
+            abort_if($orderType == OrderType::OVERTIME->value && empty($data['order_id']), 400, '原订单ID不能为空');
+            // 到店订单必须指定店铺
+            abort_if($orderType == OrderType::SHOP->value && empty($data['shop_id']), 400, '店铺ID不能为空');
+            // 应急订单必须指定地址
+            abort_if($orderType == OrderType::EMERGENCY->value && empty($data['address_id']), 400, '地址ID不能为空');
 
             // 3. 验证技师
             // 4. 根据订单类型处理
-            if ($orderType == 'normal') {
+            // 上门订单
+            if ($orderType == OrderType::VISIT->value) {
                 $this->validateCoach($data['coach_id']);
-            } else {
+            }
+            // 加钟订单
+            if ($orderType == OrderType::OVERTIME->value) {
                 $originalOrder = $this->getOriginalOrder($user, $data['order_id']);
 
                 $data['address_id'] = $originalOrder->address_id;
@@ -144,19 +165,20 @@ class OrderService
                 ->firstOrFail();
 
             // 5. 计算订单金额
+            $data['use_balance'] = $data['use_balance'] ?? false;
             $amounts = $this->calculateOrderAmount(
                 $userId,
                 $address->id,
                 $data['coach_id'],
                 $data['project_id'],
-                $project->agent_id,
-                $data['use_balance'] ?? false
+                $project?->agent_id,
+                $data['use_balance']
             );
 
             // 6. 验证金额和余额
             abort_if($amounts['total_amount'] <= 0, 400, '订单金额异常');
 
-            if ($data['payment_type'] == 'balance') {
+            if ($data['payment_type'] == PaymentMethod::BALANCE->value) {
                 $wallet = $user->wallet;
                 abort_if($wallet->available_balance < $amounts['balance_amount'], 400, '可用余额不足');
             }
@@ -164,7 +186,7 @@ class OrderService
             $order = $this->createOrderRecord($userId, $data, $orderType, $address, $data['payment_type'], (object) $amounts);
 
             // 8. 余额支付处理
-            if ($order->payment_type == 'balance') {
+            if ($order->payment_type == PaymentMethod::BALANCE->value && $orderType != OrderType::GRAB->value) {
                 $this->handleBalancePayment($user, $order, $orderType);
             }
 
@@ -178,11 +200,13 @@ class OrderService
     // 提取方法:验证技师
     public function validateCoach(int $coachId): CoachUser
     {
+        $coach = CoachUser::where('id', $coachId)->first();
+
         $coach = CoachUser::where('id', $coachId)
-            ->where('state', 'enable')
-            ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
-            ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
-            ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
+            ->where('state', TechnicianStatus::ACTIVE->value)
+            ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
+            ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
+            ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
             ->firstOrFail();
 
         return $coach;
@@ -224,14 +248,14 @@ class OrderService
         $order->project_id = $data['project_id'];
         $order->coach_id = $data['coach_id'];
         $order->type = $orderType;
-        $order->state = 'wait_pay';
-        $order->source = 'platform';
+        $order->state = OrderStatus::CREATED->value;
+        $order->source = OrderSource::PLATFORM->value;
         $order->total_amount = $amounts->total_amount;
         $order->balance_amount = $amounts->balance_amount;
         $order->pay_amount = $amounts->pay_amount;
         $order->project_amount = $amounts->project_amount;
-        $order->traffic_amount = $orderType == 'add_time' ? 0 : $amounts->delivery_fee;
-        $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? 'balance' : $payment_type;
+        $order->traffic_amount = $orderType == OrderType::OVERTIME->value ? 0 : $amounts->delivery_fee;
+        $order->payment_type = ($data['use_balance'] && $amounts->pay_amount == 0) ? PaymentMethod::BALANCE->value : $payment_type;
         $order->service_time = $data['service_time'];
         $order->address_id = $data['address_id'];
         $order->longitude = $address->longitude;
@@ -241,14 +265,26 @@ class OrderService
         $order->area_code = $address->area_code;
         $order->save();
 
+        // 创建订单记录
         OrderRecord::create([
             'order_id' => $order->id,
             'object_id' => $userId,
             'object_type' => MemberUser::class,
-            'state' => $orderType == 'add_time' ? 'add_time' : 'create',
-            'remark' => $orderType == 'add_time' ? '加钟订单' : '创建订单',
+            'state' => OrderStatus::CREATED->value,
+            'remark' => $orderType == OrderType::OVERTIME->value ? '加钟订单' : '创建订单',
         ]);
 
+        if ($orderType == OrderType::GRAB->value) {
+            // 将订单地址经纬度存入redis的geo类型
+            Redis::geoadd(
+                'order_locations',
+                $order->longitude,
+                $order->latitude,
+                'order:'.$order->id
+            );
+
+        }
+
         return $order;
     }
 
@@ -309,13 +345,13 @@ class OrderService
     {
         // 复用之前的用户验证逻辑
         $user = MemberUser::where('id', $userId)
-            ->where('state', 'enable')
+            ->where('state', UserStatus::OPEN->value)
             ->firstOrFail();
 
         // 验证订单状态
         $order = Order::where('user_id', $userId)
             ->where('id', $orderId)
-            ->whereIn('state', ['wait_pay', 'wait_receive', 'on_the_way'])
+            ->whereIn('state', [OrderStatus::CREATED->value, OrderStatus::ASSIGNED->value, OrderStatus::PAID->value, OrderStatus::ACCEPTED->value, OrderStatus::DEPARTED->value])
             ->lockForUpdate()
             ->firstOrFail();
 
@@ -330,19 +366,19 @@ class OrderService
         $user = $order->user;
 
         switch ($order->state) {
-            case 'wait_receive': // 已接单
+            case OrderStatus::ACCEPTED->value: // 已接单
                 // 扣除20%费用
                 $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.2;
                 $this->handleRefund($user, $order, $deductAmount, false);
                 break;
 
-            case 'on_the_way': // 已出发
+            case OrderStatus::DEPARTED->value: // 已出发
                 // 扣除50%费用并扣除路费
                 $deductAmount = ($order->payment_amount + $order->balance_amount - $order->traffic_amount) * 0.5;
                 $this->handleRefund($user, $order, $deductAmount, true);
                 break;
 
-            case 'wait_pay':
+            case OrderStatus::CREATED->value:
                 // 待支付状态直接取消,无需退款
                 break;
 
@@ -734,10 +770,10 @@ class OrderService
     ): float {
         try {
             // 1. 校验技师
-            $coach = CoachUser::where('state', 'enable')
-                ->whereHas('info', fn ($q) => $q->where('state', 'approved'))
-                ->whereHas('real', fn ($q) => $q->where('state', 'approved'))
-                ->whereHas('qual', fn ($q) => $q->where('state', 'approved'))
+            $coach = CoachUser::where('state', TechnicianStatus::ACTIVE->value)
+                ->whereHas('info', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
+                ->whereHas('real', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
+                ->whereHas('qual', fn ($q) => $q->where('state', TechnicianAuthStatus::PASSED->value))
                 ->with(['projects' => fn ($q) => $q->where('project_id', $projectId)])
                 ->find($coachId);
 
@@ -845,14 +881,14 @@ class OrderService
 
             // 1. 参数校验
             $user = MemberUser::find($userId);
-            abort_if(! $user || $user->state != 'enable', 404, '用户不存在或状态异常');
+            abort_if(! $user || $user->state != UserStatus::OPEN->value, 404, '用户不存在或状态异常');
 
             // 2. 查询技师项目
             $coach = $this->validateCoach($coachId);
 
             abort_if(! $coach, 404, '技师不存在或状态异常');
             $coachProject = $coach->projects()
-                ->where('state', 'enable')
+                ->where('state', ProjectStatus::OPEN->value)
                 ->where('project_id', $projectId)
                 ->first();
 
@@ -1000,7 +1036,7 @@ class OrderService
         try {
             // 查询订单信息
             $order = Order::where('id', $orderId)
-                ->whereIn('state', ['wait_pay', 'wait_service'])
+                ->whereIn('state', [OrderStatus::CREATED->value])
                 ->firstOrFail();
             // 查询抢单池列表
             $grabList = $order->grabRecords()->with(['coach.info'])->get();
@@ -1140,7 +1176,7 @@ class OrderService
         // 验证订单状态
         $order = Order::where('user_id', $userId)
             ->where('id', $orderId)
-            ->whereIn('state', ['wait_pay', 'wait_receive'])
+            ->whereIn('state', [OrderStatus::CREATED->value])
             ->lockForUpdate()
             ->firstOrFail();
 
@@ -1156,14 +1192,14 @@ class OrderService
         $order->coach_id = $coachId;
 
         // 待支付订单需要重新计算金额
-        if ($order->state == 'wait_pay') {
+        if ($order->state == OrderStatus::CREATED->value) {
             $amounts = $this->calculateOrderAmount(
                 $order->user_id,
                 $order->address_id,
                 $coachId,
                 $order->project_id,
                 $order->agent_id,
-                $order->payment_type === 'balance'
+                $order->payment_type === PaymentMethod::BALANCE->value
             );
 
             // 更新订单金额

+ 251 - 10
app/Services/Coach/OrderService.php

@@ -1,18 +1,259 @@
 <?php
 
-namespace App\Services\Client;
+namespace App\Services\Coach;
+
+use App\Enums\OrderStatus;
+use App\Enums\TechnicianAuthStatus;
+use App\Enums\TechnicianLocationType;
+use App\Enums\TechnicianStatus;
+use App\Models\MemberUser;
+use App\Models\Order;
+use App\Services\SettingItemService;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Redis;
 
 class OrderService
 {
-    // protected AgentService $agentService;
+    private SettingItemService $settingService;
+
+    public function __construct(SettingItemService $settingService)
+    {
+        $this->settingService = $settingService;
+    }
+
+    /**
+     * 获取可抢订单列表
+     *
+     * @param  int  $userId  技师用户ID
+     * @param  array  $params  请求参数
+     * @return \Illuminate\Pagination\LengthAwarePaginator
+     */
+    public function getGrabList(int $userId, array $params)
+    {
+        try {
+            // 加载用户和技师信息
+            $user = MemberUser::with([
+                'coach',
+                'coach.info',
+                'coach.real',
+                'coach.qual',
+                'coach.locations',
+                'coach.projects',
+            ])->findOrFail($userId);
+
+            // 验证技师信息
+            [$coach, $location] = $this->validateCoach($user);
+
+            // 获取技师项目信息
+            $coachProjects = $coach->projects;
+            $coachProjectIds = $coachProjects->pluck('project_id')->toArray();
+
+            // 获取系统配置
+            $settings = $this->getSystemSettings();
+
+            // 获取附近订单
+            $orderDistances = $this->getNearbyOrders($location);
+
+            // 构建订单查询
+            $query = $this->buildOrderQuery($coachProjectIds, $orderDistances);
+
+            // 处理订单数据
+            $this->processOrderData($query, $orderDistances, $coachProjects, $settings);
+
+            // 分页获取数据
+            return $query->paginate(
+                $params['per_page'] ?? 10,
+                ['*'],
+                'page',
+                $params['page'] ?? 1
+            );
+
+        } catch (\Exception $e) {
+            Log::error('获取可抢订单列表失败', [
+                'user_id' => $userId,
+                'params' => $params,
+                'error' => $e->getMessage(),
+                'trace' => $e->getTraceAsString(),
+            ]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 验证技师信息
+     */
+    private function validateCoach(MemberUser $user): array
+    {
+        $coach = $user->coach;
+        abort_if(! $coach, 404, '技师不存在');
+        abort_if(! $coach->info, 404, '技师信息不存在');
+        abort_if($coach->info->state != TechnicianStatus::ACTIVE->value, 404, '技师状态异常');
+        abort_if($coach->real->state != TechnicianAuthStatus::PASSED->value, 404, '技师实名认证未通过');
+        abort_if($coach->qual->state != TechnicianAuthStatus::PASSED->value, 404, '技师资质认证未通过');
+
+        $location = $coach->locations()
+            ->where('type', TechnicianLocationType::COMMON->value)
+            ->first();
+        abort_if(! $location, 404, '技师定位地址不存在');
+
+        return [$coach, $location];
+    }
+
+    /**
+     * 获取系统配置
+     */
+    private function getSystemSettings(): array
+    {
+        return [
+            'coach_income' => $this->settingService->getItemDetail('coach_income')->default_value ?? 0,
+            'min_distance' => $this->settingService->getItemDetail('qibugongli')->default_value ?? 0,
+            'base_price' => $this->settingService->getItemDetail('qibujia')->default_value ?? 0,
+            'per_km_price' => $this->settingService->getItemDetail('meigonglijiage')->default_value ?? 0,
+        ];
+    }
+
+    /**
+     * 获取附近订单
+     */
+    private function getNearbyOrders($location, float $maxDistance = 40): array
+    {
+        $nearbyOrders = Redis::georadius(
+            'order_locations',
+            $location->longitude,
+            $location->latitude,
+            $maxDistance,
+            'km',
+            ['WITHCOORD', 'WITHDIST']
+        );
+
+        if (! $nearbyOrders) {
+            Log::warning('获取附近订单失败', [
+                'location' => [
+                    'longitude' => $location->longitude,
+                    'latitude' => $location->latitude,
+                ],
+            ]);
+
+            return [];
+        }
+
+        $orderDistances = [];
+        foreach ($nearbyOrders as $order) {
+            $orderId = str_replace('order:', '', $order[0]);
+            $distance = round($order[1], 1);
+            $orderDistances[$orderId] = $distance;
+        }
+
+        return $orderDistances;
+    }
+
+    /**
+     * 构建订单查询
+     */
+    private function buildOrderQuery(array $coachProjectIds, array $orderDistances)
+    {
+        return Order::query()
+            ->select([
+                'id',
+                'order_no',
+                'project_id',
+                'location',
+                'address',
+                'latitude',
+                'longitude',
+                'service_time',
+                'created_at',
+                'agent_id',
+                DB::raw('0 as distance'),
+            ])
+            ->with(['project', 'agent.projects'])
+            ->where('state', OrderStatus::CREATED)
+            ->whereIn('project_id', $coachProjectIds)
+            ->whereIn('id', array_keys($orderDistances))
+            ->orderBy('created_at', 'desc');
+    }
+
+    /**
+     * 处理订单数据
+     */
+    private function processOrderData($query, array $orderDistances, $coachProjects, array $settings): void
+    {
+        $query->get()->each(function ($order) use ($orderDistances, $coachProjects, $settings) {
+            // 设置距离
+            $order->distance = $orderDistances[$order->id];
+
+            // 处理代理商项目价格
+            $this->processAgentPrice($order);
+
+            // 处理技师项目信息
+            $this->processCoachProject($order, $coachProjects, $settings);
+        });
+    }
+
+    /**
+     * 处理代理商价格
+     */
+    private function processAgentPrice($order): void
+    {
+        if ($order->agent_id && $order->agent && $order->agent->projects) {
+            $agentProject = $order->agent->projects
+                ->where('id', $order->project_id)
+                ->where('state', 1)
+                ->first();
+
+            if ($agentProject) {
+                $order->project->price = $agentProject->agent_price;
+                $order->project->duration = $agentProject->duration;
+            }
+        }
+    }
+
+    /**
+     * 处理技师项目信息
+     */
+    private function processCoachProject($order, $coachProjects, array $settings): void
+    {
+        $coachProject = $coachProjects->where('project_id', $order->project_id)->first();
+        if (! $coachProject) {
+            return;
+        }
+
+        // 计算技师佣金
+        $order->project->coach_income = round($order->project->price * $settings['coach_income'] / 100, 2);
+
+        // 处理优惠金额
+        if ($coachProject->discount_amount > 0) {
+            $order->project->discount_amount = $coachProject->discount_amount;
+            $order->project->final_price = $order->project->price - $coachProject->discount_amount;
+        }
+
+        // 处理路费
+        if ($coachProject->traffic_fee > 0) {
+            $this->calculateTrafficFee($order, $coachProject, $settings);
+        }
+    }
+
+    /**
+     * 计算路费
+     */
+    private function calculateTrafficFee($order, $coachProject, array $settings): void
+    {
+        $trafficFee = $settings['base_price'];
+
+        if ($order->distance > $settings['min_distance']) {
+            $trafficFee += ($order->distance - $settings['min_distance']) * $settings['per_km_price'];
+        }
+
+        $order->project->traffic_fee = round($trafficFee, 2);
 
-    // protected ProjectService $projectService;
+        // 双程收费
+        if ($coachProject->is_round_trip) {
+            $order->project->traffic_fee *= 2;
+        }
 
-    // public function __construct(
-    //     AgentService $agentService,
-    //     ProjectService $projectService
-    // ) {
-    //     $this->agentService = $agentService;
-    //     $this->projectService = $projectService;
-    // }
+        // 计算最终价格
+        $order->project->final_price = ($order->project->final_price ?? $order->project->price)
+            + $order->project->traffic_fee;
+    }
 }

+ 17 - 0
app/Services/CoachRealRecordService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\CoachRealRecord;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 技师实名认证
+ *
+ * @method CoachRealRecord getModel()
+ * @method CoachRealRecord|\Illuminate\Database\Query\Builder query()
+ */
+class CoachRealRecordService extends AdminService
+{
+	protected string $modelName = CoachRealRecord::class;
+}

+ 132 - 0
app/Services/SettingGroupService.php

@@ -0,0 +1,132 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\SettingGroup;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 设置分组管理
+ *
+ * @method SettingGroup getModel()
+ * @method SettingGroup|\Illuminate\Database\Query\Builder query()
+ */
+class SettingGroupService extends AdminService
+{
+    protected string $modelName = SettingGroup::class;
+
+    /**
+     * 获取设置分组列表
+     *
+     * @return mixed
+     */
+    public function getList(array $params = [])
+    {
+        try {
+            $query = $this->query();
+
+            // 添加搜索条件
+            if (! empty($params['keyword'])) {
+                $query->where(function ($q) use ($params) {
+                    $q->where('name', 'like', "%{$params['keyword']}%")
+                        ->orWhere('code', 'like', "%{$params['keyword']}%");
+                });
+            }
+
+            // 排序
+            $query->orderBy('sort', 'asc')->orderBy('id', 'desc');
+
+            return $query->paginate($params['perPage'] ?? 20);
+        } catch (\Exception $e) {
+            Log::error('获取设置分组列表失败', ['error' => $e->getMessage()]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 创建设置分组
+     *
+     * @return mixed
+     */
+    public function createGroup(array $data)
+    {
+        try {
+            return DB::transaction(function () use ($data) {
+                // 检查code是否已存在
+                abort_if(
+                    $this->query()->where('code', $data['code'])->exists(),
+                    422,
+                    '分组代码已存在'
+                );
+
+                return $this->getModel()::create($data);
+            });
+        } catch (\Exception $e) {
+            Log::error('创建设置分组失败', ['error' => $e->getMessage(), 'data' => $data]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 更新设置分组
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function updateGroup($id, array $data)
+    {
+        try {
+            return DB::transaction(function () use ($id, $data) {
+                $group = $this->query()->findOrFail($id);
+
+                // 检查code是否已存在(排除自身)
+                abort_if(
+                    $this->query()->where('code', $data['code'])->where('id', '!=', $id)->exists(),
+                    422,
+                    '分组代码已存在'
+                );
+
+                return $group->update($data);
+            });
+        } catch (\Exception $e) {
+            Log::error('更新设置分组失败', ['error' => $e->getMessage(), 'id' => $id, 'data' => $data]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 删除设置分组
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function deleteGroup($id)
+    {
+        try {
+            return DB::transaction(function () use ($id) {
+                return $this->query()->findOrFail($id)->delete();
+            });
+        } catch (\Exception $e) {
+            Log::error('删除设置分组失败', ['error' => $e->getMessage(), 'id' => $id]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取设置分组详情
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function groupDetail($id)
+    {
+        try {
+            return $this->query()->findOrFail($id);
+        } catch (\Exception $e) {
+            Log::error('获取设置分组详情失败', ['error' => $e->getMessage(), 'id' => $id]);
+            throw $e;
+        }
+    }
+}

+ 148 - 0
app/Services/SettingItemService.php

@@ -0,0 +1,148 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\SettingItem;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 设置项
+ *
+ * @method SettingItem getModel()
+ * @method SettingItem|\Illuminate\Database\Query\Builder query()
+ */
+class SettingItemService extends AdminService
+{
+    protected string $modelName = SettingItem::class;
+
+    /**
+     * 获取设置项列表
+     *
+     * @return mixed
+     */
+    public function getItemList(array $params)
+    {
+        try {
+            $query = $this->query();
+
+            if (! empty($params['keyword'])) {
+                $query->where(function ($q) use ($params) {
+                    $q->where('code', 'like', "%{$params['keyword']}%")
+                        ->orWhere('name', 'like', "%{$params['keyword']}%");
+                });
+            }
+
+            if (! empty($params['group_id'])) {
+                $query->where('group_id', $params['group_id']);
+            }
+
+            return $query->orderBy('sort')->get();
+        } catch (\Exception $e) {
+            Log::error('获取设置项列表失败', ['error' => $e->getMessage(), 'params' => $params]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 创建设置项
+     *
+     * @return mixed
+     */
+    public function createItem(array $data)
+    {
+        try {
+            return DB::transaction(function () use ($data) {
+                // 检查code是否已存在
+                abort_if(
+                    $this->query()->where('code', $data['code'])->exists(),
+                    422,
+                    '设置项代码已存在'
+                );
+
+                return $this->getModel()::create($data);
+            });
+        } catch (\Exception $e) {
+            Log::error('创建设置项失败', ['error' => $e->getMessage(), 'data' => $data]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 更新设置项
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function updateItem($id, array $data)
+    {
+        try {
+            return DB::transaction(function () use ($id, $data) {
+                $item = $this->query()->findOrFail($id);
+
+                // 检查code是否已存在(排除自身)
+                abort_if(
+                    $this->query()->where('code', $data['code'])->where('id', '!=', $id)->exists(),
+                    422,
+                    '设置项代码已存在'
+                );
+
+                return $item->update($data);
+            });
+        } catch (\Exception $e) {
+            Log::error('更新设置项失败', ['error' => $e->getMessage(), 'id' => $id, 'data' => $data]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 删除设置项
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function deleteItem($id)
+    {
+        try {
+            return DB::transaction(function () use ($id) {
+                return $this->query()->findOrFail($id)->delete();
+            });
+        } catch (\Exception $e) {
+            Log::error('删除设置项失败', ['error' => $e->getMessage(), 'id' => $id]);
+            throw $e;
+        }
+    }
+
+    /**
+     * 获取设置项详情
+     *
+     * @param  string  $id_or_code  ID或代码
+     * @return mixed
+     */
+    public function getItemDetail($id_or_code)
+    {
+        try {
+            $query = $this->query();
+
+            // 判断是否为数字ID
+            if (is_numeric($id_or_code)) {
+                $item = $query->find($id_or_code);
+            } else {
+                // 通过code查询
+                $item = $query->where('code', $id_or_code)->first();
+            }
+
+            // 如果未找到记录则抛出异常
+            abort_if(! $item, 404, '设置项不存在');
+
+            return $item;
+        } catch (\Exception $e) {
+            Log::error('获取设置项详情失败', [
+                'error' => $e->getMessage(),
+                'id_or_code' => $id_or_code,
+            ]);
+            throw $e;
+        }
+    }
+}

+ 17 - 0
app/Services/SettingPermissionService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\SettingPermission;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 设置权限管理
+ *
+ * @method SettingPermission getModel()
+ * @method SettingPermission|\Illuminate\Database\Query\Builder query()
+ */
+class SettingPermissionService extends AdminService
+{
+	protected string $modelName = SettingPermission::class;
+}

+ 17 - 0
app/Services/SettingValueService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\SettingValue;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 设置值管理
+ *
+ * @method SettingValue getModel()
+ * @method SettingValue|\Illuminate\Database\Query\Builder query()
+ */
+class SettingValueService extends AdminService
+{
+	protected string $modelName = SettingValue::class;
+}

+ 9 - 5
doc/系统设计/系统架构/多级设置解决方案.md

@@ -37,20 +37,22 @@ export_on_save:
 -   设置项分组管理
 -   完整的设置历史记录
 
-请使用mysql8、php(laravel框架)设计一套完整的解决方案
+请使用 mysql8、php(laravel 框架)设计一套完整的解决方案
+
 ## 二、数据库设计
 
 ### 1. 设置组表(setting_groups)
 
 ```sql
 CREATE TABLE `setting_groups` (
-  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `id` bigint  NOT NULL AUTO_INCREMENT COMMENT '主键ID',
   `code` varchar(50) NOT NULL COMMENT '设置组编码',
   `name` varchar(50) NOT NULL COMMENT '设置组名称',
   `description` varchar(255) DEFAULT NULL COMMENT '设置组描述',
   `sort` int NOT NULL DEFAULT 0 COMMENT '排序',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
+  `deleted_at` timestamp NULL DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_code` (`code`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设置表';
@@ -60,8 +62,8 @@ CREATE TABLE `setting_groups` (
 
 ```sql
 CREATE TABLE `setting_items` (
-  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
-  `group_id` bigint unsigned NOT NULL COMMENT '设置组ID',
+  `id` bigint  NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+  `group_id` bigint  NOT NULL COMMENT '设置组ID',
   `code` varchar(50) NOT NULL COMMENT '设置项编码',
   `name` varchar(50) NOT NULL COMMENT '设置项名称',
   `description` varchar(255) DEFAULT NULL COMMENT '设置项描述',
@@ -73,6 +75,7 @@ CREATE TABLE `setting_items` (
   `sort` int NOT NULL DEFAULT 0 COMMENT '排序',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
+  `deleted_at` timestamp NULL DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_code` (`code`),
   KEY `idx_group_id` (`group_id`),
@@ -93,6 +96,7 @@ CREATE TABLE `setting_permissions` (
   `options` json DEFAULT NULL COMMENT '可选值限制',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
+  `deleted_at` timestamp NULL DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_item_object` (`item_id`,`object_type`),
   KEY `idx_item_id` (`item_id`),
@@ -111,6 +115,7 @@ CREATE TABLE `setting_values` (
   `value` text NOT NULL COMMENT '设置值',
   `created_at` timestamp NULL DEFAULT NULL,
   `updated_at` timestamp NULL DEFAULT NULL,
+  `deleted_at` timestamp NULL DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_item_object` (`item_id`,`object_type`,`object_id`),
   KEY `idx_object` (`object_type`,`object_id`),
@@ -118,7 +123,6 @@ CREATE TABLE `setting_values` (
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设置值表';
 ```
 
-
 ## 三、代码实现
 
 ### 1. Model 定义

+ 13 - 14
routes/admin.php

@@ -8,7 +8,6 @@
 
 // =====================================================================
 
-use App\Http\Controllers\EnumController;
 use Illuminate\Routing\Router;
 use Illuminate\Support\Facades\Route;
 use Slowlyo\OwlAdmin\Admin;
@@ -18,7 +17,6 @@ Route::group([
     'prefix' => Admin::config('admin.route.prefix'),
     'middleware' => Admin::config('admin.route.middleware'),
 ], function (Router $router) {
-    Route::get('/enums', [EnumController::class, 'getEnumData']);
     // 用户
     $router->resource('member_users', \App\Admin\Controllers\MemberUserController::class);
     // 用户社交账号
@@ -65,8 +63,6 @@ Route::group([
     $router->resource('shop_auth_records', \App\Admin\Controllers\ShopAuthRecordController::class);
     // 项目分类
     $router->resource('project_cate', \App\Admin\Controllers\ProjectCateController::class);
-    // 项目服务
-    $router->resource('project', \App\Admin\Controllers\ProjectController::class);
     // 店铺服务
     $router->resource('shop_services', \App\Admin\Controllers\ShopServiceController::class);
     // 店铺技师服务项目
@@ -85,16 +81,19 @@ Route::group([
     $router->resource('agent_project_cate', \App\Admin\Controllers\AgentProjectCateController::class);
     // 代理商服务项目
     $router->resource('agent_project', \App\Admin\Controllers\AgentProjectController::class);
-    // 技师实名认证记录
-    $router->resource('coach_real_auth_records', \App\Admin\Controllers\CoachRealAuthRecordController::class);
-    // 技师开通服务项目
-    $router->resource('coach_project', \App\Admin\Controllers\CoachProjectController::class);
-    // 代理商项目设置
-    $router->resource('agent_project_config', \App\Admin\Controllers\AgentProjectConfigController::class);
-    // 我的分销团队
-    $router->resource('market_dist_teams', \App\Admin\Controllers\MarketDistTeamController::class);
     // 临时用户列表
     $router->resource('report_user_list', \App\Admin\Controllers\ReportUserListController::class);
-    // 视图管理
-    $router->get('views', [\App\Admin\Controllers\ViewController::class, 'viewList']);
+    // 项目服务
+    $router->resource('project', \App\Admin\Controllers\ProjectController::class);
+    // 技师实名认证
+    $router->resource('coach_real_records', \App\Admin\Controllers\CoachRealRecordController::class);
+    // 设置分组管理
+    $router->resource('setting_groups', \App\Admin\Controllers\SettingGroupController::class);
+    // 设置项
+    $router->resource('setting_items', \App\Admin\Controllers\SettingItemController::class);
+    // 设置权限管理
+    $router->resource('setting_permissions', \App\Admin\Controllers\SettingPermissionController::class);
+    // 设置值管理
+    $router->resource('setting_values', \App\Admin\Controllers\SettingValueController::class);
+
 });

+ 8 - 0
routes/api.php

@@ -9,6 +9,7 @@ use App\Http\Controllers\Client\ProjectController;
 use App\Http\Controllers\Client\UserAddressController;
 use App\Http\Controllers\Client\UserController;
 use App\Http\Controllers\Client\WalletController;
+use App\Http\Controllers\Coach\OrderController as CoachOrderController;
 use App\Http\Controllers\EnumController;
 use App\Http\Controllers\ScribeController;
 use Illuminate\Support\Facades\Route;
@@ -114,4 +115,11 @@ Route::middleware('auth:sanctum')->group(function () {
     Route::prefix('team')->group(function () {
         Route::get('list', [MarketDistTeamController::class, 'index'])->name('team.list');
     });
+
+    // 技师端路由组
+    Route::prefix('coach')->middleware(['auth:sanctum'])->group(function () {
+        // 技师端路由组
+        Route::get('orders/grab-list', [CoachOrderController::class, 'getGrabList']);
+    });
+
 });

+ 26 - 0
routes/web.php

@@ -17,5 +17,31 @@ Route::group([
     // 用户管理路由
     Route::get('/manage/users', [\App\Admin\Controllers\MemberUserController::class, 'manageUserList'])
         ->name('manage.users.list');
+    // 设置分组管理路由
+    Route::group(['prefix' => 'setting-groups'], function () {
+        // 获取设置分组列表
+        Route::get('/', [\App\Admin\Controllers\SettingGroupController::class, 'getList']);
+        // 创建设置分组
+        Route::post('/', [\App\Admin\Controllers\SettingGroupController::class, 'createGroup']);
+        // 更新设置分组
+        Route::put('/{id}', [\App\Admin\Controllers\SettingGroupController::class, 'updateGroup']);
+        // 删除设置分组
+        Route::delete('/{id}', [\App\Admin\Controllers\SettingGroupController::class, 'deleteGroup']);
+        // 获取设置分组详情
+        Route::get('/{id}', [\App\Admin\Controllers\SettingGroupController::class, 'groupDetail']);
+    });
 
+    // 设置项管理路由
+    Route::group(['prefix' => 'setting-items'], function () {
+        // 获取设置项列表
+        Route::get('/', [\App\Admin\Controllers\SettingItemController::class, 'getItemList']);
+        // 创建设置项
+        Route::post('/', [\App\Admin\Controllers\SettingItemController::class, 'createItem']);
+        // 更新设置项
+        Route::put('/{id}', [\App\Admin\Controllers\SettingItemController::class, 'updateItem']);
+        // 删除设置项
+        Route::delete('/{id}', [\App\Admin\Controllers\SettingItemController::class, 'deleteItem']);
+        // 获取设置项详情
+        Route::get('/{id}', [\App\Admin\Controllers\SettingItemController::class, 'getItemDetail']);
+    });
 });