Dcat Admin模型树,无限级分类的前后台设计
2021-04-04 admin laravel mysql 2454
有时候我们需要使用无限级的分类或菜单,这时就需要使用模型树。在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;
}
教程结束 。