前言
先澄清一个前提:laravel-admin 的「添加」按钮和「编辑」按钮,默认是框架根据资源路由自动生成的链接,它们不接受你直接塞参数。想让它带上上下文(比如「从某个分类页进来,新建的商品要默认归到那个分类」),只有两条路——改掉按钮本身渲染出的链接,或者关掉默认按钮、自己追加一个带参数的链接。
第二个误解是「参数传过去就完事了」。URL 上的参数对用户完全可见、可篡改,服务端必须重新校验,不能因为「链接是自己生成的」就无条件信任。这一点在后台管理里尤其重要,因为后台参数往往直接决定数据归属。
另外要提一句版本状况:laravel-admin 是个人维护项目,1.8.x 主线对 Laravel 版本有上限,较新的 Laravel 版本可能需要 fork 或改用其他后台方案。本文示例基于 laravel-admin 1.8.x 的 API,涉及方法名的地方请以你vendor/encore/laravel-admin下的源码为准。
一、先分清两类按钮
| 类型 | 出现位置 | 定制入口 | 常用方法 |
|---|
| 行内按钮 | 每行右侧的「编辑 / 删除 / 详情」 | $grid->actions(function ($actions) { ... }) | getKey()、row、append()、disableEdit() |
| 顶部工具按钮 | 表格右上角的「新建 / 刷新 / 导出」 | $grid->tools(function ($tools) { ... }) | append()、disableCreateButton() |
| 批量操作按钮 | 勾选行后出现 | $grid->batchActions(...) | 同上模式 |
两类按钮的定制套路是一样的:先关掉默认的,再 append 一个自己拼好的链接。区别只在于行内按钮能拿到当前行的主键,顶部按钮拿不到。
二、行内编辑按钮携带参数
场景:商品列表页希望点「编辑」时带上「当前是从待审核视图进来的」这个标记,编辑页据此显示不同提示。
<?php // app/Admin/Controllers/PostController.php(laravel-admin 1.8.x)
use Encore\Admin\Controllers\AdminController;
use Encore\Admin\Grid;
protected function grid()
{
$grid = new Grid(new Post());
$grid->column('id', 'ID');
$grid->column('title', '标题');
$grid->actions(function ($actions) {
// 1. 关掉框架自带的编辑按钮
$actions->disableEdit();
// 2. getKey() 取当前行的主键,row 属性是当前行的数据对象
$id = $actions->getKey();
// 3. 用 admin_url() 拼后台 URL,它会带上 admin.prefix 配置的前缀
$url = admin_url('posts/'.$id.'/edit')
.'?'.http_build_query([
'from' => 'audit',
'step' => 2,
]);
// 4. append 的内容会被原样渲染到按钮区
$actions->append(
'<a class="btn btn-xs btn-primary" href="'.$url.'">编辑</a>'
);
});
return $grid;
}几个关键点:
$actions->getKey()返回当前行主键;$actions->row是当前行的模型/数组,取其他字段用$actions->row->xxx。http_build_query()会负责 URL 编码,比手拼?a=1&b=2安全,值里带中文、空格、&都不会把 URL 拼坏。admin_url()是 laravel-admin 提供的辅助函数,用来拼后台地址(会尊重配置里的后台路径前缀)。如果你的版本里没有这个函数,直接拼相对路径也能用,但后台路径前缀改了以后要跟着改。- 关掉默认编辑按钮用
disableEdit();同理还有disableDelete()、disableView()。三个都关掉后actions列会变空,如果你还想保留一个自建的「详情」链接,就在append()里补上。
如果不想让业务逻辑暴露在 URL 里(比如「复制一条记录」),用自定义行操作类更合适:
<?php // app/Admin/Actions/Post/CopyAction.php
namespace App\Admin\Actions\Post;
use Encore\Admin\Actions\RowAction;
use Illuminate\Database\Eloquent\Model;
class CopyAction extends RowAction
{
public $name = '复制';
/**
* 处理动作:框架会以 POST 请求调用它,并注入当前行的模型。
* 注意需要在目标控制器里注册:$grid->actions(...) 中 append 该类实例。
*/
public function handle(Model $model)
{
$copy = $model->replicate();
$copy->title = $model->title.'(副本)';
$copy->save();
return $this->response()->success('已复制')->refresh();
}
/**
* 如果你希望这个按钮是「链接跳转」而不是 POST 提交,
* 就重写 href() 返回一个带参数的地址。
* 方法名以你安装版本的源码为准。
*/
public function href()
{
return admin_url('posts/'.$this->getKey().'/copy').'?from=list';
}
}挂到表格上:
<?php // 在 grid() 里
use App\Admin\Actions\Post\CopyAction;
$grid->actions(function ($actions) {
// 把自定义动作实例追加到行操作区;
// 若你的版本没有 add(),可改为 $grid->actions([new CopyAction()]),以源码为准
$actions->add(new CopyAction());
});RowAction基类已经处理了权限判断、确认弹窗、POST 提交与响应,自建类只关心业务逻辑即可。
三、顶部「添加」按钮携带参数
顶部按钮拿不到「当前行」,但经常需要带「当前筛选条件」或「父级 ID」。做法是关掉默认的新建按钮,自己 append 一个:
<?php // 在 grid() 里
$grid->disableCreateButton();
$grid->tools(function ($tools) use ($parentId) {
$url = admin_url('posts/create').'?'.http_build_query([
'parent_id' => $parentId,
'from' => 'category',
]);
$tools->append(
'<a class="btn btn-sm btn-primary" href="'.$url.'">'
.'<i class="fa fa-plus"></i> 新建文章</a>'
);
});$parentId可以从当前请求里取(request('parent_id')),也可以由控制器的grid()方法从路由参数里拿。不要把用户可控的值直接当作parent_id用,先做一次归属校验。
另一个常见需求是「让新建按钮带上当前列表的筛选条件」。laravel-admin 的筛选(filter)状态本身会按 grid 保存在会话里,翻页、刷新后都会保留,所以多数场景下不必手工传递;真正需要手工传的,是那些不在 filter 里、但对新建有默认值意义的参数。
四、表单侧接收参数
按钮把参数带过来了,接收端在表单方法里读request()并设成默认值:
<?php // app/Admin/Controllers/PostController.php
use Encore\Admin\Form;
protected function form()
{
$form = new Form(new Post());
// 用 hidden 字段把上下文参数透传到提交数据里
$form->hidden('source_type')->default(request('from', 'direct'));
// 条件默认值:只有从分类页进来时才预填分类
$form->select('category_id', '分类')
->options($this->categoryOptions())
->default(request('category_id'));
$form->text('title', '标题')->rules('required|max:120');
// 服务端二次校验:不要相信 URL 上传来的归属关系
$form->saving(function (Form $form) {
$categoryId = (int) $form->category_id;
$allowed = \App\Models\Category::query()
->whereKey($categoryId)
->where('owner_id', \Encore\Admin\Facades\Admin::user()->id)
->exists();
if (! $allowed) {
// 回调里抛出异常会中断本次保存,并作为错误提示返回;
// 具体呈现方式以你所装版本为准,也可以改用 return back()->withErrors(...)
throw new \RuntimeException('你无权在该分类下创建内容');
}
});
return $form;
}要点有三:
default()只在新建时有意义,编辑时会被模型里的值覆盖;这正好符合「新建带默认值、编辑显示原值」的预期。- 用
hidden字段把参数写进表单,提交后你还能在saving/saved回调里读到它,用于记录来源或做后续跳转。 saving回调是服务端唯一可信的校验点。URL 上的参数、隐藏字段的值,用户都能改,必须在这里重新验证。
如果表单提交后需要跳回带参数的列表页,可以在saved回调里返回一个重定向响应:
<?php // 在 form() 里
$form->saved(function (Form $form) {
$from = request('from');
if ($from === 'audit') {
return redirect(admin_url('posts?status=pending'));
}
// 返回 null 表示交给框架默认处理(回到列表页)
return null;
});回调的返回值是否被当作响应处理,以你所用版本的实现为准;如果发现不生效,改用控制器的update()/store()方法里手工return redirect(...),这条路径在任何版本上都稳定。
常见坑点
- ❌ 手拼 URL 时把用户输入直接拼接:
admin_url('posts/create?title='.$request->input('title'))。
✅ 用http_build_query()组装查询串,它会做 URL 编码,避免值里的&、空格、中文把 URL 结构破坏掉。
- ❌ 认为「参数是按钮自己生成的,所以可信」,直接把
parent_id写进数据库。
✅ URL 与隐藏字段都是客户端可控的,必须在saving回调或控制器里重新校验归属与权限。
- ❌ 关掉默认编辑按钮(
disableEdit())后忘了再 append 自己的按钮,结果行内操作列变空,用户以为后台坏了。
✅ 关掉几个就补上几个,或者只关掉确实不需要的那一个。
- ❌ 用
admin_url('/posts/1/edit')这种以斜杠开头的路径,某些版本下会拼出双斜杠或丢掉后台前缀。
✅ 传相对片段,让辅助函数自己去接前缀;不确定时打印一次生成的 URL 核对。
- ❌ 把「编辑」按钮做成普通链接跳转,却在自定义
RowAction里写了状态修改逻辑,期望它被调用。
✅RowAction::handle()是 POST 提交时执行的;纯链接跳转不会触发它。要跳转就重写href(),要执行逻辑就用默认的 POST 形态。
- ❌ 在
grid()里用request()取参数直接当作查询条件,参数缺失时查询条件变成where('x', null),列表直接空掉。
✅ 先判断参数是否存在再加条件:if (request()->filled('x')) { $grid->model()->where('x', request('x')); }。
- ❌ 自定义按钮里塞内联事件或脚本拼接参数,参数一含引号就把渲染搞坏。
✅ 参数走 URL 查询串(http_build_query())或隐藏字段,不要在 HTML 片段里裸拼用户数据。
- ❌ 以为 Grid 的
actions、tools回调可以放到任意位置调用。
✅ 这些定制必须在grid()方法返回$grid之前完成,控制器返回后再改不生效。
总结
| 需求 | 做法 | 关键点 |
|---|
| 行内编辑按钮带参数 | disableEdit()+append()自定义链接 | $actions->getKey()取行主键 |
| 顶部新建按钮带参数 | disableCreateButton()+tools()中append() | 参数从request()或路由变量取 |
| 参数安全拼接 | admin_url()+http_build_query() | 不要手拼字符串 |
| 复杂动作(复制、审核) | 继承RowAction,实现handle() | POST 提交,自动带模型 |
| 纯跳转动作 | 继承RowAction,重写href() | 方法名以源码为准 |
| 表单接收参数 | request()+default()+hidden() | default只对新建生效 |
| 服务端校验 | saving回调 | 唯一可信的校验位置 |
结论:laravel-admin 的按钮定制本质是「关掉默认的,自己拼一个链接或动作类」。行内按钮带参数靠$actions->getKey()拿主键,顶部按钮带参数靠request()拿上下文,两者都用admin_url()加http_build_query()组装 URL 最稳。而在表单侧,无论参数从哪里来,都要记住一句:URL 上的东西一律当作不可信输入,该校验的归属和权限必须在服务端重新做一遍。