前言
最近在家失业无聊,接了个单,其中有个项目就是关于后台管理系统的,发了几个模板样例给对方,有之前用过的后台框架fastadmin、laraveladmin,但是对方看了都觉得怎么跟他之前的后台一个样,要求换一个简洁一点的,于是我在网上搜了一下laravel还有哪些后台框架,于是看到了dcatadmin,把demo网站发给对方看过之后,觉得这个页面还好,比较简单,随交流沟通后,确定用这个后台。
话说dcatadmin
dcatadmin这个框架我还是第一次用到,之前用laravel的后台框架都是用的laravel-admin,相对来说比较熟悉一点,dcatadmin还是第一次听到(因为上份工作都是围绕yii框架来展开的,关于laravel的消息确实查看的不多)。
dcatadmin2.x文档-简介 | 入门 |《Dcat Admin 中文文档 2.x》| Laravel China 社区
看完整体下来,dcatadmin重点需要了解的其实就是3个部分,也是后台系统中的组成:数据表格、表单、详情
但是还是有些地方没怎么看懂,尤其是关于工具表单、异步加载这块儿。时间有限,索性就在开发的过程中,再去搞清楚吧
因为现有文档描述不清,在开发过程中自己也是踩了不少坑,以下内容总结了在dcatadmin2.x中常用的几个功能,如有遇到类似疑问的开发者,可以少走一些弯路
数据表单
工具表单
工具表单的文档:工具表单 | 数据表单 |《Dcat Admin 中文文档 2.x》| Laravel China 社区
要实现的业务场景
举个例子:
比如说你想在数据列表中,对一行数据增加某个操作按钮,实现”充值“功能

在非dcatadmin2.x框架中一般实现思路是,实现按钮的点击功能,当点击“充值”按钮时,打开一个弹窗,弹窗里是个表单,输入你要充值的金额,然后点提交
这个功能在dcatadmin中就可以通过“工具表单”来实现,具体实现方法如下:
首先可以把点击充值这个看成一个动作,在项目中的App\Admin\Actions空间下实现一个充值动作类
class RechargeUserBalance extends RowAction
{
/**
* @return string
*/
protected $title = '';
public function render()
{
// 实例化表单类并传递自定义参数
$form = RechargeUserBalanceForm::make()->payload(['id' => $this->getKey()]);
return Modal::make()
->lg()
->title(admin_trans('admin-user.fields.recharge'))
->body($form)
->button("<i class='feather'>".admin_trans('admin-user.fields.recharge')."</i>");
}
}这个类中其实只需要一个方法就可以了,重点看下
Modal::make()
->lg()
->title(admin_trans('admin-user.fields.recharge'))
->body($form)
->button("<i class='feather'>".admin_trans('admin-user.fields.recharge')."</i>");Modal::make() 表示实例一个弹窗
lg弹窗大小,可选参数有sm、xltitle弹窗标题,左上角body弹窗内容,这里我是实例了一个表单类,下文再讲button页面显示的按钮
接下来我们看表单内容的具体实现
表单实现
在命名空间App\Admin\Forms定义一个表单类,我是直接把我的代码贴过来了,可以参考下
class RechargeUserBalanceForm extends Form implements LazyRenderable
{
use LazyWidget; // 使用异步加载功能
/**
* @param array $input
* @return \Dcat\Admin\Http\JsonResponse
* @throws \Throwable
* @author mingzhongshui
* @date 2025/6/17
*/
public function handle(array $input)
{
$userId = $this->payload['id'] ?? null;
if (!$userId) {
throw new \Exception(admin_trans_field('parameter_error'));
}
$balance = $input['balance'];
if (!is_numeric($balance)) {
throw new \Exception(admin_trans('yk-user.fields.recharge_balance_format_fail'));
}
if (floatval($balance) <= 0) {
throw new \Exception(admin_trans('yk-user.fields.recharge_balance_must_greater_than_0'));
}
if (!preg_match('/^\d+(\.\d{1,3})?$/', $balance)) {
throw new \Exception(admin_trans('yk-user.fields.recharge_balance_point_fail'));
}
$user = AdminUser::find($userId);
if (!$user) {
throw new \Exception(admin_trans('yk-user.fields.recharge_user_not_exists'));
}
DB::beginTransaction();
DB::connection('mysql_user_backend')->beginTransaction();
try {
// 更新原表balance
$oldBalance = $user['balance'];
$newBalance = bcadd($oldBalance, $balance, 3);
$user->balance = $newBalance;
$user->save();
// 增加充值日志
AdminUserBalanceDetailService::createRecord($userId, AdminUserBalanceDetail::TYPE_RECHARGE, AdminUserBalanceDetail::CHANGE_TYPE_ADD, $oldBalance, $balance, $newBalance);
DB::commit();
DB::connection('mysql_user_backend')->commit();
} catch (\Throwable $e) {
DB::rollBack();
DB::connection('mysql_user_backend')->rollBack();
throw new \Exception($e->getMessage());
}
return $this->response()->success(admin_trans_field('handle_success'))->refresh();
}
/**
* Build a form here.
*/
public function form()
{
$this->decimal('balance', admin_trans_field('recharge_balance'))->required();
}
/**
* The data of the form.
*
* @return array
*/
public function default()
{
return [];
}
}handle 就是表单提交之后的操作
form 表单显示内容
定义好动作类和表单类之后,接下来就是在数据列表中引入了
控制器引入动作类
首先在控制器中的列表方法(grid)中增加一个action
protected function grid()
{
return Grid::make(new AdminUser(), function (Grid $grid) {
$grid->model()->orderBy('id', 'desc');
$grid->column('id', admin_trans_field('data_number'))->sortable();
$grid->column('username');
$grid->column('balance');
$grid->column('username');
$grid->column('status')->radio(YkUserService::getStatusOption());
$grid->column('created_at')->sortable();
$grid->filter(function (Grid\Filter $filter) {
$filter->equal('id');
});
$grid->setActionClass(Grid\Displayers\DropdownActions ::class);
$grid->actions([new RechargeUserBalance()]);
});
}$grid->actions([new RechargeUserBalance()]);就是引入咱们上文定义好的动作类。
当然你在增加的时候可以通过某些条件来判断是否要显示,比如
$grid->actions(function ($actions) {
// 获取当前行数据
$row = $actions->row;
// 判断条件
if ($row->status == \App\Models\YkCollectBloggerTask::STATUS_COMPLETED && $row->collect_data_quantity) {
// 追加动作(操作按钮)
$actions->append(new BloggerDataFilterAction());
$actions->append(new BloggerDataExportAction());
}
});又当然,比如你不想使用默认的查看、编辑、删除按钮,要自定义这些,又或者想要按照自己的要求,展示或不展示某些按钮,你还可以这样实现:
public function setTextActions(Grid $grid, array $showActions = ['view' => true, 'edit' => true, 'delete' => true])
{
// 不显示原生查看、编辑、删除按钮
$grid->disableViewButton();
$grid->disableEditButton();
$grid->disableDeleteButton();
// 按需实现要展示那些操作按钮
$grid->actions(function (Grid\Displayers\Actions $actions) use ($grid, $showActions) {
$key = $actions->getKey();
$resource = $grid->resource(); // 获取资源路径
$viewUrl = admin_url("$resource/$key");
$editUrl = admin_url("$resource/$key/edit");
$parts = [];
if (!empty($showActions['view'])) {
$parts[] = '<a href="'.$viewUrl.'"><i class="feather">查看</i></a>';
}
if (!empty($showActions['edit'])) {
$parts[] = '<a href="'.$editUrl.'"><i class="feather">编辑</i></a>';
}
if (!empty($showActions['delete'])) {
$parts[] = '<a data-url="'.$viewUrl.'" data-message="ID - '.$key.'" data-action="delete" data-redirect="'.$resource.'" class="feather grid-row-delete" style="cursor:pointer">删除</a>';
}
$actions->append(implode(' | ', $parts));
});
}这种的使用场景在一些日志类的列表展示中

工具表单不仅可以用在动作上,还可以用在工具上,比如说你想在列表上面工具栏,增加“导入”按钮

那么你也可以用action来实现
$grid->tools(function (Grid\Tools $tools) {
$tools->append(new ImportStaticIp());
});不过ImportStaticIp的父类要发生变化了,从动作的RowAction变为AbstractTool,因为你要实现的是工具类,而非动作
/**
* Class ImportAccount
*
* @package App\Admin\Actions\YkAccount
* @author mingzhongshui
* @date 2025/6/14
*/
class ImportStaticIp extends AbstractTool
{
/**
* @return string
*/
protected $title = '';
/**
* @param $title
*/
public function __construct($title = null)
{
$this->title = admin_trans('yk-account.fields.import_account');
}
public function render()
{
// 实例化表单类并传递自定义参数
$form = ImportStaticIpForms::make();
return Modal::make()
->lg()
->title($this->title)
->body($form)
->button('<div class="pull-right" data-responsive-table-toolbar="grid-table">
<a href="javascript:void(0)" class="btn btn-primary btn-outline">
<i class="feather icon-plus-circle"></i><span class="d-none d-sm-inline"> 导入</span>
</a>
</div>');
}
}title也可以在__construct中实现。
数据表格
数据行日志展示
比如说有这样一个场景,发送任务列表,要显示发送成功的数量以及日志,那应该怎么处理呢?一般情况来说,就是无非在操作页增加一个日志按钮,点击按钮的时候跳转到一个新页面展示发送日志列表,但是在dcatadmin中有着不同的实现方式
可以在数据行,发送数量字段上,给一个弹框,当点击数量的时候,打开一个弹窗,显示发送日志列表。
实现方式也很简单,只要在grid方法,字段末尾追加一个弹框方法就可以
$grid->column('actual_send_quantity', admin_trans_field('send_success_quantity'))->display(function () {
return YkMassMessageLog::where('mass_id', $this->id)->where('status', YkMassMessageLog::STATUS_SUCCESS)->count();
})->modal(admin_trans('yk-mass-message-setting.fields.send_success_details'), MassMessageSuccessLogTable::make());modal方法有两个参数

参数1:打开弹窗的标题
参数2:回调函数,也就是要显示的内容
MassMessageSuccessLogTable类实现方式如下
// LazyRenderable异步加载类
class MassMessageSuccessLogTable extends Grid\LazyRenderable
{
public function grid(): Grid
{
return Grid::make(new YkMassMessageLog(), function (Grid $grid) {
// $this->key 就是当前行的id
$grid->model()->where('mass_id', $this->key)->where('status', \App\Models\YkMassMessageLog::STATUS_SUCCESS);
$grid->column('id');
$grid->column('content_type', admin_trans('yk-mass-message-log.fields.content_type'))->display(function ($v) {
return self::getTypeOptions()[$v] ?? '';
});
// 分页数量
$grid->paginate(10);
// 不显示操作列
$grid->disableActions();
// 不显示多选
$grid->disableRowSelector();
});
}这样就可以实现,在列表中任意字段实现弹窗列表功能了;
总结
以上就是我认为dcatadmin2.x中,比较常用但是文档描述不清楚的功能点了。
