柔晶美网络工作室

柔晶美网络工作室,倾心于web技术的博客站点

关注我 微信公众号

您现在的位置是: 首页 > 博客日记

Dcat Admin模型树,无限级分类的前后台设计

2021-04-04 admin laravel  mysql  2299

有时候我们需要使用无限级的分类或菜单,这时就需要使用模型树。在Dcat Admin后台管理系统中,可以轻松创建这种模型树。

首先,直接在后台,开发者工具中创建数据表kengcheng,也可直接执行以下sql创建:

CREATE TABLE `kengcheng` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `parent_id` int(11) NOT NULL DEFAULT '0',
  `order` int(11) NOT NULL DEFAULT '0',
  `title` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

如果是开发者工具会自动创建数据模型文件和仓库,如果是sql创建的,则需要手工创建。迁移文件:

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateFlTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('fl', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('parent_id')->default('0');
            $table->integer('order')->default('0');
            $table->string('title')->default('');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('fl');
    }
}


一是模型文件app/Models/Kecheng.php文件

namespace App\Models;

use Dcat\Admin\Traits\HasDateTimeFormatter;
use Dcat\Admin\Traits\ModelTree;
use Illuminate\Database\Eloquent\Model;

class Kecheng extends Model
{
	use HasDateTimeFormatter, ModelTree;
    protected $table = 'kecheng';
    // 父级ID字段名称,默认值为 parent_id
    protected $parentColumn = 'parent_id';

    // 排序字段名称,默认值为 order
    protected $orderColumn = 'order';

    // 标题字段名称,默认值为 title
    protected $titleColumn = 'title';
    
}

数据仓库中app/Admin/Repositories创建一样的kengcheng.php:

namespace App\Admin\Repositories;

use App\Models\Kecheng as Model;
use Dcat\Admin\Repositories\EloquentRepository;

class Kecheng extends EloquentRepository
{
    /**
     * Model.
     *
     * @var string
     */
    protected $eloquentClass = Model::class;
}

创建一条路由:

$router->resource('/kcsz','KechengController');//课程设置

然后创建一个控制器:

namespace App\Admin\Controllers;

use Dcat\Admin\Http\Controllers\AdminController;
use Dcat\Admin\Http\Controllers\HasResourceActions;
use App\Models\Kecheng;
use Dcat\Admin\Layout\Row;
use Dcat\Admin\Layout\Content;
use Dcat\Admin\Tree;
use Dcat\Admin\Form;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class KechengController extends AdminController
{
    use HasResourceActions;
    //模型数显示分类
    public function index(Content $content)
    {
        return $content->header('课程设置')->description('可拖动编辑')->body(function (Row $row) {
            $tree = new Tree(new Kecheng);
            $tree->disableCreateButton();//关闭快速创建
            $tree->disableEditButton();//关闭编辑
            $row->column(12, $tree);
        });
    }
            
    protected function form()
    {
        return Form::make(new Kecheng(), function (Form $form) {
            //$form->display('id');
            $form->select('parent_id', trans('admin.parent_id'))
                ->options(Kecheng::selectOptions())
                ->saving(function ($v) {
                    return (int) $v;
                })->required();
            //$form->number('order')->default(0);//排序字段
            $form->text('title')->required();
            $form->textarea('jj');
        });
    }
    
    //分类联动
    public function city(Request $request)
    {
        $provinceId = $request->get('q');
        if(empty($provinceId))$provinceId=0;//如果没有选择,则设置为顶层节点0
        return Kecheng::where('parent_id', $provinceId)->get(['id', DB::raw('title as text')]);//DB::raw创建临时表,将title字段转为text,以适配API
    }


}

这样,后台就完成了模型树的管理了。然后说下前台如何使用,首先是读取模型树数据表kengcheng的所有数据:

    public function xuanke(Request $request)
    {
        $kecheng = Kecheng::get();
        $html = $this->print_list($kecheng);
        if(isset($request->id)){
            $teachers = AdministratorModel::whereJsonContains('xueke',(string)$request->id)->orwhereJsonContains('xueke',(int)$request->id)->get();//获取相应课程的老师
        }else{
            $teachers = AdministratorModel::where('id','>',1)->get();
        }
        return view('xuanke',compact('html','teachers'));
    }

我们这里需要使用递归,来生成无限级分类菜单:

    //递归生成li html
    public function print_list($array, $parent=0) {
        static $html;//静态变量,避免递归调用时,多次声明导致覆盖
        $html .= "<ul>";
        foreach ($array as $row) {
            if ($row->parent_id == $parent) {
                if($parent == 0){
                    $html .= "<li class='closed'><span class='folder'>$row->title</span>";
                }else{
                   $html .= "<li class='closed'><span class='file' onclick='menu($row->id)'>$row->title</span>"; 
                }
                $this->print_list($array, $row->id);
                $html .= "</li>";
            }
        }
        $html .= "</ul>";
        return $html;
    }

这样就完成了无限级分类,是不是很简单呢?最后说下,后台如何获取模型树的值,比如可多选的模型树,在表格中显示已选择的内容:

            $kecheng = (new Kecheng())->allNodes();
            $grid->column('xueke')->showTreeInDialog(function (Grid\Displayers\DialogTree $treekc) use (&$kecheng) {
                $treekc->nodes($kecheng);//设置所有节点
                // 设置节点数据字段名称,默认"id","name","parent_id"
                $treekc->setIdColumn('id');
                $treekc->setTitleColumn('title');
                $treekc->setParentColumn('parent_id');
            });

在form中选择分类多选:

            $form->tree('xueke')->nodes(Kecheng::get()->toArray())
            ->setIdColumn('id')->setTitleColumn('title')->setParentColumn('parent_id')
            ->required()->expand(false);//默认不展开;

联动下拉单选:

$form->select('classify')
                ->options(Wzfl::selectOptions())
                ->saving(function ($v) {
                    return (int) $v;
            });

如果前端要将其转换为层级数组,可用以下方法:

    //获得指定文章分类的子分类组成的树形结构
    public function cateTree($pid=0,$level=0){
        $array=array();
        $tmp = Kecheng::where("parent_id",$pid)->get()->toArray();
        if(is_array($tmp)){
            foreach($tmp as $v){
                $v['level']=$level;
                $array[count($array)]=$v;
                $sub = $this->cateTree($v['id'],$level+1);
                if(is_array($sub))$array=array_merge($array,$sub);
            }
        }
        return $array;
    }


2021.7.21补充,树型表格中的应用:

模型:

namespace App\Models;

use Dcat\Admin\Traits\HasDateTimeFormatter;
use Illuminate\Database\Eloquent\Model;
use Dcat\Admin\Traits\ModelTree;
use Spatie\EloquentSortable\Sortable;

class Fl extends Model implements Sortable
{
	use HasDateTimeFormatter,ModelTree;
	
    protected $table = 'fl';
    
    // 设置排序字段,默认order
    protected $orderColumn = 'order';
    
    //该分类下的子分类
    public function child(){
        return $this->hasMany(get_class($this),'parent_id',$this->getKeyName())->first();
    }
    
    //该分类的父分类
    public function parent(){
        return $this->hasMany(get_class($this),$this->getKeyName(),'parent_id')->first();
    }
    
}

分类控制器,管理树型表格:

class FlController extends AdminController
{
    /**
     * Make a grid builder.
     *
     * @return Grid
     */
    protected function grid()
    {
        return Grid::make(new Fl(), function (Grid $grid) {
            $grid->column('id')->bold()->sortable();
            $grid->column('order')->orderable(); // 开启排序功能
            $grid->column('title')->tree();// 开启树状表格功能
            $grid->column('created_at');
            $grid->column('updated_at')->sortable();
        
            $grid->showQuickEditButton();
            $grid->enableDialogCreate();
            $grid->disableEditButton();
            
            $grid->quickSearch(['id', 'title']);
            
            $grid->filter(function (Grid\Filter $filter) {
                $filter->equal('id');
                $filter->equal('title');
            });
        });
    }

    /**
     * Make a show builder.
     *
     * @param mixed $id
     *
     * @return Show
     */
    protected function detail($id)
    {
        return Show::make($id, new Fl(), function (Show $show) {
            $show->field('id');
            $show->field('parent_id');
            $show->field('order');
            $show->field('title');
            $show->field('created_at');
            $show->field('updated_at');
        });
    }

    /**
     * Make a form builder.
     *
     * @return Form
     */
    protected function form()
    {
        return Form::make(new Fl(), function (Form $form) {
            //$form->display('id');
            $form->select('parent_id', trans('admin.parent_id'))
                ->options(Fl::selectOptions())
                ->saving(function ($v) {
                    return (int) $v;
                })->required();
            $form->text('title','名称')->required();
            $form->number('order')->default(0);//排序字段
        });
    }
}

在学科管理器中,选择学科单元,显示用/分割,规格选择器中的使用:

namespace App\Admin\Controllers;

use App\Admin\Repositories\Ggbk;
use Dcat\Admin\Form;
use Dcat\Admin\Grid;
use Dcat\Admin\Show;
use App\Models\Fl;
use Admin;
use Dcat\Admin\Http\Controllers\AdminController;

class GgbkController extends AdminController
{
    /**
     * Make a grid builder.
     *
     * @return Grid
     */
    protected function grid()
    {
        return Grid::make(new Ggbk(), function (Grid $grid) {
            $grid->column('id')->sortable();
            $grid->column('title');
            
            $nodes = (new Fl())->allNodes();
            $grid->column('xkdy')->display(function ($id)use(&$nodes) {
                $fl = FL::find($id);
                $title = [$fl->title];
                while($fl->parent() && $fl->parent_id !== 0)
                {
                    $title[] = $fl->parent()->title;
                    $fl = FL::find($fl->parent()->id);
                }
                return implode('/',array_reverse($title));
            });
            // $grid->column('xkdy')->showTreeInDialog(function (Grid\Displayers\DialogTree $treekc) use (&$nodes) {
            //     $treekc->nodes($nodes);//设置所有节点
            //     // 设置节点数据字段名称,默认"id","name","parent_id"
            //     $treekc->setIdColumn('id');
            //     $treekc->setTitleColumn('title');
            //     $treekc->setParentColumn('parent_id');
            // });
            $grid->column('fm')->image(null,60);
            $grid->column('zz')->using(Admin::user()->pluck('name','id')->toArray());
            $grid->column('read')->sortable();
            $grid->column('open')->switch()->sortable();
            $grid->column('top')->switch()->sortable();
            $grid->column('created_at');
            //$grid->column('updated_at')->sortable();
            
            $grid->quickSearch(['title','xkdy']);//快捷搜索
            
            //规格筛选
            $grid->selector(function (Grid\Tools\Selector $selector)use($nodes) {
                $xk = $nodes->where('parent_id',0)->pluck('title','id')->toArray();//学科
                $selector->selectOne('xkdy', '学科', $xk, function ($query, $value) {
                    $fl = FL::find($value);
                    $ids = [$fl->id];
                    while($fl->child())
                    {
                        $ids[] = $fl->child()->id;
                        $fl = FL::find($fl->child()->id);
                    }
                    $query->wherein('xkdy', $ids);
                });
                
            });

            $grid->filter(function (Grid\Filter $filter) {
                $filter->equal('title');
                $filter->equal('xkdy');
        
            });
        });
    }
    
    protected function detail($id)
    {
        return Show::make($id, new Ggbk(), function (Show $show) {
            $show->field('id');
            $show->field('title');
            $show->field('xkdy');
            $show->field('fm');
            $show->field('yuxue')->unescape();
            $show->field('yanxue')->unescape();
            $show->field('tuxue')->unescape();
            $show->field('zz')->using(Admin::user()->pluck('name','id')->toArray());
            $show->field('created_at');
            $show->field('updated_at');
        });
    }

    /**
     * Make a form builder.
     *
     * @return Form
     */
    protected function form()
    {
        return Form::make(new Ggbk(), function (Form $form) {
            $form->display('id');
            $form->text('title')->required();
            $form->select('xkdy')
                ->options(Fl::selectOptions())
                ->saving(function ($v) {
                    return (int) $v;
            });
            $form->image('fm')->accept('jpg,png,jpeg')->compress(['width' => 612,'height' => 308])->default('http://roujingmei.com:20080/uploads/images/tx.jpg')->required()->autoUpload()->saveFullUrl();
            $form->editor('yuxue');
            $form->editor('yanxue');
            $form->editor('tuxue');
            $form->hidden('zz')->default(Admin::user()->id);
            $form->number('read')->default(0)->required();
            $form->switch('open')->default(1)->help('是否开放阅读');
            $form->switch('top')->default(0)->help('是否置顶');
        
            $form->display('created_at');
            $form->display('updated_at');
        });
    }
}

总体来说,树型结构的搜索比较复杂,需要使用遍历。

如果说要对同一字段进行规格筛选,比如学科、年级、上下册等,可如下设置:

            //规格筛选
            $grid->selector(function (Grid\Tools\Selector $selector) {
                $data = json_decode(json_encode(Fl::get()),true);
                $xk = Fl::where('parent_id',0)->pluck('title','id')->toArray();//学科
                $selector->selectOne('xkdy', '学科', $xk, function ($query, $value)use($data) {
                    $fl = Fl::find($value);
                    $ids = [$fl->id];
                    $ids = $this->getSubTree($data,$value);
                    $query->wherein('xkdy', $ids);
                });
                
                $nj = array_unique(Fl::where('title','like',"%年级%")->pluck('title','id')->toArray());//年级
                $selector->selectOne('xkdy2', '年级', $nj, function ($query, $value)use($nj,$data) {
                    $fl = Fl::where('title',$nj[$value])->get();
                    $ids = [];
                    foreach($fl as $value){
                        $ids[] = $value->id;
                        $ids = array_merge($ids,$this->getSubTree($data,$value->id));
                    }
                    $query->wherein('xkdy', $ids);
                });
                
                $sxc = array_merge(array_unique(Fl::wherein('title',['上册','下册'])->pluck('title')->toArray()));//上下册
                $selector->selectOne('xkdy3', '上下册', $sxc, function ($query, $value)use($sxc,$data) {
                    $fl = Fl::where('title',$sxc[$value])->get();
                    $ids = [];
                    foreach($fl as $value){
                        $ids[] = $value->id;
                        $ids = array_merge($ids,$this->getSubTree($data,$value->id));
                    }
                    $query->wherein('xkdy', $ids);
                });
                
            });


    //递归获取该节点下所有子节点
    public function getSubTree($data = [], $id = 0, $level = 0)
    {
        static $tree = [];
        foreach ($data as $key => $value) {
            if ($value['parent_id'] == $id) {
                $value['laravel'] = $level;
                $tree[] = $value['id'];//获取id
                $this->getSubTree($data, $value['id'], $level + 1);
            }
        }
        return $tree;
    }


教程结束 。

文章评论


需要 登录 才能发表评论
热门评论
0条评论

暂时没有评论!