# laravel 快速构建后台组件 SleepingOwl Admin 项目地址 https://github.com/LaravelRUS/SleepingOwlAdmin 这里是文档(没什么卵用)http://sleeping-owl.github.io/ 这处有深坑入坑需谨慎,文档跟实际上都对不上的。我是参考demo做的本次记录。 后面发现俄文4.x的文档,这个对得上了,有些内容没写出来。 http://sleepingowl.laravel.su/docs/4.0/ 安装的话建议安装我的中文版啊 符合国情哈哈 这个是我吧俄国佬的fork后,修改完发布的。 https://packagist.org/packages/weiliang/sleepingowl 项目地址 https://github.com/wl496928838/SleepingOwlAdmin # 这是什么 sleepingowl admin是你的laravel后台界面生成器模型。 # 为什么用他 界面看起来还行,所以我就做一下笔记。 # 安装 终端执行 ``` omposer require laravelrus/sleepingowl:4.*@dev ``` 添加配置 `config/app.php` ``` SleepingOwl\Admin\Providers\SleepingOwlServiceProvider::class, App\Providers\AppServiceProvider::class, ``` 终端执行 `php artisan sleepingowl:install` ## 如果是laravel5.1,需要额外处理下配置文件 `config/sleeping_owl.php` 找到中间件属性,然后去掉 ``` 'middleware' => ['web'] ``` 改成 ``` 'middleware' => [] ``` # 使用 ## 导航条(菜单栏) 左侧的导航条在 `app/Admin/navigation.php` ``` <?php use SleepingOwl\Admin\Navigation\Page; return [ [ 'title' => '哈哈', 'icon' => 'fa fa-dashboard', 'url' => route('admin.dashboard'), ], [ 'title' => '呵呵', 'icon' => 'fa fa-exclamation-circle', 'url' => route('admin.information'), ], [ 'title' => "内容", 'icon' => 'fa fa-newspaper-o', 'pages' => [ (new Page(\App\Model\News::class)) ->setIcon('fa fa-newspaper-o') ->setPriority(0), ] ], ]; ``` 访问一下 `/admin` ,看看效果吧。 {% asset_img 配置后的界面.jpg 配置后的界面 %} 那么怎么填写这些配置. # 加入增删改查的配置 从导航条开始 ``` [ //这是导航条的标题 'title' => "内容", //样式 'icon' => 'fa fa-newspaper-o', //子页面 'pages' => [ //这里是一个匿名函数,new出一个Model来 (new Page(\App\Model\News::class)) ->setIcon('fa fa-newspaper-o') ->setPriority(0), ] ], ``` 然后我们看看 `App\Model\News.php` 写了什么 ``` <?php namespace App\Model; use Illuminate\Database\Eloquent\Model; class News extends Model { //表名 protected $table = 'news'; //可以填充的字段 protected $fillable = [ 'title', 'date', 'published', 'text', ]; public function scopeLast($query) { $query->orderBy('date', 'desc')->limit(4); } } ``` 这就是laravel的标准model了,不多解释,可以查laravel文档。 然后,看到 `App\Admin\News.php`,这里有一个跟Model同名的文件。 可以看出是定义增删改查的。 ``` <?php use App\Model\News; use SleepingOwl\Admin\Model\ModelConfiguration; AdminSection::registerModel(News::class, function (ModelConfiguration $model) { $model->setTitle('新闻'); // $model->setCreateTitle('创建'); // $model->setUpdateTitle('更新'); // $model->setMessageOnCreate('创建成功'); // $model->setMessageOnDelete('删除成功'); // $model->setMessageOnUpdate('更新成功'); // $model->setMessageOnRestore('恢复成功'); // Display $model->onDisplay(function () { return AdminDisplay::table()->setApply(function($query) { $query->orderBy('date', 'desc'); })->setColumns([ AdminColumn::link('title')->setLabel('标题'), AdminColumn::datetime('date')->setLabel('时间')->setFormat('d.m.Y')->setWidth('150px'), AdminColumnEditable::checkbox('published')->setLabel('出版'), ])->paginate(5); }); // Create And Edit $model->onCreateAndEdit(function() { $form = AdminForm::form()->setItems([ AdminFormElement::text('title', '标题')->required(), AdminFormElement::date('date', '时间')->required()->setFormat('d.m.Y'), AdminFormElement::checkbox('published', '出版'), AdminFormElement::wysiwyg('text', '内容'), ]); $form->getButtons() ->setSaveButtonText('保存') ->setSaveAndCreateButtonText('保存 并且 继续创建') ->setSaveAndCloseButtonText('保存 并且 关闭') ->setCancelButtonText('取消') //隐藏按钮 保存并关闭 // ->hideSaveAndCloseButton() //隐藏按钮 保存并创建 // ->hideSaveAndCreateButton() //隐藏取消按钮 ->hideCancelButton(); return $form; }); }); ``` # 汉化 我打算汉化一下的,没想到啊没想到,这么好的东西居然没有China。 {% asset_img 没有中文语言包.jpg 没有中文语言包 %} 那就自己动手吧。这里有一个坑。 覆盖Vendor包中的语言包 http://laravelacademy.org/post/211.html 语言包在这里,我们要覆盖他。 `vendor\laravelrus\sleepingowl\resources\lang\en\lang.php` 这里所写到的不够详细。我测试了好多次都不行,最终通过看代码,解决了。 我通过 `$model->setTitle('新闻');` 找到了这个文件。 `vendor\laravelrus\sleepingowl\src\Model\ModelConfiguration.php` ``` /** * @return string|\Symfony\Component\Translation\TranslatorInterface */ public function getCreateTitle() { if (is_null($this->createTitle)) { return trans('sleeping_owl::lang.model.create', ['title' => $this->getTitle()]); } return $this->createTitle; } ``` 这里写了 `sleeping_owl::lang.model.create` 所以最终,覆盖语言包的正确姿势是这样子的。 `\resources\lang\vendor\sleeping_owl\en\lang.php` 我翻译了半天放出来了,如果有翻译的不对的地方。敬请原谅。哈哈。 ``` <?php return [ '404' => '页面没有找到。', 'auth' => [ 'title' => '授权', 'username' => '用户名', 'password' => '密码', 'login' => '登陆', 'logout' => '退出登录', 'wrong-username' => '警告 用户名', 'wrong-password' => '或者 密码', 'since' => '注册时间 :date', ], 'model' => [ 'create' => '创建记录 :title', 'edit' => '更新记录 :title', ], 'links' => [ 'index_page' => '网站', ], 'ckeditor' => [ 'upload' => [ 'success' => '文件上传: \\n- 大小: :size kb \\n- 宽度/高度: :width x :height', 'error' => [ 'common' => '无法上传文件。', 'wrong_extension' => '文件 ":file" 扩展名错误', 'filesize_limit' => '允许的最大文件大小 :size kb.', 'imagesize_max_limit' => '宽度 x 高度 = :width x :height \\n 最大的宽度 x 高度 must be: :maxwidth x :maxheight', 'imagesize_min_limit' => '宽度 x 高度 = :width x :height \\n 最小的宽度 x 高度 must be: :minwidth x :minheight', ], ], 'image_browser' => [ 'title' => '插入图像从服务器', 'subtitle' => '选择图像插入', ], ], 'table' => [ 'new-entry' => '新建', 'edit' => '编辑', 'restore' => '恢复', 'delete' => '删除', 'delete-confirm' => '你确定要删除这个条目吗?', 'delete-error' => '当删除这个条目时发生错误。', 'moveUp' => '向上移动', 'moveDown' => '向下移动', 'error' => '在您的请求时发生错误', 'filter' => '显示类似的条目', 'filter-goto' => '显示', 'save' => '保存', 'save_and_close' => '保存并关闭', 'save_and_create' => '保存并继续创建', 'cancel' => '取消', 'download' => '下载', 'all' => '所有', 'processing' => '<i class="fa fa-5x fa-circle-o-notch fa-spin"></i>', 'loadingRecords' => '加载中...', 'lengthMenu' => '显示 _MENU_ 记录', 'zeroRecords' => '没有找到匹配的记录。', 'info' => '显示 _START_ to _END_ of _TOTAL_ 记录', 'infoEmpty' => '显示 0 to 0 of 0 记录', 'infoFiltered' => '(过滤后 _MAX_ 条记录)', 'infoThousands' => ',', 'infoPostFix' => '', 'search' => '搜索: ', 'emptyTable' => '没有数据表', 'paginate' => [ 'first' => '首页', 'previous' => '上一页', 'next' => '下一页', 'last' => '尾页', ], ], 'editable' => [ 'checkbox' => [ 'checked' => 'Yes', 'unchecked' => 'No', ], ], 'select' => [ 'nothing' => '没有选择', 'selected' => '选中', 'placeholder' => '从列表中选择', ], 'image' => [ 'browse' => '选择图片', 'browseMultiple' => '选择多张图片', 'remove' => '移除图片', 'removeMultiple' => '移除', ], 'file' => [ 'browse' => '选择文件', 'remove' => '移除文件', ], 'message' => [ 'created' => '<i class="fa fa-check fa-lg"></i> 创建记录成功', 'updated' => '<i class="fa fa-check fa-lg"></i> 更新记录成功', 'deleted' => '<i class="fa fa-check fa-lg"></i> 删除记录成功', 'restored' => '<i class="fa fa-check fa-lg"></i> 恢复记录成功', ], ]; ``` ## 补充 前面的操作需要一张表的,忘记给了。不过你应该自己就知道了。我这里补充下吧。 ``` <?php use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateNewsTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('news', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->date('date'); $table->boolean('published'); $table->text('text'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::drop('news'); } } ``` # 修改配置信息 `config\sleeping_owl.php` 这里可以修改标题 logo等等,还可以设置授权的中间件。 ## 小结 到这里,似乎我已经会使用这个玩意了。 # 不甘寂寞,发布到composer https://packagist.org/packages/weiliang/sleepingowl 然后只需要 `composer require weiliang/sleepingowl` 就可以得到,支持中文的啦。 然后还在git上面 请求合并,不知道俄国佬会不会同意加入中文包啊。 好吧,说了这么多。再来实践一边。 加上这个 `--prefer-source` 就是说 去git下载似乎速度快一点点 `composer req weiliang/sleepingowl --prefer-source` # 再来快速实践一边 发现俄文4.x的文档 http://sleepingowl.laravel.su/docs/4.0/ # 翻译一些文档 # 数据显示类型 ## 表(table) ``` $model->onDisplay(function () { $display = AdminDisplay::table(); ... return $display; }); ``` ## 指定列 ``` $display->setColumns([ ... AdminColumn::link('title')->setLabel('Title'), AdminColumn::datetime('created_at')->setLabel('Created')->setFormat('d.m.Y'), ... ]); // or $display->getColumns()->push( AdminColumn::text('title') ); ``` ## 列过滤器 ``` $display->setColumnFilters([ ... AdminColumnFilter::text()->setPlaceholder('Full Name'), ... ]); // or $display->getColumnFilters()->push( AdminColumnFilter::text()->setPlaceholder('Full Name') ); ``` ## 预先加载 ``` $display->with('country', 'companies'); ``` ## 查询的变化 您可以修改随意查询 ``` $display->setApply(function ($query) { $query->orderBy('title', 'asc'); }); // or $display->setApply([ ... function ($query) { $query->orderBy('title', 'asc'); }, ... ]); // or $display->getApply()->push(function ($query) { $query->orderBy('title', 'asc'); }); ``` ## 适用范围 您可以使用 eloquent scope 输出数据: ``` $display->setScopes('last'); // or $display->getScopes()->push('last'); ``` # 表中的列 在输出中创建表中的新列是一个记录列表。 ``` AdminDisplay::table() ->setColumns([ AdminColumn::link('title')->setLabel('Title'), AdminColumn::datetime('date')->setLabel('Date')->setFormat('d.m.Y')->setWidth('150px') ]) ``` `SleepingOwl\Admin\Display\TableColumn` 使所有实现接口的列 `Illuminate\Contracts\Support\Arrayable` `Illuminate\Contracts\Support\Renderable` 使用的类列特质htmlAttributes ,其中所有的列可以自定义HTML属性。 ## 支持 ``` AdminColumn::action($name) AdminColumn::checkbox() AdminColumn::count($name) AdminColumn::custom() AdminColumn::datetime($name) AdminColumn::email($name) AdminColumn::string($name) AdminColumn::link($name) AdminColumn::relatedLink($name) AdminColumn::lists($name) AdminColumn::image($name) AdminColumn::order() ``` ## 设置列标题 表中的每一列具有标题,并存储在一个单独的类 `SleepingOwl\Admin\Contracts\Display\TableHeaderColumnInterface TableHeaderColumnInterface` 设置标题的例子 ``` ->setColumns([ AdminColumn::link('title') ->setLabel('Title') ->setOrderable(false), ]); ``` 或者直接用一个头显示 ``` ->setColumns([ $link = AdminColumn::link('title') ]); $link->getHeader() ->setTitle('Title') ->setOrderable(false) ->setHtmlAttribute('class', 'bg-success text-center') ->setHtmlAttribute('data-tooltip', 'Test tooltip'); ``` ## 设置该列不排序 ``` AdminColumn::link('title')->setOrderable(false); ``` # HTML属性 这个特点是用于工作的组织与HTML中的类属性。 这是有用的,例如,如果你创建一个类,输出将被转换成HTML,你需要允许用户指定CSS类,ID和元素的其他属性。 比方说,我们有一个类TableColumn ,将其转化为 `<td>{{ $value }}</td>` 并要允许用户指定的属性来获取输出 `<td class="bg-primary" id="row-3" data-value="test"></test>` 对于这个类,您必须连接这个特质 ``` <?php namespace SleepingOwl\Admin\Display; use KodiComponents\Support\HtmlAttributes; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Renderable; class TableColumn implements Arrayable, Renderable { use HtmlAttributes; public function toArray() { return [ 'value' => 'test', 'attributes' => $this->htmlAttributesToString(), ]; } public function render() { return view( 'table_column', $this->toArray() ); } } ``` ``` <td {{ $attributes }}>{{ $value }}</td> ``` 现在你可以在这个类,你可以分配属性 ``` $column = new TableColumn(); $column->setHtmlAttribute('class', 'bg-primary'); $column->setHtmlAttribute('class', 'text-right'); $column->setHtmlAttribute('id', 'row-3'); $column->setHtmlAttribute('data-value', 'test'); // Or $column->setHtmlAttributes([ 'class' => ['bg-primary', 'text-right'], 'id' => 'row-3', 'data-value' => 'test' ]); // return <td class="bg-primary text-right" id="row-3" data-value="test">test</td> // 重新设置class $column->replaceHtmlAttribute('class', 'new-class'); // return <td class="new-class" id="row-3" data-value="test">test</td> // 检查class是否存在 $column->hasClassProperty('new-class'); // return true // 检查属性是否存在 $column->hasHtmlAttribute('data-value'); // return true // 删除属性 $column->removeHtmlAttribute('data-value'); // 删除全部属性 $column->clearHtmlAttributes(); // 字符串转换属性 $column->htmlAttributesToString(); // return "class="new-class" id="row-3" data-value="test"" ``` # 来从0开始添加一个文章模型吧 ## 添加菜单 `app\Admin\navigation.php` ``` [ 'title' => "文章", 'icon' => 'fa fa-newspaper-o', 'pages' => [ (new Page(\App\Model\Article::class)) ->setIcon('fa fa-newspaper-o') ->setPriority(0), ] ], ``` ## 添加数据模型 `app\Model\Article.php` ``` <?php namespace App\Model; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; class Articles extends Model { use SoftDeletes; protected $table = 'articles'; protected $fillable = [ 'title', 'content', ]; public $timestamps = false; } ``` ## 添加显示模板 `app\Admin\Articles.php` ``` <?php use App\Model\Articles; use SleepingOwl\Admin\Model\ModelConfiguration; AdminSection::registerModel(Articles::class, function (ModelConfiguration $model) { $model->setTitle('文章'); // 禁止恢复,让他只能从回收站那恢复 $model->disableRestoring(); // 显示 $model->onDisplay(function () { return AdminDisplay::table()->setApply(function($query) { $query->orderBy('id', 'desc'); })->setColumns([ AdminColumn::link('title')->setLabel('标题'), ])->paginate(5); }); // 创建 和 编辑 $model->onCreateAndEdit(function() { $form = AdminForm::form()->setItems([ AdminFormElement::text('title', '标题')->required(), AdminFormElement::wysiwyg('content', '内容'), ]); $form->getButtons() //隐藏按钮 保存并关闭 ->hideSaveAndCloseButton(); //隐藏取消按钮 return $form; }); }); ``` 到这里文章的增删改查就搞掂了.我们还可以做一个文章回收站呢. ## 添加回收站的菜单 `app\Admin\navigation.php` ``` [ 'title' => "内容管理", 'icon' => 'fa fa-newspaper-o', 'pages' => [ (new Page(\App\Model\Articles::class)) ->setIcon('fa fa-newspaper-o') ->setPriority(0), (new Page(\App\Model\Articles2::class)) ->setIcon('fa fa-newspaper-o') ->setPriority(0), ] ], ``` ## 添加回收站的模型 `\App\Model\Articles2` ``` <?php namespace App\Model; class Articles2 extends Articles { } ``` 这里只需要直接继承Articles ## 添加回收站的显示模板 我发现其实这里的文件名,随便起就可以了。并没有什么规律。只是建议跟model一样的名字。好区分。 `aap/admin/Articleshuishouzhan.php` ``` <?php use App\Model\Articles2; use SleepingOwl\Admin\Model\ModelConfiguration; AdminSection::registerModel(Articles2::class, function (ModelConfiguration $model) { $model->setTitle('回收站'); // 这里禁止创建 $model->disableCreating(); // 显示 $model->onDisplay(function () { return AdminDisplay::table()->setApply(function($query) { // onlyTrashed 只显示软删除的结果 $query->onlyTrashed()->orderBy('id', 'desc'); })->setColumns([ AdminColumn::link('title')->setLabel('标题'), ])->paginate(5); }); }); ``` # 最终的效果 {% asset_img 内容管理文章.jpg 内容管理文章 %} {% asset_img 内容管理回收站.jpg 内容管理回收站 %} {% asset_img 内容管理创建文章.jpg 内容管理创建文章 %} 我把表结构也发一下. ``` /*------- CREATE SQL---------*/ CREATE TABLE `laravel_articles` ( `id` int(11) NOT NULL AUTO_INCREMENT, `title` varchar(200) DEFAULT NULL, `content` text, `deleted_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB ``` # 总结一下 轻轻松松构建增删改查,还带回收站功能。简直6666啊。 美中不足的地方 * 回收站没有强制删除,也就是强制删除软删除的记录。 * 没有搜索功能,没有搜索功能,没有搜索功能,你发现了吗? * AdminDisplay::datatables()这个搜索功能是把数据库整个表都显示出来实现的搜索功能,我测试了几千条数据,显示极慢,搜索极快,残缺啊~少量数据还行。 * 打脸了,确实发现了可以异步搜索的了,datatablesAsync(). ``` $model->onDisplay(function () { return AdminDisplay::datatablesAsync()->setApply(function($query) { $ret = $_GET['search']['value']; $query->where('title','like','%'.$ret.'%')->orderBy('id', 'desc'); })->setColumns([ AdminColumn::link('title')->setLabel('标题'), ])->paginate(5); }); ``` 但是这用了非常不优雅的办法处理.暂且这样子吧哈哈.. # 最后再总结一番吧 我总结下这个玩意的优缺点 优点 * 快速做出增删改查 * 界面还不赖 * 操作起来也挺方便 * 写起代码来也挺舒服 * 高度封装傻瓜化配置 缺点 * 这封装的太666了把,我想加一个 文本框 按钮 搜搜索都不知道怎么加。 * 没有中文语言包(不过我已经自己搞掂了) * 搜索功能好渣渣(可能还有解决办法) * 太傻瓜化了,自定义性太低。 ## 那么这个东西到底能不能用呢。 当然可以啊 不然我翻译来干嘛,能用好好用啊,发挥他的长处,解决实际的问题。 他还可以做一个单页的呢。大不了自己加一个页面进去呗。大部分功能还是很爽哒。 目前只发现了搜索功能有点残缺,不过我也解决了。 好累啊整理了一天,希望熟练的掌握这些快速构建增删改查的技能。能够帮助你快速的搭建后台系统。