doge 15 часов назад
Родитель
Сommit
7f8fcab3f8

+ 8 - 0
app/Http/Controllers/admin/Egame.php

@@ -207,6 +207,7 @@ class Egame extends Controller
                 'logo' => ['nullable', 'string', 'max:500'],
                 'logo_pc' => ['nullable', 'string', 'max:500'],
                 'logo_h5' => ['nullable', 'string', 'max:500'],
+                'icon' => ['nullable', 'string', 'max:500'],
                 'logo_langs' => ['nullable', 'array'],
                 'status' => ['nullable', 'integer', Rule::in([
                     EgameItem::STATUS_DISABLED,
@@ -427,6 +428,7 @@ class Egame extends Controller
             'lobby_enabled' => 0,
             'logo' => '',
             'logo_h5' => '',
+            'icon' => '',
             'logo_langs' => null,
             'status' => EgameItem::STATUS_ENABLED,
             'sort' => 0,
@@ -440,6 +442,11 @@ class Egame extends Controller
         if (array_key_exists('logo_h5', $input)) {
             $data['logo_h5'] = $this->logoPathForStorage((string) $input['logo_h5']);
         }
+        if (array_key_exists('icon', $input)) {
+            $data['icon'] = $item->item_type === EgameItem::TYPE_GAME
+                ? ''
+                : $this->logoPathForStorage((string) $input['icon']);
+        }
 
         // logo_pc 是新版后台使用的 PC Logo 字段,数据库仍统一保存到 logo。
         // 两个字段同时出现时以 logo_pc 为准,避免列表整行回传的旧 logo
@@ -574,6 +581,7 @@ class Egame extends Controller
         $item->setAttribute('logo', $logo);
         $item->setAttribute('logo_pc', $logo);
         $item->setAttribute('logo_h5', $this->absoluteLogoUrl((string) ($item->logo_h5 ?? '')));
+        $item->setAttribute('icon', $this->absoluteLogoUrl((string) ($item->icon ?? '')));
         if (is_array($item->logo_langs)) {
             $item->setAttribute('logo_langs', array_map(
                 fn ($path): string => $this->absoluteLogoUrl((string) $path),

+ 5 - 0
app/Http/Controllers/admin/MenuBadge.php

@@ -24,6 +24,11 @@ class MenuBadge extends Controller
         return $this->run(fn () => $service->delete(request()->all()));
     }
 
+    public function status(MenuBadgeAdminService $service)
+    {
+        return $this->run(fn () => $service->status(request()->all()));
+    }
+
     private function run(callable $callback)
     {
         try {

+ 1 - 0
app/Models/EgameItem.php

@@ -55,6 +55,7 @@ class EgameItem extends BaseModel
         'lobby_enabled',
         'logo',
         'logo_h5',
+        'icon',
         'logo_langs',
         'status',
         'sort',

+ 35 - 1
app/Services/MenuBadgeAdminService.php

@@ -47,6 +47,7 @@ final class MenuBadgeAdminService
                 'zh' => ['text' => ''], 'en' => ['text' => ''], 'vi' => ['text' => ''],
             ],
             'sort' => (int) $row->sort,
+            'status' => (int) ($row->status ?? 1),
             'created_at' => (string) $row->created_at,
             'updated_at' => (string) $row->updated_at,
         ];
@@ -68,6 +69,7 @@ final class MenuBadgeAdminService
             'menu_text' => ['required', 'string', 'max:200'],
             'badge_translations' => ['required', 'array:zh,en,vi'],
             'sort' => ['nullable', 'integer', 'min:0', 'max:9999'],
+            'status' => ['nullable', 'integer', 'in:0,1'],
         ])->validate();
         $data['menu_text'] = trim($data['menu_text']);
         if ($data['menu_text'] === '') {
@@ -75,8 +77,9 @@ final class MenuBadgeAdminService
         }
         $data['badge_translations'] = $this->badgeTexts($data['badge_translations']);
         $sort = array_key_exists('sort', $data) && $data['sort'] !== null ? (int) $data['sort'] : null;
+        $status = array_key_exists('status', $data) && $data['status'] !== null ? (int) $data['status'] : null;
 
-        return DB::transaction(function () use ($data, $sort) {
+        return DB::transaction(function () use ($data, $sort, $status) {
             $now = now()->format('Y-m-d H:i:s');
             $payload = [
                 'menu_text' => $data['menu_text'],
@@ -91,11 +94,15 @@ final class MenuBadgeAdminService
                 if ($sort !== null) {
                     $payload['sort'] = $sort;
                 }
+                if ($status !== null) {
+                    $payload['status'] = $status;
+                }
                 DB::table('menu_badge_configs')->where('id', $data['id'])->update($payload);
                 OperationAuditService::record('menu_badge_config', (int) $data['id'], 'update', $this->row($before), $data);
                 $id = (int) $data['id'];
             } else {
                 $payload['sort'] = $sort ?? (int) DB::table('menu_badge_configs')->max('sort') + 1;
+                $payload['status'] = $status ?? 1;
                 $payload['created_at'] = $now;
                 $id = (int) DB::table('menu_badge_configs')->insertGetId($payload);
                 OperationAuditService::record('menu_badge_config', $id, 'create', [], $data + ['id' => $id]);
@@ -122,4 +129,31 @@ final class MenuBadgeAdminService
             return ['id' => (int) $data['id']];
         });
     }
+
+    public function status(array $input): array
+    {
+        $this->ready();
+        $data = Validator::make($input, [
+            'id' => ['required', 'integer', 'min:1'],
+            'status' => ['required', 'integer', 'in:0,1'],
+        ])->validate();
+
+        return DB::transaction(function () use ($data) {
+            $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
+            if (!$before) {
+                throw ValidationException::withMessages(['id' => '配置不存在']);
+            }
+            if ((int) $data['status'] === 1) {
+                $this->badgeTexts(json_decode((string) $before->badge_translations, true) ?: []);
+            }
+            DB::table('menu_badge_configs')->where('id', $data['id'])->update([
+                'status' => (int) $data['status'],
+                'updated_at' => now()->format('Y-m-d H:i:s'),
+            ]);
+            OperationAuditService::record('menu_badge_config', (int) $data['id'], 'status', $this->row($before), $data);
+            $saved = DB::table('menu_badge_configs')->where('id', $data['id'])->first();
+
+            return $this->row($saved);
+        });
+    }
 }

+ 33 - 0
database/migrations/2026_11_05_120000_add_egame_platform_icon.php

@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        if (!Schema::hasTable('egame_items')) {
+            return;
+        }
+
+        Schema::table('egame_items', function (Blueprint $table) {
+            if (!Schema::hasColumn('egame_items', 'icon')) {
+                $table->string('icon', 500)->default('')->after('logo_h5')->comment('平台小图标');
+            }
+        });
+    }
+
+    public function down(): void
+    {
+        if (!Schema::hasTable('egame_items')) {
+            return;
+        }
+
+        Schema::table('egame_items', function (Blueprint $table) {
+            if (Schema::hasColumn('egame_items', 'icon')) {
+                $table->dropColumn('icon');
+            }
+        });
+    }
+};

+ 2 - 0
database/migrations/2026_11_05_120000_add_egame_platform_icon.sql

@@ -0,0 +1,2 @@
+ALTER TABLE `egame_items`
+  ADD COLUMN `icon` varchar(500) NOT NULL DEFAULT '' COMMENT '平台小图标' AFTER `logo_h5`;

+ 1 - 0
database/migrations/2026_11_05_120000_create_menu_badge_configs.php

@@ -15,6 +15,7 @@ return new class extends Migration {
             $table->string('menu_text', 200)->comment('菜单文本');
             $table->json('badge_translations')->comment('角标 zh/en/vi');
             $table->unsignedInteger('sort')->default(0)->comment('展示顺序,越小越靠前');
+            $table->unsignedTinyInteger('status')->default(1)->comment('0 禁用 1 启用');
             $table->timestamps();
         });
     }

+ 27 - 0
database/migrations/2026_11_05_140000_add_status_to_menu_badge_configs.php

@@ -0,0 +1,27 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        if (!Schema::hasTable('menu_badge_configs') || Schema::hasColumn('menu_badge_configs', 'status')) {
+            return;
+        }
+        Schema::table('menu_badge_configs', function (Blueprint $table) {
+            $table->unsignedTinyInteger('status')->default(1)->comment('0 禁用 1 启用')->after('sort');
+        });
+    }
+
+    public function down(): void
+    {
+        if (!Schema::hasTable('menu_badge_configs') || !Schema::hasColumn('menu_badge_configs', 'status')) {
+            return;
+        }
+        Schema::table('menu_badge_configs', function (Blueprint $table) {
+            $table->dropColumn('status');
+        });
+    }
+};

+ 5 - 1
docs/admin-egame-api.md

@@ -10,7 +10,7 @@ Authorization: Bearer <token>
 
 POST 使用 `Content-Type: application/json`。统一响应:`code = 0` 成功,业务数据在 `data`;失败 `code != 0`,原因在 `msg`。
 
-图片先调已有上传接口 `POST /admin/upload/uploadFile`,把返回的 `path` 写进 `logo` / `logo_pc` / `logo_h5`。
+图片先调已有上传接口 `POST /admin/upload/uploadFile`,把返回的 `path` 写进 `logo` / `logo_pc` / `logo_h5` / `icon`。
 游戏目录静态资源统一由 `bot-28/public/static/img` 托管,接口返回后台 `APP_URL` 下的完整图片 URL。
 
 ---
@@ -49,6 +49,7 @@ POST 使用 `Content-Type: application/json`。统一响应:`code = 0` 成功
 | 游戏说明 | `description` | 本次新增 |
 | PC Logo / PC 游戏图片 | `logo` 或 `logo_pc` | 两个都会返回完整 URL;保存时推荐传 `logo_pc`,两个同时传以 `logo_pc` 为准 |
 | H5/APP Logo / 图片 | `logo_h5` | 返回完整 URL;未配置时为空字符串 |
+| 平台小图标 | `icon` | 正方形小图,返回完整 URL;未配置时为空字符串。大类 / 平台行可维护;游戏行固定返回空 |
 | 多语言游戏图片 | `logo_langs` | 游戏行返回语言到完整 URL 的映射;未配置时为 `null` |
 | 平台 ID | `id` | 行主键。不要用稿子里的 `FL01` |
 | 平台类型 | `plat_type` | `jdb` / `mg` / `pg` / `ag`,小写 |
@@ -182,6 +183,7 @@ php artisan egame:set-network none --plat-type=pt --force
         "logo": "https://admin-api.example.com/static/img/game-type/sx.png",
         "logo_pc": "https://admin-api.example.com/static/img/game-type/sx.png",
         "logo_h5": "",
+        "icon": "",
         "status": 1,
         "sort": 70,
         "platform_count": 5,
@@ -334,6 +336,7 @@ php artisan egame:set-network none --plat-type=pt --force
 | `description` | 游戏说明,大类用 |
 | `logo` / `logo_pc` | PC 图,二选一;两个同时传时用 `logo_pc`,避免旧 `logo` 覆盖新上传图片 |
 | `logo_h5` | H5/APP 图 |
+| `icon` | 平台小图标;大类 / 平台可维护,游戏行忽略 |
 | `ingress` | 终端,平台和游戏可改;大类固定为 `3` |
 | `network` | 网络,平台和游戏可改;大类忽略。传 `""` 表示不限制 |
 | `apply_to_games` | 改平台+大类行的 `network` 时,是否同步该平台该大类下全部游戏。默认 `true` |
@@ -363,6 +366,7 @@ php artisan egame:set-network none --plat-type=pt --force
   "name": "JDB电子",
   "logo_pc": "https://cdn.example.com/jdb-pc.png",
   "logo_h5": "https://cdn.example.com/jdb-h5.png",
+  "icon": "https://cdn.example.com/jdb-icon.svg",
   "network": "mainland",
   "apply_to_games": true
 }

+ 15 - 3
docs/前端交付/bot-28/后台/菜单角标配置-后台接口.md

@@ -10,8 +10,9 @@
 | --- | --- |
 | 菜单文本 | 单行文字,必填,最多 200 字,用于与前台菜单项匹配 |
 | 角标 badge | 中文、英文、越南文三个 Tab,各一个文字框;**三种语言都必填** 才能保存,每种最多 100 字 |
+| 状态 | `1` 启用、`0` 禁用;列表可单独切换,禁用后前台不返回该条 |
 
-可选填 **排序** `sort`(非负整数,越小越靠前);新增不传时自动排在末尾。
+可选填 **排序** `sort`(非负整数,越小越靠前);新增不传时自动排在末尾,**默认启用**。
 
 ## 接口
 
@@ -19,6 +20,7 @@
 | --- | --- | --- |
 | GET | `/admin/menuBadge/list` | 列表 |
 | POST | `/admin/menuBadge/save` | 新增或编辑 |
+| POST | `/admin/menuBadge/status` | 启用/禁用 |
 | POST | `/admin/menuBadge/delete` | 删除 |
 
 建议按钮权限 URI:`admin/menuBadge/list`、`admin/menuBadge/save`、`admin/menuBadge/delete`(菜单与路由由前端自行配置)。
@@ -35,6 +37,7 @@
     "vi": {"text": "Nóng"}
   },
   "sort": 0,
+  "status": 1,
   "created_at": "2026-11-05 12:00:00",
   "updated_at": "2026-11-05 12:00:00"
 }
@@ -53,11 +56,20 @@
     "zh": {"text": "热"},
     "en": {"text": "Hot"},
     "vi": {"text": "Nóng"}
-  }
+  },
+  "status": 1
 }
 ```
 
-编辑传 `id`,字段同上;可附带 `sort`。
+编辑传 `id`,字段同上;可附带 `sort`、`status`。
+
+### 启用/禁用
+
+```json
+{"id": 1, "status": 0}
+```
+
+`status` 为 `1` 启用、`0` 禁用。启用前须已保存完整三语角标。
 
 ### 删除
 

+ 1 - 1
docs/前端交付/melbet_sport-api/前台/菜单角标-前台接入.md

@@ -30,7 +30,7 @@ GET /api/menuBadge/list
 
 | 字段 | 说明 |
 | --- | --- |
-| `data.list` | 配置项数组,顺序与后台一致 |
+| `data.list` | 配置项数组,顺序与后台一致;**仅包含已启用** 的配置 |
 | `menu_text` | 菜单文本,与本地菜单文案 **完全一致** 时挂载角标 |
 | `badge_text` | 当前 `lang` 下的角标文字;空字符串表示不展示角标 |
 

+ 2 - 0
docs/前端接口/第三方平台小图标-前端接入.md

@@ -8,4 +8,6 @@
 
 平台小图使用 `icon`,原有大图继续使用 `logo`。两个字段都是完整图片地址;`icon` 为空时隐藏小图或使用占位图。
 
+后台可在 `/admin/egame/items` 维护平台 `icon`(与 `logo_pc` / `logo_h5` 一样先上传再保存路径)。已配置时会员端优先读目录库;未配置时仍回退 `public/static/img/platform/icon/` 内置资源。
+
 电子、捕鱼接口 `/api/egame/electronicGames` 的平台对象也新增同名 `icon` 字段。分类自身的 `data.list[].icon` 保持原用途。

+ 1 - 0
routes/admin.php

@@ -424,6 +424,7 @@ Route::middleware(['admin.jwt'])->group(function () {
         Route::prefix('/menuBadge')->group(function () {
             Route::get('/list', [\App\Http\Controllers\admin\MenuBadge::class, 'index']);
             Route::post('/save', [\App\Http\Controllers\admin\MenuBadge::class, 'save']);
+            Route::post('/status', [\App\Http\Controllers\admin\MenuBadge::class, 'status'])->defaults('permission_uri', 'admin/menuBadge/save');
             Route::post('/delete', [\App\Http\Controllers\admin\MenuBadge::class, 'delete']);
         });