doge 7 часов назад
Родитель
Сommit
c359b68632
2 измененных файлов с 136 добавлено и 2 удалено
  1. 8 2
      app/Services/MenuService.php
  2. 128 0
      tests/Integration/MenuOrderTest.php

+ 8 - 2
app/Services/MenuService.php

@@ -172,7 +172,13 @@ class MenuService extends BaseService
                     $allMenus = array_merge($allMenus, $role['menus']->toArray());
                 }
             }
-            $list = $allMenus;
+            // 相同 sort 沿用 admin 的查询顺序,不改变 admin 的排序规则。
+            $adminOrder = self::findAll(['type' => $type, 'status' => self::$MODEL::STATUS_SHOW])
+                ->pluck('id')->flip();
+            $list = collect($allMenus)->unique('id')->sort(function ($left, $right) use ($adminOrder) {
+                return ($left['sort'] <=> $right['sort'])
+                    ?: ($adminOrder->get($left['id'], PHP_INT_MAX) <=> $adminOrder->get($right['id'], PHP_INT_MAX));
+            })->values()->all();
         }
         if($type == 1){
             $tree = self::toTree($list,0,0,'parent_id');
@@ -210,4 +216,4 @@ class MenuService extends BaseService
         }
         
     }
-}
+}

+ 128 - 0
tests/Integration/MenuOrderTest.php

@@ -0,0 +1,128 @@
+<?php
+
+namespace Tests\Integration;
+
+use App\Services\MenuService;
+use Illuminate\Config\Repository;
+use Illuminate\Database\Capsule\Manager;
+use Illuminate\Database\Eloquent\Model;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Foundation\Application;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Facade;
+use PHPUnit\Framework\TestCase;
+
+class MenuOrderTest extends TestCase
+{
+    protected function setUp(): void
+    {
+        Facade::clearResolvedInstances();
+        Model::clearBootedModels();
+        $app = new Application(dirname(__DIR__, 2));
+        $app->instance('config', new Repository(['app' => ['url' => 'https://admin.example.test']]));
+        Facade::setFacadeApplication($app);
+        $db = new Manager($app);
+        $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
+        $db->setAsGlobal();
+        $db->bootEloquent();
+        $app->instance('db', $db->getDatabaseManager());
+        $db->schema()->create('menus', function (Blueprint $t) {
+            $t->id();
+            $t->integer('parent_id')->default(0);
+            $t->string('title');
+            $t->integer('sort')->default(0);
+            $t->integer('status')->default(1);
+            $t->integer('type')->default(1);
+            $t->string('uri')->default('');
+        });
+        $db->schema()->create('roles', function (Blueprint $t) {
+            $t->id();
+            $t->string('name');
+        });
+        foreach (['role_user' => 'user_id', 'role_menu' => 'menu_id'] as $table => $field) {
+            $db->schema()->create($table, function (Blueprint $t) use ($field) {
+                $t->id();
+                $t->integer('role_id');
+                $t->integer($field);
+            });
+        }
+    }
+
+    protected function tearDown(): void
+    {
+        DB::disconnect();
+        Facade::clearResolvedInstances();
+        Model::clearBootedModels();
+        parent::tearDown();
+    }
+
+    private function menu(int $id, int $sort, int $parent = 0, int $status = 1, int $type = 1): void
+    {
+        DB::table('menus')->insert([
+            'id' => $id, 'title' => 'Menu ' . $id, 'sort' => $sort,
+            'parent_id' => $parent, 'status' => $status, 'type' => $type,
+        ]);
+    }
+
+    private function role(int $id, array $menus, array $users): void
+    {
+        DB::table('roles')->insert(['id' => $id, 'name' => 'Role ' . $id]);
+        foreach ($menus as $menuId) {
+            DB::table('role_menu')->insert(['role_id' => $id, 'menu_id' => $menuId]);
+        }
+        foreach ($users as $userId) {
+            DB::table('role_user')->insert(['role_id' => $id, 'user_id' => $userId]);
+        }
+    }
+
+    private function treeIds($tree): array
+    {
+        $result = [];
+        foreach ($tree as $node) {
+            $result[] = ['id' => (int) $node['id'], 'children' => $this->treeIds($node['children'])];
+        }
+        return $result;
+    }
+
+    public function test_single_and_multiple_roles_follow_admin_order_for_roots_children_and_equal_sort(): void
+    {
+        foreach ([[10, 30], [20, 10], [30, 10]] as [$id, $sort]) {
+            $this->menu($id, $sort);
+        }
+        foreach ([[50, 30], [60, 5], [70, 5], [80, 20]] as [$id, $sort]) {
+            $this->menu($id, $sort, 20);
+        }
+        $this->role(1, [10, 30, 50, 70], [20]);
+        $this->role(2, [20, 30, 60, 70, 80], [20]);
+        $this->role(3, [70, 50, 30, 10, 80, 60, 20], [21]);
+
+        $expected = $this->treeIds(MenuService::getUserMenu(1));
+        $this->assertSame([20, 30, 10], array_column($expected, 'id'));
+        $this->assertSame([60, 70, 80, 50], array_column($expected[0]['children'], 'id'));
+        $this->assertSame($expected, $this->treeIds(MenuService::getUserMenu(20)));
+        $this->assertSame($expected, $this->treeIds(MenuService::getUserMenu(21)));
+    }
+
+    public function test_sorting_preserves_role_permissions_existing_hidden_menus_and_menu_types(): void
+    {
+        $this->menu(10, 30);
+        $this->menu(20, 10);
+        $this->menu(30, 20, status: 2);
+        $this->menu(40, 20, type: 2);
+        $this->menu(50, 10, type: 2);
+        $this->menu(60, 0, type: 2);
+        $this->role(1, [10, 30, 40, 50], [20]);
+        $this->role(2, [20], [21]);
+
+        $this->assertSame([30, 10], array_column($this->treeIds(MenuService::getUserMenu(20)), 'id'));
+        $this->assertSame([20], array_column($this->treeIds(MenuService::getUserMenu(21)), 'id'));
+        $buttons = MenuService::getUserMenu(20, 2);
+        $this->assertSame([50, 40], array_column($buttons, 'id'));
+        $this->assertSame([], MenuService::getUserMenu(99));
+        $this->assertSame([], MenuService::getUserMenu(99, 2));
+
+        // Admin still uses its existing sort-only query and visible-menu filter.
+        $this->assertSame([20, 10], array_column($this->treeIds(MenuService::getUserMenu(1)), 'id'));
+        $this->assertSame([60, 50, 40], MenuService::getUserMenu(1, 2)->pluck('id')->all());
+    }
+}