feat: 新增预设库管理与一键创建英雄向导

1.  新增服务端模板持久化接口,支持触发/光环预设的读写
2.  前端新增预设库管理页面,支持增删改查触发/光环预设
3.  新增英雄创建向导,支持按职业快速生成英雄并套用预设
4.  调整本地开发端口与代理地址
5.  补充职业默认配置与攻速映射等常量定义
This commit is contained in:
panFD
2026-09-13 11:43:18 +08:00
parent d86f54e7a4
commit d3e7438bca
8 changed files with 973 additions and 9 deletions

View File

@@ -0,0 +1,132 @@
{
"triggerPresets": [
{
"id": "tp-atking-fenli",
"label": "每3次普攻·奋力一击",
"group": "atking",
"uuid": 6004,
"tags": ["伤害"],
"entry": { "lv": 4, "s_lv": 1, "name": "风暴打击", "t_num": 3, "overrides": { "ap": 300 }, "desc": "立刻打击{target},造成攻击力{ap}%伤害" }
},
{
"id": "tp-atking-poison",
"label": "每3次普攻·蚀骨毒箭",
"group": "atking",
"uuid": 6007,
"tags": ["伤害", "持续"],
"entry": { "lv": 4, "s_lv": 1, "name": "蚀骨毒箭", "t_num": 3, "overrides": { "ap": 80, "timed_debuff_id": 7005, "debuff_value": 40, "debuff_chance": 100 }, "desc": "追加射出蚀骨毒箭,对{target}造成攻击力{ap}%伤害,必定使目标中毒" }
},
{
"id": "tp-atking-heavy-arrow",
"label": "每3次普攻·追加重箭",
"group": "atking",
"uuid": 6008,
"tags": ["伤害", "击退"],
"entry": { "lv": 4, "s_lv": 1, "name": "", "t_num": 3, "overrides": { "ap": 120, "bck_chance": 50 }, "desc": "" }
},
{
"id": "tp-atking-shield",
"label": "每3次普攻·附加护盾",
"group": "atking",
"uuid": 6301,
"tags": ["防御", "团队"],
"entry": { "lv": 4, "s_lv": 1, "name": "圣愈之盾", "t_num": 3, "overrides": { "ap": 30 }, "desc": "{target}获得生命最大值{ap}%的护盾" }
},
{
"id": "tp-atked-shield-team",
"label": "受击3次·全队护盾",
"group": "atked",
"uuid": 6301,
"tags": ["防御", "团队"],
"entry": { "lv": 4, "s_lv": 1, "name": "全体圣盾", "t_num": 3, "overrides": { "ap": 10 }, "desc": "全队获得本英雄生命最大值{ap}%的护盾" }
},
{
"id": "tp-atked-heal-self",
"label": "受击3次·自我治疗",
"group": "atked",
"uuid": 6302,
"tags": ["治疗"],
"entry": { "lv": 4, "s_lv": 1, "name": "", "t_num": 3, "overrides": { "ap": 5 }, "desc": "为自己释放治疗,恢复生命值的{ap}%" }
},
{
"id": "tp-atked-thorns",
"label": "受击3次·荆棘反伤",
"group": "atked",
"uuid": 6516,
"tags": ["反伤", "防御"],
"entry": { "lv": 4, "s_lv": 1, "name": "荆棘回应", "t_num": 3, "overrides": { "timed_buff_id": 7028, "buff_value": 10 }, "desc": "凝聚荆棘护体,受击时反弹{buff_value}%所受伤害" }
},
{
"id": "tp-fstart-crit-burst",
"label": "开场·团队暴击",
"group": "fstart",
"uuid": 6504,
"tags": ["增益", "团队", "暴击"],
"entry": { "lv": 4, "s_lv": 1, "name": "猎杀时刻", "t_num": 1, "overrides": { "buff_value": 30 }, "desc": "战斗开始时全体友方暴击率提升{buff_value}%" }
},
{
"id": "tp-fstart-armor-break",
"label": "开场·先手破甲",
"group": "fstart",
"uuid": 6012,
"tags": ["减益", "破甲"],
"entry": { "lv": 4, "s_lv": 1, "name": "先手蚀甲", "t_num": 1, "overrides": { "ap": 150, "timed_debuff_id": 7008 }, "desc": "战斗开始掷出蚀甲酸瓶,必定使其破甲" }
},
{
"id": "tp-fstart-lightning",
"label": "开场·雷击先手",
"group": "fstart",
"uuid": 6203,
"tags": ["伤害", "麻痹"],
"entry": { "lv": 4, "s_lv": 1, "name": "雷击先手", "t_num": 1, "overrides": { "ap": 150, "para": 30 }, "desc": "战斗开始释放雷击,对{target}造成{ap}%伤害,{para}%概率麻痹" }
}
],
"fieldPresets": [
{
"id": "fp-crit-master",
"label": "暴击双光环(暴击率+暴伤)",
"tags": ["暴击"],
"lv": 4,
"uuids": [
{ "uuid": 7004, "value": 0.12, "name": "剑刃风暴" },
{ "uuid": 7005, "value": 0.2, "name": "斩魂利刃" }
]
},
{
"id": "fp-atk-aura",
"label": "攻击光环 15%",
"tags": ["增伤"],
"lv": 4,
"uuids": [
{ "uuid": 7002, "value": 0.15, "name": "" }
]
},
{
"id": "fp-atk-speed",
"label": "攻速光环 15%",
"tags": ["攻速"],
"lv": 4,
"uuids": [
{ "uuid": 7006, "value": 0.15, "name": "" }
]
},
{
"id": "fp-armor-pierce",
"label": "穿透光环 10%",
"tags": ["破甲"],
"lv": 4,
"uuids": [
{ "uuid": 7009, "value": 0.1, "name": "锐矛之赐" }
]
},
{
"id": "fp-thorns-aura",
"label": "荆棘领域 8",
"tags": ["反伤", "防御"],
"lv": 4,
"uuids": [
{ "uuid": 7022, "value": 8, "name": "荆棘领域" }
]
}
]
}

View File

@@ -103,6 +103,47 @@ app.post('/api/heroes', (req, res) => {
}
})
const TEMPLATES_PATH = path.join(DATA_DIR, 'templates.json')
/** 预设库持久化:GET 读取(无文件时返回空池) */
app.get('/api/templates', (_req, res) => {
try {
if (!fs.existsSync(TEMPLATES_PATH)) return res.json({ triggerPresets: [], fieldPresets: [] })
const data = JSON.parse(fs.readFileSync(TEMPLATES_PATH, 'utf8'))
// 兼容旧格式:若根上是 templates 数组,返回空池让前端走默认
if (Array.isArray(data.templates)) return res.json({ triggerPresets: [], fieldPresets: [] })
res.json({
triggerPresets: Array.isArray(data.triggerPresets) ? data.triggerPresets : [],
fieldPresets: Array.isArray(data.fieldPresets) ? data.fieldPresets : [],
})
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 职业模板持久化:POST 全量覆盖保存(结构:{ triggerPresets: [], fieldPresets: [] }) */
app.post('/api/templates', (req, res) => {
const body = req.body || {}
const triggerPresets = body.triggerPresets
const fieldPresets = body.fieldPresets
if (!Array.isArray(triggerPresets) || !Array.isArray(fieldPresets)) {
return res.status(400).json({ error: 'triggerPresets 与 fieldPresets 必须为数组' })
}
// 触发预设必须绑定合法触发组
const groups = ['atking', 'atked', 'fstart', 'dead']
const bad = triggerPresets.find((p) => !groups.includes(p.group))
if (bad) {
return res.status(400).json({ error: `触发预设 ${bad.id || bad.label} 的 group 非法:${bad.group}` })
}
try {
fs.mkdirSync(DATA_DIR, { recursive: true })
fs.writeFileSync(TEMPLATES_PATH, JSON.stringify({ triggerPresets, fieldPresets }, null, 2), 'utf8')
res.json({ ok: true, path: TEMPLATES_PATH, triggerCount: triggerPresets.length, fieldCount: fieldPresets.length })
} catch (e) {
res.status(500).json({ error: String(e?.message || e) })
}
})
/** 序列化 overrides:TGroup 以字符串存储(如 "Team"),导出时还原为枚举表达式 TGroup.Team;
* buff_target 以数值存储(0-3),导出时还原为 BuffTarget.X 枚举表达式提升可读性 */
const BUFF_TARGET_NAMES = ['Hit', 'Self', 'Ally', 'Enemy']