You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
139 lines
4.4 KiB
139 lines
4.4 KiB
<?php
|
|
|
|
namespace App\Http\Controllers\Admin;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Menu;
|
|
use App\Support\ApiResponse;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Collection;
|
|
|
|
class MenuController extends Controller
|
|
{
|
|
use ApiResponse;
|
|
|
|
public function index(): JsonResponse
|
|
{
|
|
$rows = Menu::query()->orderBy('sort')->orderBy('id')->get();
|
|
$tree = $this->buildMenuTree($rows);
|
|
|
|
return $this->ok($tree->values()->all());
|
|
}
|
|
|
|
public function store(Request $request): JsonResponse
|
|
{
|
|
$data = $this->validated($request);
|
|
|
|
$menu = Menu::query()->create($data);
|
|
|
|
return $this->ok(['id' => $menu->id], '已创建');
|
|
}
|
|
|
|
public function update(Request $request, int $menu): JsonResponse
|
|
{
|
|
$model = Menu::query()->findOrFail($menu);
|
|
$data = $this->validated($request, false);
|
|
$model->fill($data);
|
|
$model->save();
|
|
|
|
return $this->ok(null, '已保存');
|
|
}
|
|
|
|
public function destroy(int $menu): JsonResponse
|
|
{
|
|
$model = Menu::query()->findOrFail($menu);
|
|
|
|
if (Menu::query()->where('parent_id', $model->id)->exists()) {
|
|
return $this->fail('请先删除子菜单', 422);
|
|
}
|
|
|
|
$model->delete();
|
|
|
|
return $this->ok(null, '已删除');
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
protected function validated(Request $request, bool $creating = true): array
|
|
{
|
|
$rules = [
|
|
'parent_id' => ['nullable', 'integer', 'exists:menus,id'],
|
|
'path' => ['required', 'string', 'max:255'],
|
|
'name' => ['required', 'string', 'max:128'],
|
|
'title' => ['required', 'string', 'max:128'],
|
|
'component' => ['nullable', 'string', 'max:255'],
|
|
'icon' => ['nullable', 'string', 'max:64'],
|
|
'sort' => ['nullable', 'integer'],
|
|
'visible' => ['nullable', 'integer', 'in:0,1'],
|
|
'keep_alive' => ['nullable', 'integer', 'in:0,1'],
|
|
'permission_code' => ['nullable', 'string', 'max:128'],
|
|
'status' => ['nullable', 'integer', 'in:0,1'],
|
|
];
|
|
|
|
$data = $request->validate($rules);
|
|
|
|
if (array_key_exists('component', $data) && $data['component'] !== null && $data['component'] !== '') {
|
|
$this->assertSafeComponent($data['component']);
|
|
}
|
|
|
|
if (! $creating) {
|
|
foreach (['sort', 'visible', 'keep_alive', 'status'] as $k) {
|
|
if (array_key_exists($k, $data)) {
|
|
$data[$k] = (int) $data[$k];
|
|
}
|
|
}
|
|
} else {
|
|
$data['sort'] = (int) ($data['sort'] ?? 0);
|
|
$data['status'] = (int) ($data['status'] ?? 1);
|
|
$data['visible'] = (int) ($data['visible'] ?? 1);
|
|
$data['keep_alive'] = (int) ($data['keep_alive'] ?? 0);
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
protected function assertSafeComponent(string $component): void
|
|
{
|
|
if (str_contains($component, '..') || ! preg_match('/^[a-zA-Z0-9\/_-]+$/', $component)) {
|
|
abort(422, 'component 不符合白名单规则');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param Collection<int, Menu> $menus
|
|
*/
|
|
protected function buildMenuTree(Collection $menus): Collection
|
|
{
|
|
$byParent = $menus->groupBy(fn (Menu $m) => $m->parent_id ?? 0);
|
|
|
|
$build = function ($parentId) use (&$build, $byParent): Collection {
|
|
return ($byParent->get($parentId, collect()))->map(function (Menu $m) use (&$build) {
|
|
$node = [
|
|
'id' => $m->id,
|
|
'parent_id' => $m->parent_id,
|
|
'path' => $m->path,
|
|
'name' => $m->name,
|
|
'title' => $m->title,
|
|
'component' => $m->component,
|
|
'icon' => $m->icon,
|
|
'sort' => (int) $m->sort,
|
|
'visible' => (int) $m->visible,
|
|
'keep_alive' => (int) $m->keep_alive,
|
|
'permission_code' => $m->permission_code,
|
|
'status' => (int) $m->status,
|
|
];
|
|
$children = $build($m->id);
|
|
if ($children->isNotEmpty()) {
|
|
$node['children'] = $children->values()->all();
|
|
}
|
|
|
|
return $node;
|
|
});
|
|
};
|
|
|
|
return $build(0);
|
|
}
|
|
}
|