人称外号大脸猫

Laravel树形菜单接口终极指南:状态过滤与空子节点优化实战

下面我们以menus表 菜单表为例

// database/migrations/xxxx_create_menus_table.php
Schema::create('menus', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->unsignedBigInteger('parent_id')->nullable();
    $table->tinyInteger('status')->default(1)->comment('1启用 0禁用');
    $table->integer('order')->default(0);
    $table->timestamps();

    $table->foreign('parent_id')->references('id')->on('menus');
});

模型

// app/Models/Menu.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Menu extends Model
{
    protected $fillable = ['name', 'parent_id', 'status', 'order'];
    
    // 递归子菜单关系(只获取启用状态的子菜单)
    public function children()
    {
        return $this->hasMany(Menu::class, 'parent_id')
            ->where('status', 1)
            ->orderBy('order')
            ->with('children'); // 递归预加载
    }
}

方法一:

// app/Http/Controllers/MenuController.php
namespace App\Http\Controllers;

use App\Models\Menu;
use Illuminate\Http\Request;

class MenuController extends Controller
{
    public function tree()
    {
        // 获取所有顶级菜单(parent_id=null)且状态为1,并递归加载子菜单
        $menus = Menu::where('status', 1)
            ->whereNull('parent_id')
            ->with('children')
            ->orderBy('order')
            ->get();

        return response()->json($menus);
    }
}
public function tree()
{
    $menus = Menu::where('status', 1)->get();
    
    $tree = $menus->whereNull('parent_id')
        ->map(function ($menu) use ($menus) {
            $menu->children = $this->buildTree($menus, $menu->id);
            return $menu;
        });
    
    return $tree;
}
private function buildTree($menus, $parentId)
{
    return $menus->where('parent_id', $parentId)
        ->map(function ($child) use ($menus) {
            $child->children = $this->buildTree($menus, $child->id);
            return $child;
        });
}

方法二:

菜单资源类 (MenuResource)

php artisan make:resource MenuResource
// app/Http/Resources/MenuResource.php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

class MenuResource extends JsonResource
{
    public function toArray($request)
    {
        $data = [
            'id' => $this->id,
            'name' => $this->name,
            'status' => $this->status,
            'order' => $this->order,
            // 其他需要的字段...
        ];

        // 只有当子菜单存在且非空时,才包含 children 字段
        if ($this->relationLoaded('children') && $this->children->isNotEmpty()) {
            $data['children'] = MenuResource::collection($this->children);
        }

        return $data;
    }
}

更新model
// app/Models/Menu.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Menu extends Model
{
    protected $fillable = ['name', 'parent_id', 'status', 'order'];
    
    public function children()
    {
        return $this->hasMany(Menu::class, 'parent_id')
            ->where('status', 1) // 只获取启用状态的子菜单
            ->orderBy('order')
            ->with('children'); // 递归加载子菜单
    }
}


更新控制器
// app/Http/Controllers/MenuController.php

namespace App\Http\Controllers;

use App\Models\Menu;
use App\Http\Resources\MenuResource;

class MenuController extends Controller
{
    public function tree()
    {
        $menus = Menu::where('status', 1)
            ->whereNull('parent_id') // 顶级菜单
            ->with('children')      // 预加载子菜单
            ->orderBy('order')
            ->get();

        return MenuResource::collection($menus);
    }
}

代替方案

// 在控制器中
public function tree()
{
    $menus = Menu::where('status', 1)
        ->whereNull('parent_id')
        ->with('children')
        ->orderBy('order')
        ->get()
        ->map(function ($menu) {
            // 递归处理子菜单
            $this->removeEmptyChildren($menu);
            return $menu;
        });
    
    return response()->json($menus);
}

private function removeEmptyChildren(&$menu)
{
    if ($menu->children->isNotEmpty()) {
        $menu->children->each(function ($child) {
            $this->removeEmptyChildren($child);
        });
    } else {
        unset($menu->children);
    }
}

缓存支持:添加缓存提高性能

use Illuminate\Support\Facades\Cache;

public function tree()
{
    return Cache::remember('menu-tree', 3600, function () {
        $menus = Menu::where('status', 1)
            ->whereNull('parent_id')
            ->with('children')
            ->orderBy('order')
            ->get();
        
        return MenuResource::collection($menus);
    });
}

预期结果

{
  "id": 2,
  "name": "产品",
  "children": [ // 无空children字段
    {
      "id": 3,
      "name": "手机",
      "children": [ // 嵌套结构保持
        {"id": 5, "name": "配件"} 
      ]
    }
  ]
}
copyright ©2025 ahimu.com all rights reserved 皖ICP备19021547号-1