10分钟用Laravel-admin快速自定义创建tree菜单

一、介绍

有时候,我们需要实现一些表自关联的菜单效果,比如类目(Category )管理等,laravel-admin能够帮助我们快速,创建树形效果菜单。laravel-admin有丰富的组件库,其底层是基于boostrap封装的,因此基本组件没有的可以自己定义实现。查看了laravel-admin的官方文档,使用介绍也非常详细,基本上一分钟即可搭建一个高可用的基础后台管理系统。结合php和laravel,三者都是在提速增效。做全栈快速应对业务变化,做一些小程序、公众号、cms的开发,简直就是神速。官网https://laravel-admin.org/

  • 默认系统自带效果如下。今天就是自定义实现这个效果。


    image.png
  • 实现参考文档


    image.png

二、实现步骤

  • 创建表迁移文件
php artisan make:migration create_menus_table 

在生成的迁移文件中,添加以下代码:


image.png
 Schema::create('menus', function (Blueprint $table) {
            $table->comment('菜单表');
            $table->id()->comment('主键');
            $table->bigInteger('pid')->comment('父ID');
            $table->integer('order')->comment('排序');
            $table->string('title')->unique()->comment('名称');
            $table->string('icon',64)->comment('图标');
            $table->string('uri')->comment('路径');
            $table->timestamps();
 });
  • 执行迁移命令
 php artisan migrate 

即可生成如下表:

CREATE TABLE `menus` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `pid` bigint(20) NOT NULL COMMENT '父ID',
  `order` int(11) NOT NULL COMMENT '排序',
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '名称',
  `icon` varchar(64) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '图标',
  `uri` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '路径',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `menus_title_unique` (`title`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='菜单表';
  • 生成菜单模型
php artisan make:model Menu 

执行生成前,需要正确安装好了laravel-admin,其安装此处省略。

<?php

namespace App\Models;

use App\Helpers\core\SerializeDate;
use Encore\Admin\Traits\ModelTree;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Menu extends Model
{
    use HasFactory;
    use SerializeDate;
    use ModelTree;

    protected $fillable = ['pid', 'order', 'title', 'icon', 'uri'];

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->setParentColumn('pid');  // 父ID
        $this->setOrderColumn('order'); // 排序
        $this->setTitleColumn('title'); // 标题
    }
}

其中关键代码:

use Encore\Admin\Traits\ModelTree;
use ModelTree;

    protected $fillable = ['pid', 'order', 'title', 'icon', 'uri'];

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        $this->setParentColumn('pid');  // 父ID
        $this->setOrderColumn('order'); // 排序
        $this->setTitleColumn('title'); // 标题
    }
  • 剩下什么都不需要做,直接生成控制器。指定--model=App\Models\Menu生成。
php artisan admin:make MenuController --model=App\Models\Menu

清空app/Admin/Controllers/MenuController.php自动生成内容,采用如下代码替换。

<?php

namespace App\Admin\Controllers;

use App\Models\Menu;
use Encore\Admin\Controllers\AdminController;
use Encore\Admin\Controllers\HasResourceActions;
use Encore\Admin\Form;
use Encore\Admin\Layout\Column;
use Encore\Admin\Layout\Content;
use Encore\Admin\Layout\Row;
use Encore\Admin\Tree;
use Encore\Admin\Widgets\Box;

class MenuController extends AdminController
{
    use HasResourceActions;
    /**
     * Title for current resource.
     *
     * @var string
     */
    protected $title = 'Menu';

    public function index(Content $content)
    {
        return $content
            ->title('菜单')
            ->description('菜单描述')
            ->header('菜单')
            ->row(function (Row $row) {
                $row->column(6, $this->treeView()->render());
                $row->column(6, function (Column $column) {
                    $form = new \Encore\Admin\Widgets\Form();
                    $form->action(admin_url('menus'));
                    $form->select('pid', trans('admin.parent_id'))->options(Menu::selectOptions());
                    $form->text('title', trans('admin.title'))->rules('required');
                    $form->icon('icon', trans('admin.icon'))->default('fa-bars')->rules('required')->help($this->iconHelp());
                    $form->text('uri', trans('admin.uri'));
                    $form->number('order', trans('admin.order'))->default(99)->help('越小越靠前');
                    $form->hidden('_token')->default(csrf_token());
                    $column->append((new Box(trans('admin.new'), $form))->style('success'));
                });
            });
    }

    public function show($id, Content $content)
    {
        return redirect()->route('admin.menus.edit', ['menu' => $id]);
    }

    protected function treeView()
    {
        $tree = new Tree(new Menu());
        $tree->disableCreate();
        $tree->branch(function ($branch) {
            $payload = "<i class='fa {$branch['icon']}'></i>&nbsp;<strong>{$branch['title']}</strong>";

            if (!isset($branch['children'])) {
                if (url()->isValidUrl($branch['uri'])) {
                    $uri = $branch['uri'];
                } else {
                    $uri = url($branch['uri']);
                }

                $payload .= "&nbsp;&nbsp;&nbsp;<a href=\"$uri\" class=\"dd-nodrag\">$uri</a>";
            }

            return $payload;
        });

        return $tree;
    }

    public function edit($id, Content $content)
    {
        return $content
            ->title(trans('admin.menu'))
            ->description(trans('admin.edit'))
            ->row($this->form()->edit($id));
    }

    public function form()
    {
        $form = new Form(new Menu());

        $form->display('id', 'ID');
        $form->select('pid', trans('admin.parent_id'))->options(Menu::selectOptions());
        $form->text('title', trans('admin.title'))->rules('required');
        $form->icon('icon', trans('admin.icon'))->default('fa-bars')->rules('required')->help($this->iconHelp());
        $form->text('uri', trans('admin.uri'));
        $form->number('order', trans('admin.order'))->default(99)->help('越小越靠前');
        $form->display('created_at', trans('admin.created_at'));
        $form->display('updated_at', trans('admin.updated_at'));

        return $form;
    }

    protected function iconHelp()
    {
        return 'For more icons please see <a href="http://fontawesome.io/icons/" target="_blank">http://fontawesome.io/icons/</a>';
    }
}
  • 至此,即可实现完成。实现非常简单,参考官网源码即可。
    vendor/encore/laravel-admin/src/Controllers/MenuController.php
    打开vendor目录,找到控制器目录下的MenuController.php文件然后复制粘贴即可。
  • 记得添加路由


    image.png

    就一行代码搞定:

   $router->resource('menus', \App\Admin\Controllers\MenuController::class);

自动实现了相关路由:


image.png
  • 细节处理
    菜单的编辑页面的查看按钮,自动生成无法隐藏,因此也直接跳转到编辑页面。
    image.png

    执行php artisan route:list找到admin/auth/menu/{menu}/edit路由的别名admin.menus.editshow函数中填写如下即可。
 public function show($id, Content $content)
    {
        return redirect()->route('admin.menus.edit', ['menu' => $id]);
    }

添加一个排序字段,测试效果。

 $form->number('order', trans('admin.order'))->default(99)->help('越小越靠前');

注意添加csrf隐藏域,目的是为了防止csrf攻击,laravel默认开启了csrf的token。这有别于一般的其他语言的框架,使得使用laravel编写项目,同时安全性也得到保障。

$form->hidden('_token')->default(csrf_token());

三、最终效果

image.png
  • 至此,所有效果均已实现。其它类似效果,参考实现即可。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,941评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,397评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,345评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,851评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,868评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,688评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,414评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,319评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,775评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,945评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,096评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,789评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,437评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,993评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,107评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,308评论 3 372
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,037评论 2 355

推荐阅读更多精彩内容