删除父菜单后子菜单不会自动归属,需通过数据库外键(set null)或模型deleting事件手动更新pid实现自动挂载到根节点或指定父级。

删除父菜单后,子菜单默认不会自动归属到顶级或某个指定父级,Laravel-admin 本身不内置“自动重挂载”逻辑。但可通过数据库设计 + 模型事件 + 少量代码实现子菜单自动归属(如挂到根节点或指定备用父级),关键在于控制 pid 字段的更新行为。
确保表结构支持安全迁移
菜单表必须包含 pid 字段(建议设为 unsignedBigInteger,允许为 0 或 NULL 表示无父级),并建立外键约束(可选,但推荐加 ON DELETE SET NULL 或 ON DELETE CASCADE):
-
若用
SET NULL:删除父菜单时,子菜单的pid自动设为NULL,后续可在模型中统一处理为根级 -
若用
CASCADE:子菜单会被一并删除——不符合“自动归属”需求,应避免 -
不加外键时:需手动在删除前捕获子项并重置
pid
在 Menu 模型中监听删除事件
利用 Laravel 的模型事件,在父菜单被删除前,将其子菜单的 pid 更新为目标父级 ID(例如设为 0 表示顶级,或指定一个“回收站父级”ID):
- 在
app/Models/Menu.php中添加如下逻辑:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Menu extends Model
{
protected $fillable = ['title', 'pid', 'order', 'uri', 'icon'];
protected static function boot(): void
{
parent::boot();
static::deleting(function (self $menu) {
// 查找所有直接子菜单
$children = $menu->children;
if ($children->isNotEmpty()) {
// 方案1:全部提升为顶级(pid = 0)
$children->each(fn($child) => $child->update(['pid' => 0]));
// 方案2:挂到固定备用父级(如 ID=1 的“其他分类”)
// $children->each(fn($child) => $child->update(['pid' => 1]));
}
});
}
public function children(): HasMany
{
return $this->hasMany(static::class, 'pid');
}
}
配合 Laravel-admin 的 Tree 组件正确渲染
ModelTree 默认按 pid 构建层级。只要子菜单的 pid 已更新为有效值(如 0 或新父级 ID),刷新后台树形菜单即可自动显示新归属关系:
- 确保控制器中使用了
Encore\Admin\Grid\Displayers\Tree或继承ModelTreetrait - 例如在
MenuController中:
use Encore\Admin\Tree;
use App\Models\Menu;
class MenuController extends Controller
{
public function index(Content $content)
{
return $content->header('菜单管理')->body(Tree::make(new Menu));
}
}
补充:防止误删的关键校验
可在删除前增加业务判断,避免意外清空整棵树:
- 在控制器的
destroy方法中检查是否有子项,并提示用户 - 或限制仅允许删除无子项的菜单(前端禁用按钮 + 后端二次校验)
- 示例校验逻辑:
if ($menu->children()->count() > 0) {
return response()->json(['message' => '该菜单含有子项,无法直接删除'], 400);
}











