445 lines
15 KiB
JavaScript
445 lines
15 KiB
JavaScript
|
/**
|
|||
|
* 工具函数模块
|
|||
|
* @type {module}
|
|||
|
*/
|
|||
|
import { saveFileContent } from './file-operations.js';
|
|||
|
|
|||
|
/**
|
|||
|
* 格式化XML字符串
|
|||
|
* @param {string} xml - XML字符串
|
|||
|
* @returns {string} 格式化后的XML字符串
|
|||
|
*/
|
|||
|
export function formatXml(xml) {
|
|||
|
if (!xml || !xml.trim()) return '';
|
|||
|
|
|||
|
try {
|
|||
|
let formatted = '';
|
|||
|
const parser = new DOMParser();
|
|||
|
const doc = parser.parseFromString(xml, 'text/xml');
|
|||
|
|
|||
|
// 检查是否解析错误
|
|||
|
const parseError = doc.querySelector('parsererror');
|
|||
|
if (parseError) {
|
|||
|
console.warn('XML解析错误,返回原始文本');
|
|||
|
return xml; // 如果解析错误,返回原始文本
|
|||
|
}
|
|||
|
|
|||
|
// 使用XMLSerializer序列化为字符串
|
|||
|
const serializer = new XMLSerializer();
|
|||
|
formatted = serializer.serializeToString(doc);
|
|||
|
|
|||
|
// 简单格式化:替换'><'为'>\n<'添加换行
|
|||
|
formatted = formatted.replace(/><(?!\/)/g, '>\n<');
|
|||
|
|
|||
|
// 添加缩进
|
|||
|
const lines = formatted.split('\n');
|
|||
|
let indent = 0;
|
|||
|
formatted = lines.map(line => {
|
|||
|
if (line.match(/<\/[^>]+>/)) {
|
|||
|
// 关闭标签,减少缩进
|
|||
|
indent--;
|
|||
|
}
|
|||
|
|
|||
|
const result = ' '.repeat(Math.max(0, indent * 2)) + line;
|
|||
|
|
|||
|
if (line.match(/<[^\/][^>]*[^\/]>/) && !line.match(/<.*\/>/) && !line.match(/<[^>]+><\/[^>]+>/)) {
|
|||
|
// 开放标签,增加缩进
|
|||
|
indent++;
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}).join('\n');
|
|||
|
|
|||
|
return formatted;
|
|||
|
} catch (e) {
|
|||
|
console.error('XML格式化失败:', e);
|
|||
|
return xml;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 转义HTML字符
|
|||
|
* @param {string} unsafe - 不安全的HTML字符串
|
|||
|
* @returns {string} 转义后的HTML字符串
|
|||
|
*/
|
|||
|
export function escapeHtml(unsafe) {
|
|||
|
return unsafe
|
|||
|
.replace(/&/g, "&")
|
|||
|
.replace(/</g, "<")
|
|||
|
.replace(/>/g, ">")
|
|||
|
.replace(/"/g, """)
|
|||
|
.replace(/'/g, "'");
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 创建模态对话框
|
|||
|
* @param {HTMLElement} shadowRoot - Shadow DOM根元素
|
|||
|
* @param {string} title - 对话框标题
|
|||
|
* @param {HTMLElement|string} content - 对话框内容
|
|||
|
* @param {Function} confirmCallback - 确认按钮回调函数
|
|||
|
* @returns {Object} 对话框引用
|
|||
|
*/
|
|||
|
export function createModal(shadowRoot, title, content, confirmCallback) {
|
|||
|
// 创建模态框容器
|
|||
|
const modalOverlay = document.createElement('div');
|
|||
|
modalOverlay.className = 'modal-overlay active';
|
|||
|
modalOverlay.style.position = 'fixed';
|
|||
|
modalOverlay.style.top = '0';
|
|||
|
modalOverlay.style.left = '0';
|
|||
|
modalOverlay.style.right = '0';
|
|||
|
modalOverlay.style.bottom = '0';
|
|||
|
modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
|
|||
|
modalOverlay.style.display = 'flex';
|
|||
|
modalOverlay.style.alignItems = 'center';
|
|||
|
modalOverlay.style.justifyContent = 'center';
|
|||
|
modalOverlay.style.zIndex = '1000';
|
|||
|
|
|||
|
// 创建模态框
|
|||
|
const modal = document.createElement('div');
|
|||
|
modal.className = 'modal-content';
|
|||
|
modal.style.backgroundColor = 'white';
|
|||
|
modal.style.borderRadius = '12px';
|
|||
|
modal.style.padding = '24px';
|
|||
|
modal.style.width = '500px';
|
|||
|
modal.style.maxWidth = '90%';
|
|||
|
modal.style.maxHeight = '90vh';
|
|||
|
modal.style.overflowY = 'auto';
|
|||
|
modal.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
|
|||
|
|
|||
|
// 创建模态框头部
|
|||
|
const modalHeader = document.createElement('div');
|
|||
|
modalHeader.className = 'modal-header';
|
|||
|
modalHeader.style.display = 'flex';
|
|||
|
modalHeader.style.justifyContent = 'space-between';
|
|||
|
modalHeader.style.alignItems = 'center';
|
|||
|
modalHeader.style.marginBottom = '16px';
|
|||
|
|
|||
|
// 创建标题
|
|||
|
const modalTitle = document.createElement('h2');
|
|||
|
modalTitle.className = 'modal-title';
|
|||
|
modalTitle.textContent = title;
|
|||
|
modalTitle.style.fontSize = '20px';
|
|||
|
modalTitle.style.fontWeight = 'bold';
|
|||
|
modalTitle.style.color = '#2c3e50';
|
|||
|
modalTitle.style.margin = '0';
|
|||
|
|
|||
|
// 关闭按钮
|
|||
|
const closeBtn = document.createElement('button');
|
|||
|
closeBtn.className = 'close-btn';
|
|||
|
closeBtn.innerHTML = '×';
|
|||
|
closeBtn.style.background = 'none';
|
|||
|
closeBtn.style.border = 'none';
|
|||
|
closeBtn.style.fontSize = '24px';
|
|||
|
closeBtn.style.cursor = 'pointer';
|
|||
|
closeBtn.style.color = '#718096';
|
|||
|
closeBtn.style.lineHeight = '1';
|
|||
|
closeBtn.style.padding = '0';
|
|||
|
|
|||
|
// 添加关闭按钮事件
|
|||
|
closeBtn.onclick = () => {
|
|||
|
modalOverlay.remove();
|
|||
|
};
|
|||
|
|
|||
|
// 组装头部
|
|||
|
modalHeader.appendChild(modalTitle);
|
|||
|
modalHeader.appendChild(closeBtn);
|
|||
|
|
|||
|
// 创建表单容器
|
|||
|
const formContainer = document.createElement('form');
|
|||
|
formContainer.style.marginBottom = '0';
|
|||
|
|
|||
|
// 添加内容
|
|||
|
if (content) {
|
|||
|
// 如果内容已经是DOM元素,直接使用
|
|||
|
if (content instanceof HTMLElement) {
|
|||
|
// 为内容添加样式
|
|||
|
content.style.margin = '0 0 20px 0';
|
|||
|
formContainer.appendChild(content);
|
|||
|
} else {
|
|||
|
// 否则创建新的内容容器
|
|||
|
const contentDiv = document.createElement('div');
|
|||
|
contentDiv.innerHTML = content;
|
|||
|
contentDiv.style.margin = '0 0 20px 0';
|
|||
|
formContainer.appendChild(contentDiv);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 创建按钮容器
|
|||
|
const modalFooter = document.createElement('div');
|
|||
|
modalFooter.className = 'modal-footer';
|
|||
|
modalFooter.style.display = 'flex';
|
|||
|
modalFooter.style.justifyContent = 'flex-end';
|
|||
|
modalFooter.style.gap = '12px';
|
|||
|
modalFooter.style.marginTop = '24px';
|
|||
|
|
|||
|
// 取消按钮
|
|||
|
const cancelButton = document.createElement('button');
|
|||
|
cancelButton.className = 'btn btn-secondary';
|
|||
|
cancelButton.type = 'button';
|
|||
|
cancelButton.textContent = '取消';
|
|||
|
cancelButton.style.padding = '10px 16px';
|
|||
|
cancelButton.style.borderRadius = '6px';
|
|||
|
cancelButton.style.fontSize = '15px';
|
|||
|
cancelButton.style.cursor = 'pointer';
|
|||
|
cancelButton.style.transition = 'all 0.3s';
|
|||
|
cancelButton.style.backgroundColor = '#f8f9fa';
|
|||
|
cancelButton.style.border = '1px solid #ddd';
|
|||
|
cancelButton.style.color = '#718096';
|
|||
|
|
|||
|
// 添加取消按钮事件
|
|||
|
cancelButton.onclick = () => {
|
|||
|
modalOverlay.remove();
|
|||
|
};
|
|||
|
|
|||
|
// 确定按钮
|
|||
|
const confirmButton = document.createElement('button');
|
|||
|
confirmButton.className = 'btn btn-primary';
|
|||
|
confirmButton.type = 'button';
|
|||
|
confirmButton.textContent = '确定';
|
|||
|
confirmButton.style.padding = '10px 16px';
|
|||
|
confirmButton.style.borderRadius = '6px';
|
|||
|
confirmButton.style.fontSize = '15px';
|
|||
|
confirmButton.style.cursor = 'pointer';
|
|||
|
confirmButton.style.transition = 'all 0.3s';
|
|||
|
confirmButton.style.background = 'linear-gradient(135deg, #667eea, #764ba2)';
|
|||
|
confirmButton.style.border = 'none';
|
|||
|
confirmButton.style.color = 'white';
|
|||
|
|
|||
|
// 添加确定按钮事件
|
|||
|
confirmButton.onclick = () => {
|
|||
|
if (confirmCallback) {
|
|||
|
confirmCallback();
|
|||
|
modalOverlay.remove(); // 确保回调执行后关闭对话框
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// 点击背景关闭
|
|||
|
modalOverlay.onclick = (e) => {
|
|||
|
if (e.target === modalOverlay) {
|
|||
|
modalOverlay.remove();
|
|||
|
}
|
|||
|
};
|
|||
|
|
|||
|
// 组装底部按钮
|
|||
|
modalFooter.appendChild(cancelButton);
|
|||
|
modalFooter.appendChild(confirmButton);
|
|||
|
|
|||
|
// 组装表单
|
|||
|
formContainer.appendChild(modalFooter);
|
|||
|
|
|||
|
// 阻止表单提交默认行为
|
|||
|
formContainer.onsubmit = (e) => {
|
|||
|
e.preventDefault();
|
|||
|
if (confirmCallback) {
|
|||
|
confirmCallback();
|
|||
|
modalOverlay.remove(); // 确保回调执行后关闭对话框
|
|||
|
}
|
|||
|
return false;
|
|||
|
};
|
|||
|
|
|||
|
// 组装整个模态框
|
|||
|
modal.appendChild(modalHeader);
|
|||
|
modal.appendChild(formContainer);
|
|||
|
modalOverlay.appendChild(modal);
|
|||
|
|
|||
|
// 添加到DOM
|
|||
|
shadowRoot.appendChild(modalOverlay);
|
|||
|
|
|||
|
// 返回引用
|
|||
|
return {
|
|||
|
modal: modalOverlay,
|
|||
|
confirmButton: confirmButton
|
|||
|
};
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 标记编辑状态
|
|||
|
* @param {HTMLElement} shadowRoot - Shadow DOM根元素
|
|||
|
* @param {boolean} isEdited - 是否已编辑
|
|||
|
* @returns {boolean} 新的编辑状态
|
|||
|
*/
|
|||
|
export function markEdited(shadowRoot, isEdited) {
|
|||
|
if (!isEdited) {
|
|||
|
// 更新保存按钮样式
|
|||
|
const saveButton = shadowRoot.getElementById('saveConfig');
|
|||
|
if (saveButton) {
|
|||
|
saveButton.classList.add('modified');
|
|||
|
saveButton.title = '文件已修改,请保存';
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
return isEdited;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 重置编辑状态
|
|||
|
* @param {HTMLElement} shadowRoot - Shadow DOM根元素
|
|||
|
* @returns {boolean} 重置后的编辑状态(false)
|
|||
|
*/
|
|||
|
export function resetEditState(shadowRoot) {
|
|||
|
// 更新保存按钮样式
|
|||
|
const saveButton = shadowRoot.getElementById('saveConfig');
|
|||
|
if (saveButton) {
|
|||
|
saveButton.classList.remove('modified');
|
|||
|
saveButton.title = '';
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* 检查是否需要保存当前更改
|
|||
|
* @param {HTMLElement} component - 组件实例
|
|||
|
* @returns {Promise<boolean>} 如果可以继续操作返回true,否则返回false
|
|||
|
*/
|
|||
|
export async function checkSaveNeeded(component) {
|
|||
|
// 检查是否有未保存的修改
|
|||
|
const saveButton = component.shadowRoot.getElementById('saveConfig');
|
|||
|
const hasUnsavedChanges = saveButton && saveButton.classList.contains('modified');
|
|||
|
|
|||
|
if (hasUnsavedChanges) {
|
|||
|
// 使用自定义对话框替代简单的confirm
|
|||
|
const dialogResult = await new Promise(resolve => {
|
|||
|
// 创建模态框
|
|||
|
const modal = document.createElement('div');
|
|||
|
modal.className = 'modal';
|
|||
|
modal.id = 'saveConfirmModal';
|
|||
|
|
|||
|
modal.innerHTML = `
|
|||
|
<style>
|
|||
|
.modal {
|
|||
|
display: block;
|
|||
|
position: fixed;
|
|||
|
z-index: 1000;
|
|||
|
left: 0;
|
|||
|
top: 0;
|
|||
|
width: 100%;
|
|||
|
height: 100%;
|
|||
|
overflow: auto;
|
|||
|
background-color: rgba(0,0,0,0.4);
|
|||
|
}
|
|||
|
.modal-content {
|
|||
|
background-color: #fefefe;
|
|||
|
margin: 15% auto;
|
|||
|
padding: 20px;
|
|||
|
border: 1px solid #888;
|
|||
|
width: 80%;
|
|||
|
max-width: 400px;
|
|||
|
border-radius: 5px;
|
|||
|
position: relative;
|
|||
|
text-align: center;
|
|||
|
}
|
|||
|
.modal-title {
|
|||
|
margin-top: 0;
|
|||
|
color: #333;
|
|||
|
font-size: 18px;
|
|||
|
margin-bottom: 20px;
|
|||
|
}
|
|||
|
.modal-buttons {
|
|||
|
display: flex;
|
|||
|
justify-content: center;
|
|||
|
gap: 10px;
|
|||
|
margin-top: 20px;
|
|||
|
}
|
|||
|
.btn {
|
|||
|
padding: 8px 15px;
|
|||
|
border: none;
|
|||
|
border-radius: 4px;
|
|||
|
cursor: pointer;
|
|||
|
font-size: 14px;
|
|||
|
min-width: 80px;
|
|||
|
}
|
|||
|
.btn-save {
|
|||
|
background-color: #7986E7;
|
|||
|
color: white;
|
|||
|
}
|
|||
|
.btn-save:hover {
|
|||
|
background-color: #6875D6;
|
|||
|
}
|
|||
|
.btn-dont-save {
|
|||
|
background-color: #E77979;
|
|||
|
color: white;
|
|||
|
}
|
|||
|
.btn-dont-save:hover {
|
|||
|
background-color: #D66868;
|
|||
|
}
|
|||
|
.btn-cancel {
|
|||
|
background-color: #f5f5f5;
|
|||
|
color: #333;
|
|||
|
}
|
|||
|
.btn-cancel:hover {
|
|||
|
background-color: #e0e0e0;
|
|||
|
}
|
|||
|
</style>
|
|||
|
<div class="modal-content">
|
|||
|
<h3 class="modal-title">当前文件有未保存的更改</h3>
|
|||
|
<p>您想要保存这些更改吗?</p>
|
|||
|
<div class="modal-buttons">
|
|||
|
<button class="btn btn-save" id="saveBtn">保存</button>
|
|||
|
<button class="btn btn-dont-save" id="dontSaveBtn">不保存</button>
|
|||
|
<button class="btn btn-cancel" id="cancelBtn">取消</button>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
`;
|
|||
|
|
|||
|
document.body.appendChild(modal);
|
|||
|
|
|||
|
// 添加事件监听
|
|||
|
const saveBtn = modal.querySelector('#saveBtn');
|
|||
|
const dontSaveBtn = modal.querySelector('#dontSaveBtn');
|
|||
|
const cancelBtn = modal.querySelector('#cancelBtn');
|
|||
|
|
|||
|
const closeModal = () => {
|
|||
|
document.body.removeChild(modal);
|
|||
|
};
|
|||
|
|
|||
|
saveBtn.addEventListener('click', () => {
|
|||
|
closeModal();
|
|||
|
resolve('save');
|
|||
|
});
|
|||
|
|
|||
|
dontSaveBtn.addEventListener('click', () => {
|
|||
|
closeModal();
|
|||
|
resolve('dont-save');
|
|||
|
});
|
|||
|
|
|||
|
cancelBtn.addEventListener('click', () => {
|
|||
|
closeModal();
|
|||
|
resolve('cancel');
|
|||
|
});
|
|||
|
|
|||
|
// 点击模态窗口外部也取消
|
|||
|
modal.addEventListener('click', (event) => {
|
|||
|
if (event.target === modal) {
|
|||
|
closeModal();
|
|||
|
resolve('cancel');
|
|||
|
}
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
// 根据对话框结果执行相应操作
|
|||
|
if (dialogResult === 'save') {
|
|||
|
try {
|
|||
|
// 保存文件
|
|||
|
if (component.currentFile && component.xmlContent) {
|
|||
|
await saveFileContent(component, component.currentFile, component.xmlContent);
|
|||
|
resetEditState(component.shadowRoot);
|
|||
|
return true; // 继续执行后续操作
|
|||
|
} else {
|
|||
|
return false; // 无法保存,不继续执行
|
|||
|
}
|
|||
|
} catch (error) {
|
|||
|
console.error('保存出错:', error);
|
|||
|
return false; // 保存失败,不继续执行
|
|||
|
}
|
|||
|
} else if (dialogResult === 'dont-save') {
|
|||
|
// 不保存,但继续执行后续操作
|
|||
|
return true;
|
|||
|
} else {
|
|||
|
// 用户取消,不执行后续操作
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// 没有编辑状态,直接返回true允许继续操作
|
|||
|
return true;
|
|||
|
}
|