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

2 weeks ago
<?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);
}
}