extend
<h1>开发扩展</h1>
<h2>新版本预告</h2>
<p><code>Dcat Admin</code>计划在<code>2.0</code>版本上线插件市场功能,将对整个扩展功能进行重构,以提升用户体验。
新的扩展系统将可以让用户只需在管理页面点点鼠标即可完成插件的<code>安装</code>、<code>更新</code>和<code>卸载</code>等操作。
并且会上线插件付费功能,以激励开发者开发高质量的插件。</p>
<p>如果有任何建议,欢迎提<code>issue</code>或者私信我,<code>Dcat Admin</code>团队将会致力于构建一个于开发者和用户都有利的生态,感谢大家的支持!</p>
<p><a name="pre"></a></p>
<h2>开始</h2>
<p><code>Dcat Admin</code>支持安装扩展工具来帮助丰富你的后台功能。
>需要注意的是,<code>Laravel Admin</code>原有的扩展无法直接在<code>Dcat Admin</code>中使用,但大部分扩展只需要做一些微小的调整就可以正常使用了,有兴趣的同学可以自行移植。</p>
<p>如果大家在使用的过程中有在<code>Dcat Admin</code>的基础上添加一些自己的功能或者组件,不妨做成一个<code>Dcat Admin</code>扩展,这样可以给其它<code>Dcat Admin</code>使用者提供帮助,并且在其它人的使用反馈中的提升扩展的质量。</p>
<p>这篇文档将会以开发一个<code>干货集中营</code>扩展为例,一步一步的开发一个扩展,并且发布给他人使用,最终的效果参考[Demo]()。</p>
<p><a name="composer"></a></p>
<h2>创建composer包</h2>
<p><code>Dcat Admin</code>的包将会用<code>composer</code>安装,所以先要创建一个<code>composer</code>包,可以用内置的<code>admin:extend</code>命令来生成一个扩展的骨架。</p>
<p>运行命令的时候,可能会提示输入一个目录来存放你的扩展文件,你可以在<code>config/admin.php</code>里面增加一个配置<code>'extension_dir' =&gt; admin_path('Extensions')</code>,这样扩展文件将会存放在<code>app/Admin/Extensions目录下</code>,当然你也可以放在任何其它目录。</p>
<pre><code class="language-php">php artisan admin:extend dcat-admin-extensions/gank --namespace=&quot;Dcat\Admin\Extension\Gank&quot;</code></pre>
<p>其中<code>dcat-admin-extensions/gank</code>是包名,<code>namespace</code>选项是这个包使用的顶级命名空间,运行这个命令之后, 将会在在<code>config/admin.php</code>中设置的扩展目录中生成目录<code>dcat-admin-extensions/gank</code>和下面的文件结构:</p>
<pre><code>├── 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</code></pre>
<p><code>bootstrap.php</code>用于注册扩展,此文件的代码不需要做任何修改;<code>resources</code>用来放置视图文件和静态资源文件;<code>src</code>主要用来放置逻辑代码; <code>routes/web.php</code>用来存放这个扩展的路由设置,<code>database</code>用来放置数据库迁移文件和数据<code>seeders</code>。</p>
<p><a name="dev"></a></p>
<h2>功能开发</h2>
<p>这个扩展的功能主要用来展示<a href="http://gank.io">gank.io</a>接口的内容,集成进<code>Dcat Admin</code>中,它将会有一个路由和一个控制器,没有数据库文件、模板和静态资源文件,我们可以将没有用到的文件或目录清理掉,清理之后的目录文件为:</p>
<pre><code>├── LICENSE
├── README.md
├── composer.json
├── bootstrap.php
├── routes
│ └── web.php
└── src
├── Gank.php
├── GankServiceProvider.php
└── Http
└── Controllers
└── GankController.php</code></pre>
<p>生成完扩展框架之后,你可能需要一边调试一边开发,所以可以参考下面的本地安装,先把扩展安装进系统中,继续开发</p>
<p><a name="route"></a></p>
<h3>添加路由</h3>
<p>首先添加一个路由,在<code>routes/web.php</code>中已经自动生成好了一个路由配置</p>
<pre><code class="language-php">&lt;?php
use Dcat\Admin\Extension\Gank\Http\Controllers;
Route::get('gank', Controllers\GankController::class.'@index');</code></pre>
<p>访问路径<code>http://localhost:8000/admin/gank</code>,将会由<code>Dcat\Admin\Extension\Gank\Http\Controllers\GankController</code>控制器的<code>index</code>方法来处理这个请求。</p>
<p><a name="attrs"></a></p>
<h3>设置扩展属性</h3>
<p><code>src/Gank.php</code>作为扩展类,用来设置扩展的属性</p>
<pre><code class="language-php">&lt;?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' =&gt; 'var(--purple)',
'前端' =&gt; 'var(--primary)',
'拓展资源' =&gt; 'var(--primary-dark)',
'瞎推荐' =&gt; 'var(--blue)',
'福利' =&gt; 'var(--danger)',
'Android' =&gt; 'var(--purple-dark)',
'iOS' =&gt; 'var(--info)',
'休息视频' =&gt; 'var(--warning)',
];
}
</code></pre>
<p>这个文件用来设置这个扩展的一些属性,<code>$name</code>是这个扩展的别名,如果这个扩展有视图文件需要渲染,则必须指定这个扩展的<code>$views</code>属性,同样如果有静态资源文件需要发布,则必须设置<code>$assets</code>属性, 如果需要在左侧边栏增加一项菜单按钮,设置<code>$menu</code>属性,可以根据需要去掉不必要的属性。</p>
<p>然后打开<code>src/GankServiceProvider.php</code>,这个<code>ServiceProvider</code>将会在启用扩展的时候运行(不需要开发者自己添加),用来将这个扩展的一些服务注册进系统中。</p>
<p><a name="loadview"></a></p>
<h3>加载视图</h3>
<p>如果这个扩展需要加载视图文件,在<code>src/GankServiceProvider.php</code>的<code>boot</code>方法中加入以下的代码:</p>
<pre><code class="language-php">$extension = Gank::make();
if ($extension-&gt;views) {
$this-&gt;loadViewsFrom($extension-&gt;views, 'gank');
}</code></pre>
<p><code>loadViewsFrom</code>方法的的第一个参数为在扩展类<code>src/Gank.php</code>中设置的视图属性,第二个参数是视图文件目录的命名空间,设置为<code>gank</code>之后,在控制器中用<code>view('gank::index')</code>来加载<code>resources/views</code>目录下的视图文件。</p>
<p><a name="loadassets"></a></p>
<h3>引入静态资源</h3>
<p>如果你的项目中有静态资源文件需要引入,先把文件放在<code>resources/assets</code>目录中,比如放入<code>resources/assets/foo.js</code>和<code>resources/assets/bar.css</code>这两个文件。</p>
<p>接着在扩展类<code>src/Gank.php</code>中设置<code>$assets</code>属性即可:</p>
<pre><code class="language-php">public $assets = __DIR__.'/../resources/assets';</code></pre>
<p>安装完成之后,文件将会复制到<code>public/vendor/dcat-admin-extensions/gank</code>目录中。</p>
<p>然后我们可以在需要使用这些静态资源的地方加上下面的代码即可</p>
<p>> {tip} 因为<code>Dcat Admin</code>支持静态资源按需加载,所以不推荐直接在<code>ServiceProvider</code>或者是<code>bootstrap.php</code>中引入静态资源,这样会拖慢所有页面的加载时间。</p>
<pre><code class="language-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');</code></pre>
<p>这样就完成了静态资源的引入,在<code>gank</code>这个扩展中,由于没有静态资源需要引入,所以可以忽略掉这一步。</p>
<p><a name="addmenu"></a></p>
<h3>添加菜单</h3>
<p>添加菜单的方式有两种:</p>
<ul>
<li>在<code>src/Gank.php</code>设置<code>$menu</code>属性,这种方式是把菜单写入到菜单表中,用户更易于控制</li>
<li>使用<code>Admin::menu</code>接口通过数组的方式添加菜单,这种方式更加方便,并且支持添加多级菜单</li>
</ul>
<p>这里使用的是第二种方式添加菜单,在<code>src/GankServiceProvider.php</code>的<code>boot</code>方法加入下面的代码:</p>
<pre><code class="language-php">Admin::menu()-&gt;add([
[
'id' =&gt; 1,
'title' =&gt; '干货集中营',
'icon' =&gt; ' fa-newspaper-o',
'uri' =&gt; 'gank',
'parent_id' =&gt; 0,
'permission_id' =&gt; 'gank', // 绑定权限
'roles' =&gt; [['slug' =&gt; 'gank']], // 绑定角色
],
[
'id' =&gt; 2,
'title' =&gt; '所有干货',
'icon' =&gt; 'fa-smile-o',
'uri' =&gt; 'gank',
'parent_id' =&gt; 1,
'permission_id' =&gt; 'gank', // 绑定权限
'roles' =&gt; [['slug' =&gt; 'gank']], // 绑定角色
],
]);</code></pre>
<p>最终的<code>src/GankServiceProvider.php</code>代码如下:</p>
<pre><code class="language-php">&lt;?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-&gt;views) {
$this-&gt;loadViewsFrom($extension-&gt;views, 'gank');
}
if ($extension-&gt;lang) {
$this-&gt;loadTranslationsFrom($extension-&gt;lang, 'gank');
}
if ($extension-&gt;migrations) {
$this-&gt;loadMigrationsFrom($extension-&gt;migrations);
}
$this-&gt;app-&gt;booted(function () use ($extension) {
$extension-&gt;routes(__DIR__.'/../routes/web.php');
});
// 添加菜单
$this-&gt;registerMenus();
}
protected function registerMenus()
{
Admin::menu()-&gt;add([
[
'id' =&gt; 1,
'title' =&gt; '干货集中营',
'icon' =&gt; ' fa-newspaper-o',
'uri' =&gt; 'gank',
'parent_id' =&gt; 0,
'permission_id' =&gt; 'gank', // 绑定权限
'roles' =&gt; [['slug' =&gt; 'gank']], // 绑定角色
],
[
'id' =&gt; 2,
'title' =&gt; '所有干货',
'icon' =&gt; 'fa-smile-o',
'uri' =&gt; 'gank',
'parent_id' =&gt; 1,
'permission_id' =&gt; 'gank', // 绑定权限
'roles' =&gt; [['slug' =&gt; 'gank']], // 绑定角色
],
]);
}
}</code></pre>
<p><a name="importcode"></a></p>
<h2>添加自定义安装逻辑</h2>
<p>如果你想在导入扩展时执行一些自定义逻辑,可以在<code>src/Gank.php</code>中添加以下代码:</p>
<p>>注意请保证重复执行导入命令不会报错</p>
<pre><code class="language-php">...
public function import(Command $command)
{
parent::import($command); // TODO: Change the autogenerated stub
// 在这里写入你的导入逻辑
$command-&gt;info('导入成功');
}
</code></pre>
<p><a name="code"></a></p>
<h2>代码逻辑开发</h2>
<p>这个扩展主要是调用[gank.io]()提供的接口,再把数据展示出来。</p>
<p>我们需要新增一个数据仓库文件用于获取[gank.io]()的数据,创建文件<code>src/Repositories/Ganks.php</code>:</p>
<pre><code class="language-php">&lt;?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-&gt;getCurrentPage();
$perPage = $model-&gt;getPerPage();
// 获取筛选参数
$category = $model-&gt;filter()-&gt;input(Grid\Filter\Scope::QUERY_NAME, 'all');
$keyword = trim($model-&gt;filter()-&gt;input('keyword'));
$api = $keyword ? $this-&gt;searchApi : $this-&gt;api;
$client = new \GuzzleHttp\Client();
$response = $client-&gt;get(str_replace(
['{category}', '{limit}', '{page}', '{key}'],
[$category, $perPage, $currentPage, $keyword],
$api
));
$data = collect(
json_decode((string)$response-&gt;getBody(), true)['results'] ?? []
);
$total = $keyword ? 400 : ($category == 'all' ? 1000 : 500);
$paginator = new LengthAwarePaginator(
$data,
$category == '福利' ? 680 : $total,
$perPage, // 传入每页显示行数
$currentPage // 传入当前页码
);
$paginator-&gt;setPath(\url()-&gt;current());
return $paginator;
}
public function getKeyName()
{
return '_id';
}
}</code></pre>
<p>修改控制器代码如下:</p>
<pre><code class="language-php">&lt;?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-&gt;grid();
$grid-&gt;disableFilter();
$grid-&gt;filter()
-&gt;withoutInputBorder()
-&gt;expand()
-&gt;resetPosition()
-&gt;hiddenResetButtonText();
return $content
-&gt;header('所有干货')
-&gt;description('每日分享妹子图 和 技术干货')
-&gt;body($grid-&gt;filter())
-&gt;body(function (Row $row) {
$items = array_keys(Gank::$categoryColorsMap);
array_unshift($items, '全部');
$navbar = Navbar::make('#', array_combine($items, $items))
-&gt;checked(request(Grid\Filter\Scope::QUERY_NAME, '全部'))
-&gt;click()
-&gt;map(function ($v) {
if ($v == '全部') {
$url = '?';
} else {
$url = '?'.Grid\Filter\Scope::QUERY_NAME.'='.$v;
}
return &quot;&lt;a href='$url'&gt;$v&lt;/a&gt;&quot;;
})
-&gt;style('max-width:705px');
$row-&gt;column(7, $navbar);
})
-&gt;body($grid);
}
protected function grid()
{
$this-&gt;define();
$grid = new Grid(new Ganks);
$grid-&gt;number();
$grid-&gt;desc('描述')-&gt;width('300px');
$grid-&gt;images('图片')-&gt;image(150);
$grid-&gt;type('类别');
$grid-&gt;who('作者')-&gt;label();
$grid-&gt;publishedAt('发布于');
$grid-&gt;disableActions();
$grid-&gt;disableBatchDelete();
$grid-&gt;disableExport();
$grid-&gt;disableCreateButton();
$grid-&gt;disableFilterButton();
$grid-&gt;disableQuickCreateButton();
$grid-&gt;perPages([]);
return $grid-&gt;filter(function (Grid\Filter $filter) {
$category = $filter-&gt;input(Grid\Filter\Scope::QUERY_NAME, '全部');
if ($category != '福利') {
$filter-&gt;like('keyword', ucfirst($category))-&gt;width('300px')-&gt;placeholder('请输入');
}
});
}
protected function define()
{
Grid\Column::define('desc', function ($v) {
if ($this-&gt;type == '福利') {
$width = '150';
$height = '200';
return &quot;&lt;img data-init='preview' src='{$this-&gt;url}' style='max-width:{$width}px;max-height:{$height}px;cursor:pointer' class='img img-thumbnail' /&gt;&quot;;
}
return sprintf('&lt;a href=&quot;%s&quot; target=&quot;_blank&quot;&gt;%s&lt;/a&gt;', $this-&gt;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 &quot;&lt;span class='label' style='background:{$map[$v]}'&gt;$v&lt;/span&gt;&quot;;
});
}
}</code></pre>
<p>这样一个完整的扩展就开发完成了。</p>
<p><a name="updatejson"></a></p>
<h2>修改 composer.json & README.md</h2>
<p>代码部分完成之后,需要修改<code>composer.json</code>里面的内容,将<code>description</code>、<code>keywords</code>、<code>license</code>、<code>authors</code>等内容替换为你的信息,然后不要忘记完善<code>README.md</code>,补充使用文档等相关信息。</p>
<p><a name="install"></a></p>
<h2>安装</h2>
<p>完成了扩展开发之后,根据情况可以用下面的的方式安装你的扩展</p>
<p><a name="local"></a></p>
<h3>本地安装</h3>
<p>在开发的过程中,一般需要一边调试一边开发,所以先按照下面的方式进行本地安装</p>
<p>打开你的项目中<code>composer.json</code>文件,在加入下面的配置</p>
<pre><code>&quot;repositories&quot;: [
{
&quot;type&quot;: &quot;path&quot;,
&quot;url&quot;: &quot;app/Admin/Extensions/dcat-admin-extensions/gank&quot;
}
]</code></pre>
<p>然后运行<code>composer require dcat-admin-extensions/gank @dev</code>完成安装,如果有静态文件需要发布,运行下面的命令:</p>
<pre><code>php artisan vendor:publish --provider=Dcat\Admin\Extension\GankServiceProvider</code></pre>
<p><a name="remote"></a></p>
<h3>远程安装</h3>
<p>如果开发完成之后,希望开源出来给大家使用,按照下面的步骤进行</p>
<p><a name="github"></a></p>
<h4>上传到Github</h4>
<p>先登录你的Github,创建一个仓库,然后按照页面上的提示把你的代码push上去</p>
<pre><code>git init
git remote add origin https://github.com/&lt;your-name&gt;/&lt;your-repository&gt;.git
git add .
git commit -am &quot;Initial commit.&quot;
git push origin master</code></pre>
<p><a name="release"></a></p>
<h4>发布release</h4>
<p>可以用下面的方式在本地发布版本</p>
<pre><code>git tag 0.0.1 &amp;&amp; git push --tags</code></pre>
<p>也可以在Github的仓库页面的Releases页面手动设置</p>
<p><a name="packagist"></a></p>
<h4>发布到Packagist.org</h4>
<p>接下来就是发布你的项目到<code>Packagist.org</code>,如果没有账号的话,先注册一个,然后打开顶部导航的<code>Submit</code>, 填入仓库地址提交</p>
<p>默认情况下,当您推送新代码时,<code>Packagist.org</code>不会自动更新,所以,您需要创建一个GitHub服务钩子, 你也可以使用点击页面上的<code>Update</code>按钮手动更新它,但我建议自动执行这个过程</p>
<p>提交之后,由于各地的镜像同步时间的延迟,可能在用composer安装的时候,会暂时找不到你的项目,这个时候可能需要等待同步完成</p>
<p>发布完成之后就可以通过composer安装你的扩展了</p>
<p>加入到https://github.com/dcat-admin-extensions
如果想把你开发的扩展加入到<code>dcat-admin-extensions</code>,欢迎用各种方式联系我,这样可以让更多人看到并使用你开发的工具。</p>
<p><a name="import"></a></p>
<h2>导入和启用扩展</h2>
<p><a name="importext"></a></p>
<h3>导入扩展</h3>
<p>安装完成后,运行以下命令即可以导入扩展,导入的文件主要有:静态资源文件、菜单、权限等。</p>
<p>>视图、数据库迁移文件、语言包一般不需要导入。如果要导入其他文件,需要扩展开发者自行定义。
>另外此命令允许重复执行。</p>
<pre><code class="language-php">php artisan admin:import Dcat\Admin\Extension\Gank\Gank</code></pre>
<p><a name="enable"></a></p>
<h3>启用扩展</h3>
<p>扩展的启用与否是通过配置文件控制的,打开<code>/config/admin-extension.php</code>,加入以下代码:</p>
<pre><code class="language-php">return [
'gank' =&gt; [
'enable' =&gt; true,
],
];</code></pre>
<p><a name="view"></a></p>
<h3>可视化管理扩展</h3>
<p>访问<code>http://localhost:8000/admin/helpers/extensions</code>,可以通过页面操作启用或关闭扩展、导入扩展。
>如果<code>/config/admin-extension.php</code>不存在,系统会自动创建</p>
<p>这样就完成了安装,打开<code>http://localhost/admin/gank</code>访问这个扩展。</p>