添加了Config.xml 编辑器
This commit is contained in:
parent
247cfbd0a9
commit
04bf94df0c
64
src/api/record.js
Normal file
64
src/api/record.js
Normal file
@ -0,0 +1,64 @@
|
||||
import axiosInstance from './axiosConfig';
|
||||
|
||||
/**
|
||||
* 上传处理后的录像文件
|
||||
* 路由: /record/upload
|
||||
* 方法: POST
|
||||
* 需要admin权限
|
||||
* @param {file} file - 表单负载"file"上传
|
||||
* @returns {id<int>} - HTTP_202_ACCEPTED 录像id
|
||||
*/
|
||||
export const uploadRecord = async (file) => {
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const response = await axiosInstance.post('/record/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取录像解析状态
|
||||
* 路由: /record/{id}
|
||||
* 方法: GET
|
||||
* 需要登录
|
||||
* @param {int} id - 录像id
|
||||
* @returns {id<int>} id - 录像id
|
||||
* @returns {status<string>} status - 状态processing 处理中success 处理成功fail 处理失败
|
||||
* @returns {data<json>} data - 录像数据仅当处理成功时有值
|
||||
*/
|
||||
export const getRecordStatus = async (id) => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/record/${id}`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取单位信息
|
||||
* 路由: /unit
|
||||
* 方法: GET
|
||||
* 三个参数仅使用一个即可,如果传入多个优先选择上面的
|
||||
* @param {Object} params - 参数 { id, code, name }
|
||||
* @returns {id<string>} id
|
||||
* @returns {code<string>} code
|
||||
* @returns {name<string>} name
|
||||
*/
|
||||
export const unitInfo = async (params = {}) => {
|
||||
try {
|
||||
const response = await axiosInstance.get('/unit', { params });
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -25,7 +25,7 @@ const props = defineProps({
|
||||
})
|
||||
const emit = defineEmits(['close', 'apply'])
|
||||
|
||||
function handleClose() {
|
||||
function handleClose() {
|
||||
emit('close')
|
||||
}
|
||||
function handleApply() {
|
||||
|
112
src/components/backend/AdminChangesPwd.vue
Normal file
112
src/components/backend/AdminChangesPwd.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<div class="temp-privilege-form">
|
||||
<h2>管理员使用qq发送修改密码邮件</h2>
|
||||
<form @submit.prevent="handleSubmit">
|
||||
<div class="form-group">
|
||||
<label for="uuid">用户qq:</label>
|
||||
<input id="uuid" v-model="qq_code" type="text" placeholder="请输入用户qq" required />
|
||||
</div>
|
||||
<button class="submit-btn" type="submit" :disabled="loading">提交</button>
|
||||
</form>
|
||||
<div v-if="msg" :class="{'success-msg': msgType==='success', 'error-msg': msgType==='error'}">{{ msg }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import {getUserByInfo,requestResetPassword} from '@/api/login'
|
||||
const qq_code = ref('')
|
||||
const loading = ref(false)
|
||||
const msg = ref('')
|
||||
const msgType = ref('')
|
||||
|
||||
|
||||
async function handleSubmit() {
|
||||
msg.value = ''
|
||||
msgType.value = ''
|
||||
loading.value = true
|
||||
try {
|
||||
//api
|
||||
const user = await getUserByInfo({qq_code:qq_code.value})
|
||||
console.log(user)
|
||||
await requestResetPassword(user.uuid)
|
||||
msg.value = '发送成功!'
|
||||
msgType.value = 'success'
|
||||
qq_code.value = ''
|
||||
|
||||
} catch (e) {
|
||||
msg.value = '发送失败请稍后再试'
|
||||
msgType.value = 'error'
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.temp-privilege-form {
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
padding: 32px 24px 24px 24px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
||||
max-width: 420px;
|
||||
margin: 40px auto 0 auto;
|
||||
}
|
||||
.temp-privilege-form h2 {
|
||||
margin-bottom: 24px;
|
||||
color: #2563eb;
|
||||
text-align: center;
|
||||
}
|
||||
.form-group {
|
||||
margin-bottom: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.form-group label {
|
||||
margin-bottom: 6px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
.form-group input,
|
||||
.form-group select {
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
transition: border 0.2s;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select:focus {
|
||||
border-color: #2563eb;
|
||||
}
|
||||
.custom-exp-input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
background: #2563eb;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 10px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.submit-btn:disabled {
|
||||
background: #a5b4fc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.success-msg {
|
||||
color: #22c55e;
|
||||
text-align: center;
|
||||
margin-top: 18px;
|
||||
}
|
||||
.error-msg {
|
||||
color: #ef4444;
|
||||
text-align: center;
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
@ -111,7 +111,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import {getCaptcha, forgetPassword, requestResetPassword, getUserByInfo} from '../api/login'
|
||||
import {getCaptcha, requestResetPassword, getUserByInfo} from '../api/login'
|
||||
import ErrorDialog from './ErrorDialog.vue'
|
||||
import SuccessDialog from './SuccessDialog.vue'
|
||||
|
||||
|
@ -50,6 +50,12 @@ const routes = [
|
||||
component: () => import('@/views/weapon/WeaponMatch.vue'),
|
||||
meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
|
||||
},
|
||||
{
|
||||
path: 'configEditor',
|
||||
name: 'ConfigEditor',
|
||||
component: () => import('@/views/index/ConfigEditor.vue'),
|
||||
meta: { requiresAuth: true, requiredPrivilege: ['lv-admin','lv-mod'] }
|
||||
},
|
||||
{
|
||||
path: 'competition',
|
||||
name: 'Competition',
|
||||
|
@ -98,7 +98,6 @@ export const loginSuccess = (accessToken, userId) => {
|
||||
if (userId) {
|
||||
localStorage.setItem('user_id', userId);
|
||||
}
|
||||
|
||||
// 设置登录标志
|
||||
justLoggedIn.value = true;
|
||||
|
||||
|
@ -27,6 +27,9 @@
|
||||
<li :class="{active: currentAdminView === 'admin-edit-user-privilege'}">
|
||||
<a @click="selectAdminView('admin-edit-user-privilege')">管理员修改用户权限</a>
|
||||
</li>
|
||||
<li :class="{active: currentAdminView === 'admin-edit-user-password'}">
|
||||
<a @click="selectAdminView('admin-edit-user-password')">管理员把你密码扬了</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
@ -71,6 +74,7 @@
|
||||
<AdminEditUserPrivilege v-if="currentAdminView === 'admin-edit-user-privilege'" />
|
||||
<AffairManagement v-if="currentAdminView === 'affair-management'" />
|
||||
<TempPrivilegeReview v-if="currentAdminView === 'permission-review'" />
|
||||
<AdminChangesPwd v-if="currentAdminView === 'admin-edit-user-password'" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -83,6 +87,7 @@ import { getUserInfo } from '@/utils/jwt'
|
||||
import AdminEditUserPrivilege from '@/components/backend/AdminEditUserPrivilege.vue'
|
||||
import AffairManagement from '@/components/backend/AffairManagement.vue'
|
||||
import TempPrivilegeReview from '@/components/backend/TempPrivilegeReview.vue'
|
||||
import AdminChangesPwd from '@/components/backend/AdminChangesPwd.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const hasToken = ref(false)
|
||||
|
@ -378,6 +378,7 @@ function handlePasswordChangeError(errorMessage) {
|
||||
<span class="nav-link">在线工具</span>
|
||||
<div class="dropdown-content">
|
||||
<router-link to="/weapon-match" class="nav-link" @click.prevent="handleNavClick('/weapon-match', ['lv-admin','lv-mod'])">Weapon 匹配</router-link>
|
||||
<router-link to="/configEditor" class="nav-link" @click.prevent="handleNavClick('/configEditor', ['lv-admin','lv-mod'])">Config.xml 编辑器</router-link>
|
||||
<router-link to="/PIC2TGA" class="nav-link" @click.prevent="handleNavClick('/PIC2TGA', ['lv-admin','lv-mod','lv-map','lv-competitor'])">在线转tga工具</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
468
src/views/index/ConfigEditor.vue
Normal file
468
src/views/index/ConfigEditor.vue
Normal file
@ -0,0 +1,468 @@
|
||||
|
||||
<template>
|
||||
<div class="config-editor">
|
||||
<div class="page-header">
|
||||
<h1>Config.xml 编辑器</h1>
|
||||
</div>
|
||||
|
||||
<div class="editor-container">
|
||||
<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="insertTemplate('LogicCommand')" class="gradient-btn">预设 LogicCommand</button>
|
||||
<button @click="insertTemplate('LogicCommandSet')" class="gradient-btn">预设 LogicCommandSet</button>
|
||||
<button @click="clearAll" class="gradient-btn">清空所有</button>
|
||||
</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 标签:</label>
|
||||
<textarea
|
||||
id="newEntry"
|
||||
v-model="newEntry"
|
||||
placeholder="输入 LogicCommand 或 LogicCommandSet 标签"
|
||||
class="entry-textarea"
|
||||
></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>文件大小: {{ 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 } 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 entries = ref([])
|
||||
const newEntry = ref('')
|
||||
const xmlContent = ref('')
|
||||
const errorMsg = ref('')
|
||||
const warningMsg = ref('')
|
||||
const lastModified = ref('')
|
||||
|
||||
// 方法
|
||||
const addEntry = () => {
|
||||
const trimmed = newEntry.value.trim()
|
||||
if (!trimmed.startsWith('<LogicCommand') && !trimmed.startsWith('<LogicCommandSet')) {
|
||||
errorMsg.value = '只能添加 LogicCommand 或 LogicCommandSet 标签'
|
||||
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 = ''
|
||||
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 allElements = [...commands, ...commandSets]
|
||||
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) => {
|
||||
if (type === 'LogicCommand') {
|
||||
newEntry.value = `<LogicCommand
|
||||
Type="UNIT_BUILD"
|
||||
id="Command_ConstructSovietAntiVehicleVehicleTech2">
|
||||
<Object>SovietAntiVehicleVehicleTech2</Object>
|
||||
</LogicCommand>`
|
||||
} else if (type === 'LogicCommandSet') {
|
||||
newEntry.value = `<LogicCommandSet
|
||||
id="GenericCommandSet">
|
||||
<Cmd>Command_Stop</Cmd>
|
||||
</LogicCommandSet>`
|
||||
}
|
||||
}
|
||||
|
||||
const clearAll = () => {
|
||||
entries.value = []
|
||||
newEntry.value = ''
|
||||
xmlContent.value = ''
|
||||
errorMsg.value = ''
|
||||
warningMsg.value = ''
|
||||
lastModified.value = ''
|
||||
}
|
||||
|
||||
const getLogicCommandCount = () => {
|
||||
if (!xmlContent.value) return 0
|
||||
const matches = xmlContent.value.match(/<LogicCommand/g)
|
||||
return matches ? matches.length : 0
|
||||
}
|
||||
|
||||
const getLogicCommandSetCount = () => {
|
||||
if (!xmlContent.value) return 0
|
||||
const matches = xmlContent.value.match(/<LogicCommandSet/g)
|
||||
return matches ? matches.length : 0
|
||||
}
|
||||
|
||||
const getFileSize = () => {
|
||||
if (!xmlContent.value) return '0 B'
|
||||
const bytes = new Blob([xmlContent.value]).size
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-editor {
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.input-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.input-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #1a237e;
|
||||
}
|
||||
|
||||
.entry-textarea {
|
||||
width: 100%;
|
||||
height: 120px;
|
||||
font-family: 'Courier New', monospace;
|
||||
padding: 12px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.entry-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #1a237e;
|
||||
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.button-group .gradient-btn {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.message-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.warning-message {
|
||||
color: #856404;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffeaa7;
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.editor-section h3 {
|
||||
color: #1a237e;
|
||||
margin-bottom: 12px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.entry-count {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.xml-stats {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.xml-stats span {
|
||||
background: #f8f9fa;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.xml-textarea {
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
font-family: 'Courier New', monospace;
|
||||
padding: 16px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
transition: all 0.3s ease;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.xml-textarea:focus {
|
||||
outline: none;
|
||||
border-color: #1a237e;
|
||||
box-shadow: 0 0 0 2px rgba(26, 35, 126, 0.1);
|
||||
background: white;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.config-editor {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.editor-container {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.button-group .gradient-btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.entry-textarea,
|
||||
.xml-textarea {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
x
Reference in New Issue
Block a user