XNSim/XNSimHtml/components/service-config.js
2025-04-28 12:25:20 +08:00

704 lines
26 KiB
JavaScript
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.

/**
* 服务配置组件 - 主入口文件
* @type {module}
*/
import { FileOperations } from './service-config/file-operations.js';
import { XmlUtils } from './service-config/xml-utils.js';
import { CommandDialog } from './service-config/command-dialog.js';
import { DateTimeDialog } from './service-config/date-time-dialog.js';
import { VisualEditor } from './service-config/visual-editor.js';
class ServiceConfig extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
this.serviceFiles = [];
this.isActive = true; // 添加活动状态标记
}
async connectedCallback() {
this.isActive = true;
this.render();
await this.loadServiceFiles();
this.setupEventListeners();
// 延迟调用renderComplete确保DOM已更新
setTimeout(() => {
this.renderComplete();
}, 100);
}
disconnectedCallback() {
this.isActive = false;
}
// 在组件完成渲染后恢复状态
renderComplete() {
// 在DOM渲染完成后更新文件选择器
setTimeout(() => {
// 如果有选中的文件,尝试恢复它
if (this.selectedFile && (this.xmlContent || this.xmlDoc)) {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
// 确认文件在列表中
const fileExists = Array.from(fileSelector.options).some(opt => opt.value === this.selectedFile);
if (fileExists) {
// 设置选中的文件
fileSelector.value = this.selectedFile;
// 更新内容显示
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea && this.xmlDoc) {
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
VisualEditor.render(visualEditor, this.xmlDoc, () => {
this.updateXmlFromEditor();
this.markEdited();
});
// 恢复编辑状态标记
if (this.isEdited) {
this.markEdited();
}
}
} else {
console.log('ServiceConfig: 无法找到内容区域或XML文档不存在');
}
} else {
// 文件不存在,清除状态
this.selectedFile = null;
this.xmlContent = '';
this.xmlDoc = null;
this.isEdited = false;
}
} else {
console.log('ServiceConfig: 未找到文件选择器');
}
}
}, 50); // 增加延迟确保DOM已完全加载
}
// 重新激活组件的方法(当标签页重新被选中时调用)
async reactivate() {
if (this.isActive) {
return;
}
this.isActive = true;
try {
// 先重新渲染一次UI以确保Shadow DOM结构完整
this.render();
// 加载文件列表
await this.loadServiceFiles();
// 设置事件监听器
this.setupEventListeners();
// 调用renderComplete来恢复状态
this.renderComplete();
} catch (error) {
console.error('ServiceConfig: 重新激活组件时出错:', error);
}
}
async loadServiceFiles() {
this.serviceFiles = await FileOperations.loadServiceFiles();
this.updateFileSelector();
}
updateFileSelector() {
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (!fileSelector) return;
// 清空现有选项
fileSelector.innerHTML = '<option value="">-- 选择服务配置文件 --</option>';
// 按修改时间排序,最新的在前面
const sortedFiles = [...this.serviceFiles].sort((a, b) =>
new Date(b.mtime) - new Date(a.mtime)
);
// 添加文件到选择器
sortedFiles.forEach(file => {
const option = document.createElement('option');
option.value = file.path;
option.textContent = file.name;
fileSelector.appendChild(option);
});
}
async loadFileContent(filePath) {
const result = await FileOperations.loadFileContent(filePath);
if (result.error) {
const configContent = this.shadowRoot.querySelector('.config-content');
configContent.innerHTML = `
<div class="error-message">
<h3>加载失败</h3>
<p>${result.error}</p>
${result.content ? `<pre>${XmlUtils.escapeHtml(result.content)}</pre>` : ''}
</div>
`;
return;
}
this.selectedFile = filePath;
this.xmlContent = result.content;
this.xmlDoc = result.xmlDoc;
this.updateFileContent();
this.resetEditState();
}
updateFileContent() {
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (!this.xmlDoc || !this.selectedFile) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个服务配置文件查看内容</div>`;
return;
}
contentArea.innerHTML = `
<div class="editor-container">
<div class="editor-panel active" id="visualEditor"></div>
</div>
`;
const visualEditor = this.shadowRoot.querySelector('#visualEditor');
if (visualEditor) {
VisualEditor.render(visualEditor, this.xmlDoc, () => {
this.updateXmlFromEditor();
this.markEdited();
});
}
}
render() {
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
height: 100%;
overflow: auto;
padding: 16px;
box-sizing: border-box;
}
.config-container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 16px;
height: 100%;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.config-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e0e0e0;
}
.file-selector-header {
flex-grow: 1;
margin-right: 16px;
display: flex;
align-items: center;
}
.file-selector-label {
font-size: 18px;
font-weight: bold;
color: #333;
margin-right: 10px;
white-space: nowrap;
}
.file-selector-header select {
flex-grow: 1;
padding: 6px;
border-radius: 4px;
border: 1px solid #e0e0e0;
appearance: none;
padding-right: 30px;
background-image: url('assets/icons/png/chevron-down_b.png');
background-repeat: no-repeat;
background-position: right 8px center;
background-size: 14px;
}
.refresh-button {
width: 28px;
height: 28px;
cursor: pointer;
margin-left: 8px;
border: none;
background-color: transparent;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-image: url('assets/icons/png/refresh_b.png');
transition: transform 0.3s ease;
opacity: 0.7;
}
.refresh-button:hover {
opacity: 1;
}
.refresh-button.refreshing {
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.action-buttons {
display: flex;
gap: 8px;
}
.action-button {
background-color: #7986E7;
color: white;
border: none;
border-radius: 4px;
padding: 6px 12px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
}
.action-button.save-button.modified {
background-color: #E77979;
}
.action-button.save-button.modified:hover {
background-color: #D66868;
}
.action-button:hover {
background-color: #6875D6;
}
.action-button.secondary {
background-color: #f0f0f0;
color: #333;
}
.action-button.secondary:hover {
background-color: #e0e0e0;
}
.config-content {
padding: 0;
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: auto;
}
.editor-container {
height: 100%;
display: flex;
flex-direction: column;
}
.editor-panel {
flex: 1;
height: 100%;
overflow: auto;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 12px;
background-color: #f9f9f9;
}
.editor-panel.active {
display: block;
}
.no-file-selected {
color: #888;
font-style: italic;
text-align: center;
margin-top: 80px;
}
.error-message {
color: #d32f2f;
background-color: #ffebee;
padding: 10px;
border-radius: 4px;
margin-bottom: 15px;
}
</style>
<div class="config-container">
<div class="config-header">
<div class="file-selector-header">
<div class="file-selector-label">服务配置文件:</div>
<select id="fileSelector">
<option value="">-- 请选择服务配置文件 --</option>
</select>
<button class="refresh-button" id="refreshFiles" title="刷新文件列表"></button>
</div>
<div class="action-buttons">
<button class="action-button" id="newConfig">新建</button>
<button class="action-button save-button" id="saveConfig">保存</button>
<button class="action-button" id="saveAsConfig">另存为</button>
</div>
</div>
<div class="config-content">
<div id="contentArea">
<div class="no-file-selected">请选择一个服务配置文件查看内容</div>
</div>
</div>
</div>
`;
}
// 检查是否需要保存当前更改
async checkSaveNeeded() {
if (this.isEdited) {
// 使用自定义对话框替代简单的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 {
// 确保获取当前可视化编辑器的所有值更新到XML文档
this.updateXmlFromEditor();
// 保存文件
await FileOperations.saveFileContent(this.selectedFile, this.xmlContent);
this.resetEditState();
return true; // 继续执行后续操作
} catch (error) {
console.error('保存出错:', error);
alert('保存失败: ' + error.message);
return false; // 保存失败,不继续执行
}
} else if (dialogResult === 'dont-save') {
// 不保存,但继续执行后续操作
return true;
} else {
// 用户取消,不执行后续操作
return false;
}
}
// 没有编辑状态直接返回true允许继续操作
return true;
}
setupEventListeners() {
// 文件选择器
const fileSelector = this.shadowRoot.getElementById('fileSelector');
fileSelector.addEventListener('change', async (e) => {
const filePath = e.target.value;
if (filePath) {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
this.loadFileContent(filePath);
} else {
// 如果用户取消或保存失败,恢复选择器的值
fileSelector.value = this.selectedFile || '';
}
}
});
// 刷新文件列表
const refreshButton = this.shadowRoot.getElementById('refreshFiles');
refreshButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
refreshButton.classList.add('refreshing');
try {
// 重新加载文件列表
await this.loadServiceFiles();
// 清除编辑状态和内容
this.resetEditState();
// 清空内容区域
this.xmlContent = '';
this.xmlDoc = null;
const contentArea = this.shadowRoot.querySelector('#contentArea');
if (contentArea) {
contentArea.innerHTML = `<div class="no-file-selected">请选择一个服务配置文件查看内容</div>`;
}
// 清空文件选择
const fileSelector = this.shadowRoot.getElementById('fileSelector');
if (fileSelector) {
fileSelector.value = '';
}
// 重置选中的文件
this.selectedFile = null;
} catch (error) {
console.error('刷新文件列表失败:', error);
alert('刷新文件列表失败: ' + error.message);
} finally {
// 无论成功失败,都移除刷新动画
setTimeout(() => {
refreshButton.classList.remove('refreshing');
}, 500);
}
}
// 如果用户点击取消,什么也不做,保留当前内容
});
// 新建配置
const newButton = this.shadowRoot.getElementById('newConfig');
newButton.addEventListener('click', async () => {
// 检查是否需要保存当前文件
if (await this.checkSaveNeeded()) {
CommandDialog.showNewConfigDialog(async (fileName) => {
try {
const result = await FileOperations.createNewConfig(fileName);
await this.loadServiceFiles();
await this.loadFileContent(result.path);
} catch (error) {
alert('创建失败: ' + error.message);
}
});
}
});
// 保存配置
const saveButton = this.shadowRoot.getElementById('saveConfig');
saveButton.addEventListener('click', async () => {
if (!this.selectedFile) {
alert('请先选择一个文件或创建新文件');
return;
}
try {
// 确保获取当前可视化编辑器的所有值更新到XML文档
this.updateXmlFromEditor();
// 保存文件
await FileOperations.saveFileContent(this.selectedFile, this.xmlContent);
this.resetEditState();
alert('保存成功');
} catch (error) {
console.error('保存出错:', error);
alert('保存失败: ' + error.message);
}
});
// 另存为
const saveAsButton = this.shadowRoot.getElementById('saveAsConfig');
saveAsButton.addEventListener('click', () => {
if (!this.xmlContent) {
alert('没有内容可保存');
return;
}
CommandDialog.showSaveAsDialog(this.selectedFile, async (fileName) => {
try {
const result = await FileOperations.saveFileAs(fileName, this.xmlContent, this.selectedFile);
if (!result.success) {
if (result.data && result.data.code === 'FILE_EXISTS') {
if (confirm('文件已存在,是否覆盖?')) {
const retryResult = await FileOperations.saveFileAs(fileName, this.xmlContent, this.selectedFile, true);
if (retryResult.success) {
this.selectedFile = retryResult.data.path;
this.resetEditState();
await this.loadServiceFiles();
} else {
alert('保存失败: ' + retryResult.error);
}
}
} else {
alert('保存失败: ' + result.error);
}
} else {
this.selectedFile = result.data.path;
this.resetEditState();
await this.loadServiceFiles();
}
} catch (error) {
alert('保存失败: ' + error.message);
}
});
});
}
// 标记编辑状态
markEdited() {
this.isEdited = true;
// 更新保存按钮样式
const saveButton = this.shadowRoot.querySelector('.save-button');
if (saveButton) {
saveButton.classList.add('modified');
saveButton.title = '文件已修改,请保存';
}
}
// 重置编辑状态
resetEditState() {
this.isEdited = false;
// 更新保存按钮样式
const saveButton = this.shadowRoot.querySelector('.save-button');
if (saveButton) {
saveButton.classList.remove('modified');
saveButton.title = '保存';
}
}
// 更新XML
updateXmlFromEditor() {
if (!this.xmlDoc) return;
// 从可视化编辑器中获取所有输入字段的值并更新到XML文档
// 这部分逻辑已经在VisualEditor中处理这里只需序列化XML即可
// 重新序列化XML内容
const serializer = new XMLSerializer();
this.xmlContent = XmlUtils.formatXml(serializer.serializeToString(this.xmlDoc));
}
}
customElements.define('service-config', ServiceConfig);