Files
pixelheros/put/web/src/App.vue
pan 7c536ec023 fix: 调整触发技能默认等级与提示文本
1. 将触发技能默认生成等级从6改为4,默认激活等级同步更新
2. 修复复活技能成长档位描述错误,从lv1/lv9/lv16改为lv1/lv8/lv16
3. 为普攻技能新增默认的lv2强化档位配置
2026-09-03 11:07:49 +08:00

277 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="hero-app">
<header class="hero-header">
<h1>英雄配置编辑器</h1>
<div class="actions">
<el-button size="small" @click="refreshMeta" :loading="loadingMeta">刷新配置数据</el-button>
<el-button size="small" type="warning" @click="doExport" :disabled="!heroes.length">导出 hexport.ts</el-button>
</div>
</header>
<div class="hero-main">
<aside class="hero-sidebar">
<div class="side-head">
<span>英雄列表 ({{ heroes.length }})</span>
<el-button size="small" type="primary" text @click="addHero">+ 新增</el-button>
</div>
<div class="hero-list">
<div
v-for="(h, i) in heroes"
:key="i"
class="hero-item"
:class="{ active: i === activeIdx }"
@click="activeIdx = i"
>
<div>
<div>{{ h.uuid }} {{ h.name || '(未命名)' }}</div>
<div class="meta">{{ roleLabel(h.roleKey) }} · {{ typeLabel(h.typeKey) }} · ★{{ h.rarity }}</div>
</div>
<el-button type="danger" size="small" text @click.stop="removeHero(i)">删</el-button>
</div>
<div v-if="!heroes.length" class="empty">点击右上角"新增"创建英雄</div>
</div>
</aside>
<section class="hero-editor" v-if="current">
<!-- 基础属性 -->
<div class="panel">
<h3>基础属性</h3>
<div class="form-grid">
<el-form-item label="uuid">
<el-input-number v-model="current.uuid" :min="5001" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="current.name" />
</el-form-item>
<el-form-item label="资源路径 path">
<el-input v-model="current.path" placeholder="hm1 / hk2 ..." />
</el-form-item>
<el-form-item label="品质 rarity">
<el-select v-model="current.rarity">
<el-option v-for="n in 5" :key="n" :label="`${n} 星`" :value="n" />
</el-select>
</el-form-item>
<el-form-item label="职业 role">
<el-select v-model="current.roleKey">
<el-option v-for="r in HERO_ROLES" :key="r.key" :label="r.label" :value="r.key" />
</el-select>
</el-form-item>
<el-form-item label="成长 grow_type">
<el-select v-model="current.growKey">
<el-option v-for="g in GROW_TYPES" :key="g.key" :label="g.label" :value="g.key" />
</el-select>
</el-form-item>
<el-form-item label="形态 type">
<el-select v-model="current.typeKey">
<el-option v-for="t in H_TYPES" :key="t.key" :label="t.label" :value="t.key" />
</el-select>
</el-form-item>
<el-form-item label="攻击距离 dis(可选)">
<el-input-number v-model="current.dis" :min="0" :controls="false" style="width: 100%" placeholder="默认按形态" />
</el-form-item>
<el-form-item label="hp 偏移">
<el-input-number v-model="current.statBase.hp" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="ap 偏移">
<el-input-number v-model="current.statBase.ap" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="def 偏移">
<el-input-number v-model="current.statBase.def" :controls="false" style="width: 100%" />
</el-form-item>
<el-form-item label="注释">
<el-input v-model="current.comment" placeholder="导出文件中显示在条目上方" />
</el-form-item>
</div>
<div class="muted">基础值 = HERO_ROLE_BASE[{{ current.roleKey }}] → hp {{ roleBase?.hp }} / ap {{ roleBase?.ap }} / def {{ roleBase?.def }}</div>
</div>
<!-- 普攻 -->
<div class="panel">
<h3>普攻 skills[0]</h3>
<SkillEditor v-if="basicSkill" :skill="basicSkill" :options="basicSkillOptions" />
<el-button v-else size="small" type="primary" @click="addBasicSkill">+ 添加普攻</el-button>
</div>
<!-- 大招 -->
<div class="panel">
<h3>大招 skills[1](与 revive 互斥)</h3>
<template v-if="current.revive.length === 0">
<SkillEditor v-if="ultSkill" :skill="ultSkill" :options="ultSkillOptions" :is-ult="true" />
<el-button v-else size="small" type="primary" @click="addUltSkill">+ 添加大招</el-button>
</template>
<el-alert v-else type="info" :closable="false">已配置 revive,大招不可用</el-alert>
</div>
<!-- 触发技能 -->
<div class="panel">
<h3>触发技能(lv4 激活)</h3>
<el-tabs>
<el-tab-pane label="atking 攻击后"><TriggerGroupEditor :model="current.atking" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
<el-tab-pane label="atked 受击后"><TriggerGroupEditor :model="current.atked" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
<el-tab-pane label="fstart 战斗开始"><TriggerGroupEditor :model="current.fstart" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
<el-tab-pane label="dead 死亡后"><TriggerGroupEditor :model="current.dead" :skill-options="allSkills" :skill-map="skillMap" /></el-tab-pane>
</el-tabs>
</div>
<!-- 光环 -->
<div class="panel">
<h3>驻场光环 field</h3>
<FieldEditor :model="current.field" :field-options="fieldOptions" />
</div>
<!-- 复活 -->
<div class="panel">
<h3>复活 revive(与大招互斥)</h3>
<ReviveEditor :model="current.revive" :revive-options="reviveOptions" />
</div>
<!-- 额外加成 -->
<div class="panel">
<h3>额外属性加成</h3>
<el-tabs>
<el-tab-pane label="ap_bonus"><BonusEditor :model="current.ap_bonus" /></el-tab-pane>
<el-tab-pane label="hp_bonus"><BonusEditor :model="current.hp_bonus" /></el-tab-pane>
<el-tab-pane label="def_bonus"><BonusEditor :model="current.def_bonus" /></el-tab-pane>
</el-tabs>
</div>
<!-- 描述 -->
<div class="panel">
<h3>英雄描述 info</h3>
<el-input v-model="current.info" type="textarea" :rows="3" />
</div>
</section>
<section v-else class="hero-editor"><div class="empty">请先选择或新增英雄</div></section>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, h } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { api } from './api'
import {
HERO_ROLES, GROW_TYPES, H_TYPES,
ROLE_ATK_SKILL, ROLE_ULT_SKILL, SUPPORT_ULT,
defaultHero, defaultSkill,
} from './constants'
import SkillEditor from './components/SkillEditor.vue'
import TriggerGroupEditor from './components/TriggerGroupEditor.vue'
import FieldEditor from './components/FieldEditor.vue'
import ReviveEditor from './components/ReviveEditor.vue'
import BonusEditor from './components/BonusEditor.vue'
const heroes = ref([])
const activeIdx = ref(-1)
const meta = ref(null)
const loadingMeta = ref(false)
const current = computed(() => heroes.value[activeIdx.value])
const skillMap = computed(() => meta.value?.SkillSet || {})
const allSkills = computed(() => Object.values(skillMap.value).sort((a, b) => a.uuid - b.uuid))
const fieldOptions = computed(() => Object.values(meta.value?.FieldSkillSet || {}).sort((a, b) => a.uuid - b.uuid))
const reviveOptions = computed(() => allSkills.value.filter((s) => s.uuid >= 6500 && s.uuid < 6600))
const roleBase = computed(() => {
if (!current.value || !meta.value?.HERO_ROLE_BASE) return null
const roleEnum = HERO_ROLES.find((r) => r.key === current.value.roleKey)?.value
return meta.value.HERO_ROLE_BASE[roleEnum]
})
const basicSkill = computed(() => current.value?.skills?.[0])
const ultSkill = computed(() => current.value?.skills?.[1])
const basicSkillOptions = computed(() => {
if (!current.value) return []
const allowed = ROLE_ATK_SKILL[current.value.roleKey] || []
return allSkills.value.filter((s) => allowed.includes(s.uuid))
})
const ultSkillOptions = computed(() => {
if (!current.value) return []
const allowed = [...(ROLE_ULT_SKILL[current.value.roleKey] || []), ...SUPPORT_ULT]
return allSkills.value.filter((s) => allowed.includes(s.uuid))
})
function roleLabel(key) { return HERO_ROLES.find((r) => r.key === key)?.label || key }
function typeLabel(key) { return H_TYPES.find((t) => t.key === key)?.label || key }
async function loadMeta() {
loadingMeta.value = true
try {
meta.value = await api.meta()
} catch (e) {
ElMessage.error('加载配置数据失败: ' + (e?.response?.data?.error || e.message))
} finally {
loadingMeta.value = false
}
}
async function refreshMeta() {
try {
await api.refresh()
await loadMeta()
ElMessage.success('已刷新')
} catch (e) {
ElMessage.error('刷新失败')
}
}
function addHero() {
const nh = defaultHero()
const used = new Set(heroes.value.map((h) => h.uuid))
let uuid = 5001
while (used.has(uuid)) uuid++
nh.uuid = uuid
heroes.value.push(nh)
activeIdx.value = heroes.value.length - 1
}
function removeHero(i) {
heroes.value.splice(i, 1)
if (activeIdx.value >= heroes.value.length) activeIdx.value = heroes.value.length - 1
}
function addBasicSkill() {
if (!current.value) return
const first = basicSkillOptions.value[0]
if (!first) return ElMessage.warning('当前职业无可用普攻底座')
const sk = defaultSkill(first.uuid)
// 普攻默认提供 lv2 强化档位
sk.lv_entries = [
{ lv: 2, s_lv: 2, overrides: { ap: 120 }, desc: '' },
]
current.value.skills[0] = sk
}
function addUltSkill() {
if (!current.value) return
const first = ultSkillOptions.value[0]
if (!first) return ElMessage.warning('当前职业无可用大招底座')
const sk = defaultSkill(first.uuid)
// 大招默认 summon_count=1,并提供 lv9/lv16 档位
sk.cd = 8
sk.cdExpr = null
sk.overrides = { summon_count: 1 }
sk.lv_entries = [
{ lv: 9, s_lv: 2, overrides: { summon_count: 1 }, desc: '' },
{ lv: 16, s_lv: 3, overrides: { summon_count: 2 }, desc: '' },
]
current.value.skills[1] = sk
}
async function doExport() {
try {
await ElMessageBox.confirm(
`将写入 ${heroes.value.length} 个英雄到 assets/script/game/common/config/hexport.ts`,
'导出确认',
{ confirmButtonText: '写入', cancelButtonText: '取消', type: 'warning' }
)
} catch { return }
try {
const r = await api.exportHeroes(heroes.value)
ElMessage.success(`已写入 ${r.bytes} 字节 → ${r.path}`)
} catch (e) {
ElMessage.error('导出失败: ' + (e?.response?.data?.error || e.message))
}
}
onMounted(loadMeta)
</script>