feat: 完成英雄配置编辑器全流程功能开发
本次提交实现了完整的英雄配置管理系统: 1. 新增服务器端英雄数据持久化接口,支持读取和保存英雄配置 2. 重构前端英雄编辑器UI,替换侧边栏为下拉选择器适配移动端 3. 统一调整前端页面样式,压缩间距适配更多内容 4. 修复Tooltip组件显示逻辑,优化技能飘字效果 5. 导入CSV英雄配置数据,补全全职业技能配置 6. 调整多个英雄预制体的尺寸和位置参数 7. 生成完整的英雄配置导出文件hexport.ts
This commit is contained in:
@@ -4,32 +4,28 @@
|
||||
<h1>英雄配置编辑器</h1>
|
||||
<div class="actions">
|
||||
<el-button size="small" @click="refreshMeta" :loading="loadingMeta">刷新配置数据</el-button>
|
||||
<el-button size="small" type="success" @click="doSave" :disabled="!heroes.length">保存到服务器</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>
|
||||
<!-- 英雄选择工具栏:下拉选择,适配手机竖屏 -->
|
||||
<div class="hero-toolbar">
|
||||
<el-select
|
||||
v-model="activeIdx"
|
||||
class="hero-select"
|
||||
:placeholder="`选择英雄(共 ${heroes.length} 个)`"
|
||||
filterable
|
||||
:disabled="!heroes.length"
|
||||
>
|
||||
<el-option v-for="(h, i) in heroes" :key="i" :label="`${h.uuid} ${h.name || '(未命名)'}`" :value="i">
|
||||
<span>{{ h.uuid }} {{ h.name || '(未命名)' }}</span>
|
||||
<span class="hero-option-meta">{{ roleLabel(h.roleKey) }} · {{ typeLabel(h.typeKey) }} · ★{{ h.rarity }}</span>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="addHero">+ 新增</el-button>
|
||||
<el-button type="danger" plain :disabled="activeIdx < 0" @click="removeHero(activeIdx)">删当前</el-button>
|
||||
</div>
|
||||
<section class="hero-editor" v-if="current">
|
||||
<!-- 基础属性 -->
|
||||
<div class="panel">
|
||||
@@ -256,6 +252,27 @@ function addUltSkill() {
|
||||
current.value.skills[1] = sk
|
||||
}
|
||||
|
||||
async function loadHeroes() {
|
||||
try {
|
||||
const r = await api.fetchHeroes()
|
||||
if (Array.isArray(r.heroes) && r.heroes.length) {
|
||||
heroes.value = r.heroes
|
||||
activeIdx.value = 0
|
||||
}
|
||||
} catch (e) {
|
||||
ElMessage.error('加载英雄数据失败: ' + (e?.response?.data?.error || e.message))
|
||||
}
|
||||
}
|
||||
|
||||
async function doSave() {
|
||||
try {
|
||||
const r = await api.saveHeroes(heroes.value)
|
||||
ElMessage.success(`已保存 ${r.count} 个英雄 → ${r.path}`)
|
||||
} catch (e) {
|
||||
ElMessage.error('保存失败: ' + (e?.response?.data?.error || e.message))
|
||||
}
|
||||
}
|
||||
|
||||
async function doExport() {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
@@ -272,5 +289,8 @@ async function doExport() {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadMeta)
|
||||
onMounted(() => {
|
||||
loadMeta()
|
||||
loadHeroes()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -3,6 +3,8 @@ import axios from 'axios'
|
||||
export const api = {
|
||||
meta: () => axios.get('/api/meta').then((r) => r.data),
|
||||
refresh: () => axios.post('/api/refresh').then((r) => r.data),
|
||||
fetchHeroes: () => axios.get('/api/heroes').then((r) => r.data),
|
||||
saveHeroes: (heroes) => axios.post('/api/heroes', { heroes }).then((r) => r.data),
|
||||
exportHeroes: (heroes) => axios.post('/api/export', { heroes }).then((r) => r.data),
|
||||
fetchExport: () => axios.get('/api/export', { responseType: 'text' }).then((r) => r.data),
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ export const ATK_SPEEDS = [
|
||||
export const ROLE_ATK_SKILL = {
|
||||
Tank: [6004],
|
||||
Warrior: [6004],
|
||||
Assassin: [6001, 6002, 6003],
|
||||
Archer: [6006, 6007, 6008],
|
||||
Assassin: [6001, 6002, 6003, 6004],
|
||||
Archer: [6004, 6006, 6007, 6008],
|
||||
Mage: [6009, 6010, 6011, 6012],
|
||||
Support: [6009, 6010, 6011, 6012],
|
||||
Support: [6004, 6009, 6010, 6011, 6012],
|
||||
}
|
||||
|
||||
// 按职业映射大招底座
|
||||
|
||||
@@ -14,8 +14,27 @@ body {
|
||||
font-family: 'Helvetica Neue', Helvetica, 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
/* 编辑页全局缩小字号,同屏容纳更多字段 */
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* Element Plus 表单控件整体缩小一档(默认 14px → 12px) */
|
||||
.hero-app .el-input__wrapper,
|
||||
.hero-app .el-select__wrapper,
|
||||
.hero-app .el-textarea__inner,
|
||||
.hero-app .el-form-item__label {
|
||||
font-size: 12px;
|
||||
}
|
||||
/* 行间距压缩:表单项下边距 + 行高收紧 */
|
||||
.hero-app .el-form-item { margin-bottom: 8px; line-height: 1.2; }
|
||||
.hero-app .el-form-item__label { line-height: 1.2; padding-bottom: 2px; }
|
||||
.hero-app .el-button { font-size: 12px; height: 26px; padding: 8px 10px; }
|
||||
.hero-app .el-button--small { height: 22px; padding: 5px 8px; }
|
||||
.hero-app .el-input__inner { --el-input-height: 26px; }
|
||||
.hero-app .el-tabs__item { font-size: 12px; height: 30px; }
|
||||
.hero-app .el-tabs__header { margin-bottom: 8px; }
|
||||
.hero-app .el-tabs__nav-wrap::after { height: 1px; }
|
||||
|
||||
.hero-app {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -31,49 +50,32 @@ body {
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 8px rgba(74, 144, 226, 0.25);
|
||||
}
|
||||
.hero-header h1 { font-size: 18px; margin: 0; font-weight: 600; }
|
||||
.hero-header h1 { font-size: 15px; margin: 0; font-weight: 600; }
|
||||
.hero-header .actions { display: flex; gap: 8px; }
|
||||
|
||||
.hero-main {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.hero-sidebar {
|
||||
width: 260px;
|
||||
/* 英雄选择工具栏:下拉选择替代侧边栏列表,适配手机竖屏 */
|
||||
.hero-toolbar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
padding: 10px 14px;
|
||||
background: var(--panel);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.hero-sidebar .side-head {
|
||||
padding: 12px 14px;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.hero-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6px;
|
||||
.hero-toolbar .hero-select { flex: 1; min-width: 0; }
|
||||
.hero-option-meta {
|
||||
float: right;
|
||||
font-size: 11px;
|
||||
color: var(--text-light);
|
||||
}
|
||||
.hero-item {
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
margin-bottom: 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.hero-item:hover { background: #eef5ff; }
|
||||
.hero-item.active { background: #d6e8ff; color: var(--brand); font-weight: 600; }
|
||||
.hero-item .meta { font-size: 12px; color: var(--text-light); }
|
||||
|
||||
.hero-editor {
|
||||
flex: 1;
|
||||
@@ -84,14 +86,14 @@ body {
|
||||
.panel {
|
||||
background: var(--panel);
|
||||
border-radius: 8px;
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid var(--border);
|
||||
box-shadow: 0 1px 3px rgba(46, 91, 160, 0.05);
|
||||
}
|
||||
.panel h3 {
|
||||
margin: 0 0 12px;
|
||||
font-size: 14px;
|
||||
margin: 0 0 8px;
|
||||
font-size: 13px;
|
||||
color: var(--brand);
|
||||
border-left: 3px solid var(--brand);
|
||||
padding-left: 8px;
|
||||
@@ -100,7 +102,7 @@ body {
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 10px 18px;
|
||||
gap: 4px 14px;
|
||||
}
|
||||
|
||||
.muted { color: var(--text-light); font-size: 12px; }
|
||||
@@ -132,6 +134,17 @@ body {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.hero-sidebar { width: 200px; }
|
||||
/* 手机竖屏适配:头部按钮换行、表单单列、编辑区全宽滚动 */
|
||||
@media (max-width: 600px) {
|
||||
.hero-header {
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.hero-header .actions { flex-wrap: wrap; }
|
||||
.hero-editor { padding: 12px 12px 40px; }
|
||||
.hero-toolbar { padding: 8px 10px; }
|
||||
.hero-toolbar .el-button { margin-left: 0; }
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
.override-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user