Browse Source

feat:项目管理

刘学玺 4 months ago
parent
commit
0fdedfb2b6

+ 58 - 0
app/Admin/Controllers/AgentProjectCateController.php

@@ -0,0 +1,58 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\AgentProjectCateService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 代理商项目分类
+ *
+ * @property AgentProjectCateService $service
+ */
+class AgentProjectCateController extends AdminController
+{
+	protected string $serviceName = AgentProjectCateService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('agent_id', '代理商ID'),
+				amis()->TableColumn('cate_id', '项目分类ID'),
+				amis()->TableColumn('state', '状态(enable:启用 disable:禁用)'),
+				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('agent_id', '代理商ID'),
+			amis()->TextControl('cate_id', '项目分类ID'),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('agent_id', '代理商ID')->static(),
+			amis()->TextControl('cate_id', '项目分类ID')->static(),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

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

@@ -0,0 +1,67 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\AgentProjectService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 代理商服务项目
+ *
+ * @property AgentProjectService $service
+ */
+class AgentProjectController extends AdminController
+{
+	protected string $serviceName = AgentProjectService::class;
+
+	public function list()
+	{
+		$crud = $this->baseCRUD()
+			->filterTogglable(false)
+			->headerToolbar([
+				$this->createButton('dialog'),
+				...$this->baseHeaderToolBar()
+			])
+			->columns([
+				amis()->TableColumn('id', 'ID')->sortable(),
+				amis()->TableColumn('cate_id', '代理商项目分类ID'),
+				amis()->TableColumn('project_id', '项目ID'),
+				amis()->TableColumn('price', '项目金额'),
+				amis()->TableColumn('duration', '服务时长(分钟)')->sortable(),
+				amis()->TableColumn('distance', '接单距离(米)')->sortable(),
+				amis()->TableColumn('state', '状态(enable:启用 disable:禁用)'),
+				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('cate_id', '代理商项目分类ID'),
+			amis()->TextControl('project_id', '项目ID'),
+			amis()->TextControl('price', '项目金额'),
+			amis()->TextControl('duration', '服务时长(分钟)'),
+			amis()->TextControl('distance', '接单距离(米)'),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('cate_id', '代理商项目分类ID')->static(),
+			amis()->TextControl('project_id', '项目ID')->static(),
+			amis()->TextControl('price', '项目金额')->static(),
+			amis()->TextControl('duration', '服务时长(分钟)')->static(),
+			amis()->TextControl('distance', '接单距离(米)')->static(),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

+ 73 - 0
app/Admin/Controllers/CoachProjectController.php

@@ -0,0 +1,73 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\CoachProjectService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 技师开通服务项目
+ *
+ * @property CoachProjectService $service
+ */
+class CoachProjectController extends AdminController
+{
+	protected string $serviceName = CoachProjectService::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', '技师ID'),
+				amis()->TableColumn('project_id', '项目ID'),
+				amis()->TableColumn('discount_amount', '优惠金额'),
+				amis()->TableColumn('service_gender', '服务性别(all:不限 male:男 female:女)'),
+				amis()->TableColumn('service_distance', '服务距离(米)')->sortable(),
+				amis()->TableColumn('traffic_fee_type', '收取路费(free:免费 one_way:单程 round_trip:双程)'),
+				amis()->TableColumn('traffic_fee', '路费金额'),
+				amis()->TableColumn('state', '状态(enable:启用 disable:禁用)'),
+				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', '技师ID'),
+			amis()->TextControl('project_id', '项目ID'),
+			amis()->TextControl('discount_amount', '优惠金额'),
+			amis()->TextControl('service_gender', '服务性别(all:不限 male:男 female:女)'),
+			amis()->TextControl('service_distance', '服务距离(米)'),
+			amis()->TextControl('traffic_fee_type', '收取路费(free:免费 one_way:单程 round_trip:双程)'),
+			amis()->TextControl('traffic_fee', '路费金额'),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)'),
+		]);
+	}
+
+	public function detail()
+	{
+		return $this->baseDetail()->body([
+			amis()->TextControl('id', 'ID')->static(),
+			amis()->TextControl('coach_id', '技师ID')->static(),
+			amis()->TextControl('project_id', '项目ID')->static(),
+			amis()->TextControl('discount_amount', '优惠金额')->static(),
+			amis()->TextControl('service_gender', '服务性别(all:不限 male:男 female:女)')->static(),
+			amis()->TextControl('service_distance', '服务距离(米)')->static(),
+			amis()->TextControl('traffic_fee_type', '收取路费(free:免费 one_way:单程 round_trip:双程)')->static(),
+			amis()->TextControl('traffic_fee', '路费金额')->static(),
+			amis()->TextControl('state', '状态(enable:启用 disable:禁用)')->static(),
+			amis()->TextControl('created_at', admin_trans('admin.created_at'))->static(),
+			amis()->TextControl('updated_at', admin_trans('admin.updated_at'))->static(),
+		]);
+	}
+}

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

@@ -0,0 +1,79 @@
+<?php
+
+namespace App\Admin\Controllers;
+
+use App\Services\CoachRealAuthRecordService;
+use Slowlyo\OwlAdmin\Controllers\AdminController;
+
+/**
+ * 技师实名认证记录
+ *
+ * @property CoachRealAuthRecordService $service
+ */
+class CoachRealAuthRecordController extends AdminController
+{
+	protected string $serviceName = CoachRealAuthRecordService::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', '状态'),
+				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(),
+		]);
+	}
+}

+ 84 - 4
app/Http/Controllers/Client/CoachController.php

@@ -16,22 +16,102 @@ class CoachController extends Controller
     }
 
     /**
-     * 获取技师列表
+     * [技师管理] 获取技师列表
+     *
+     * 根据经纬度获取技师列表
+     *
+     * @return \Illuminate\Http\JsonResponse
+     *
+     * @OA\Get(
+     *     path="/api/coaches",
+     *     summary="获取技师列表",
+     *     description="根据经纬度获取技师列表",
+     *     tags={"Coaches"},
+     *
+     *     @OA\Parameter(
+     *         name="latitude",
+     *         in="query",
+     *         required=true,
+     *
+     *         @OA\Schema(type="number", format="float"),
+     *         description="纬度"
+     *     ),
+     *
+     *     @OA\Parameter(
+     *         name="longitude",
+     *         in="query",
+     *         required=true,
+     *
+     *         @OA\Schema(type="number", format="float"),
+     *         description="经度"
+     *     ),
+     *
+     *     @OA\Response(
+     *         response=200,
+     *         description="成功获取技师列表"
+     *     )
+     * )
      */
     public function list(Request $request)
     {
         $latitude = $request->input('latitude');
-        $longitude = $request->input('longitude'); 
+        $longitude = $request->input('longitude');
+
         return $this->service->getCoachList($latitude, $longitude);
     }
 
     /**
-     * 获取技师详情
+     * [技师管理] 获取技师详情
+     *
+     * 根据ID获取技师的详细信息
+     *
+     * @param  int  $id
+     * @return \Illuminate\Http\JsonResponse
+     *
+     * @OA\Get(
+     *     path="/api/coaches/{id}",
+     *     summary="获取技师详情",
+     *     description="根据ID获取技师的详细信息",
+     *     tags={"Coaches"},
+     *
+     *     @OA\Parameter(
+     *         name="id",
+     *         in="path",
+     *         required=true,
+     *
+     *         @OA\Schema(type="integer"),
+     *         description="技师ID"
+     *     ),
+     *
+     *     @OA\Parameter(
+     *         name="latitude",
+     *         in="query",
+     *         required=false,
+     *
+     *         @OA\Schema(type="number", format="float"),
+     *         description="纬度"
+     *     ),
+     *
+     *     @OA\Parameter(
+     *         name="longitude",
+     *         in="query",
+     *         required=false,
+     *
+     *         @OA\Schema(type="number", format="float"),
+     *         description="经度"
+     *     ),
+     *
+     *     @OA\Response(
+     *         response=200,
+     *         description="成功获取技师详情"
+     *     )
+     * )
      */
     public function detail(Request $request, $id)
     {
         $latitude = $request->input('latitude');
         $longitude = $request->input('longitude');
+
         return $this->service->getCoachDetail($id, $latitude, $longitude);
     }
-} 
+}

+ 118 - 4
app/Http/Controllers/Client/ProjectController.php

@@ -6,6 +6,9 @@ use App\Http\Controllers\Controller;
 use App\Services\Client\ProjectService;
 use Illuminate\Http\Request;
 
+/**
+ * 项目管理相关的API接口
+ */
 class ProjectController extends Controller
 {
     protected ProjectService $service;
@@ -15,28 +18,139 @@ class ProjectController extends Controller
         $this->service = $service;
     }
 
+    /**
+     * [项目管理] 获取项目列表
+     *
+     * 根据区域代码获取项目列表
+     *
+     * @authenticated
+     *
+     * @queryParam area_code string 区域代码. Example: 330100
+     * @queryParam project_cate_id integer 项目分类ID. Example: 1
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": {
+     *     "current_page": 1,
+     *     "data": [
+     *       {
+     *         "id": 1,
+     *         "name": "项目名称",
+     *         "description": "项目描述",
+     *         "price": "100.00",
+     *         "duration": 60,
+     *         "category": {
+     *           "id": 1,
+     *           "name": "分类名称"
+     *         }
+     *       }
+     *     ],
+     *     "total": 10,
+     *     "per_page": 10
+     *   }
+     * }
+     */
     public function index(Request $request)
     {
         $areaCode = $request->input('area_code');
-        return $this->service->getProjectList($areaCode);
+        $projectCateId = $request->input('project_cate_id');
+
+        return $this->service->getProjectList($areaCode, $projectCateId);
     }
 
     /**
-     * 获取项目详情
+     * [项目管理] 获取项目详情
+     *
+     * 获取指定项目的详细信息
+     *
+     * @authenticated
+     *
+     * @urlParam id integer required 项目ID. Example: 1
+     *
+     * @queryParam area_code string required 区域代码. Example: 330100
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": {
+     *     "id": 1,
+     *     "name": "项目名称",
+     *     "description": "项目描述",
+     *     "price": "100.00",
+     *     "duration": 60,
+     *     "category": {
+     *       "id": 1,
+     *       "name": "分类名称"
+     *     },
+     *     "agent": {
+     *       "id": 1,
+     *       "name": "代理商名称",
+     *       "contact": "联系人",
+     *       "mobile": "13800138000"
+     *     }
+     *   }
+     * }
+     * @response 404 {
+     *   "code": 404,
+     *   "message": "项目不存在"
+     * }
+     * @response 400 {
+     *   "code": 400,
+     *   "message": "该区域暂无代理商"
+     * }
      */
     public function detail(Request $request, $id)
     {
         $areaCode = $request->input('area_code');
+
         return $this->service->getProjectDetail($id, $areaCode);
     }
 
     /**
-     * 获取技师开通的项目列表
+     * [项目管理] 获取技师项目列表
+     *
+     * 获取指定技师已开通的项目列表
+     *
+     * @authenticated
+     *
+     * @queryParam coach_id integer required 技师ID. Example: 1
+     * @queryParam area_code string required 区域代码. Example: 330100
+     * @queryParam project_cate_id integer 项目分类ID. Example: 1
+     *
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": {
+     *     "current_page": 1,
+     *     "data": [
+     *       {
+     *         "id": 1,
+     *         "name": "项目名称",
+     *         "description": "项目描述",
+     *         "price": "100.00",
+     *         "duration": 60,
+     *         "category": {
+     *           "id": 1,
+     *           "name": "分类名称"
+     *         }
+     *       }
+     *     ],
+     *     "total": 10,
+     *     "per_page": 10
+     *   }
+     * }
+     * @response 404 {
+     *   "code": 404,
+     *   "message": "技师不存在或未通过认证"
+     * }
      */
     public function coachProjectList(Request $request)
     {
         $coachId = $request->input('coach_id');
         $areaCode = $request->input('area_code');
-        return $this->service->getCoachProjectList($coachId, $areaCode);
+        $projectCateId = $request->input('project_cate_id');
+
+        return $this->service->getCoachProjectList($coachId, $areaCode, $projectCateId);
     }
 }

+ 12 - 1
app/Http/Controllers/Client/WalletController.php

@@ -15,10 +15,21 @@ class WalletController extends Controller
     }
 
     /**
+     * [钱包管理] 获取钱包明细
+     *
      * 获取钱包明细
+     *
+     * @authenticated
+     * @response {
+     *   "code": 200,
+     *   "message": "获取成功",
+     *   "data": {
+     *     "records": []
+     *   }
+     * }
      */
     public function records()
     {
         return $this->service->getWalletRecords();
     }
-} 
+}

+ 16 - 0
app/Models/AgentProject.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 代理商服务项目
+ */
+class AgentProject extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'agent_project';
+}

+ 16 - 0
app/Models/AgentProjectCate.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 代理商项目分类
+ */
+class AgentProjectCate extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'agent_project_cate';
+}

+ 16 - 0
app/Models/CoachProject.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\SoftDeletes;
+use Slowlyo\OwlAdmin\Models\BaseModel as Model;
+
+/**
+ * 技师开通服务项目
+ */
+class CoachProject extends Model
+{
+	use SoftDeletes;
+
+	protected $table = 'coach_project';
+}

+ 16 - 0
app/Models/CoachRealAuthRecord.php

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

+ 17 - 0
app/Services/AgentProjectCateService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\AgentProjectCate;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 代理商项目分类
+ *
+ * @method AgentProjectCate getModel()
+ * @method AgentProjectCate|\Illuminate\Database\Query\Builder query()
+ */
+class AgentProjectCateService extends AdminService
+{
+	protected string $modelName = AgentProjectCate::class;
+}

+ 17 - 0
app/Services/AgentProjectService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\AgentProject;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 代理商服务项目
+ *
+ * @method AgentProject getModel()
+ * @method AgentProject|\Illuminate\Database\Query\Builder query()
+ */
+class AgentProjectService extends AdminService
+{
+	protected string $modelName = AgentProject::class;
+}

+ 27 - 15
app/Services/Client/CoachService.php

@@ -4,9 +4,7 @@ namespace App\Services\Client;
 
 use App\Models\CoachUser;
 use App\Models\MemberUser;
-use App\Models\CoachLocation;
 use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\DB;
 
 class CoachService
 {
@@ -16,9 +14,8 @@ class CoachService
     public function getCoachList($latitude, $longitude)
     {
         // 获取当前用户
-        $userId = Auth::id();
-        $user = MemberUser::findOrFail($userId);
-        
+        $user = Auth::user();
+
         // 检查用户状态
         if ($user->state !== 'enable') {
             throw new \Exception('用户状态异常');
@@ -27,15 +24,30 @@ class CoachService
         // 获取附近的技师
         $coaches = CoachUser::query()
             ->where('state', 'enable')
-            ->where('auth_state', 'passed')
-            ->with(['user:id,nickname,avatar,gender'])
-            ->with(['location'])
-            ->whereHas('location', function($query) use ($latitude, $longitude) {
-                // TODO: 实现经纬度范围查询
-                $query->where('state', 'enable');
+            ->whereHas('info', function ($query) {
+                $query->where('state', 'approved');
             })
+            ->whereHas('real', function ($query) {
+                $query->where('state', 'approved');
+            })
+            ->whereHas('qual', function ($query) {
+                $query->where('state', 'approved');
+            })
+            ->with(['info:id,nickname,avatar,gender'])
+            ->with(['locations'])
+            ->whereHas('locations', function ($query) use ($latitude, $longitude) {
+                $query->where('state', 'enable')
+                    ->whereRaw(
+                        '(6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) < ?',
+                        [$latitude, $longitude, $latitude, 40]
+                    );
+            })
+            ->selectRaw(
+                '*, (6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude)))) as distance',
+                [$latitude, $longitude, $latitude]
+            )
             ->paginate(10);
-            
+
         return $coaches;
     }
 
@@ -47,7 +59,7 @@ class CoachService
         // 获取当前用户
         $userId = Auth::id();
         $user = MemberUser::findOrFail($userId);
-        
+
         // 检查用户状态
         if ($user->state !== 'enable') {
             throw new \Exception('用户状态异常');
@@ -68,7 +80,7 @@ class CoachService
         }
 
         $coach->distance = $distance;
-        
+
         return $coach;
     }
-} 
+}

+ 84 - 70
app/Services/Client/ProjectService.php

@@ -2,125 +2,139 @@
 
 namespace App\Services\Client;
 
+use App\Models\AgentInfo;
 use App\Models\CoachUser;
-use App\Models\ShopCoachService;
 use App\Models\Project;
-use App\Models\AgentInfo;
-use Illuminate\Support\Facades\DB;
+use App\Models\ProjectCate;
 
 class ProjectService
 {
-
     /**
      * 获取项目列表
      */
-    public function getProjectList($areaCode)
+    public function getProjectList($areaCode, $projectCateId = null)
     {
         // 根据区域代码获取代理商
         $agent = $this->findAvailableAgent($areaCode);
 
         // 获取项目列表
         if ($agent) {
+            dd($agent->cates());
             $projects = Project::where('state', 'enable')
                 ->where('agent_id', $agent->id)
                 ->paginate(10);
         } else {
-            $projects = Project::where('state', 'enable')
-                ->paginate(10);
+            $projectCate = ProjectCate::find($projectCateId);
+            $projects = $projectCate?->services;
         }
 
         return $projects;
     }
 
     /**
-     * 递归查找可用的代理商
-     *
-     * @param string $areaCode 区域代码
-     * @return Agent|null
+     * 获取项目详情
      */
-    private function findAvailableAgent($areaCode)
+    public function getProjectDetail($projectId, $areaCode)
     {
-        // 先查找当前区域的代理商
-        $agent = AgentInfo::where('area_code', $areaCode)
-            ->where('state', 'enable')
-            ->first();
+        // 根据区域代码获取代理商
+        $agent = $this->findAvailableAgent($areaCode);
 
-        if ($agent) {
-            return $agent;
+        // 查询系统项目
+        $project = Project::where('state', 'enable')->find($projectId);
+
+        if (! $project) {
+            throw new \Exception('项目不存在');
         }
 
-        // 如果找不到,且区域代码长度大于2,则向上查找
-        if (strlen($areaCode) > 2) {
-            // 去掉最后两位,获取上级区域代码
-            $parentAreaCode = substr($areaCode, 0, -2);
-            return $this->findAvailableAgent($parentAreaCode);
+        if ($agent) {
+            // 查询代理商项目
+            $project = $agent->projects()->where('project_id', $projectId)->first();
         }
 
-        return null;
+        return $project;
     }
 
     /**
-     * 获取项目详情
+     * 获取技师开通的项目列表
      */
-    public function getProjectDetail($projectId, $areaCode)
+    public function getCoachProjectList($coachId, $areaCode, $projectCateId)
     {
-        // 根据区域代码获取代理商
-        $agent = AgentInfo::where('business_area', 'like', "%{$areaCode}%")
+        // 查询技师信息
+        $coach = CoachUser::where('id', $coachId)
             ->where('state', 'enable')
-            ->first();
+            ->find($coachId);
 
-        if (!$agent) {
-            throw new \Exception('该区域暂无代理商');
+        if (! $coach) {
+            throw new \Exception('技师不存在');
         }
 
-        // 获取项目详情
-        $project = Project::where('id', $projectId)
-            ->where('state', 'enable')
-            ->where(function($query) use ($areaCode) {
-                // 根据区域地址过滤项目
-                $query->where('area_code', $areaCode);
-            })
-            ->with([
-                'category:id,name',
-                'agent' => function($query) use ($agent) {
-                    $query->where('id', $agent->id);
-                }
-            ])
-            ->firstOrFail();
+        $coachInfo = $coach->info;
+        if (! $coachInfo) {
+            throw new \Exception('技师信息不存在');
+        }
 
-        return $project;
+        if ($coachInfo->state !== 'approved') {
+            throw new \Exception('技师未通过审核');
+        }
+
+        $coachQual = $coach->qual;
+        if (! $coachQual) {
+            throw new \Exception('技师资格证书不存在');
+        }
+
+        if ($coachQual->state !== 'approved') {
+            throw new \Exception('技师资格证书未通过审核');
+        }
+
+        $coachReal = $coach->real;
+
+        if (! $coachReal) {
+            throw new \Exception('技师实名认证记录不存在');
+        }
+
+        if ($coachReal->state !== 'approved') {
+            throw new \Exception('技师实名认证未通过审核');
+        }
+
+        // 获取技师开通的项目ID列表
+        $projectIds = $coach->projects()->where('state', 'enable')->pluck('project_id');
+
+        // 根据区域代码获取代理商
+        $agent = $this->findAvailableAgent($areaCode);
+
+        if ($agent) {
+            $agentCate = $agent->cates()->where('project_cate_id', $projectCateId)->first();
+            $projectIds = $agentCate->projects()->whereIn('project_id', $projectIds)->pluck('project_id');
+        }
+
+        return $coach->projects()->with('basicInfo')->whereIn('project_id', $projectIds)->get();
     }
 
     /**
-     * 获取技师开通的项目列表
+     * 递归查找可用的代理商
+     *
+     * @param  string  $areaCode  区域代码
+     * @return Agent|null
      */
-    public function getCoachProjectList($coachId, $areaCode)
+    private function findAvailableAgent($areaCode)
     {
-        // 查询技师信息
-        $coach = CoachUser::where('id', $coachId)
+        // 先查找当前区域的代理商
+        $agent = AgentInfo::where('area_code', $areaCode)
             ->where('state', 'enable')
-            ->where('auth_state', 'passed')
-            ->firstOrFail();
+            ->first();
 
-        // 获取技师开通的项目ID列表
-        $projectIds = ShopCoachService::where('coach_id', $coachId)
-            ->where('state', 'enable')
-            ->pluck('shop_service_id');
+        if ($agent) {
+            return $agent;
+        }
 
-        // 查询项目列表
-        $projects = Project::whereIn('id', $projectIds)
-            ->where('state', 'enable')
-            ->where(function($query) use ($areaCode) {
-                // TODO: 根据区域地址过滤项目
-                $query->where('area_code', $areaCode);
-            })
-            ->with([
-                'category:id,name',
-                // 其他关联
-            ])
-            ->orderBy('sort', 'desc')
-            ->paginate(10);
+        // 如果找不到,且区域代码长度大于2,则向上查找
+        if (strlen($areaCode) > 2) {
+            // 去掉最后两位,获取上级区域代码
+            $parentAreaCode = substr($areaCode, 0, -2);
 
-        return $projects;
+            return $this->findAvailableAgent($parentAreaCode);
+        }
+
+        return null;
     }
 }

+ 17 - 0
app/Services/CoachProjectService.php

@@ -0,0 +1,17 @@
+<?php
+
+namespace App\Services;
+
+use App\Models\CoachProject;
+use Slowlyo\OwlAdmin\Services\AdminService;
+
+/**
+ * 技师开通服务项目
+ *
+ * @method CoachProject getModel()
+ * @method CoachProject|\Illuminate\Database\Query\Builder query()
+ */
+class CoachProjectService extends AdminService
+{
+	protected string $modelName = CoachProject::class;
+}

+ 17 - 0
app/Services/CoachRealAuthRecordService.php

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

+ 36 - 0
database/migrations/2024_11_20_100445_create_agent_project_cate_table.php

@@ -0,0 +1,36 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('agent_project_cate', function (Blueprint $table) {
+            $table->comment('代理商项目分类');
+            $table->increments('id');
+            $table->unsignedBigInteger('agent_id')->comment('代理商ID');
+            $table->unsignedBigInteger('cate_id')->comment('项目分类ID');
+            $table->string('state')->default('enable')->comment('状态(enable:启用 disable:禁用)');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('agent_project_cate');
+    }
+};

+ 39 - 0
database/migrations/2024_11_20_100525_create_agent_project_table.php

@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('agent_project', function (Blueprint $table) {
+            $table->comment('代理商服务项目');
+            $table->increments('id');
+            $table->unsignedBigInteger('cate_id')->comment('代理商项目分类ID');
+            $table->unsignedBigInteger('project_id')->comment('项目ID');
+            $table->decimal('price')->default(new \Illuminate\Database\Query\Expression('0.00'))->comment('项目金额');
+            $table->integer('duration')->default(new \Illuminate\Database\Query\Expression('0'))->comment('服务时长(分钟)');
+            $table->integer('distance')->default(new \Illuminate\Database\Query\Expression('0'))->comment('接单距离(米)');
+            $table->string('state')->default('enable')->comment('状态(enable:启用 disable:禁用)');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('agent_project');
+    }
+};

+ 43 - 0
database/migrations/2024_11_20_131419_create_coach_real_auth_records_table.php

@@ -0,0 +1,43 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('coach_real_auth_records', function (Blueprint $table) {
+            $table->comment('技师实名认证记录');
+            $table->increments('id');
+            $table->unsignedBigInteger('coach_id')->comment('技师编号');
+            $table->string('real_name')->default('')->comment('真实姓名');
+            $table->string('id_card')->default('')->comment('身份证号');
+            $table->string('id_card_front_photo')->default('')->comment('身份证正面照片');
+            $table->string('id_card_back_photo')->default('')->comment('身份证反面照片');
+            $table->string('id_card_hand_photo')->default('')->comment('手持身份证照片');
+            $table->string('auditor')->nullable()->comment('审核人');
+            $table->timestamp('audit_time')->nullable()->comment('审核时间');
+            $table->string('audit_remark')->nullable()->comment('审核回执');
+            $table->string('state')->default('pending')->comment('状态');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('coach_real_auth_records');
+    }
+};

+ 41 - 0
database/migrations/2024_11_20_133233_create_coach_project_table.php

@@ -0,0 +1,41 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::create('coach_project', function (Blueprint $table) {
+            $table->comment('技师开通服务项目');
+            $table->increments('id');
+            $table->unsignedBigInteger('coach_id')->comment('技师ID');
+            $table->unsignedBigInteger('project_id')->comment('项目ID');
+            $table->decimal('discount_amount')->default(new \Illuminate\Database\Query\Expression('0.00'))->comment('优惠金额');
+            $table->string('service_gender')->default('all')->comment('服务性别(all:不限 male:男 female:女)');
+            $table->integer('service_distance')->default(new \Illuminate\Database\Query\Expression('0'))->comment('服务距离(米)');
+            $table->string('traffic_fee_type')->default('free')->comment('收取路费(free:免费 one_way:单程 round_trip:双程)');
+            $table->decimal('traffic_fee')->default(new \Illuminate\Database\Query\Expression('0.00'))->comment('路费金额');
+            $table->string('state')->default('enable')->comment('状态(enable:启用 disable:禁用)');
+            $table->timestamps();
+            $table->softDeletes();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::dropIfExists('coach_project');
+    }
+};

+ 8 - 0
routes/admin.php

@@ -79,5 +79,13 @@ Route::group([
     $router->resource('sys_region', \App\Admin\Controllers\SysRegionController::class);
     // 系统设置
     $router->resource('sys_config', \App\Admin\Controllers\SysConfigController::class);
+    // 代理商项目分类
+    $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);
 
 });

+ 10 - 3
routes/api.php

@@ -1,9 +1,10 @@
 <?php
 
 use App\Http\Controllers\Client\AccountController;
+use App\Http\Controllers\Client\CoachController;
+use App\Http\Controllers\Client\ProjectController;
 use App\Http\Controllers\Client\UserController;
 use App\Http\Controllers\ScribeController;
-use App\Http\Controllers\Client\ProjectController;
 use Illuminate\Support\Facades\Route;
 
 // API文档相关
@@ -48,7 +49,13 @@ Route::middleware('auth:sanctum')->group(function () {
     });
 
     Route::prefix('project')->group(function () {
-        Route::get('/{id}/detail', [ProjectController::class, 'detail']);
-        Route::get('/coach-list', [ProjectController::class, 'coachProjectList']);
+        Route::get('/', [ProjectController::class, 'index']); // 获取项目列表
+        Route::get('/{id}/detail', [ProjectController::class, 'detail']); // 获取项目详情
+        Route::get('/coach-list', [ProjectController::class, 'coachProjectList']); // 获取技师开通的项目列表
+    });
+
+    Route::prefix('coach')->group(function () {
+        Route::get('/', [CoachController::class, 'list']); // 获取技师列表
+        Route::get('/{id}', [CoachController::class, 'detail']); // 获取技师详情
     });
 });