From 9786dab979e4f4fc283c0ea64d8148d87cde0f58 Mon Sep 17 00:00:00 2001 From: zhenhui <1276357500@qq.com> Date: Thu, 2 Apr 2026 11:04:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=A1=86=E6=9E=B6=E6=9D=83?= =?UTF-8?q?=E9=99=90=E9=89=B4=E6=9D=83=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/admin/controller/game/Config.php | 278 +++++++++++++ app/common/model/GameConfig.php | 32 ++ app/common/validate/GameConfig.php | 31 ++ app/functions.php | 19 +- web/src/lang/backend/en/game/config.ts | 21 + web/src/lang/backend/zh-cn/game/config.ts | 21 + web/src/views/backend/game/config/index.vue | 164 ++++++++ .../views/backend/game/config/popupForm.vue | 375 ++++++++++++++++++ 8 files changed, 940 insertions(+), 1 deletion(-) create mode 100644 app/admin/controller/game/Config.php create mode 100644 app/common/model/GameConfig.php create mode 100644 app/common/validate/GameConfig.php create mode 100644 web/src/lang/backend/en/game/config.ts create mode 100644 web/src/lang/backend/zh-cn/game/config.ts create mode 100644 web/src/views/backend/game/config/index.vue create mode 100644 web/src/views/backend/game/config/popupForm.vue diff --git a/app/admin/controller/game/Config.php b/app/admin/controller/game/Config.php new file mode 100644 index 0000000..e971b9a --- /dev/null +++ b/app/admin/controller/game/Config.php @@ -0,0 +1,278 @@ +model = new \app\common\model\GameConfig(); + return null; + } + + /** + * @throws Throwable + */ + protected function _add(): Response + { + if ($this->request && $this->request->method() === 'POST') { + $data = $this->request->post(); + if (!$data) { + return $this->error(__('Parameter %s can not be empty', [''])); + } + + $data = $this->applyInputFilter($data); + $data = $this->excludeFields($data); + + $err = $this->validateGameWeightPayload($data, null); + if ($err !== null) { + return $this->error($err); + } + + if ($this->dataLimit && $this->dataLimitFieldAutoFill) { + $data[$this->dataLimitField] = $this->auth->id; + } + + $result = false; + $this->model->startTrans(); + try { + if ($this->modelValidate) { + $validate = str_replace("\\model\\", "\\validate\\", get_class($this->model)); + if (class_exists($validate)) { + $validate = new $validate(); + if ($this->modelSceneValidate) { + $validate->scene('add'); + } + $validate->check($data); + } + } + $result = $this->model->save($data); + $this->model->commit(); + } catch (Throwable $e) { + $this->model->rollback(); + return $this->error($e->getMessage()); + } + if ($result !== false) { + return $this->success(__('Added successfully')); + } + return $this->error(__('No rows were added')); + } + + return $this->error(__('Parameter error')); + } + + /** + * @throws Throwable + */ + protected function _edit(): Response + { + $pk = $this->model->getPk(); + $id = $this->request ? ($this->request->post($pk) ?? $this->request->get($pk)) : null; + $row = $this->model->find($id); + if (!$row) { + return $this->error(__('Record not found')); + } + + $dataLimitAdminIds = $this->getDataLimitAdminIds(); + if ($dataLimitAdminIds && !in_array($row[$this->dataLimitField], $dataLimitAdminIds)) { + return $this->error(__('You have no permission')); + } + + if ($this->request && $this->request->method() === 'POST') { + $data = $this->request->post(); + if (!$data) { + return $this->error(__('Parameter %s can not be empty', [''])); + } + + $data = $this->applyInputFilter($data); + $data = $this->excludeFields($data); + + if (!$this->auth->isSuperAdmin()) { + $data['channel_id'] = $row['channel_id']; + $data['group'] = $row['group']; + $data['name'] = $row['name']; + $data['title'] = $row['title']; + } + + $err = $this->validateGameWeightPayload($data, $row['value'] ?? null); + if ($err !== null) { + return $this->error($err); + } + + $result = false; + $this->model->startTrans(); + try { + if ($this->modelValidate) { + $validate = str_replace("\\model\\", "\\validate\\", get_class($this->model)); + if (class_exists($validate)) { + $validate = new $validate(); + if ($this->modelSceneValidate) { + $validate->scene('edit'); + } + $data[$pk] = $row[$pk]; + $validate->check($data); + } + } + $result = $row->save($data); + $this->model->commit(); + } catch (Throwable $e) { + $this->model->rollback(); + return $this->error($e->getMessage()); + } + if ($result !== false) { + return $this->success(__('Update successful')); + } + return $this->error(__('No rows updated')); + } + + return $this->success('', [ + 'row' => $row + ]); + } + + /** + * game_weight:校验数值、键不可改(编辑)、和为 100(特定 name) + * + * @param array $data + */ + private function validateGameWeightPayload(array $data, ?string $originalValue): ?string + { + $group = $data['group'] ?? ''; + if ($group !== 'game_weight') { + return null; + } + $name = $data['name'] ?? ''; + $value = $data['value'] ?? ''; + if (!is_string($value)) { + return __('Parameter error'); + } + + $decoded = json_decode($value, true); + if (!is_array($decoded)) { + return __('Parameter error'); + } + + $keys = []; + $numbers = []; + foreach ($decoded as $item) { + if (!is_array($item)) { + return __('Parameter error'); + } + foreach ($item as $k => $v) { + $keys[] = $k; + if (!is_numeric($v)) { + return __('Game config weight value must be numeric'); + } + $num = (float) $v; + if ($num > 100) { + return __('Game config weight each value must not exceed 100'); + } + $numbers[] = $num; + } + } + + if (count($numbers) === 0) { + return __('Parameter %s can not be empty', ['value']); + } + + if ($originalValue !== null && $originalValue !== '') { + $oldKeys = $this->extractGameWeightKeys($originalValue); + if ($oldKeys !== $keys) { + return __('Game config weight keys cannot be modified'); + } + } + + if (in_array($name, self::WEIGHT_SUM_100_NAMES, true)) { + $sum = array_sum($numbers); + if (abs($sum - 100.0) > 0.000001) { + return __('Game config weight sum must equal 100'); + } + } + + return null; + } + + /** + * @return list + */ + private function extractGameWeightKeys(string $value): array + { + $decoded = json_decode($value, true); + if (!is_array($decoded)) { + return []; + } + $keys = []; + foreach ($decoded as $item) { + if (!is_array($item)) { + continue; + } + foreach ($item as $k => $_) { + $keys[] = $k; + } + } + return $keys; + } + + /** + * 查看 + * @throws Throwable + */ + protected function _index(): Response + { + // 如果是 select 则转发到 select 方法,若未重写该方法,其实还是继续执行 index + if ($this->request && $this->request->get('select')) { + return $this->select($this->request); + } + + /** + * 1. withJoin 不可使用 alias 方法设置表别名,别名将自动使用关联模型名称(小写下划线命名规则) + * 2. 以下的别名设置了主表别名,同时便于拼接查询参数等 + * 3. paginate 数据集可使用链式操作 each(function($item, $key) {}) 遍历处理 + */ + list($where, $alias, $limit, $order) = $this->queryBuilder(); + $res = $this->model + ->withJoin($this->withJoinTable, $this->withJoinType) + ->with($this->withJoinTable) + ->visible(['channel' => ['name']]) + ->alias($alias) + ->where($where) + ->order($order) + ->paginate($limit); + + return $this->success('', [ + 'list' => $res->items(), + 'total' => $res->total(), + 'remark' => get_route_remark(), + ]); + } + + /** + * 若需重写查看、编辑、删除等方法,请复制 @see \app\admin\library\traits\Backend 中对应方法至此进行重写 + */ +} diff --git a/app/common/model/GameConfig.php b/app/common/model/GameConfig.php new file mode 100644 index 0000000..f944fee --- /dev/null +++ b/app/common/model/GameConfig.php @@ -0,0 +1,32 @@ + 'integer', + 'update_time' => 'integer', + ]; + + + public function channel(): \think\model\relation\BelongsTo + { + return $this->belongsTo(\app\common\model\GameChannel::class, 'channel_id', 'id'); + } +} \ No newline at end of file diff --git a/app/common/validate/GameConfig.php b/app/common/validate/GameConfig.php new file mode 100644 index 0000000..5e40c19 --- /dev/null +++ b/app/common/validate/GameConfig.php @@ -0,0 +1,31 @@ + [], + 'edit' => [], + ]; + +} diff --git a/app/functions.php b/app/functions.php index 89a08a7..18b52be 100644 --- a/app/functions.php +++ b/app/functions.php @@ -204,7 +204,24 @@ if (!function_exists('get_controller_path')) { if (count($parts) < 2) { return $parts[0] ?? null; } - return implode('/', array_slice($parts, 1, -1)) ?: $parts[1]; + $segments = array_slice($parts, 1, -1); + if ($segments === []) { + return $parts[1] ?? null; + } + // ThinkPHP 风格段 game.Config -> game/config,与 $request->controller 解析结果一致(否则权限节点对不上) + $normalized = []; + foreach ($segments as $seg) { + if (str_contains($seg, '.')) { + $dotPos = strpos($seg, '.'); + $mod = substr($seg, 0, $dotPos); + $ctrl = substr($seg, $dotPos + 1); + $normalized[] = strtolower($mod); + $normalized[] = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $ctrl)); + } else { + $normalized[] = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $seg)); + } + } + return implode('/', $normalized); } } diff --git a/web/src/lang/backend/en/game/config.ts b/web/src/lang/backend/en/game/config.ts new file mode 100644 index 0000000..4e5f9b5 --- /dev/null +++ b/web/src/lang/backend/en/game/config.ts @@ -0,0 +1,21 @@ +export default { + ID: 'ID', + channel_id: 'channel_id', + channel__name: 'name', + group: 'group', + name: 'name', + title: 'title', + value: 'value', + 'weight key': 'Key', + 'weight value': 'Value', + 'weight sum must 100': 'The sum of weights for default_tier_weight / default_kill_score_weight must equal 100', + 'weight each max 100': 'Each weight value must not exceed 100', + 'weight value numeric': 'Weight values must be valid numbers', + sort: 'sort', + instantiation: 'instantiation', + 'instantiation 0': 'instantiation 0', + 'instantiation 1': 'instantiation 1', + create_time: 'create_time', + update_time: 'update_time', + 'quick Search Fields': 'ID', +} diff --git a/web/src/lang/backend/zh-cn/game/config.ts b/web/src/lang/backend/zh-cn/game/config.ts new file mode 100644 index 0000000..4509e33 --- /dev/null +++ b/web/src/lang/backend/zh-cn/game/config.ts @@ -0,0 +1,21 @@ +export default { + ID: 'ID', + channel_id: '渠道id', + channel__name: '渠道名', + group: '分组', + name: '配置标识', + title: '配置名称', + value: '值', + 'weight key': '键', + 'weight value': '数值', + 'weight sum must 100': 'default_tier_weight / default_kill_score_weight 的权重之和必须等于 100', + 'weight each max 100': '每项权重不能超过 100', + 'weight value numeric': '权重值必须为有效数字', + sort: '排序', + instantiation: '实例化', + 'instantiation 0': '不需要', + 'instantiation 1': '需要', + create_time: '创建时间', + update_time: '更新时间', + 'quick Search Fields': 'ID', +} diff --git a/web/src/views/backend/game/config/index.vue b/web/src/views/backend/game/config/index.vue new file mode 100644 index 0000000..22a873e --- /dev/null +++ b/web/src/views/backend/game/config/index.vue @@ -0,0 +1,164 @@ + + + + + diff --git a/web/src/views/backend/game/config/popupForm.vue b/web/src/views/backend/game/config/popupForm.vue new file mode 100644 index 0000000..220fe06 --- /dev/null +++ b/web/src/views/backend/game/config/popupForm.vue @@ -0,0 +1,375 @@ + + + + +