From 04cea0fbb2c0a055158289eced6177add7843782 Mon Sep 17 00:00:00 2001 From: panFD Date: Mon, 5 Oct 2026 17:18:01 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E6=8A=80=E8=83=BD?= =?UTF-8?q?=E7=BA=A7=E5=B1=80=E9=83=A8=E6=9B=B4=E6=96=B0=E7=9A=84=20PATCH?= =?UTF-8?q?=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增 PATCH /api/heroes/:uuid/skills/:skillUuid 接口,支持按层级合并技能字段及 lv_entries,避免整表替换技能数据。同时更新 API 文档,说明该接口的功能与使用方式。 --- put/server/data/api-skill-guide.md | 3 +- put/server/index.js | 81 ++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/put/server/data/api-skill-guide.md b/put/server/data/api-skill-guide.md index af4500a8..b7db8423 100644 --- a/put/server/data/api-skill-guide.md +++ b/put/server/data/api-skill-guide.md @@ -30,7 +30,8 @@ npm run dev:remote # 线上设计版(本地模拟 remote) | `GET` | `/api/heroes` | 获取全部英雄列表 | | `GET` | `/api/heroes/:uuid` | 获取单个英雄完整配置 | | `POST` | `/api/heroes/:uuid` | **新增或覆盖**单个英雄(body 为完整英雄对象) | -| `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段) | +| `PUT` | `/api/heroes/:uuid` | **局部更新**单个英雄(body 为需修改的字段,顶层浅合并) | +| `PATCH` | `/api/heroes/:uuid/skills/:skillUuid` | **技能级局部更新**:支持按 `lv` 合并 `lv_entries`,避免整表替换 `skills` | | `DELETE` | `/api/heroes/:uuid` | 删除单个英雄 | | `POST` | `/api/refresh` | 刷新底座缓存(修改 SkillSet.ts 等后调用) | diff --git a/put/server/index.js b/put/server/index.js index 8beb9726..98102225 100644 --- a/put/server/index.js +++ b/put/server/index.js @@ -214,6 +214,87 @@ app.delete('/api/heroes/:uuid', async (req, res) => { } }) +/** + * 技能级局部更新:PATCH /api/heroes/:uuid/skills/:skillUuid + * 支持对技能字段及 lv_entries 按 lv 合并,避免整表替换 skills 数组。 + * + * body 格式: + * { + * name?, cd?, cdExpr?, desc?, // 技能基础字段,存在则覆盖 + * overrides?, // 与现有 overrides 浅合并(不是替换) + * lv_entries?: [ // 按 lv 合并:存在则更新该档,不存在则新增 + * { lv, s_lv?, overrides?, desc? } + * ] + * } + */ +app.patch('/api/heroes/:uuid/skills/:skillUuid', async (req, res) => { + try { + if (!(await guardVersion(res))) return + const uuid = Number(req.params.uuid) + const skillUuid = Number(req.params.skillUuid) + if (Number.isNaN(uuid) || Number.isNaN(skillUuid)) { + return res.status(400).json({ error: 'uuid 与 skillUuid 必须为数字' }) + } + const patch = req.body + if (!patch || typeof patch !== 'object') { + return res.status(400).json({ error: 'body 必须为技能补丁对象' }) + } + + const heroes = await store.getHeroes() + const hIdx = heroes.findIndex((h) => h.uuid === uuid) + if (hIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在` }) + + const hero = heroes[hIdx] + const sIdx = hero.skills.findIndex((s) => s.uuid === skillUuid) + if (sIdx < 0) return res.status(404).json({ error: `英雄 ${uuid} 不存在技能 ${skillUuid}` }) + + const skill = hero.skills[sIdx] + + // 基础字段直接覆盖 + if (patch.name !== undefined) skill.name = patch.name + if (patch.cd !== undefined) skill.cd = patch.cd + if (patch.cdExpr !== undefined) skill.cdExpr = patch.cdExpr + if (patch.desc !== undefined) skill.desc = patch.desc + + // overrides 浅合并(不是替换) + if (patch.overrides && typeof patch.overrides === 'object') { + skill.overrides = { ...skill.overrides, ...patch.overrides } + } + + // lv_entries 按 lv 合并:同 lv 更新,不同 lv 新增 + if (Array.isArray(patch.lv_entries)) { + if (!Array.isArray(skill.lv_entries)) skill.lv_entries = [] + for (const entry of patch.lv_entries) { + if (!entry || typeof entry !== 'object') continue + const lv = entry.lv + if (lv === undefined) continue + const eIdx = skill.lv_entries.findIndex((e) => e.lv === lv) + if (eIdx >= 0) { + // 同 lv:浅合并 overrides,覆盖其他字段 + const existing = skill.lv_entries[eIdx] + skill.lv_entries[eIdx] = { + ...existing, + ...entry, + overrides: entry.overrides + ? { ...existing.overrides, ...entry.overrides } + : existing.overrides, + } + } else { + // 新 lv:直接插入 + skill.lv_entries.push(entry) + } + } + // 按 lv 升序排序,保持数据整洁 + skill.lv_entries.sort((a, b) => a.lv - b.lv) + } + + await store.saveHeroes(heroes) + res.json({ ok: true, uuid, skillUuid, action: 'skill_patched' }) + } catch (e) { + res.status(500).json({ error: String(e?.message || e) }) + } +}) + /** 预设库:GET 从线上库读取 */ app.get('/api/templates', async (_req, res) => { try {