1784 lines
57 KiB
Vue
1784 lines
57 KiB
Vue
<template>
|
||
<div class="config-editor">
|
||
<div class="page-header">
|
||
<h1>Config.xml 编辑器</h1>
|
||
<div class="auto-load-status" v-if="isLoading">
|
||
<span class="loading-spinner"></span>
|
||
<span>正在加载默认XML文件...</span>
|
||
</div>
|
||
<div class="file-info" v-if="currentFileName">
|
||
<span>当前文件: {{ currentFileName }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="editor-container">
|
||
<!-- 初始提示 -->
|
||
<div class="welcome-message" v-if="!uploadedTags.length">
|
||
<h2>欢迎使用 Config.xml 编辑器</h2>
|
||
<p>请上传XML文件或使用预设模板开始编辑</p>
|
||
<div class="welcome-buttons">
|
||
<button @click="triggerFileInput" class="welcome-btn">上传 XML 文件</button>
|
||
<button @click="loadDefaultXmlFiles" class="welcome-btn">加载默认 XML 文件</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 侧边栏 -->
|
||
<div class="sidebar" v-if="tagList.length > 0 && uploadedTags.length > 0">
|
||
<h3>标签列表</h3>
|
||
<!-- 搜索框 -->
|
||
<div class="search-box">
|
||
<input
|
||
type="text"
|
||
v-model="searchQuery"
|
||
placeholder="输入ID搜索..."
|
||
@input="searchTags"
|
||
/>
|
||
<button @click="clearSearch" class="clear-btn">×</button>
|
||
</div>
|
||
<div class="tag-list">
|
||
<div
|
||
v-for="tag in filteredTags"
|
||
:key="tag.id"
|
||
class="tag-item"
|
||
draggable="true"
|
||
@dragstart="dragStart(tag)"
|
||
@click="insertTag(tag)"
|
||
>
|
||
<span class="tag-type">{{ tag.type }}</span>
|
||
<span class="tag-id">{{ tag.id }}</span>
|
||
<!-- 缓存值预览 -->
|
||
<span class="tag-preview" v-if="tag.content.length > 50">
|
||
{{ tag.content.substring(0, 50) }}...
|
||
</span>
|
||
<span class="tag-preview" v-else>
|
||
{{ tag.content }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="input-section">
|
||
<div class="button-group">
|
||
<button @click="addEntry" class="gradient-btn">添加</button>
|
||
<button @click="validateXml" class="gradient-btn">验证 XML</button>
|
||
<button @click="download" class="gradient-btn">下载 XML</button>
|
||
<button @click="clearAll" class="gradient-btn">清空所有</button>
|
||
<input type="file" id="fileInput" accept=".xml" @change="handleFileUpload" style="display: none">
|
||
<button @click="triggerFileInput" class="gradient-btn">上传 XML</button>
|
||
</div>
|
||
|
||
<!-- 预设按钮区域 -->
|
||
<div class="preset-buttons">
|
||
<h3>预设模板</h3>
|
||
<div class="preset-button-group">
|
||
<button @click="insertTemplate('LogicCommand')" class="preset-btn">LogicCommand</button>
|
||
<button @click="insertTemplate('LogicCommandSet')" class="preset-btn">LogicCommandSet</button>
|
||
<button @click="insertTemplate('SpecialPower')" class="preset-btn">SpecialPower</button>
|
||
<button @click="insertTemplate('Locomotor')" class="preset-btn">LocomotorTemplate</button>
|
||
<button @click="insertTemplate('LocomotorSet')" class="preset-btn">LocomotorSet</button>
|
||
<button @click="insertTemplate('UnitAbilityButtonTemplate')" class="preset-btn">UnitAbilityButtonTemplate</button>
|
||
<button @click="insertTemplate('ButtonStateData')" class="preset-btn">ButtonStateData</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="message-section">
|
||
<p v-if="errorMsg" class="error-message">{{ errorMsg }}</p>
|
||
<p v-if="warningMsg" class="warning-message">{{ warningMsg }}</p>
|
||
</div>
|
||
|
||
<div class="input-group">
|
||
<label for="newEntry">输入 LogicCommand、LogicCommandSet、SpecialPower、LocomotorTemplate、LocomotorSet、UnitAbilityButtonTemplate 或 ButtonSingleStateData 标签:</label>
|
||
<textarea
|
||
id="newEntry"
|
||
v-model="newEntry"
|
||
placeholder="输入 LogicCommand、LogicCommandSet、SpecialPower、LocomotorTemplate 或 LocomotorSet 标签"
|
||
class="entry-textarea"
|
||
@dragover.prevent
|
||
@drop="dropTag"
|
||
></textarea>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="editor-section">
|
||
<h3>编辑区 <span class="entry-count">({{ entries.length }} 个条目)</span></h3>
|
||
<div class="xml-stats" v-if="xmlContent">
|
||
<span>LogicCommand: {{ getLogicCommandCount() }}</span>
|
||
<span>LogicCommandSet: {{ getLogicCommandSetCount() }}</span>
|
||
<span>SpecialPower: {{ getSpecialPowerCount() }}</span>
|
||
<span>LocomotorTemplate: {{ getLocomotorCount() }}</span>
|
||
<span>LocomotorSet: {{ getLocomotorSetCount() }}</span>
|
||
<span>文件大小: {{ getFileSize() }}</span>
|
||
<span v-if="lastModified">最后修改: {{ lastModified }}</span>
|
||
</div>
|
||
<textarea
|
||
v-model="xmlContent"
|
||
class="xml-textarea"
|
||
placeholder="XML内容将在这里显示..."
|
||
></textarea>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, computed, onMounted } from 'vue'
|
||
import '../../assets/styles/common.css'
|
||
|
||
// 响应式数据
|
||
const header = ref(`<?xml version="1.0" encoding="utf-8"?>\n<AssetDeclaration xmlns="uri:ea.com:eala:asset">`)
|
||
const footer = ref(`</AssetDeclaration>`)
|
||
const isLoading = ref(false)
|
||
const entries = ref([])
|
||
const newEntry = ref('')
|
||
const xmlContent = ref('')
|
||
const errorMsg = ref('')
|
||
const warningMsg = ref('')
|
||
const lastModified = ref('')
|
||
const draggedTag = ref(null)
|
||
const searchQuery = ref('')
|
||
const filteredTags = ref([])
|
||
// 存储所有上传的标签
|
||
const uploadedTags = ref([])
|
||
// 存储当前单位使用的命令集和特殊能力ID
|
||
const unitRelatedIds = ref([])
|
||
|
||
// 修改计算属性:只从uploadedTags中提取标签列表,不再从编辑区提取,并根据unitRelatedIds过滤
|
||
const tagList = computed(() => {
|
||
const tags = []
|
||
|
||
// 不再从编辑区条目中提取标签
|
||
|
||
// 只从上传的标签中提取,并根据unitRelatedIds过滤
|
||
uploadedTags.value.forEach(tag => {
|
||
// 如果unitRelatedIds为空或者包含当前标签的id,则添加到列表中
|
||
if (unitRelatedIds.value.length === 0 || unitRelatedIds.value.includes(tag.id)) {
|
||
tags.push({
|
||
id: tag.id,
|
||
type: tag.type,
|
||
content: tag.content,
|
||
source: 'upload',
|
||
inheritFrom: tag.inheritFrom,
|
||
locomotorId: tag.locomotorId,
|
||
cmdIds: tag.cmdIds
|
||
})
|
||
}
|
||
|
||
// 如果是继承的LocomotorTemplate,也添加父模板
|
||
if (tag.inheritFrom && unitRelatedIds.value.includes(tag.id)) {
|
||
// 查找父模板
|
||
const parentTag = uploadedTags.value.find(t => t.id === tag.inheritFrom)
|
||
if (parentTag && !tags.some(t => t.id === parentTag.id)) {
|
||
tags.push({
|
||
id: parentTag.id,
|
||
type: parentTag.type,
|
||
content: parentTag.content,
|
||
source: 'upload',
|
||
inheritFrom: parentTag.inheritFrom,
|
||
isParentTemplate: true
|
||
})
|
||
}
|
||
}
|
||
|
||
// 如果是LocomotorSet,也添加引用的Locomotor
|
||
if (tag.locomotorId && unitRelatedIds.value.includes(tag.id)) {
|
||
// 查找引用的Locomotor
|
||
const locomotorTag = uploadedTags.value.find(t => t.id === tag.locomotorId)
|
||
if (locomotorTag && !tags.some(t => t.id === locomotorTag.id)) {
|
||
tags.push({
|
||
id: locomotorTag.id,
|
||
type: locomotorTag.type,
|
||
content: locomotorTag.content,
|
||
source: 'upload',
|
||
inheritFrom: locomotorTag.inheritFrom,
|
||
isReferencedLocomotor: true
|
||
})
|
||
|
||
// 如果引用的Locomotor有继承关系,也添加其父模板
|
||
if (locomotorTag.inheritFrom) {
|
||
const parentLocomotorTag = uploadedTags.value.find(t => t.id === locomotorTag.inheritFrom)
|
||
if (parentLocomotorTag && !tags.some(t => t.id === parentLocomotorTag.id)) {
|
||
tags.push({
|
||
id: parentLocomotorTag.id,
|
||
type: parentLocomotorTag.type,
|
||
content: parentLocomotorTag.content,
|
||
source: 'upload',
|
||
inheritFrom: parentLocomotorTag.inheritFrom,
|
||
isParentTemplate: true
|
||
})
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 如果是LogicCommandSet,也添加引用的LogicCommand
|
||
if (tag.cmdIds && tag.cmdIds.length > 0 && (unitRelatedIds.value.length === 0 || unitRelatedIds.value.includes(tag.id))) {
|
||
// 遍历所有的Cmd标签
|
||
tag.cmdIds.forEach(cmdId => {
|
||
// 查找对应的LogicCommand
|
||
const cmdTag = uploadedTags.value.find(t => t.id === cmdId)
|
||
if (cmdTag && !tags.some(t => t.id === cmdTag.id)) {
|
||
tags.push({
|
||
id: cmdTag.id,
|
||
type: cmdTag.type,
|
||
content: cmdTag.content,
|
||
source: 'upload',
|
||
isReferencedCommand: true,
|
||
referencedBy: tag.id
|
||
})
|
||
|
||
// 查找引用这个LogicCommand的UnitAbilityButtonTemplate
|
||
const buttonTemplates = uploadedTags.value.filter(t =>
|
||
t.type === 'UnitAbilityButtonTemplate' &&
|
||
t.logicCommandId === cmdId
|
||
)
|
||
|
||
// 添加找到的UnitAbilityButtonTemplate
|
||
buttonTemplates.forEach(buttonTemplate => {
|
||
if (!tags.some(t => t.id === buttonTemplate.id)) {
|
||
tags.push({
|
||
id: buttonTemplate.id,
|
||
type: buttonTemplate.type,
|
||
content: buttonTemplate.content,
|
||
source: 'upload',
|
||
isReferencedButtonTemplate: true,
|
||
referencedByCommand: cmdId
|
||
})
|
||
|
||
// 查找并添加ButtonStateData
|
||
if (buttonTemplate.stateDataIds && buttonTemplate.stateDataIds.length > 0) {
|
||
buttonTemplate.stateDataIds.forEach(stateDataId => {
|
||
// 查找对应的ButtonStateData
|
||
const stateDataTag = uploadedTags.value.find(t =>
|
||
(t.type === 'ButtonSingleStateData' || t.type === 'ButtonDoubleStateData') &&
|
||
t.id === stateDataId
|
||
)
|
||
|
||
if (stateDataTag && !tags.some(t => t.id === stateDataTag.id)) {
|
||
tags.push({
|
||
id: stateDataTag.id,
|
||
type: stateDataTag.type,
|
||
content: stateDataTag.content,
|
||
source: 'upload',
|
||
isReferencedStateData: true,
|
||
referencedByButtonTemplate: buttonTemplate.id
|
||
})
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
}
|
||
})
|
||
}
|
||
})
|
||
|
||
// 初始化过滤后的标签
|
||
filteredTags.value = [...tags]
|
||
return tags
|
||
})
|
||
|
||
// 拖拽开始
|
||
const dragStart = (tag) => {
|
||
draggedTag.value = tag
|
||
}
|
||
|
||
//搜索标签
|
||
const searchTags = () => {
|
||
if (!searchQuery.value.trim()) {
|
||
filteredTags.value = [...tagList.value]
|
||
return
|
||
}
|
||
|
||
const query = searchQuery.value.toLowerCase()
|
||
filteredTags.value = tagList.value.filter(tag =>
|
||
tag.id.toLowerCase().includes(query) ||
|
||
tag.content.toLowerCase().includes(query)
|
||
)
|
||
}
|
||
|
||
//清除搜索
|
||
const clearSearch = () => {
|
||
searchQuery.value = ''
|
||
filteredTags.value = [...tagList.value]
|
||
}
|
||
|
||
// 放置标签
|
||
const dropTag = (e) => {
|
||
e.preventDefault()
|
||
if (draggedTag.value) {
|
||
insertTag(draggedTag.value)
|
||
draggedTag.value = null
|
||
}
|
||
}
|
||
|
||
// 插入标签
|
||
const insertTag = (tag) => {
|
||
// 直接使用标签的内容,无论是来自编辑区还是上传
|
||
newEntry.value = tag.content
|
||
}
|
||
|
||
// 触发文件输入
|
||
const triggerFileInput = () => {
|
||
document.getElementById('fileInput').click()
|
||
}
|
||
|
||
// 处理文件上传
|
||
const handleFileUpload = (event) => {
|
||
const file = event.target.files[0]
|
||
if (!file) return
|
||
|
||
const reader = new FileReader()
|
||
reader.onload = (e) => {
|
||
try {
|
||
const content = e.target.result
|
||
|
||
// 检查是否是单位XML文件
|
||
if (content.includes('<GameObject') && content.includes('CommandSet=')) {
|
||
// 解析单位XML文件,提取命令集和特殊能力ID
|
||
extractUnitRelatedIds(content)
|
||
|
||
// 如果是单位文件,自动加载默认XML文件
|
||
loadDefaultXmlFiles()
|
||
.then(() => {
|
||
warningMsg.value = `已加载单位相关的命令集和特殊能力`
|
||
|
||
// 检查是否有CommandSet但没有找到对应的LogicCommand
|
||
const commandSets = unitRelatedIds.value.filter(id => id.includes('CommandSet'))
|
||
if (commandSets.length > 0) {
|
||
// 查找所有CommandSet引用的LogicCommand
|
||
let foundLogicCommands = false
|
||
commandSets.forEach(commandSetId => {
|
||
const commandSetTag = uploadedTags.value.find(tag => tag.id === commandSetId)
|
||
if (commandSetTag && commandSetTag.cmdIds && commandSetTag.cmdIds.length > 0) {
|
||
// 检查是否找到了所有引用的LogicCommand
|
||
const missingCommands = commandSetTag.cmdIds.filter(cmdId =>
|
||
!uploadedTags.value.some(tag => tag.id === cmdId && tag.type === 'LogicCommand')
|
||
)
|
||
|
||
if (missingCommands.length > 0) {
|
||
console.log(`CommandSet ${commandSetId} 引用的以下LogicCommand未找到: ${missingCommands.join(', ')}`)
|
||
} else {
|
||
foundLogicCommands = true
|
||
console.log(`已找到CommandSet ${commandSetId} 引用的所有LogicCommand`)
|
||
}
|
||
}
|
||
})
|
||
|
||
if (!foundLogicCommands) {
|
||
warningMsg.value += `。未找到CommandSet引用的LogicCommand,请确保已上传LogicCommand.xml文件`
|
||
}
|
||
}
|
||
})
|
||
.catch(error => {
|
||
errorMsg.value = '加载默认XML文件时出错: ' + error.message
|
||
})
|
||
} else {
|
||
// 如果不是单位文件,正常解析XML
|
||
parseAndImportXml(content)
|
||
}
|
||
} catch (error) {
|
||
errorMsg.value = '文件解析失败: ' + error.message
|
||
}
|
||
}
|
||
reader.readAsText(file)
|
||
}
|
||
|
||
// 查找与LogicCommand相关的UnitAbilityButtonTemplate和ButtonStateData
|
||
const findRelatedButtonTemplates = (logicCommandId) => {
|
||
// 查找引用这个LogicCommand的UnitAbilityButtonTemplate
|
||
const buttonTemplates = uploadedTags.value.filter(tag =>
|
||
tag.type === 'UnitAbilityButtonTemplate' &&
|
||
tag.logicCommandId === logicCommandId
|
||
)
|
||
|
||
if (buttonTemplates.length > 0) {
|
||
console.log(`找到 ${buttonTemplates.length} 个引用LogicCommand ${logicCommandId} 的UnitAbilityButtonTemplate`)
|
||
|
||
// 将找到的UnitAbilityButtonTemplate添加到unitRelatedIds中
|
||
buttonTemplates.forEach(buttonTemplate => {
|
||
if (!unitRelatedIds.value.includes(buttonTemplate.id)) {
|
||
unitRelatedIds.value.push(buttonTemplate.id)
|
||
console.log(`添加UnitAbilityButtonTemplate: ${buttonTemplate.id}`)
|
||
|
||
// 查找并添加ButtonStateData
|
||
if (buttonTemplate.stateDataIds && buttonTemplate.stateDataIds.length > 0) {
|
||
buttonTemplate.stateDataIds.forEach(stateDataId => {
|
||
if (!unitRelatedIds.value.includes(stateDataId)) {
|
||
unitRelatedIds.value.push(stateDataId)
|
||
console.log(`添加ButtonStateData: ${stateDataId}`)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
} else {
|
||
console.log(`未找到引用LogicCommand ${logicCommandId} 的UnitAbilityButtonTemplate`)
|
||
}
|
||
}
|
||
|
||
// 提取单位相关的命令集、特殊能力ID和运动器ID
|
||
const extractUnitRelatedIds = (xmlContent) => {
|
||
try {
|
||
// 清空之前的相关ID
|
||
unitRelatedIds.value = []
|
||
|
||
const parser = new DOMParser()
|
||
const xmlDoc = parser.parseFromString(xmlContent, 'application/xml')
|
||
|
||
// 查找GameObject节点
|
||
const gameObjects = xmlDoc.getElementsByTagName('GameObject')
|
||
if (gameObjects.length === 0) {
|
||
warningMsg.value = '未找到GameObject节点'
|
||
return
|
||
}
|
||
|
||
// 遍历所有GameObject节点
|
||
for (let i = 0; i < gameObjects.length; i++) {
|
||
const gameObject = gameObjects[i]
|
||
|
||
// 提取CommandSet属性
|
||
const commandSet = gameObject.getAttribute('CommandSet')
|
||
if (commandSet) {
|
||
unitRelatedIds.value.push(commandSet)
|
||
|
||
// 记录单位ID和名称,用于显示
|
||
const unitId = gameObject.getAttribute('id')
|
||
const unitName = gameObject.getAttribute('EditorName') || unitId
|
||
warningMsg.value = `已加载单位 ${unitName} (${unitId}) 的相关配置,命令集: ${commandSet}`
|
||
|
||
// 查找对应的CommandSet标签,提取其中的LogicCommand ID
|
||
const commandSetTag = uploadedTags.value.find(tag => tag.id === commandSet && tag.type === 'LogicCommandSet')
|
||
if (commandSetTag && commandSetTag.cmdIds && commandSetTag.cmdIds.length > 0) {
|
||
// 将CommandSet中引用的所有LogicCommand ID添加到unitRelatedIds中
|
||
commandSetTag.cmdIds.forEach(cmdId => {
|
||
if (!unitRelatedIds.value.includes(cmdId)) {
|
||
unitRelatedIds.value.push(cmdId)
|
||
console.log(`添加CommandSet ${commandSet} 引用的LogicCommand: ${cmdId}`)
|
||
|
||
// 查找与LogicCommand相关的UnitAbilityButtonTemplate
|
||
findRelatedButtonTemplates(cmdId)
|
||
}
|
||
})
|
||
} else {
|
||
console.log(`未找到CommandSet ${commandSet} 或其中没有引用LogicCommand`)
|
||
}
|
||
}
|
||
|
||
// 查找Behaviors节点
|
||
const behaviors = gameObject.getElementsByTagName('Behaviors')
|
||
if (behaviors.length > 0) {
|
||
// 查找所有SpecialPower节点
|
||
const specialPowers = behaviors[0].getElementsByTagName('SpecialPower')
|
||
for (let j = 0; j < specialPowers.length; j++) {
|
||
const specialPower = specialPowers[j]
|
||
const template = specialPower.getAttribute('SpecialPowerTemplate')
|
||
if (template) {
|
||
unitRelatedIds.value.push(template)
|
||
console.log(`找到特殊能力模板: ${template}`)
|
||
}
|
||
}
|
||
|
||
// 查找所有ToggleStatusSpecialAbilityUpdate节点
|
||
const toggleUpdates = behaviors[0].getElementsByTagName('ToggleStatusSpecialAbilityUpdate')
|
||
for (let j = 0; j < toggleUpdates.length; j++) {
|
||
const toggleUpdate = toggleUpdates[j]
|
||
const template = toggleUpdate.getAttribute('SpecialPowerTemplate')
|
||
if (template) {
|
||
unitRelatedIds.value.push(template)
|
||
console.log(`找到切换特殊能力模板: ${template}`)
|
||
}
|
||
}
|
||
|
||
// 查找所有LocomotorSet节点
|
||
const locomotorSets = behaviors[0].getElementsByTagName('LocomotorSet')
|
||
for (let j = 0; j < locomotorSets.length; j++) {
|
||
const locomotorSet = locomotorSets[j]
|
||
const locomotorId = locomotorSet.getAttribute('Locomotor')
|
||
if (locomotorId) {
|
||
unitRelatedIds.value.push(locomotorId)
|
||
console.log(`找到运动器模板: ${locomotorId}`)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 递归查找所有节点中的SpecialPowerTemplate属性
|
||
const findAllSpecialPowerTemplates = (node) => {
|
||
if (node.nodeType === 1) { // 元素节点
|
||
const template = node.getAttribute('SpecialPowerTemplate')
|
||
if (template && !unitRelatedIds.value.includes(template)) {
|
||
unitRelatedIds.value.push(template)
|
||
console.log(`找到额外的特殊能力模板: ${template}`)
|
||
}
|
||
|
||
// 递归查找子节点
|
||
for (let i = 0; i < node.childNodes.length; i++) {
|
||
findAllSpecialPowerTemplates(node.childNodes[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// 从根节点开始递归查找
|
||
findAllSpecialPowerTemplates(xmlDoc.documentElement)
|
||
|
||
// 查找SpecialPowerTemplate标签中的id,这些可能是其他特殊能力模板引用的
|
||
const specialPowerTemplates = xmlDoc.getElementsByTagName('SpecialPowerTemplate')
|
||
for (let i = 0; i < specialPowerTemplates.length; i++) {
|
||
const template = specialPowerTemplates[i]
|
||
const id = template.getAttribute('id')
|
||
if (id && !unitRelatedIds.value.includes(id)) {
|
||
unitRelatedIds.value.push(id)
|
||
console.log(`找到SpecialPowerTemplate标签: ${id}`)
|
||
}
|
||
}
|
||
|
||
// 查找LocomotorSet节点
|
||
const locomotorSets = xmlDoc.getElementsByTagName('LocomotorSet')
|
||
for (let i = 0; i < locomotorSets.length; i++) {
|
||
const locomotorSet = locomotorSets[i]
|
||
const locomotorId = locomotorSet.getAttribute('Locomotor')
|
||
if (locomotorId && !unitRelatedIds.value.includes(locomotorId)) {
|
||
unitRelatedIds.value.push(locomotorId)
|
||
console.log(`找到LocomotorSet中的Locomotor: ${locomotorId}`)
|
||
}
|
||
}
|
||
|
||
console.log('提取的相关ID:', unitRelatedIds.value)
|
||
|
||
// 确保标签列表被更新
|
||
filteredTags.value = tagList.value.filter(tag =>
|
||
unitRelatedIds.value.length === 0 || unitRelatedIds.value.includes(tag.id)
|
||
)
|
||
|
||
// 如果没有找到任何标签,显示警告
|
||
if (unitRelatedIds.value.length > 0 && filteredTags.value.length === 0) {
|
||
warningMsg.value += `。未找到相关标签,请确保已上传包含这些ID的XML文件。`
|
||
} else if (unitRelatedIds.value.length > 0) {
|
||
// 显示找到的特殊能力模板和运动器模板
|
||
const specialPowerIds = unitRelatedIds.value.filter(id => id.includes('SpecialPower_'))
|
||
const locomotorIds = unitRelatedIds.value.filter(id => id.includes('Locomotor'))
|
||
|
||
let additionalInfo = ''
|
||
if (specialPowerIds.length > 0) {
|
||
additionalInfo += `,找到特殊能力模板: ${specialPowerIds.join(', ')}`
|
||
}
|
||
if (locomotorIds.length > 0) {
|
||
additionalInfo += `,找到运动器模板: ${locomotorIds.join(', ')}`
|
||
}
|
||
|
||
if (additionalInfo) {
|
||
warningMsg.value += additionalInfo
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('提取单位相关ID时出错:', error)
|
||
errorMsg.value = '提取单位相关ID时出错: ' + error.message
|
||
}
|
||
}
|
||
|
||
// 加载默认XML文件
|
||
const loadDefaultXmlFiles = async () => {
|
||
try {
|
||
errorMsg.value = ''
|
||
isLoading.value = true
|
||
|
||
// 不清空之前上传的标签,而是追加新标签
|
||
// uploadedTags.value = []
|
||
|
||
let totalTagsLoaded = 0
|
||
let totalTagsAdded = 0
|
||
|
||
// 定义要加载的XML文件列表,优先级较低的文件
|
||
const xmlFiles = [
|
||
{ name: 'SpecialPowerTemplates.xml', required: true },
|
||
{ name: 'Locomotor.xml', required: true },
|
||
{ name: 'LogicCommand.xml', required: true },
|
||
{ name: 'UnitAbilityButtonTemplates.xml', required: true },
|
||
{ name: 'ButtonStateDataCommon.xml', required: true }
|
||
];
|
||
|
||
// 添加加载状态指示
|
||
let loadingStatus = document.createElement('div');
|
||
loadingStatus.className = 'loading-status';
|
||
loadingStatus.textContent = '正在加载XML文件...';
|
||
loadingStatus.style.position = 'fixed';
|
||
loadingStatus.style.top = '50%';
|
||
loadingStatus.style.left = '50%';
|
||
loadingStatus.style.transform = 'translate(-50%, -50%)';
|
||
loadingStatus.style.padding = '15px 30px';
|
||
loadingStatus.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
|
||
loadingStatus.style.color = 'white';
|
||
loadingStatus.style.borderRadius = '8px';
|
||
loadingStatus.style.zIndex = '9999';
|
||
loadingStatus.style.fontSize = '16px';
|
||
document.body.appendChild(loadingStatus);
|
||
|
||
// 依次加载每个XML文件
|
||
for (const file of xmlFiles) {
|
||
try {
|
||
const response = await fetch(`/${file.name}`)
|
||
if (response.ok) {
|
||
const xmlContent = await response.text()
|
||
const beforeCount = uploadedTags.value.length
|
||
const parsedCount = parseAndImportXml(xmlContent, false) // 不显示消息
|
||
const afterCount = uploadedTags.value.length
|
||
totalTagsLoaded += parsedCount
|
||
totalTagsAdded += (afterCount - beforeCount)
|
||
console.log(`成功加载 ${file.name},解析了 ${parsedCount} 个标签,新增 ${afterCount - beforeCount} 个标签`)
|
||
|
||
// 如果是LogicCommand.xml,特别记录一下
|
||
if (file.name === 'LogicCommand.xml') {
|
||
console.log(`成功加载LogicCommand.xml,找到 ${parsedCount} 个LogicCommand标签`)
|
||
}
|
||
} else {
|
||
if (file.required) {
|
||
console.error(`必需的文件 ${file.name} 加载失败: ${response.status}`)
|
||
warningMsg.value = `必需的文件 ${file.name} 加载失败,请检查public目录`
|
||
} else {
|
||
console.warn(`可选文件 ${file.name} 加载失败: ${response.status}`)
|
||
}
|
||
}
|
||
} catch (e) {
|
||
if (file.required) {
|
||
console.error(`必需的文件 ${file.name} 加载出错:`, e)
|
||
errorMsg.value = `必需的文件 ${file.name} 加载出错: ${e.message}`
|
||
} else {
|
||
console.log(`可选文件 ${file.name} 不存在或无法加载`, e)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 移除加载状态指示
|
||
if (loadingStatus && loadingStatus.parentNode) {
|
||
loadingStatus.parentNode.removeChild(loadingStatus);
|
||
}
|
||
isLoading.value = false
|
||
|
||
// 如果有单位相关ID,计算匹配的标签数量
|
||
let matchedCount = 0
|
||
if (unitRelatedIds.value.length > 0) {
|
||
matchedCount = uploadedTags.value.filter(tag => unitRelatedIds.value.includes(tag.id)).length
|
||
warningMsg.value = `已成功加载默认XML文件,解析了 ${totalTagsLoaded} 个标签,找到 ${matchedCount} 个与单位相关的标签`
|
||
} else if (totalTagsLoaded > 0) {
|
||
if (totalTagsAdded > 0) {
|
||
warningMsg.value = `已成功加载默认XML文件,解析了 ${totalTagsLoaded} 个标签,新增 ${totalTagsAdded} 个标签`
|
||
} else {
|
||
warningMsg.value = `已成功加载默认XML文件,解析了 ${totalTagsLoaded} 个标签,所有标签已存在`
|
||
}
|
||
} else {
|
||
warningMsg.value = '未能加载任何默认XML文件,请检查public目录中是否存在这些文件'
|
||
}
|
||
|
||
return { totalTagsLoaded, totalTagsAdded, matchedCount }
|
||
} catch (error) {
|
||
errorMsg.value = '加载默认XML文件失败: ' + error.message
|
||
warningMsg.value = ''
|
||
throw error // 重新抛出错误,以便调用者可以捕获
|
||
}
|
||
}
|
||
|
||
// 组件挂载时自动加载默认XML文件
|
||
onMounted(() => {
|
||
// 自动加载默认XML文件
|
||
console.log('组件已挂载,开始加载默认XML文件...')
|
||
// 清空单位相关ID,确保加载所有标签
|
||
unitRelatedIds.value = []
|
||
|
||
// 确保优先加载LogicCommand.xml、LogicCommandSet.xml、Locomotor.xml、UnitAbilityButtonTemplates.xml和ButtonStateDataCommon.xml
|
||
const priorityFiles = ['LogicCommand.xml', 'LogicCommandSet.xml', 'Locomotor.xml', 'UnitAbilityButtonTemplates.xml', 'ButtonStateDataCommon.xml'];
|
||
|
||
// 当前文件名
|
||
const currentFileName = ref('');
|
||
|
||
// 创建一个加载状态提示
|
||
isLoading.value = true;
|
||
warningMsg.value = '正在加载默认XML文件...';
|
||
|
||
// 先尝试加载这三个必需的文件
|
||
Promise.all(priorityFiles.map(file =>
|
||
fetch(`/${file}`)
|
||
.then(response => {
|
||
if (!response.ok) {
|
||
throw new Error(`${file} 加载失败: ${response.status}`);
|
||
}
|
||
return response.text();
|
||
})
|
||
.then(content => {
|
||
console.log(`成功获取 ${file} 内容`);
|
||
return { file, content };
|
||
})
|
||
.catch(error => {
|
||
console.error(`加载 ${file} 时出错:`, error);
|
||
return { file, error };
|
||
})
|
||
))
|
||
.then(results => {
|
||
// 处理加载结果
|
||
let successCount = 0;
|
||
let totalTags = 0;
|
||
|
||
results.forEach(result => {
|
||
if (result.content) {
|
||
// 成功加载文件,解析内容
|
||
const parsedCount = parseAndImportXml(result.content, false);
|
||
totalTags += parsedCount;
|
||
successCount++;
|
||
console.log(`成功解析 ${result.file},找到 ${parsedCount} 个标签`);
|
||
currentFileName.value = result.file;
|
||
}
|
||
});
|
||
|
||
// 更新加载状态
|
||
isLoading.value = false;
|
||
|
||
if (successCount === priorityFiles.length) {
|
||
warningMsg.value = `已成功加载默认XML文件,共解析 ${totalTags} 个标签`;
|
||
} else {
|
||
// 如果有文件加载失败,显示警告
|
||
warningMsg.value = `部分默认XML文件加载失败,成功加载了 ${successCount}/${priorityFiles.length} 个文件,共解析 ${totalTags} 个标签`;
|
||
}
|
||
|
||
// 然后加载其他可选文件
|
||
loadDefaultXmlFiles()
|
||
.then(({ totalTagsLoaded }) => {
|
||
console.log('所有默认XML文件加载完成,共加载', totalTagsLoaded, '个标签');
|
||
})
|
||
.catch(error => {
|
||
console.error('加载其他默认XML文件时出错:', error);
|
||
});
|
||
})
|
||
.catch(error => {
|
||
isLoading.value = false;
|
||
console.error('加载默认XML文件时出错:', error);
|
||
errorMsg.value = '加载默认XML文件时出错: ' + error.message;
|
||
});
|
||
})
|
||
|
||
// 修改后的解析并导入XML内容
|
||
const parseAndImportXml = (xmlString, showMessage = true) => {
|
||
try {
|
||
const parser = new DOMParser()
|
||
const xmlDoc = parser.parseFromString(xmlString, 'application/xml')
|
||
|
||
// 检查解析错误
|
||
const parserError = xmlDoc.querySelector('parsererror')
|
||
if (parserError) {
|
||
throw new Error('XML格式错误: ' + parserError.textContent)
|
||
}
|
||
|
||
// 提取LogicCommand、LogicCommandSet、SpecialPower、SpecialPowerTemplate和LocomotorTemplate节点
|
||
const commands = xmlDoc.getElementsByTagName('LogicCommand')
|
||
const commandSets = xmlDoc.getElementsByTagName('LogicCommandSet')
|
||
const specialPowers = xmlDoc.getElementsByTagName('SpecialPower')
|
||
const specialPowerTemplates = xmlDoc.getElementsByTagName('SpecialPowerTemplate')
|
||
const locomotors = xmlDoc.getElementsByTagName('LocomotorTemplate')
|
||
|
||
// 记录解析前的标签数量
|
||
const beforeCount = uploadedTags.value.length
|
||
|
||
// 添加提取的节点到上传标签列表
|
||
for (let i = 0; i < commands.length; i++) {
|
||
const command = commands[i]
|
||
const id = command.getAttribute('id')
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'LogicCommand')
|
||
|
||
// 获取原始标签内容(不经过XMLSerializer处理)
|
||
const rawContent = command.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '') // 移除xmlns属性
|
||
.replace(/\s+/g, ' ') // 压缩多余空格
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'LogicCommand',
|
||
content: rawContent
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
for (let i = 0; i < commandSets.length; i++) {
|
||
const commandSet = commandSets[i]
|
||
const id = commandSet.getAttribute('id')
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'LogicCommandSet')
|
||
|
||
// 获取原始标签内容(不经过XMLSerializer处理)
|
||
const rawContent = commandSet.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '') // 移除xmlns属性
|
||
.replace(/\s+/g, ' ') // 压缩多余空格
|
||
.trim()
|
||
|
||
// 提取Cmd标签内容
|
||
const cmdElements = commandSet.getElementsByTagName('Cmd')
|
||
const cmdIds = []
|
||
|
||
for (let j = 0; j < cmdElements.length; j++) {
|
||
const cmdElement = cmdElements[j]
|
||
const cmdId = cmdElement.textContent.trim()
|
||
if (cmdId) {
|
||
cmdIds.push(cmdId)
|
||
console.log(`在CommandSet ${id} 中找到LogicCommand引用: ${cmdId}`)
|
||
|
||
// 如果当前单位使用了这个CommandSet,也将其引用的LogicCommand添加到unitRelatedIds中
|
||
if (unitRelatedIds.value.includes(id) && !unitRelatedIds.value.includes(cmdId)) {
|
||
unitRelatedIds.value.push(cmdId)
|
||
console.log(`将LogicCommand ${cmdId} 添加到单位相关ID中`)
|
||
}
|
||
}
|
||
}
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
uploadedTags.value[existingIndex].cmdIds = cmdIds
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'LogicCommandSet',
|
||
content: rawContent,
|
||
cmdIds: cmdIds
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加SpecialPower节点
|
||
for (let i = 0; i < specialPowers.length; i++) {
|
||
const specialPower = specialPowers[i]
|
||
const id = specialPower.getAttribute('id')
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'SpecialPower')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = specialPower.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'SpecialPower',
|
||
content: rawContent
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加SpecialPowerTemplate节点
|
||
for (let i = 0; i < specialPowerTemplates.length; i++) {
|
||
const template = specialPowerTemplates[i]
|
||
const id = template.getAttribute('id')
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'SpecialPowerTemplate')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = template.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'SpecialPowerTemplate',
|
||
content: rawContent
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加LocomotorTemplate节点
|
||
for (let i = 0; i < locomotors.length; i++) {
|
||
const locomotor = locomotors[i]
|
||
const id = locomotor.getAttribute('id')
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'LocomotorTemplate')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = locomotor.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
// 检查是否有继承关系
|
||
const inheritFrom = locomotor.getAttribute('inheritFrom')
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
if (inheritFrom) {
|
||
uploadedTags.value[existingIndex].inheritFrom = inheritFrom
|
||
}
|
||
} else {
|
||
// 添加新标签
|
||
const newTag = {
|
||
id: id,
|
||
type: 'LocomotorTemplate',
|
||
content: rawContent
|
||
}
|
||
|
||
if (inheritFrom) {
|
||
newTag.inheritFrom = inheritFrom
|
||
}
|
||
|
||
uploadedTags.value.push(newTag)
|
||
|
||
// 如果有继承关系,确保父模板也被添加到相关ID中
|
||
if (inheritFrom && !unitRelatedIds.value.includes(inheritFrom)) {
|
||
unitRelatedIds.value.push(inheritFrom)
|
||
console.log(`添加继承的运动器模板: ${inheritFrom}`)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理LocomotorSet节点
|
||
const locomotorSets = xmlDoc.getElementsByTagName('LocomotorSet')
|
||
for (let i = 0; i < locomotorSets.length; i++) {
|
||
const locomotorSet = locomotorSets[i]
|
||
const id = locomotorSet.getAttribute('id')
|
||
const locomotorId = locomotorSet.getAttribute('Locomotor')
|
||
|
||
if (id && locomotorId) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'LocomotorSet')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = locomotorSet.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
uploadedTags.value[existingIndex].locomotorId = locomotorId
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'LocomotorSet',
|
||
content: rawContent,
|
||
locomotorId: locomotorId
|
||
})
|
||
}
|
||
|
||
// 确保引用的Locomotor也被添加到相关ID中
|
||
if (!unitRelatedIds.value.includes(locomotorId)) {
|
||
unitRelatedIds.value.push(locomotorId)
|
||
console.log(`添加LocomotorSet引用的运动器: ${locomotorId}`)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理UnitAbilityButtonTemplate节点
|
||
const buttonTemplates = xmlDoc.getElementsByTagName('UnitAbilityButtonTemplate')
|
||
for (let i = 0; i < buttonTemplates.length; i++) {
|
||
const buttonTemplate = buttonTemplates[i]
|
||
const id = buttonTemplate.getAttribute('id')
|
||
const logicCommandId = buttonTemplate.getAttribute('LogicCommand')
|
||
|
||
if (id && logicCommandId) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'UnitAbilityButtonTemplate')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = buttonTemplate.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
// 提取StateData标签内容
|
||
const stateDataElements = buttonTemplate.getElementsByTagName('StateData')
|
||
const stateDataIds = []
|
||
|
||
for (let j = 0; j < stateDataElements.length; j++) {
|
||
const stateDataElement = stateDataElements[j]
|
||
const stateDataId = stateDataElement.textContent.trim()
|
||
if (stateDataId) {
|
||
stateDataIds.push(stateDataId)
|
||
console.log(`在UnitAbilityButtonTemplate ${id} 中找到StateData引用: ${stateDataId}`)
|
||
}
|
||
}
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
uploadedTags.value[existingIndex].logicCommandId = logicCommandId
|
||
uploadedTags.value[existingIndex].stateDataIds = stateDataIds
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'UnitAbilityButtonTemplate',
|
||
content: rawContent,
|
||
logicCommandId: logicCommandId,
|
||
stateDataIds: stateDataIds
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理ButtonStateData节点(包括ButtonSingleStateData和ButtonDoubleStateData)
|
||
const singleStateData = xmlDoc.getElementsByTagName('ButtonSingleStateData')
|
||
const doubleStateData = xmlDoc.getElementsByTagName('ButtonDoubleStateData')
|
||
|
||
// 处理ButtonSingleStateData
|
||
for (let i = 0; i < singleStateData.length; i++) {
|
||
const stateData = singleStateData[i]
|
||
const id = stateData.getAttribute('id')
|
||
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'ButtonSingleStateData')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = stateData.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'ButtonSingleStateData',
|
||
content: rawContent
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 处理ButtonDoubleStateData
|
||
for (let i = 0; i < doubleStateData.length; i++) {
|
||
const stateData = doubleStateData[i]
|
||
const id = stateData.getAttribute('id')
|
||
|
||
if (id) {
|
||
// 检查是否已存在相同ID的标签
|
||
const existingIndex = uploadedTags.value.findIndex(tag => tag.id === id && tag.type === 'ButtonDoubleStateData')
|
||
|
||
// 获取原始标签内容
|
||
const rawContent = stateData.outerHTML
|
||
.replace(/ xmlns="[^"]*"/g, '')
|
||
.replace(/\s+/g, ' ')
|
||
.trim()
|
||
|
||
if (existingIndex >= 0) {
|
||
// 更新现有标签
|
||
uploadedTags.value[existingIndex].content = rawContent
|
||
} else {
|
||
// 添加新标签
|
||
uploadedTags.value.push({
|
||
id: id,
|
||
type: 'ButtonDoubleStateData',
|
||
content: rawContent
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
// 计算新增的标签数量
|
||
const addedCount = uploadedTags.value.length - beforeCount
|
||
const totalTags = commands.length + commandSets.length + specialPowers.length + specialPowerTemplates.length + locomotors.length + locomotorSets.length + buttonTemplates.length + singleStateData.length + doubleStateData.length
|
||
|
||
// 调试输出
|
||
console.log(`解析XML文件,找到 LogicCommand: ${commands.length}, LogicCommandSet: ${commandSets.length}, SpecialPower: ${specialPowers.length}, SpecialPowerTemplate: ${specialPowerTemplates.length}, LocomotorTemplate: ${locomotors.length}, LocomotorSet: ${locomotorSets.length}, UnitAbilityButtonTemplate: ${buttonTemplates.length}, ButtonStateData: ${singleStateData.length + doubleStateData.length}`)
|
||
|
||
if (showMessage) {
|
||
if (addedCount > 0) {
|
||
warningMsg.value = `成功解析 ${totalTags} 个标签,新增 ${addedCount} 个标签到侧边栏`
|
||
} else {
|
||
warningMsg.value = `成功解析 ${totalTags} 个标签,所有标签已存在`
|
||
}
|
||
errorMsg.value = ''
|
||
}
|
||
|
||
// 更新最后修改时间
|
||
lastModified.value = new Date().toLocaleString()
|
||
|
||
return totalTags
|
||
} catch (error) {
|
||
if (showMessage) {
|
||
errorMsg.value = 'XML解析错误: ' + error.message
|
||
warningMsg.value = ''
|
||
}
|
||
return 0
|
||
}
|
||
}
|
||
|
||
// 方法
|
||
const addEntry = () => {
|
||
const trimmed = newEntry.value.trim()
|
||
if (!trimmed.startsWith('<LogicCommand') &&
|
||
!trimmed.startsWith('<LogicCommandSet') &&
|
||
!trimmed.startsWith('<SpecialPower') &&
|
||
!trimmed.startsWith('<LocomotorTemplate') &&
|
||
!trimmed.startsWith('<LocomotorSet') &&
|
||
!trimmed.startsWith('<UnitAbilityButtonTemplate') &&
|
||
!trimmed.startsWith('<ButtonSingleStateData') &&
|
||
!trimmed.startsWith('<ButtonDoubleStateData')) {
|
||
errorMsg.value = '只能添加 LogicCommand、LogicCommandSet、SpecialPower、LocomotorTemplate、LocomotorSet、UnitAbilityButtonTemplate 或 ButtonStateData 标签'
|
||
return
|
||
}
|
||
|
||
try {
|
||
const parser = new DOMParser()
|
||
const doc = parser.parseFromString(trimmed, 'application/xml')
|
||
const errorNode = doc.querySelector('parsererror')
|
||
if (errorNode) throw new Error()
|
||
|
||
// 检查新条目中的ID
|
||
const idMatch = trimmed.match(/id=["']([^"']+)["']/)
|
||
if (!idMatch || !idMatch[1]) {
|
||
errorMsg.value = '标签必须包含 id 属性'
|
||
return
|
||
}
|
||
|
||
// 检查现有条目中的重复ID
|
||
const newId = idMatch[1]
|
||
const allEntries = xmlContent.value.split('\n').join(' ') + ' ' + trimmed
|
||
const idRegex = /id=["']([^"']+)["']/g
|
||
const ids = []
|
||
let match
|
||
while ((match = idRegex.exec(allEntries)) !== null) {
|
||
ids.push(match[1])
|
||
}
|
||
|
||
const duplicateIds = ids.filter((id, index) => ids.indexOf(id) !== index)
|
||
if (duplicateIds.includes(newId)) {
|
||
errorMsg.value = `ID "${newId}" 已经存在,请使用唯一的ID`
|
||
return
|
||
}
|
||
|
||
entries.value.push(trimmed)
|
||
updateContent()
|
||
newEntry.value = ''
|
||
errorMsg.value = ''
|
||
warningMsg.value = '条目添加成功!'
|
||
formatXml()
|
||
} catch (e) {
|
||
errorMsg.value = 'XML 标签不合法,请检查格式'
|
||
}
|
||
}
|
||
|
||
const updateContent = () => {
|
||
const joined = entries.value.join('\n')
|
||
xmlContent.value = `${header.value}\n${joined}\n${footer.value}`
|
||
lastModified.value = new Date().toLocaleString()
|
||
}
|
||
|
||
const formatXml = () => {
|
||
if (!xmlContent.value.trim()) {
|
||
errorMsg.value = 'XML内容不能为空'
|
||
warningMsg.value = ''
|
||
return
|
||
}
|
||
try {
|
||
let content = xmlContent.value.trim()
|
||
|
||
// 检查是否已经包含完整的XML结构
|
||
const hasXmlDeclaration = content.includes('<?xml version="1.0" encoding="utf-8"?>')
|
||
const hasAssetDeclaration = content.includes('<AssetDeclaration xmlns="uri:ea.com:eala:asset">')
|
||
|
||
if (hasXmlDeclaration && hasAssetDeclaration) {
|
||
// 用户输入的是完整XML,直接格式化整个内容
|
||
const parser = new DOMParser()
|
||
const xml = parser.parseFromString(content, 'application/xml')
|
||
const pretty = prettify(xml.documentElement, 0)
|
||
xmlContent.value = `<?xml version="1.0" encoding="utf-8"?>\n${pretty}`
|
||
} else {
|
||
// 用户输入的是片段,添加header和footer
|
||
const xmlToFormat = `${header.value}\n${content}\n${footer.value}`
|
||
const parser = new DOMParser()
|
||
const xml = parser.parseFromString(xmlToFormat, 'application/xml')
|
||
const pretty = prettify(xml.documentElement, 0)
|
||
xmlContent.value = `${header.value}\n${pretty}\n${footer.value}`
|
||
}
|
||
|
||
errorMsg.value = ''
|
||
warningMsg.value = 'XML格式化成功!'
|
||
} catch (e) {
|
||
errorMsg.value = '无法格式化,请检查 XML 内容'
|
||
warningMsg.value = ''
|
||
}
|
||
}
|
||
|
||
const prettify = (node, level) => {
|
||
let indent = ' '.repeat(level)
|
||
let output = `${indent}<${node.nodeName}`
|
||
|
||
for (let attr of node.attributes) {
|
||
output += ` ${attr.name}="${attr.value}"`
|
||
}
|
||
if (node.childNodes.length === 0) {
|
||
return output + `/>`
|
||
} else {
|
||
output += `>`
|
||
let children = Array.from(node.childNodes).filter(n => n.nodeType === 1)
|
||
if (children.length > 0) {
|
||
output += `\n` + children.map(c => prettify(c, level + 1)).join('\n') + `\n${indent}`
|
||
} else {
|
||
let text = node.textContent.trim()
|
||
output += text
|
||
}
|
||
return output + `</${node.nodeName}>`
|
||
}
|
||
}
|
||
|
||
const validateXml = () => {
|
||
errorMsg.value = ''
|
||
warningMsg.value = ''
|
||
|
||
// 检查内容是否为空
|
||
if (!xmlContent.value.trim()) {
|
||
errorMsg.value = 'XML内容不能为空'
|
||
return false
|
||
}
|
||
|
||
// 检查头部和尾部
|
||
const hasHeader = xmlContent.value.includes(header.value)
|
||
const hasFooter = xmlContent.value.includes(footer.value)
|
||
|
||
if (!hasHeader && !hasFooter) {
|
||
errorMsg.value = '缺少XML文件头和文件尾'
|
||
return false
|
||
}
|
||
|
||
if (!hasHeader) {
|
||
errorMsg.value = '缺少XML文件头'
|
||
return false
|
||
}
|
||
|
||
if (!hasFooter) {
|
||
errorMsg.value = '缺少XML文件尾'
|
||
return false
|
||
}
|
||
|
||
// 检查重复的头部/尾部
|
||
const headerCount = (xmlContent.value.match(new RegExp(header.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length
|
||
const footerCount = (xmlContent.value.match(new RegExp(footer.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length
|
||
|
||
if (headerCount > 1) {
|
||
errorMsg.value = 'XML文件头重复'
|
||
return false
|
||
}
|
||
|
||
if (footerCount > 1) {
|
||
errorMsg.value = 'XML文件尾重复'
|
||
return false
|
||
}
|
||
|
||
// 检查重复的ID
|
||
try {
|
||
const parser = new DOMParser()
|
||
const xmlDoc = parser.parseFromString(xmlContent.value, 'application/xml')
|
||
|
||
const commands = xmlDoc.getElementsByTagName('LogicCommand')
|
||
const commandSets = xmlDoc.getElementsByTagName('LogicCommandSet')
|
||
const specialPowers = xmlDoc.getElementsByTagName('SpecialPower')
|
||
const locomotorTemplates = xmlDoc.getElementsByTagName('LocomotorTemplate')
|
||
const locomotorSets = xmlDoc.getElementsByTagName('LocomotorSet')
|
||
|
||
const allElements = [...commands, ...commandSets, ...specialPowers, ...locomotorTemplates, ...locomotorSets]
|
||
const ids = []
|
||
|
||
for (let element of allElements) {
|
||
const id = element.getAttribute('id')
|
||
if (!id) {
|
||
errorMsg.value = `发现缺少id属性的标签: ${element.tagName}`
|
||
return false
|
||
}
|
||
if (ids.includes(id)) {
|
||
errorMsg.value = `发现重复的id: ${id}`
|
||
return false
|
||
}
|
||
ids.push(id)
|
||
}
|
||
|
||
warningMsg.value = `XML验证通过!共找到 ${allElements.length} 个有效标签。`
|
||
return true
|
||
} catch (e) {
|
||
errorMsg.value = 'XML解析错误: ' + e.message
|
||
return false
|
||
}
|
||
}
|
||
|
||
const download = () => {
|
||
if (!validateXml()) {
|
||
return
|
||
}
|
||
|
||
const blob = new Blob([xmlContent.value], { type: 'application/xml' })
|
||
const link = document.createElement('a')
|
||
link.href = URL.createObjectURL(blob)
|
||
link.download = 'config.xml'
|
||
link.click()
|
||
}
|
||
|
||
const insertTemplate = (type) => {
|
||
let template = ''
|
||
|
||
switch (type) {
|
||
case 'LogicCommand':
|
||
template = `<LogicCommand id="NewCommand_${Date.now()}" CommandClass="DoAttackMove">
|
||
<Parameter Type="Target" />
|
||
</LogicCommand>`
|
||
break
|
||
case 'LogicCommandSet':
|
||
template = `<LogicCommandSet id="NewCommandSet_${Date.now()}">
|
||
<CommandCount>1</CommandCount>
|
||
<Command>Attack</Command>
|
||
</LogicCommandSet>`
|
||
break
|
||
case 'SpecialPower':
|
||
template = `<SpecialPower id="NewSpecialPower_${Date.now()}" CreateFX="SpecialPowerCreate">
|
||
<RadiusCursorRadius>50.0</RadiusCursorRadius>
|
||
<MaxCastRange>100.0</MaxCastRange>
|
||
<ViewObjectRange>100.0</ViewObjectRange>
|
||
<EnergyCost>50.0</EnergyCost>
|
||
<ReloadTime>10.0</ReloadTime>
|
||
</SpecialPower>`
|
||
break
|
||
case 'Locomotor':
|
||
template = `<LocomotorTemplate
|
||
id="NewLocomotor_${Date.now()}"
|
||
Surfaces="GROUND"
|
||
TurnTimeSeconds="0.2s"
|
||
TurnTimeDamagedSeconds="0.2s"
|
||
FastTurnRadius="1.0"
|
||
SlowTurnRadius="1.0"
|
||
AccelerationSeconds="0.21s"
|
||
FormationPriority="NO_FORMATION"
|
||
BrakingSeconds="0.0s"
|
||
MinTurnSpeed="0%"
|
||
BehaviorZ="NO_MOTIVE_FORCE"
|
||
Appearance="TWO_LEGS"
|
||
StickToGround="true"
|
||
AirborneTargetingHeight="30"
|
||
/>`
|
||
break
|
||
case 'LocomotorSet':
|
||
template = `<LocomotorSet
|
||
id="NewLocomotorSet_${Date.now()}"
|
||
Locomotor="DefaultLocomotor"
|
||
Condition="NORMAL"
|
||
Speed="100.0" />`
|
||
break
|
||
case 'UnitAbilityButtonTemplate':
|
||
template = `<UnitAbilityButtonTemplate id="NewButton_${Date.now()}" LogicCommand="Attack">
|
||
<ButtonImage>CommandButton_Attack</ButtonImage>
|
||
<ButtonBorderType>COMMAND</ButtonBorderType>
|
||
<StateName>NORMAL</StateName>
|
||
<StateData>ButtonStateData_Normal</StateData>
|
||
<StateName>DISABLED</StateName>
|
||
<StateData>ButtonStateData_Disabled</StateData>
|
||
</UnitAbilityButtonTemplate>`
|
||
break
|
||
case 'ButtonStateData':
|
||
template = `<ButtonSingleStateData id="NewButtonStateData_${Date.now()}">
|
||
<Frame>CommandButtonFrame</Frame>
|
||
<ButtonBorder>CommandButtonBorder</ButtonBorder>
|
||
<ButtonText>新按钮</ButtonText>
|
||
<ButtonTextColor>255 255 255 255</ButtonTextColor>
|
||
</ButtonSingleStateData>`
|
||
break
|
||
}
|
||
|
||
newEntry.value = template
|
||
errorMsg.value = ''
|
||
warningMsg.value = `已插入 ${type} 模板`
|
||
}
|
||
|
||
const clearAll = () => {
|
||
if (confirm('确定要清空所有条目吗?')) {
|
||
entries.value = []
|
||
updateContent()
|
||
errorMsg.value = ''
|
||
warningMsg.value = '已清空所有条目'
|
||
}
|
||
}
|
||
|
||
// 获取LogicCommand数量
|
||
const getLogicCommandCount = () => {
|
||
return (xmlContent.value.match(/<LogicCommand/g) || []).length
|
||
}
|
||
|
||
// 获取LogicCommandSet数量
|
||
const getLogicCommandSetCount = () => {
|
||
return (xmlContent.value.match(/<LogicCommandSet/g) || []).length
|
||
}
|
||
|
||
// 获取SpecialPower数量
|
||
const getSpecialPowerCount = () => {
|
||
return (xmlContent.value.match(/<SpecialPower/g) || []).length
|
||
}
|
||
|
||
// 获取LocomotorTemplate数量
|
||
const getLocomotorCount = () => {
|
||
return (xmlContent.value.match(/<LocomotorTemplate/g) || []).length
|
||
}
|
||
|
||
// 获取LocomotorSet数量
|
||
const getLocomotorSetCount = () => {
|
||
return (xmlContent.value.match(/<LocomotorSet/g) || []).length
|
||
}
|
||
|
||
// 获取文件大小
|
||
const getFileSize = () => {
|
||
const bytes = new Blob([xmlContent.value]).size
|
||
if (bytes < 1024) return bytes + ' B'
|
||
else if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'
|
||
else return (bytes / (1024 * 1024)).toFixed(2) + ' MB'
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.config-editor {
|
||
padding: 20px;
|
||
font-family: Arial, sans-serif;
|
||
}
|
||
|
||
.page-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.page-header h1 {
|
||
margin: 0;
|
||
color: #333;
|
||
}
|
||
|
||
.auto-load-status {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.loading-spinner {
|
||
display: inline-block;
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid rgba(0, 0, 0, 0.1);
|
||
border-left-color: #09f;
|
||
border-radius: 50%;
|
||
margin-right: 8px;
|
||
animation: spin 1s linear infinite;
|
||
}
|
||
|
||
@keyframes spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
.editor-container {
|
||
display: flex;
|
||
gap: 20px;
|
||
}
|
||
|
||
.sidebar {
|
||
width: 250px;
|
||
background-color: #f5f5f5;
|
||
border-radius: 8px;
|
||
padding: 15px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.sidebar h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 10px;
|
||
color: #333;
|
||
}
|
||
|
||
.search-box {
|
||
position: relative;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.search-box input {
|
||
width: 100%;
|
||
padding: 8px 30px 8px 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.clear-btn {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
background: none;
|
||
border: none;
|
||
font-size: 18px;
|
||
color: #999;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.tag-list {
|
||
max-height: 500px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.tag-item {
|
||
padding: 8px;
|
||
background-color: #fff;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
margin-bottom: 8px;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.tag-item:hover {
|
||
background-color: #f0f0f0;
|
||
border-color: #ccc;
|
||
}
|
||
|
||
.tag-type {
|
||
display: block;
|
||
font-weight: bold;
|
||
color: #0066cc;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.tag-id {
|
||
display: block;
|
||
font-size: 14px;
|
||
color: #333;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.tag-preview {
|
||
display: block;
|
||
font-size: 12px;
|
||
color: #666;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.input-section {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.button-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.gradient-btn {
|
||
padding: 8px 15px;
|
||
background: linear-gradient(to bottom, #4CAF50, #45a049);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.gradient-btn:hover {
|
||
background: linear-gradient(to bottom, #45a049, #409343);
|
||
}
|
||
|
||
/* 预设按钮区域样式 */
|
||
.preset-buttons {
|
||
margin-bottom: 15px;
|
||
padding: 15px;
|
||
background-color: #f5f5f5;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.preset-buttons h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 10px;
|
||
color: #333;
|
||
}
|
||
|
||
.preset-button-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.preset-btn {
|
||
padding: 8px 15px;
|
||
background: linear-gradient(to bottom, #2196F3, #1976D2);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.preset-btn:hover {
|
||
background: linear-gradient(to bottom, #1976D2, #1565C0);
|
||
}
|
||
|
||
/* 欢迎信息区域样式 */
|
||
.welcome-message {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 40px;
|
||
background-color: #f5f5f5;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
text-align: center;
|
||
}
|
||
|
||
.welcome-message h2 {
|
||
margin-bottom: 15px;
|
||
color: #333;
|
||
}
|
||
|
||
.welcome-message p {
|
||
margin-bottom: 25px;
|
||
color: #666;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.welcome-buttons {
|
||
display: flex;
|
||
gap: 15px;
|
||
}
|
||
|
||
.welcome-btn {
|
||
padding: 10px 20px;
|
||
background: linear-gradient(to bottom, #4CAF50, #45a049);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
transition: all 0.3s;
|
||
}
|
||
|
||
.welcome-btn:hover {
|
||
background: linear-gradient(to bottom, #45a049, #409343);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.message-section {
|
||
margin-bottom: 15px;
|
||
min-height: 24px;
|
||
}
|
||
|
||
.error-message {
|
||
color: #d32f2f;
|
||
margin: 0;
|
||
}
|
||
|
||
.warning-message {
|
||
color: #ff9800;
|
||
margin: 0;
|
||
}
|
||
|
||
.input-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.input-group label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.entry-textarea {
|
||
width: 100%;
|
||
height: 150px;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-family: monospace;
|
||
resize: vertical;
|
||
}
|
||
|
||
.editor-section {
|
||
flex: 2;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.editor-section h3 {
|
||
margin-top: 0;
|
||
margin-bottom: 10px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.entry-count {
|
||
font-size: 14px;
|
||
color: #666;
|
||
font-weight: normal;
|
||
}
|
||
|
||
.xml-stats {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 15px;
|
||
margin-bottom: 10px;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.xml-textarea {
|
||
flex: 1;
|
||
padding: 10px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
font-family: monospace;
|
||
resize: vertical;
|
||
min-height: 400px;
|
||
}
|
||
|
||
@media (max-width: 1200px) {
|
||
.editor-container {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.sidebar {
|
||
width: 100%;
|
||
margin-bottom: 20px;
|
||
}
|
||
}
|
||
</style>
|