DCFronted/src/views/index/ConfigEditor.vue
2025-08-01 02:05:23 +08:00

1784 lines
57 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="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">输入 LogicCommandLogicCommandSetSpecialPowerLocomotorTemplateLocomotorSetUnitAbilityButtonTemplate 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>