开发扩展

未匹配的标注
本文档最新版为 2.x,旧版本可能放弃维护,推荐阅读最新版!

开发扩展

新版本预告

Dcat Admin计划在2.0版本上线插件市场功能,将对整个扩展功能进行重构,以提升用户体验。
新的扩展系统将可以让用户只需在管理页面点点鼠标即可完成插件的安装更新卸载等操作。
并且会上线插件付费功能,以激励开发者开发高质量的插件。

如果有任何建议,欢迎提issue或者私信我,Dcat Admin团队将会致力于构建一个于开发者和用户都有利的生态,感谢大家的支持!

开始

Dcat Admin支持安装扩展工具来帮助丰富你的后台功能。

需要注意的是,Laravel Admin原有的扩展无法直接在Dcat Admin中使用,但大部分扩展只需要做一些微小的调整就可以正常使用了,有兴趣的同学可以自行移植。

如果大家在使用的过程中有在Dcat Admin的基础上添加一些自己的功能或者组件,不妨做成一个Dcat Admin扩展,这样可以给其它Dcat Admin使用者提供帮助,并且在其它人的使用反馈中的提升扩展的质量。

这篇文档将会以开发一个干货集中营扩展为例,一步一步的开发一个扩展,并且发布给他人使用,最终的效果参考Demo

创建composer包

Dcat Admin的包将会用composer安装,所以先要创建一个composer包,可以用内置的admin:extend命令来生成一个扩展的骨架。

运行命令的时候,可能会提示输入一个目录来存放你的扩展文件,你可以在config/admin.php里面增加一个配置'extension_dir' => admin_path('Extensions'),这样扩展文件将会存放在app/Admin/Extensions目录下,当然你也可以放在任何其它目录。

php artisan admin:extend dcat-admin-extensions/gank --namespace="Dcat\Admin\Extension\Gank"

其中dcat-admin-extensions/gank是包名,namespace选项是这个包使用的顶级命名空间,运行这个命令之后, 将会在config/admin.php中设置的扩展目录中生成目录dcat-admin-extensions/gank和下面的文件结构:

├── LICENSE
├── README.md
├── composer.json
├── bootstrap.php
├── database
│   ├── migrations
│   └── seeds
├── resources
│   ├── assets
│   └── views
│       └── index.blade.php
├── routes
│   └── web.php
└── src
    ├── Gank.php
    ├── GankServiceProvider.php
    └── Http
        └── Controllers
            └── GankController.php

bootstrap.php用于注册扩展,此文件的代码不需要做任何修改;resources用来放置视图文件和静态资源文件;src主要用来放置逻辑代码; routes/web.php用来存放这个扩展的路由设置,database用来放置数据库迁移文件和数据seeders

功能开发

这个扩展的功能主要用来展示gank.io接口的内容,集成进Dcat Admin中,它将会有一个路由和一个控制器,没有数据库文件、模板和静态资源文件,我们可以将没有用到的文件或目录清理掉,清理之后的目录文件为:

├── LICENSE
├── README.md
├── composer.json
├── bootstrap.php
├── routes
│   └── web.php
└── src
    ├── Gank.php
    ├── GankServiceProvider.php
    └── Http
        └── Controllers
            └── GankController.php

生成完扩展框架之后,你可能需要一边调试一边开发,所以可以参考下面的本地安装,先把扩展安装进系统中,继续开发

添加路由

首先添加一个路由,在routes/web.php中已经自动生成好了一个路由配置

<?php

use Dcat\Admin\Extension\Gank\Http\Controllers;

Route::get('gank', Controllers\GankController::class.'@index');

访问路径http://localhost:8000/admin/gank,将会由Dcat\Admin\Extension\Gank\Http\Controllers\GankController控制器的index方法来处理这个请求。

设置扩展属性

src/Gank.php作为扩展类,用来设置扩展的属性

<?php

namespace Dcat\Admin\Extension\Gank;

use Dcat\Admin\Extension;

class Gank extends Extension
{
    // 扩展别名,用于在配置文件中获取配置信息
    const NAME = 'gank';

    // service provider类名,这个用自动生成的就行,不需要改
    public $serviceProvider = GankServiceProvider::class;

    // 静态资源目录,如果不需要静态资源可用删除掉  
//    public $assets = __DIR__.'/../resources/assets';

    // composer配置文件路径,用默认的即可
    public $composerJson = __DIR__.'/../composer.json';

    // 自定义属性
    public static $categoryColorsMap = [
        'App'      => 'var(--purple)',
        '前端'     => 'var(--primary)',
        '拓展资源' => 'var(--primary-dark)',
        '瞎推荐'   => 'var(--blue)',
        '福利'     => 'var(--danger)',
        'Android'  => 'var(--purple-dark)',
        'iOS'      => 'var(--info)',
        '休息视频' => 'var(--warning)',
    ];
}

这个文件用来设置这个扩展的一些属性,$name是这个扩展的别名,如果这个扩展有视图文件需要渲染,则必须指定这个扩展的$views属性,同样如果有静态资源文件需要发布,则必须设置$assets属性, 如果需要在左侧边栏增加一项菜单按钮,设置$menu属性,可以根据需要去掉不必要的属性。

然后打开src/GankServiceProvider.php,这个ServiceProvider将会在启用扩展的时候运行(不需要开发者自己添加),用来将这个扩展的一些服务注册进系统中。

加载视图

如果这个扩展需要加载视图文件,在src/GankServiceProvider.phpboot方法中加入以下的代码:

$extension = Gank::make();

if ($extension->views) {
    $this->loadViewsFrom($extension->views, 'gank');
}

loadViewsFrom方法的的第一个参数为在扩展类src/Gank.php中设置的视图属性,第二个参数是视图文件目录的命名空间,设置为gank之后,在控制器中用view('gank::index')来加载resources/views目录下的视图文件。

引入静态资源

如果你的项目中有静态资源文件需要引入,先把文件放在resources/assets目录中,比如放入resources/assets/foo.jsresources/assets/bar.css这两个文件。

接着在扩展类src/Gank.php中设置$assets属性即可:

public $assets = __DIR__.'/../resources/assets';

安装完成之后,文件将会复制到public/vendor/dcat-admin-extensions/gank目录中。

然后我们可以在需要使用这些静态资源的地方加上下面的代码即可

因为Dcat Admin支持静态资源按需加载,所以不推荐直接在ServiceProvider或者是bootstrap.php中引入静态资源,这样会拖慢所有页面的加载时间。

use Dcat\Admin\Admin;

Admin::js('vendor/dcat-admin-extensions/gank/phpinfo/foo.js');
Admin::css('vendor/dcat-admin-extensions/gank/phpinfo/bar.css');

这样就完成了静态资源的引入,在gank这个扩展中,由于没有静态资源需要引入,所以可以忽略掉这一步。

添加菜单

添加菜单的方式有两种:

  • src/Gank.php设置$menu属性,这种方式是把菜单写入到菜单表中,用户更易于控制
  • 使用Admin::menu接口通过数组的方式添加菜单,这种方式更加方便,并且支持添加多级菜单

这里使用的是第二种方式添加菜单,在src/GankServiceProvider.phpboot方法加入下面的代码:

Admin::menu()->add([
    [
        'id'            => 1,
        'title'         => '干货集中营',
        'icon'          => ' fa-newspaper-o',
        'uri'           => 'gank',
        'parent_id'     => 0,
        'permission_id' => 'gank', // 绑定权限
        'roles'         => [['slug' => 'gank']], // 绑定角色
    ],
    [
        'id'            => 2,
        'title'         => '所有干货',
        'icon'          => 'fa-smile-o',
        'uri'           => 'gank',
        'parent_id'     => 1,
        'permission_id' => 'gank', // 绑定权限
        'roles'         => [['slug' => 'gank']], // 绑定角色
    ],
]);

最终的src/GankServiceProvider.php代码如下:

<?php

namespace Dcat\Admin\Extension\Gank;

use Dcat\Admin\Admin;
use Illuminate\Support\ServiceProvider;

class GankServiceProvider extends ServiceProvider
{
    /**
     * {@inheritdoc}
     */
    public function boot()
    {
        $extension = Gank::make();

        if ($extension->views) {
            $this->loadViewsFrom($extension->views, 'gank');
        }

        if ($extension->lang) {
            $this->loadTranslationsFrom($extension->lang, 'gank');
        }

        if ($extension->migrations) {
            $this->loadMigrationsFrom($extension->migrations);
        }

        $this->app->booted(function () use ($extension) {
            $extension->routes(__DIR__.'/../routes/web.php');
        });

        // 添加菜单
        $this->registerMenus();
    }

    protected function registerMenus()
    {
        Admin::menu()->add([
            [
                'id'            => 1,
                'title'         => '干货集中营',
                'icon'          => ' fa-newspaper-o',
                'uri'           => 'gank',
                'parent_id'     => 0,
                'permission_id' => 'gank', // 绑定权限
                'roles'         => [['slug' => 'gank']], // 绑定角色
            ],
            [
                'id'            => 2,
                'title'         => '所有干货',
                'icon'          => 'fa-smile-o',
                'uri'           => 'gank',
                'parent_id'     => 1,
                'permission_id' => 'gank', // 绑定权限
                'roles'         => [['slug' => 'gank']], // 绑定角色
            ],
        ]);
    }
}

添加自定义安装逻辑

如果你想在导入扩展时执行一些自定义逻辑,可以在src/Gank.php中添加以下代码:

注意请保证重复执行导入命令不会报错

...

    public function import(Command $command)
    {
        parent::import($command); // TODO: Change the autogenerated stub

        // 在这里写入你的导入逻辑

        $command->info('导入成功');

    }

代码逻辑开发

这个扩展主要是调用gank.io提供的接口,再把数据展示出来。

我们需要新增一个数据仓库文件用于获取gank.io的数据,创建文件src/Repositories/Ganks.php

<?php

namespace Dcat\Admin\Extension\Gank\Repositories;

use Dcat\Admin\Grid;
use Dcat\Admin\Repositories\Repository;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;

class Ganks extends Repository
{
    protected $api = 'http://gank.io/api/data/{category}/{limit}/{page}';

    protected $searchApi = 'http://gank.io/api/search/query/{key}/category/{category}/count/{limit}/page/{page}';

    /**
     * 查询表格数据
     *
     * @param Grid\Model $model
     * @return Collection
     */
    public function get(Grid\Model $model)
    {
        $currentPage = $model->getCurrentPage();
        $perPage = $model->getPerPage();

        // 获取筛选参数
        $category = $model->filter()->input(Grid\Filter\Scope::QUERY_NAME, 'all');
        $keyword  = trim($model->filter()->input('keyword'));

        $api = $keyword ? $this->searchApi : $this->api;

        $client = new \GuzzleHttp\Client();

        $response = $client->get(str_replace(
            ['{category}', '{limit}', '{page}', '{key}'],
            [$category, $perPage, $currentPage, $keyword],
            $api
        ));
        $data = collect(
            json_decode((string)$response->getBody(), true)['results'] ?? []
        );

        $total = $keyword ? 400 : ($category == 'all' ? 1000 : 500);

        $paginator = new LengthAwarePaginator(
            $data,
            $category == '福利' ? 680 : $total,
            $perPage, // 传入每页显示行数
            $currentPage // 传入当前页码
        );

        $paginator->setPath(\url()->current());

        return $paginator;
    }

    public function getKeyName()
    {
        return '_id';
    }

}

修改控制器代码如下:

<?php

namespace Dcat\Admin\Extension\Gank\Http\Controllers;

use Dcat\Admin\Extension\Gank\Gank;
use Dcat\Admin\Extension\Gank\Repositories\Ganks;
use Dcat\Admin\Grid;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Layout\Row;
use Illuminate\Routing\Controller;
use Dcat\Admin\Widgets\Navbar;

class GankController extends Controller
{
    public function index(Content $content)
    {
        $grid = $this->grid();

        $grid->disableFilter();
        $grid->filter()
            ->withoutInputBorder()
            ->expand()
            ->resetPosition()
            ->hiddenResetButtonText();

        return $content
            ->header('所有干货')
            ->description('每日分享妹子图 和 技术干货')
            ->body($grid->filter())
            ->body(function (Row $row) {
                $items = array_keys(Gank::$categoryColorsMap);

                array_unshift($items, '全部');

                $navbar = Navbar::make('#', array_combine($items, $items))
                    ->checked(request(Grid\Filter\Scope::QUERY_NAME, '全部'))
                    ->click()
                    ->map(function ($v) {
                        if ($v == '全部') {
                            $url = '?';
                        } else {
                            $url = '?'.Grid\Filter\Scope::QUERY_NAME.'='.$v;
                        }

                        return "<a href='$url'>$v</a>";
                    })
                    ->style('max-width:705px');

                $row->column(7, $navbar);

            })
            ->body($grid);
    }

    protected function grid()
    {
        $this->define();

        $grid = new Grid(new Ganks);

        $grid->number();
        $grid->desc('描述')->width('300px');
        $grid->images('图片')->image(150);
        $grid->type('类别');
        $grid->who('作者')->label();
        $grid->publishedAt('发布于');

        $grid->disableActions();
        $grid->disableBatchDelete();
        $grid->disableExport();
        $grid->disableCreateButton();
        $grid->disableFilterButton();
        $grid->disableQuickCreateButton();
        $grid->perPages([]);

        return $grid->filter(function (Grid\Filter $filter) {
            $category = $filter->input(Grid\Filter\Scope::QUERY_NAME, '全部');

            if ($category != '福利') {
                $filter->like('keyword', ucfirst($category))->width('300px')->placeholder('请输入');
            }

        });
    }

    protected function define()
    {
        Grid\Column::define('desc', function ($v) {
            if ($this->type == '福利') {
                $width = '150';
                $height = '200';
                return "<img data-init='preview' src='{$this->url}' style='max-width:{$width}px;max-height:{$height}px;cursor:pointer' class='img img-thumbnail' />";
            }

            return sprintf('<a href="%s" target="_blank">%s</a>', $this->url, $v);
        });

        Grid\Column::define('publishedAt', function ($v) {
            return date('Y-m-d', strtotime($v));
        });
        Grid\Column::define('datetime', function ($v) {
            return date('Y-m-d H:i:s', strtotime($v));
        });

        Grid\Column::define('type', function ($v) {
            $map = Gank::$categoryColorsMap;

            return "<span class='label' style='background:{$map[$v]}'>$v</span>";
        });
    }
}

这样一个完整的扩展就开发完成了。

修改 composer.json & README.md

代码部分完成之后,需要修改composer.json里面的内容,将descriptionkeywordslicenseauthors等内容替换为你的信息,然后不要忘记完善README.md,补充使用文档等相关信息。

安装

完成了扩展开发之后,根据情况可以用下面的的方式安装你的扩展

本地安装

在开发的过程中,一般需要一边调试一边开发,所以先按照下面的方式进行本地安装

打开你的项目中composer.json文件,在加入下面的配置

"repositories": [
    {
        "type": "path",
        "url": "app/Admin/Extensions/dcat-admin-extensions/gank"
    }
]

然后运行composer require dcat-admin-extensions/gank完成安装,如果有静态文件需要发布,运行下面的命令

远程安装

如果开发完成之后,希望开源出来给大家使用,按照下面的步骤进行

上传到Github

先登录你的Github,创建一个仓库,然后按照页面上的提示把你的代码push上去

git init
git remote add origin https://github.com/<your-name>/<your-repository>.git
git add .
git commit -am "Initial commit."
git push origin master

发布release

可以用下面的方式在本地发布版本

git tag 0.0.1 && git push --tags

也可以在Github的仓库页面的Releases页面手动设置

发布到Packagist.org

接下来就是发布你的项目到Packagist.org,如果没有账号的话,先注册一个,然后打开顶部导航的Submit, 填入仓库地址提交

默认情况下,当您推送新代码时,Packagist.org不会自动更新,所以,您需要创建一个GitHub服务钩子, 你也可以使用点击页面上的Update按钮手动更新它,但我建议自动执行这个过程

提交之后,由于各地的镜像同步时间的延迟,可能在用composer安装的时候,会暂时找不到你的项目,这个时候可能需要等待同步完成

发布完成之后就可以通过composer安装你的扩展了

加入到github.com/dcat-admin-extensions
如果想把你开发的扩展加入到dcat-admin-extensions,欢迎用各种方式联系我,这样可以让更多人看到并使用你开发的工具。

导入和启用扩展

导入扩展

安装完成后,运行以下命令即可以导入扩展,导入的文件主要有:静态资源文件、菜单、权限等。

视图、数据库迁移文件、语言包一般不需要导入。如果要导入其他文件,需要扩展开发者自行定义。
另外此命令允许重复执行。

php artisan admin:import Dcat\Admin\Extension\Gank\Gank

启用扩展

扩展的启用与否是通过配置文件控制的,打开/config/admin-extension.php,加入以下代码:

return [
    'gank' => [
        'enable' => true,
    ],
];

可视化管理扩展

访问http://localhost:8000/admin/helpers/extensions,可以通过页面操作启用或关闭扩展、导入扩展。

如果/config/admin-extension.php不存在,系统会自动创建

这样就完成了安装,打开http://localhost/admin/gank访问这个扩展。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 10
发起讨论 只看当前版本


luxiaofeng
dcat admin 详情页如何做成选项卡的展示
4 个点赞 | 15 个回复 | 问答 | 课程版本 2.x
halweg
在form A的編輯页,提交后如和新增一条B的记录?
0 个点赞 | 7 个回复 | 问答 | 课程版本 2.x
luxiaofeng
dcat admin 表格怎么根据当前行数据的值隐藏或显示某个按钮
0 个点赞 | 4 个回复 | 问答 | 课程版本 2.x
蒙挚
Dcat Admin 新建和编辑怎么使用不同的页面和处理逻辑
0 个点赞 | 4 个回复 | 问答 | 课程版本 2.x
maxsky
为什么开发工具菜单在非 admin 用户下也能看见呢?
0 个点赞 | 1 个回复 | 问答 | 课程版本 2.x
Mutoulee
Dcat Admin 模型树depth字段疑问
0 个点赞 | 1 个回复 | 问答 | 课程版本 2.x
lezhl821125
Dcat2版本的代码生成器 在laravel 9的版本报错
0 个点赞 | 1 个回复 | 问答 | 课程版本 2.x
zhuameng
如果 MySQL 8 运行脚本出错,可以试试下面这个:
0 个点赞 | 0 个回复 | 分享 | 课程版本 2.x
esssd
小程序心跳
0 个点赞 | 0 个回复 | 代码速记 | 课程版本 2.x