本文将会讲解如何创建菜单及菜单项的 CRUD 操作。一个菜单可以有多个菜单项,Laravel 已经为此提供了一种嵌套资源的解决方案。我们将使用 Laravel 的 嵌套资源 来创建菜单及菜单项的 CRUD 操作。
创建路由
首先,我们需要在 routes/admin.php
文件中添加嵌套资源路由。
Route::resource('menu', 'MenuController')->except([
'show'
]);
Route::resource('menu.item', 'MenuItemController');
“点”符号被用来声明嵌套资源。对于菜单的 CRUD 操作,我们不需要显示页面,因此使用 except 函数来加载 Partial Resource Routes。
这个 menu.item
路由定义将会创建以下路由:
菜单模型
我们将会在控制器中使用 Laravel Menu 包中的模型。在上一篇文章中,我们已经创建了一个通用的菜单包。
创建菜单控制器
在 app\Http\Controllers\Admin\
中创建菜单控制器。
app\Http\Controllers\Admin\MenuController.php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use BalajiDharma\LaravelMenu\Models\Menu;
use BalajiDharma\LaravelAdminCore\Requests\StoreMenuRequest;
use BalajiDharma\LaravelAdminCore\Requests\UpdateMenuRequest;
class MenuController extends Controller
{
public function __construct()
{
$this->middleware('can:menu list', ['only' => ['index']]);
$this->middleware('can:menu create', ['only' => ['create', 'store']]);
$this->middleware('can:menu edit', ['only' => ['edit', 'update']]);
$this->middleware('can:menu delete', ['only' => ['destroy']]);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\View\View
*/
public function index()
{
$menus = (new Menu)->newQuery();
if (request()->has('search')) {
$menus->where('name', 'Like', '%'.request()->input('search').'%');
}
if (request()->query('sort')) {
$attribute = request()->query('sort');
$sort_order = 'ASC';
if (strncmp($attribute, '-', 1) === 0) {
$sort_order = 'DESC';
$attribute = substr($attribute, 1);
}
$menus->orderBy($attribute, $sort_order);
} else {
$menus->latest();
}
$menus = $menus->paginate(5)->onEachSide(2);
return view('admin.menu.index', compact('menus'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('admin.menu.create');
}
/**
* Store a newly created resource in storage.
*
* @param StoreMenuRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreMenuRequest $request)
{
Menu::create([
'name' => $request->name,
'machine_name' => $request->machine_name,
'description' => $request->description,
]);
return redirect()->route('menu.index')
->with('message', 'Menu created successfully.');
}
/**
* Show the form for editing the specified resource.
*
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @return \Illuminate\View\View
*/
public function edit(Menu $menu)
{
return view('admin.menu.edit', compact('menu'));
}
/**
* Update the specified resource in storage.
*
* @param UpdateMenuRequest $request
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateMenuRequest $request, Menu $menu)
{
$menu->update($request->all());
return redirect()->route('menu.index')
->with('message', 'Menu updated successfully.');
}
/**
* Remove the specified resource from storage.
*
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Menu $menu)
{
$menu->delete();
return redirect()->route('menu.index')
->with('message', __('Menu deleted successfully'));
}
}
StoreMenuRequest
和 UpdateMenuRequest
被添加到 admin core 包中。
创建菜单视图
在 resources/views/admin/menu
文件夹中创建 index
、create
和 edit
视图。
从 GitHub 上复制这些视图 https://github.com/balajidharma/basic-laravel-admin-panel/tree/1.x/resources/views/admin/menu。
machine_name
被用来标识菜单,machine_name
应该是唯一的。
在菜单包中添加 Traits
对于菜单项,我们需要将它们作为树形结构显示,并且需要将菜单项构建为下拉菜单。
为了实现这些功能,我们将会在 menu 包 中创建新的 Traits。这个 MenuTree
Trait 是参考了 https://github.com/z-song/laravel-admin/blob/master/src/Traits/ModelTree.php 创建的。
src\Traits\MenuTree.php
<?php
namespace BalajiDharma\LaravelMenu\Traits;
use BalajiDharma\LaravelMenu\Exceptions\InvalidParent;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Request;
trait MenuTree
{
/**
* @var \Closure
*/
protected $queryCallback;
/**
* Get children of current node.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function children()
{
return $this->hasMany(static::class, $this->getParentColumn());
}
/**
* Get parent of current node.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function parent()
{
return $this->belongsTo(static::class, $this->getParentColumn());
}
/**
* GET all parents.
*
* @return \Illuminate\Support\Collection
*/
public function parents()
{
$parents = collect([]);
$parent = $this->parent;
while (!is_null($parent)) {
$parents->push($parent);
$parent = $parent->parent;
}
return $parents;
}
/**
* @return string
*/
public function getParentColumn()
{
if (property_exists($this, 'parentColumn')) {
return $this->parentColumn;
}
return 'parent_id';
}
/**
* @return mixed
*/
public function getParentKey()
{
return $this->{$this->getParentColumn()};
}
/**
* Set parent column.
*
* @param string $column
*/
public function setParentColumn($column)
{
$this->parentColumn = $column;
}
/**
* Get title column.
*
* @return string
*/
public function getTitleColumn()
{
if (property_exists($this, 'titleColumn')) {
return $this->titleColumn;
}
return 'name';
}
/**
* Set title column.
*
* @param string $column
*/
public function setTitleColumn($column)
{
$this->titleColumn = $column;
}
/**
* Get order column name.
*
* @return string
*/
public function getOrderColumn()
{
if (property_exists($this, 'orderColumn')) {
return $this->orderColumn;
}
return 'weight';
}
/**
* Set order column.
*
* @param string $column
*/
public function setOrderColumn($column)
{
$this->orderColumn = $column;
}
/**
* @return string
*/
public function getMenuRelationColumn()
{
if (property_exists($this, 'menuRelationColumn')) {
return $this->menuRelationColumn;
}
return 'menu_id';
}
/**
* Set menu relation column.
*
* @param string $column
*/
public function setMenuRelationColumn($column)
{
$this->menuRelationColumn = $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($menuId)
{
return $this->buildNestedArray($menuId);
}
/**
* Build Nested array.
*
* @param array $nodes
* @param int $parentId
*
* @return array
*/
protected function buildNestedArray($menuId, array $nodes = [], $parentId = 0)
{
$branch = [];
if (empty($nodes)) {
$nodes = $this->allNodes($menuId);
}
foreach ($nodes as $node) {
if ($node[$this->getParentColumn()] == $parentId) {
$children = $this->buildNestedArray($menuId, $nodes, $node[$this->getKeyName()]);
if ($children) {
$node['children'] = $children;
}
$branch[] = $node;
}
}
return $branch;
}
/**
* Get all elements.
*
* @return mixed
*/
public function allNodes($menuId, $ignoreItemId = null)
{
$self = new static();
if ($this->queryCallback instanceof \Closure) {
$self = call_user_func($this->queryCallback, $self);
}
if($ignoreItemId) {
return $self->where($this->getMenuRelationColumn(), $menuId)
->where(function ($query) use ($ignoreItemId) {
$query->where($this->getParentColumn(), '!=', $ignoreItemId)->orWhereNull($this->getParentColumn());
})
->orderBy($this->getOrderColumn())->get()->toArray();
}
return $self->where($this->getMenuRelationColumn(), $menuId)->orderBy($this->getOrderColumn())->get()->toArray();
}
/**
* Get options for Select field in form.
*
* @param \Closure|null $closure
* @param string $rootText
*
* @return array
*/
public static function selectOptions($menuId, $ignoreItemId = null, \Closure $closure = null)
{
$options = (new static())->withQuery($closure)->buildSelectOptions($menuId, $ignoreItemId);
return collect($options)->all();
}
/**
* Build options of select field in form.
*
* @param array $nodes
* @param int $parentId
* @param string $prefix
* @param string $space
*
* @return array
*/
protected function buildSelectOptions($menuId, $ignoreItemId, array $nodes = [], $parentId = 0, $prefix = '', $space = ' ')
{
$prefix = $prefix ?: '┝'.$space;
$options = [];
if (empty($nodes)) {
$nodes = $this->allNodes($menuId, $ignoreItemId);
}
foreach ($nodes as $index => $node) {
if ($node[$this->getParentColumn()] == $parentId) {
$node[$this->getTitleColumn()] = $prefix.$space.$node[$this->getTitleColumn()];
$childrenPrefix = str_replace('┝', str_repeat($space, 6), $prefix).'┝'.str_replace(['┝', $space], '', $prefix);
$children = $this->buildSelectOptions($menuId, null, $nodes, $node[$this->getKeyName()], $childrenPrefix);
$options[$node[$this->getKeyName()]] = $node[$this->getTitleColumn()];
if ($children) {
$options += $children;
}
}
}
return $options;
}
/**
* Build the link based on uri
*
*/
protected function getLinkAttribute()
{
$uri = trim($this->uri);
if (strpos($uri, '<nolink>') !== false) {
$uri = '';
}
if (strpos($uri, '<admin>') !== false) {
$uri = str_replace('<admin>', config('admin.prefix', 'admin'), $uri);
}
return $uri;
}
/**
* {@inheritdoc}
*/
public function delete()
{
$parentColumn = $this->getParentColumn();
$newParent = $this->$parentColumn ?? null;
$this->where($this->getParentColumn(), $this->getKey())->update([$this->getParentColumn() => $newParent]);
return parent::delete();
}
public function initializeMenuTree()
{
$this->appends = array_unique(array_merge($this->appends, ['link']));
}
/**
* {@inheritdoc}
*/
protected static function boot()
{
parent::boot();
static::saving(function (Model $branch) {
$parentColumn = $branch->getParentColumn();
if (Request::filled($parentColumn) && Request::input($parentColumn) == $branch->getKey()) {
throw InvalidParent::create();
}
return $branch;
});
}
}
现在,在我们的 MenuItem
模型中包含这个 trait。
src/Models/MenuItem.php
<?php
namespace BalajiDharma\LaravelMenu\Models;
use BalajiDharma\LaravelMenu\Traits\MenuTree;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class MenuItem extends Model
{
use MenuTree {
MenuTree::boot as treeBoot;
}
创建菜单项控制器
创建 MenuItemController
来管理菜单项。
app\Http\Controllers\Admin\MenuItemController.php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use BalajiDharma\LaravelAdminCore\Requests\StoreMenuItemRequest;
use BalajiDharma\LaravelAdminCore\Requests\UpdateMenuItemRequest;
use BalajiDharma\LaravelMenu\Models\Menu;
use BalajiDharma\LaravelMenu\Models\MenuItem;
class MenuItemController extends Controller
{
public function __construct()
{
$this->middleware('can:menu list', ['only' => ['index', 'show']]);
$this->middleware('can:menu create', ['only' => ['create', 'store']]);
$this->middleware('can:menu edit', ['only' => ['edit', 'update']]);
$this->middleware('can:menu delete', ['only' => ['destroy']]);
}
/**
* Display a listing of the resource.
*
* @return \Illuminate\View\View
*/
public function index(Menu $menu)
{
$items = (new MenuItem)->toTree($menu->id);
return view('admin.menu.item.index', compact('items', 'menu'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\View\View
*/
public function create(Menu $menu)
{
$item_options = MenuItem::selectOptions($menu->id);
return view('admin.menu.item.create', compact('menu', 'item_options'));
}
/**
* Store a newly created resource in storage.
*
* @param StoreMenuItemRequest $request
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @return \Illuminate\Http\RedirectResponse
*/
public function store(StoreMenuItemRequest $request, Menu $menu)
{
$menu->menuItems()->create($request->all());
return redirect()->route('menu.item.index', $menu->id)
->with('message', 'Menu created successfully.');
}
/**
* Show the form for editing the specified resource.
*
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @return \Illuminate\View\View
*/
public function edit(Menu $menu, MenuItem $item)
{
$item_options = MenuItem::selectOptions($menu->id, $item->parent_id ?? $item->id);
return view('admin.menu.item.edit', compact('menu', 'item', 'item_options'));
}
/**
* Update the specified resource in storage.
*
* @param UpdateMenuItemRequest $request
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @param \BalajiDharma\LaravelMenu\Models\MenuItem $item
* @return \Illuminate\Http\RedirectResponse
*/
public function update(UpdateMenuItemRequest $request, Menu $menu, MenuItem $item)
{
$item->update($request->all());
return redirect()->route('menu.item.index', $menu->id)
->with('message', 'Menu Item updated successfully.');
}
/**
* Remove the specified resource from storage.
*
* @param \BalajiDharma\LaravelMenu\Models\Menu $menu
* @param \BalajiDharma\LaravelMenu\Models\MenuItem $menuItem
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Menu $menu, MenuItem $item)
{
$item->delete();
return redirect()->route('menu.item.index', $menu->id)
->with('message', __('Menu deleted successfully'));
}
}
所有的菜单项视图都被添加到 resources/views/admin/menu/item
文件夹中。
从 GitHub 上复制并使用这些视图文件 https://github.com/balajidharma/basic-laravel-admin-panel/tree/1.x/resources/views/admin/menu/item。
我们已经创建了菜单及菜单项的 CRUD 操作。
Laravel 后台管理面板可以在 https://github.com/balajidharma/basic-laravel-admin-panel 中找到。安装后,请分享你的反馈。
译自:https://blog.devgenius.io/laravel-creates-crud-with-nested-resources-ac97667609a4
评论(0)