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": "配件"}
]
}
]
}