感謝
- laravel的開發團隊
- laravel-admin的作者
- nestedset的作者
場景
在laravel-admin中做一個可排序的無限極分類資料模型樹的功能,laravel-admin中自帶的資料模型樹部分功能無法達到我要的效果,正好之前有使用過nestedset,於是打算把ModelTree這個trait替換成nestedset中的NodeTrait
Larave-admin中的資料模型樹實現
準備工作
composer require kalnoy/nestedset
修改遷移,這裡我們以'areas'這個表舉例
// areas表中的欄位需要包含
// title 資料名稱
// order 資料排序
// parent_id 資料父id
// _lft
// _rgt
Schema::create('areas', function (Blueprint $table) {
...
/*新增nestedset需要的相關欄位*/
$table->nestedSet();
});
程式碼實現
修改Area模型
<?php
namespace Your\Namespace\Models\Area;
use Encore\Admin\Traits\AdminBuilder;
//use Encore\Admin\Traits\ModelTree;
use Illuminate\Database\Eloquent\Model;
use Kalnoy\Nestedset\NodeTrait;
use Your\Namespace\Traits;
class Area extends Model
{
//use ModelTree, AdminBuilder;
use NodeTrait,NodeModelTreeTrait,AdminBuilder;
}
建立NodeModelTreeTrait
//內容基本來自vendor\encore\laravel-admin\src\Traits\ModelTree.php
//saveOrder()方法中做了修改,新增了重置資料模型樹的fixTree()方法
//刪除了ModelTree中的parent()方法和children()方法,因為 NodeTrait 中有同名方法
<?php
namespace Your\Namespace\Traits;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;
use Kalnoy\Nestedset\NestedSet;
trait NodeModelTreeTrait
{
/**
* @var array
*/
protected static $branchOrder = [];
/**
* @var string
*/
protected $titleColumn = 'title';
/**
* @var string
*/
protected $orderColumn = 'order';
/**
* @var \Closure
*/
protected $queryCallback;
/**
* @return string
*/
public function getParentColumn()
{
return NestedSet::PARENT_ID;
}
/**
* Get title column.
*
* @return string
*/
public function getTitleColumn()
{
return $this->titleColumn;
}
/**
* Set title column.
*
* @param string $column
*/
public function setTitleColumn($column)
{
$this->titleColumn = $column;
}
/**
* Get order column name.
*
* @return string
*/
public function getOrderColumn()
{
return $this->orderColumn;
}
/**
* Set order column.
*
* @param string $column
*/
public function setOrderColumn($column)
{
$this->orderColumn = $column;
}
/**
* Set query callback to model.
*
* @param \Closure|null $query
*
* @return $this
*/
public function withQuery(\Closure $query = null)
{
$this->queryCallback = $query;
return $this;
}
/**
* Format data to tree like array.
*
* @return array
*/
public function toTree()
{
return $this->buildNestedArray();
}
/**
* Build Nested array.
*
* @param array $nodes
* @param int $parentId
*
* @return array
*/
protected function buildNestedArray(array $nodes = [], $parentId = 0)
{
$branch = [];
if (empty($nodes)) {
$nodes = $this->allNodes();
}
foreach ($nodes as $node) {
if ($node[$this->getParentColumn()] == $parentId) {
$children = $this->buildNestedArray($nodes, $node[$this->getKeyName()]);
if ($children) {
$node['children'] = $children;
}
$branch[] = $node;
}
}
return $branch;
}
/**
* Get all elements.
*
* @return mixed
*/
public function allNodes()
{
$orderColumn = \DB::getQueryGrammar()->wrap($this->orderColumn);
$byOrder = $orderColumn . ' = 0,' . $orderColumn;
$self = new static();
if ($this->queryCallback instanceof \Closure) {
$self = call_user_func($this->queryCallback, $self);
}
return $self->orderByRaw($byOrder)->get()->toArray();
}
/**
* Set the order of branches in the tree.
*
* @param array $order
*
* @return void
*/
protected static function setBranchOrder(array $order)
{
static::$branchOrder = array_flip(array_flatten($order));
static::$branchOrder = array_map(function ($item) {
return ++$item;
}, static::$branchOrder);
}
/**
* Save tree order from a tree like array.
*
* @param array $tree
* @param int $parentId
*/
public static function saveOrder($tree = [], $parentId = 0)
{
if (empty(static::$branchOrder)) {
static::setBranchOrder($tree);
}
static::fixTree();
foreach ($tree as $branch) {
$node = static::find($branch['id']);
$node->{$node->getParentColumn()} = $parentId;
$node->{$node->getOrderColumn()} = static::$branchOrder[$branch['id']];
$node->save();
if (isset($branch['children'])) {
static::saveOrder($branch['children'], $branch['id']);
}
}
}
/**
* Get options for Select field in form.
*
* @param \Closure|null $closure
* @param string $rootText
*
* @return array
*/
public static function selectOptions(\Closure $closure = null, $rootText = 'Root')
{
$options = (new static())->withQuery($closure)->buildSelectOptions();
return collect($options)->prepend($rootText, 0)->all();
}
/**
* Build options of select field in form.
*
* @param array $nodes
* @param int $parentId
* @param string $prefix
*
* @return array
*/
protected function buildSelectOptions(array $nodes = [], $parentId = 0, $prefix = '')
{
$prefix = $prefix ?: str_repeat(' ', 6);
$options = [];
if (empty($nodes)) {
$nodes = $this->allNodes();
}
foreach ($nodes as $node) {
$node[$this->titleColumn] = $prefix . ' ' . $node[$this->titleColumn];
if ($node[$this->getParentColumn()] == $parentId) {
$children = $this->buildSelectOptions($nodes, $node[$this->getKeyName()], $prefix . $prefix);
$options[$node[$this->getKeyName()]] = $node[$this->titleColumn];
if ($children) {
$options += $children;
}
}
}
return $options;
}
/**
* {@inheritdoc}
*/
protected static function boot()
{
parent::boot();
static::saving(function (Model $branch) {
$parentColumn = $branch->getParentColumn();
if (Request::has($parentColumn) && Request::input($parentColumn) == $branch->getKey()) {
throw new \Exception(trans('admin.parent_select_error'));
}
if (Request::has('_order')) {
$order = Request::input('_order');
Request::offsetUnset('_order');
static::tree()->saveOrder($order);
return false;
}
return $branch;
});
}
}
使用
- 可直接在相關頁面拖動資料生成新的資料模型樹
- 可使用laravel-admin的後臺直接新增相關資料
- 可直接使用查詢到相關資料模型樹的資訊
Your\Namespace\Models\Area::orderBy('order','desc')->get()->toTree();