XNSim/XNSimHtml/components/service-development.js

1755 lines
76 KiB
JavaScript
Raw Normal View History

2025-04-28 12:25:20 +08:00
class ServiceDevelopment extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.services = [];
this.currentServices = [];
this.currentServiceVersions = [];
this.currentVersion = null;
this.currentView = 'services'; // 'services', 'versions', 'versionForm'
this.currentService = null;
this.isEditMode = false;
// 初始化DOM结构
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
padding: 20px;
color: #333;
box-sizing: border-box;
height: calc(100vh - 40px); /* 限制总高度 */
overflow: hidden; /* 防止整体滚动 */
}
*, *:before, *:after {
box-sizing: inherit;
}
.page-title {
color: #5c6bc0;
margin-bottom: 20px;
font-weight: 500;
font-size: 24px;
}
.subtitle {
font-size: 16px;
color: #666;
font-weight: normal;
margin-left: 10px;
}
.service-dev-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
max-height: calc(100vh - 200px); /* 调整容器高度,留出足够空间 */
overflow-y: auto;
padding-right: 10px;
padding-top: 10px; /* 添加上边距 */
width: 100%;
/* 添加以下属性确保每行高度一致 */
grid-auto-rows: 1fr;
/* 添加底部padding确保滚动时最后一行完全可见 */
padding-bottom: 20px;
margin-bottom: 50px; /* 添加底部边距,确保滚动条箭头完全可见 */
}
.service-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
/* 添加以下属性,确保卡片大小一致 */
box-sizing: border-box;
height: 100%;
width: 100%;
/* 添加顶部margin防止悬停时上边界消失 */
margin-top: 5px;
/* 添加底部padding防止内容靠近下边界 */
padding-bottom: 25px;
/* 添加底部margin确保下边界可见 */
margin-bottom: 5px;
}
.service-card:hover {
/* 减小上移距离防止超出边界 */
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.15);
}
.service-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
color: #333;
}
.service-subtitle {
font-size: 16px;
color: #666;
}
.version-card {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: transform 0.3s ease;
cursor: pointer;
height: 100%;
display: flex;
flex-direction: column;
/* 添加以下属性确保与service-card一致 */
box-sizing: border-box;
width: 100%;
margin-top: 5px;
margin-bottom: 5px;
padding-bottom: 25px;
}
.version-card:hover {
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.version-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.version-title {
font-size: 18px;
font-weight: 600;
color: #333;
}
.version-number {
font-size: 15px;
font-weight: 500;
color: #5c6bc0;
padding: 3px 8px;
background: #eef0ff;
border-radius: 4px;
}
.version-details {
display: grid;
grid-template-columns: 1fr;
gap: 6px;
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px solid #eee;
}
.version-detail {
font-size: 14px;
color: #666;
}
.label {
font-weight: 500;
color: #555;
margin-right: 5px;
}
.version-description {
font-size: 14px;
color: #666;
line-height: 1.5;
flex-grow: 1;
}
.back-button {
grid-column: 1 / -1;
cursor: pointer;
color: #667eea;
font-weight: 500;
margin-bottom: 15px;
}
.back-button:hover {
text-decoration: underline;
}
.add-version-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-size: 14px;
cursor: pointer;
margin-bottom: 20px;
}
.add-version-button:hover {
background-color: #5c6bc0;
}
.versions-grid {
display: grid;
grid-column: 1 / -1;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
grid-auto-rows: 1fr; /* 确保每行高度一致 */
width: 100%; /* 确保宽度占满整个容器 */
padding-bottom: 20px; /* 底部边距 */
margin-bottom: 30px; /* 增加底部边距,确保滚动条完全可见 */
}
.services-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
width: 100%;
padding-bottom: 20px;
margin-bottom: 30px; /* 增加底部边距,确保滚动条完全可见 */
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-actions {
margin-top: 20px;
text-align: right;
}
.save-button {
background-color: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 10px 20px;
font-size: 14px;
cursor: pointer;
}
.save-button:hover {
background-color: #5c6bc0;
}
.version-form {
grid-column: 1 / -1;
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
width: 100%;
}
.loading {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
}
.error-state {
text-align: center;
padding: 40px;
color: #e74c3c;
grid-column: 1 / -1;
}
.retry-btn {
background: #667eea;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
font-size: 14px;
margin-top: 15px;
}
.empty-state {
text-align: center;
padding: 40px;
color: #666;
grid-column: 1 / -1;
margin-top: 20px;
background-color: #f9fafb;
border-radius: 8px;
border: 1px dashed #e2e8f0;
}
/* 新建版本卡片样式 */
.new-version-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8fafc;
border: 1px solid #cbd5e1;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
transition: all 0.3s ease;
/* 添加以下属性确保与其他卡片大小一致 */
box-sizing: border-box;
height: 100%;
width: 100%;
margin-top: 5px;
margin-bottom: 5px;
padding-bottom: 25px;
min-height: 180px;
}
.new-version-card:hover {
background-color: #f1f5f9;
border-color: #94a3b8;
transform: translateY(-3px);
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.12);
}
.new-version-icon {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
height: 50px;
background-color: #eef2ff;
border-radius: 50%;
margin-bottom: 12px;
}
.new-version-icon svg {
width: 25px;
height: 25px;
fill: #6366f1;
}
.new-version-title {
font-size: 16px;
font-weight: 600;
color: #4f46e5;
margin-bottom: 5px;
}
.new-version-description {
font-size: 13px;
color: #64748b;
line-height: 1.4;
margin-bottom: 0;
}
/* 新建服务卡片样式 */
.new-service-card {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
background-color: #f8fafc;
border: 1px solid #cbd5e1;
padding: 30px 20px;
transition: all 0.3s ease;
/* 确保内容垂直居中,不会太靠近底部 */
min-height: 180px;
/* 添加以下属性确保与其他卡片大小一致 */
box-sizing: border-box;
height: 100%;
width: 100%;
/* 添加顶部margin防止悬停时上边界消失 */
margin-top: 5px;
/* 添加底部margin确保下边界可见 */
margin-bottom: 5px;
}
.new-service-card:hover {
background-color: #eef2ff;
border-color: #818cf8;
/* 修改transform值减少上移距离防止超出边界 */
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(79, 70, 229, 0.15);
}
</style>
<div class="service-development">
<h2 class="page-title">服务列表</h2>
<div class="service-dev-container">
<div class="loading">正在加载数据...</div>
</div>
</div>
`;
// 开始初始化数据
this.init();
}
async init() {
try {
const response = await fetch('/api/services');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.services = await response.json();
this.renderServices();
} catch (error) {
console.error('初始化服务开发页面失败:', error);
this.renderError('加载服务列表失败,请稍后重试。');
}
}
async fetchServiceVersions(className, serviceName) {
try {
const response = await fetch(`/api/service-versions/${encodeURIComponent(className)}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
this.currentServiceVersions = await response.json();
this.currentService = { className, name: serviceName };
this.renderServiceVersions();
} catch (error) {
console.error(`获取服务${className}的版本数据失败:`, error);
this.renderError(error.message);
}
}
async saveServiceVersion(versionData) {
try {
// 确保 CmdList 和 OtherParam 是有效的 JSON 字符串
if (versionData.CmdList) {
try {
const cmdList = JSON.parse(versionData.CmdList);
if (!Array.isArray(cmdList)) {
throw new Error('CmdList 必须是数组');
}
// 验证每个命令对象的格式
cmdList.forEach(cmd => {
if (!cmd.Name || !cmd.Description || !cmd.Call) {
throw new Error('每个命令必须包含 Name、Description 和 Call 字段');
}
});
} catch (e) {
throw new Error(`CmdList 格式错误: ${e.message}`);
}
} else {
versionData.CmdList = '[]';
}
if (versionData.OtherParam) {
try {
JSON.parse(versionData.OtherParam);
} catch (e) {
throw new Error(`OtherParam 格式错误: ${e.message}`);
}
} else {
versionData.OtherParam = '{}';
}
2025-04-28 12:25:20 +08:00
const response = await fetch('/api/service-versions', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(versionData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
}
const result = await response.json();
return result;
} catch (error) {
console.error('保存服务版本失败:', error);
throw error;
}
}
renderServices() {
this.currentView = 'services';
// 更新标题
const title = this.shadowRoot.querySelector('.page-title');
if (title) {
title.textContent = '服务列表';
}
const container = this.shadowRoot.querySelector('.service-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
// 清空容器并重置样式
container.innerHTML = '';
container.style = ''; // 清除所有行内样式
// 重新应用基本样式
Object.assign(container.style, {
display: 'block', // 改为块级元素
width: '100%',
maxHeight: 'calc(100vh - 200px)', // 调整高度,留出滚动条箭头空间
overflowY: 'auto',
paddingRight: '10px',
paddingTop: '10px',
paddingBottom: '20px',
marginBottom: '50px' // 添加底部边距,确保滚动条箭头可见
});
// 创建服务网格
const servicesGrid = document.createElement('div');
servicesGrid.className = 'services-grid';
// 确保使用网格布局
Object.assign(servicesGrid.style, {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
gap: '20px',
width: '100%',
paddingBottom: '20px'
});
// 添加新建服务卡片
const newServiceCard = document.createElement('div');
newServiceCard.className = 'service-card new-service-card';
newServiceCard.innerHTML = `
<div style="display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; background-color: #eef2ff; border-radius: 50%; margin-bottom: 12px;">
<svg viewBox="0 0 24 24" width="25" height="25" fill="#6366f1">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</div>
<div class="service-title" style="text-align: center; color: #4f46e5;">添加新服务</div>
<div class="service-subtitle" style="text-align: center;">创建一个新的服务定义</div>
`;
newServiceCard.addEventListener('click', () => this.showServiceCreationDialog());
servicesGrid.appendChild(newServiceCard);
if (this.services.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.style.gridColumn = '1 / -1';
emptyState.textContent = '未找到服务数据';
servicesGrid.appendChild(emptyState);
} else {
// 添加从后端获取的服务卡片
this.services.forEach(service => {
const card = document.createElement('div');
card.className = 'service-card';
card.innerHTML = `
<div class="service-title">${service.ServiceName}</div>
<div class="service-subtitle">中文名称: ${service.ServiceName_CN || '无'}</div>
<div class="service-detail"><span class="label">类名:</span> ${service.ClassName}</div>
<div class="service-detail"><span class="label">描述:</span> ${service.Description || ''}</div>
`;
card.addEventListener('click', () => this.fetchServiceVersions(service.ClassName, service.ServiceName));
servicesGrid.appendChild(card);
});
}
// 将服务网格添加到容器
container.appendChild(servicesGrid);
}
renderServiceVersions() {
this.currentView = 'versions';
// 更新标题为服务名称
const title = this.shadowRoot.querySelector('.page-title');
title.textContent = this.currentService.name;
const container = this.shadowRoot.querySelector('.service-dev-container');
// 清空容器并重置样式
container.innerHTML = '';
container.style = ''; // 清除所有行内样式
// 重新应用基本样式
Object.assign(container.style, {
display: 'block', // 改为块级元素
width: '100%',
maxHeight: 'calc(100vh - 200px)', // 调整高度,留出滚动条箭头空间
overflowY: 'auto',
paddingRight: '10px',
paddingTop: '10px',
paddingBottom: '20px',
marginBottom: '50px' // 添加底部边距,确保滚动条箭头可见
});
// 添加返回按钮(作为独立元素)
const backButton = document.createElement('div');
backButton.className = 'back-button';
backButton.innerHTML = `<span>← 返回服务列表</span>`;
backButton.addEventListener('click', () => this.renderServices());
container.appendChild(backButton);
// 创建版本卡片容器
const versionsGrid = document.createElement('div');
versionsGrid.className = 'versions-grid';
// 确保使用网格布局
Object.assign(versionsGrid.style, {
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(250px, 1fr))',
gap: '20px',
width: '100%',
paddingBottom: '20px',
marginBottom: '30px' // 增加底部边距,确保滚动条箭头可见
});
// 添加新建服务版本卡片(始终是第一个)
const newVersionCard = document.createElement('div');
newVersionCard.className = 'version-card new-version-card';
newVersionCard.innerHTML = `
<div class="new-version-icon">
<svg viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</svg>
</div>
<div class="new-version-title">新建服务版本</div>
<div class="new-version-description">为${this.currentService.name}创建新的版本</div>
`;
newVersionCard.addEventListener('click', () => this.createNewVersion());
versionsGrid.appendChild(newVersionCard);
if (this.currentServiceVersions.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.style.gridColumn = '1 / -1';
emptyState.textContent = '该服务没有版本信息,点击"新建服务版本"创建第一个版本';
versionsGrid.appendChild(emptyState);
} else {
// 对版本进行排序 - 按版本号从高到低排序
const sortedVersions = [...this.currentServiceVersions].sort((a, b) => {
return this.compareVersions(b.Version, a.Version);
});
// 添加从后端获取的版本卡片
sortedVersions.forEach(version => {
const card = document.createElement('div');
card.className = 'version-card';
// 获取显示名称 - 优先使用Name字段如果没有则使用当前服务名称
const displayName = version.Name || version.ServiceName || this.currentService.name;
card.innerHTML = `
<div class="version-header">
<div class="version-title">${displayName}</div>
<div class="version-number">v${version.Version || '未知版本'}</div>
</div>
<div class="version-details">
<div class="version-detail"><span class="label">作者:</span> ${version.Author || ''}</div>
<div class="version-detail"><span class="label">创建时间:</span> ${version.CreatTime || ''}</div>
<div class="version-detail"><span class="label">修改时间:</span> ${version.ChangeTime || ''}</div>
</div>
<div class="version-description">${version.Description || '无描述'}</div>
`;
card.addEventListener('click', () => this.showVersionEditor(version));
versionsGrid.appendChild(card);
});
}
// 将版本网格添加到容器
container.appendChild(versionsGrid);
}
// 比较版本号的方法
compareVersions(versionA, versionB) {
if (!versionA) return -1;
if (!versionB) return 1;
const partsA = versionA.toString().split('.').map(Number);
const partsB = versionB.toString().split('.').map(Number);
// 确保两个数组长度相同,以便比较
const maxLength = Math.max(partsA.length, partsB.length);
for (let i = 0; i < maxLength; i++) {
// 处理长度不同的情况用0填充
const partA = i < partsA.length ? partsA[i] : 0;
const partB = i < partsB.length ? partsB[i] : 0;
if (partA > partB) return 1;
if (partA < partB) return -1;
}
return 0; // 版本号相同
}
renderError(message) {
// 更新当前视图状态
this.currentView = 'error';
const container = this.shadowRoot.querySelector('.service-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
// 清空容器并重置样式
container.innerHTML = '';
container.style = ''; // 清除所有行内样式
// 重新应用基本样式
Object.assign(container.style, {
display: 'block',
width: '100%',
maxHeight: 'calc(100vh - 200px)', // 调整高度,留出滚动条箭头空间
overflowY: 'auto',
paddingRight: '10px',
paddingTop: '10px',
paddingBottom: '20px',
marginBottom: '50px' // 添加底部边距,确保滚动条箭头可见
});
// 创建错误容器
const errorContainer = document.createElement('div');
errorContainer.className = 'error-state';
errorContainer.innerHTML = `
<p>加载数据失败: ${message}</p>
<button class="retry-btn">重试</button>
`;
// 添加到容器
container.appendChild(errorContainer);
const retryBtn = errorContainer.querySelector('.retry-btn');
if (retryBtn) {
retryBtn.addEventListener('click', () => {
if (this.currentView === 'services') {
this.init();
} else if (this.currentView === 'versions' && this.currentService) {
this.fetchServiceVersions(this.currentService.className, this.currentService.name);
} else {
this.init();
}
});
}
}
// 组件被重新激活时调用
reactivate() {
2025-04-28 12:25:20 +08:00
}
showVersionEditor(versionData) {
this.currentView = 'versionEditor';
// 确保 CmdList 是有效的数组
if (versionData.CmdList) {
try {
if (typeof versionData.CmdList === 'string') {
// 如果已经是字符串,尝试解析它
const parsed = JSON.parse(versionData.CmdList);
versionData.CmdList = Array.isArray(parsed) ? parsed : [];
} else if (!Array.isArray(versionData.CmdList)) {
// 如果不是数组,初始化为空数组
versionData.CmdList = [];
}
} catch (e) {
console.error('解析 CmdList 失败:', e);
versionData.CmdList = [];
}
} else {
versionData.CmdList = [];
}
2025-04-28 12:25:20 +08:00
this.currentVersion = versionData;
this.isEditMode = versionData && versionData.Version ? true : false;
const container = this.shadowRoot.querySelector('.service-dev-container');
if (!container) {
console.error('找不到容器元素');
return;
}
// 更新标题
const title = this.shadowRoot.querySelector('.page-title');
if (title) {
title.textContent = this.isEditMode
? `编辑版本: ${versionData.Name || this.currentService.name} (v${versionData.Version})`
: `${this.currentService.name} 创建新版本`;
}
container.innerHTML = '';
// 添加工具栏按钮
const toolbarDiv = document.createElement('div');
toolbarDiv.className = 'editor-toolbar';
toolbarDiv.style.cssText = 'display: flex; justify-content: space-between; margin-bottom: 20px; grid-column: 1 / -1; width: 100%;';
// 添加返回按钮
const backButton = document.createElement('div');
backButton.className = 'back-button';
backButton.innerHTML = `<span>← 返回版本列表</span>`;
backButton.style.cssText = 'margin: 0; cursor: pointer; color: #667eea; font-weight: 500;';
backButton.addEventListener('click', () => {
this.fetchServiceVersions(this.currentService.className, this.currentService.name);
});
// 添加按钮组
const actionsDiv = document.createElement('div');
actionsDiv.className = 'toolbar-actions';
actionsDiv.style.cssText = 'display: flex; gap: 10px;';
// 创建各种按钮
const buttonConfigs = [
{
text: '刷新',
color: '#38a169',
action: async () => {
try {
// 保存当前版本ID用于刷新后重新定位
const versionId = this.currentVersion.Version;
// 先刷新版本列表
await this.fetchServiceVersions(this.currentService.className, this.currentService.name);
// 然后找到当前编辑的版本并重新打开
if (versionId) {
const refreshedVersion = this.currentServiceVersions.find(v => v.Version === versionId);
if (refreshedVersion) {
this.showVersionEditor(refreshedVersion);
} else {
alert('未能找到当前版本,可能已被删除');
// 如果找不到当前版本,回到版本列表
this.renderServiceVersions();
}
}
} catch (error) {
console.error('刷新版本数据失败:', error);
alert(`刷新失败: ${error.message}`);
}
}
},
{
text: '保存',
color: '#3182ce',
action: () => {
try {
// 获取表单元素
const form = this.shadowRoot.querySelector('#versionEditorForm');
if (form) {
// 触发提交事件
form.dispatchEvent(new Event('submit'));
} else {
console.error('找不到表单元素');
alert('保存失败: 找不到表单元素');
}
} catch (error) {
console.error('保存时发生错误:', error);
alert(`保存失败: ${error.message}`);
}
}
},
{ text: '生成代码', color: '#805ad5', action: () => alert('生成代码功能即将上线') },
{ text: '编辑代码', color: '#d69e2e', action: () => alert('编辑代码功能即将上线') },
{ text: '服务编译', color: '#dd6b20', action: () => alert('服务编译功能即将上线') },
{ text: '服务提交', color: '#e53e3e', action: () => alert('服务提交功能即将上线') }
];
buttonConfigs.forEach(config => {
const button = document.createElement('button');
button.className = 'toolbar-button';
button.textContent = config.text;
button.style.cssText = `background-color: ${config.color}; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer; font-weight: 500;`;
button.addEventListener('click', config.action);
actionsDiv.appendChild(button);
});
toolbarDiv.appendChild(backButton);
toolbarDiv.appendChild(actionsDiv);
container.appendChild(toolbarDiv);
// 创建表单容器 - 使用网格布局
const formContainer = document.createElement('div');
formContainer.className = 'form-container';
formContainer.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 20px; grid-column: 1 / -1; width: 100%;';
// 创建表单
const form = document.createElement('form');
form.className = 'version-form';
form.id = 'versionEditorForm';
form.style.cssText = 'background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); padding: 20px; grid-column: 1 / -1;';
// 创建两列布局的基本信息部分
const basicInfoSection = document.createElement('div');
basicInfoSection.className = 'form-section basic-info';
basicInfoSection.style.cssText = 'display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;';
// 左列 - 基本信息
const leftColumn = document.createElement('div');
leftColumn.className = 'form-column';
leftColumn.innerHTML = `
<div class="form-group">
<label for="className">类名 (ClassName)*</label>
<input type="text" id="className" name="ClassName" value="${this.currentVersion.ClassName || this.currentService.className}" readonly title="服务类名,不可修改" required>
</div>
<div class="form-group">
<label for="name">名称 (Name)*</label>
<input type="text" id="name" name="Name" value="${this.currentVersion.Name || this.currentService.name}" readonly title="服务名称,不可修改" required>
</div>
<div class="form-group">
<label for="version">版本 (Version)*</label>
<input type="text" id="version" name="Version" value="${this.currentVersion.Version || '1.0.0'}" pattern="[0-9.]+" title="版本号只能包含数字和小数点例如1.0.0" required>
</div>
`;
// 右列 - 描述和其他信息
const rightColumn = document.createElement('div');
rightColumn.className = 'form-column';
rightColumn.innerHTML = `
<div class="form-group">
<label for="description">描述 (Description)</label>
<input type="text" id="description" name="Description" value="${this.currentVersion.Description || ''}" title="服务版本的详细描述,包括主要功能和改进">
</div>
2025-04-28 12:25:20 +08:00
<div class="form-group">
<label for="author">作者 (Author)*</label>
<input type="text" id="author" name="Author" value="${this.currentVersion.Author || ''}" title="服务版本的作者姓名" required>
</div>
<div class="form-group">
<label for="changeTime">修改时间 (ChangeTime)*</label>
<div style="display: flex; gap: 8px;">
<input type="text" id="changeTime" name="ChangeTime" value="${this.currentVersion.ChangeTime || this.getCurrentDateTime()}" title="最后修改时间" style="flex: 1;" required>
<button type="button" id="dateTimePickerButton" style="background-color: #667eea; color: white; border: none; border-radius: 4px; width: 40px; display: flex; justify-content: center; align-items: center;">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
</button>
</div>
</div>
`;
basicInfoSection.appendChild(leftColumn);
basicInfoSection.appendChild(rightColumn);
// 代码路径
const codePathSection = document.createElement('div');
codePathSection.className = 'form-section code-path';
codePathSection.style.cssText = 'margin-top: 20px; border-top: 1px solid #e2e8f0; padding-top: 20px;';
const codePathGroup = document.createElement('div');
codePathGroup.className = 'form-group';
codePathGroup.innerHTML = `
<label for="codePath">代码路径 (CodePath)</label>
<input type="text" id="codePath" name="CodePath" value="${this.currentVersion.CodePath || ''}" title="服务代码文件的路径">
`;
codePathSection.appendChild(codePathGroup);
// 添加 CmdList 和 OtherParam 字段
const jsonFieldsSection = document.createElement('div');
jsonFieldsSection.className = 'form-section json-fields';
jsonFieldsSection.style.cssText = 'margin-top: 20px; border-top: 1px solid #e2e8f0; padding-top: 20px;';
// CmdList 字段
const cmdListGroup = document.createElement('div');
cmdListGroup.className = 'form-group';
// 确保 CmdList 是有效的 JSON 字符串
let cmdListValue = '[]';
if (this.currentVersion.CmdList) {
try {
if (typeof this.currentVersion.CmdList === 'string') {
// 如果已经是字符串,验证它是否是有效的 JSON
JSON.parse(this.currentVersion.CmdList);
cmdListValue = this.currentVersion.CmdList;
} else if (Array.isArray(this.currentVersion.CmdList)) {
// 如果是数组,转换为 JSON 字符串
cmdListValue = JSON.stringify(this.currentVersion.CmdList);
}
} catch (e) {
console.error('CmdList 格式错误:', e);
cmdListValue = '[]';
}
}
2025-04-28 12:25:20 +08:00
cmdListGroup.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<label>指令列表 (CmdList)</label>
<button type="button" id="addCmdBtn" style="background-color: #667eea; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">
添加指令
</button>
</div>
<div class="cmd-list-container">
<table id="cmdListTable" style="width: 100%; border-collapse: collapse; margin-bottom: 10px;">
<thead>
<tr style="background-color: #f8fafc;">
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">名称</th>
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">描述</th>
<th style="padding: 8px; text-align: left; border: 1px solid #e2e8f0;">调用函数</th>
<th style="padding: 8px; text-align: center; border: 1px solid #e2e8f0; width: 80px;">操作</th>
</tr>
</thead>
<tbody id="cmdListBody">
<tr>
<td colspan="4" style="text-align: center; padding: 20px; color: #718096;">暂无指令参数</td>
</tr>
</tbody>
</table>
</div>
<input type="hidden" id="cmdList" name="CmdList" value="${cmdListValue}">
`;
jsonFieldsSection.appendChild(cmdListGroup);
// OtherParam 字段
const otherParamGroup = document.createElement('div');
otherParamGroup.className = 'form-group';
otherParamGroup.innerHTML = `
<label for="otherParam">其他参数 (OtherParam)</label>
<textarea id="otherParam" name="OtherParam" style="height: 100px; font-family: monospace;" title="JSON字符串包含其他参数">${this.currentVersion.OtherParam || '{}'}</textarea>
<small style="display: block; margin-top: 5px; color: #718096; font-size: 12px;">请输入有效的JSON字符串例如: {"key": "value"}</small>
2025-04-28 12:25:20 +08:00
`;
jsonFieldsSection.appendChild(otherParamGroup);
2025-04-28 12:25:20 +08:00
// 组装表单
form.appendChild(basicInfoSection);
form.appendChild(codePathSection);
form.appendChild(jsonFieldsSection);
2025-04-28 12:25:20 +08:00
formContainer.appendChild(form);
container.appendChild(formContainer);
// 为日期时间按钮添加事件监听器
setTimeout(() => {
const dateTimePickerButton = this.shadowRoot.querySelector('#dateTimePickerButton');
const changeTimeInput = this.shadowRoot.querySelector('#changeTime');
if (dateTimePickerButton && changeTimeInput) {
dateTimePickerButton.addEventListener('click', () => {
this.showDateTimeDialog(changeTimeInput);
});
}
}, 0);
// 添加表单提交事件
form.addEventListener('submit', async (e) => {
e.preventDefault();
try {
// 验证版本号格式
const versionInput = form.querySelector('#version');
if (versionInput && !/^[0-9.]+$/.test(versionInput.value)) {
throw new Error('版本号只能包含数字和小数点');
}
// 验证 CmdList 和 OtherParam 是否为有效的 JSON
const cmdListInput = form.querySelector('#cmdList');
const otherParamInput = form.querySelector('#otherParam');
try {
JSON.parse(cmdListInput.value);
} catch (e) {
throw new Error('CmdList 必须是有效的 JSON 数组字符串');
}
try {
JSON.parse(otherParamInput.value);
} catch (e) {
throw new Error('OtherParam 必须是有效的 JSON 字符串');
}
2025-04-28 12:25:20 +08:00
// 构建版本数据
const versionData = {
ClassName: form.querySelector('#className').value,
Name: form.querySelector('#name').value,
Version: form.querySelector('#version').value,
Author: form.querySelector('#author').value,
Description: form.querySelector('#description').value,
ChangeTime: form.querySelector('#changeTime').value,
CodePath: form.querySelector('#codePath').value,
CmdList: cmdListInput.value,
OtherParam: otherParamInput.value,
2025-04-28 12:25:20 +08:00
isUpdate: this.isEditMode,
originalVersion: this.isEditMode ? this.currentVersion.Version : null
};
// 验证必填字段
const requiredFields = ['ClassName', 'Name', 'Version', 'Author', 'ChangeTime'];
for (const field of requiredFields) {
if (!versionData[field]) {
throw new Error(`${field}是必填字段`);
}
}
// 保存版本数据
const result = await this.saveServiceVersion(versionData);
// 显示保存成功提示
alert(this.isEditMode ? '版本更新成功!' : '新版本创建成功!');
// 刷新版本列表
this.fetchServiceVersions(this.currentService.className, this.currentService.name);
} catch (error) {
alert(`保存失败: ${error.message}`);
}
});
// 初始化 CmdList 表格
const cmdListBody = cmdListGroup.querySelector('#cmdListBody');
const cmdListInput = cmdListGroup.querySelector('#cmdList');
const addCmdBtn = cmdListGroup.querySelector('#addCmdBtn');
// 渲染 CmdList 表格行
const renderCmdListRows = () => {
try {
const cmdListInput = cmdListGroup.querySelector('#cmdList');
if (!cmdListInput) {
console.error('找不到 CmdList 输入元素');
return;
}
let cmdList;
try {
// 如果 currentVersion.CmdList 是数组,直接使用它
if (Array.isArray(this.currentVersion.CmdList)) {
cmdList = this.currentVersion.CmdList;
cmdListInput.value = JSON.stringify(cmdList);
} else {
// 否则尝试解析输入值
cmdList = JSON.parse(cmdListInput.value);
}
} catch (e) {
console.error('解析 CmdList 失败:', e);
cmdList = [];
cmdListInput.value = '[]';
2025-04-28 12:25:20 +08:00
}
if (!Array.isArray(cmdList)) {
console.error('CmdList 不是数组:', cmdList);
cmdList = [];
cmdListInput.value = '[]';
}
if (cmdList.length === 0) {
cmdListBody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #718096;">暂无指令参数</td></tr>';
return;
}
cmdListBody.innerHTML = cmdList.map((cmd, index) => `
<tr>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-name" value="${cmd.Name || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-description" value="${cmd.Description || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0;">
<input type="text" class="cmd-call" value="${cmd.Call || ''}" style="width: 100%; border: none; padding: 4px;">
</td>
<td style="padding: 8px; border: 1px solid #e2e8f0; text-align: center;">
<button type="button" class="delete-cmd" data-index="${index}" style="background-color: #e53e3e; color: white; border: none; border-radius: 4px; padding: 4px 8px; cursor: pointer;">
删除
</button>
</td>
</tr>
`).join('');
// 添加删除按钮事件
cmdListBody.querySelectorAll('.delete-cmd').forEach(btn => {
btn.addEventListener('click', (e) => {
const index = parseInt(e.target.dataset.index);
try {
const cmdList = Array.isArray(this.currentVersion.CmdList) ?
this.currentVersion.CmdList :
JSON.parse(cmdListInput.value);
cmdList.splice(index, 1);
this.currentVersion.CmdList = cmdList;
cmdListInput.value = JSON.stringify(cmdList);
renderCmdListRows();
} catch (e) {
console.error('删除指令失败:', e);
alert('删除指令失败,请重试');
}
});
});
// 添加输入框事件
cmdListBody.querySelectorAll('input').forEach(input => {
input.addEventListener('change', () => {
try {
const cmdList = Array.isArray(this.currentVersion.CmdList) ?
this.currentVersion.CmdList :
JSON.parse(cmdListInput.value);
const row = input.closest('tr');
const index = Array.from(row.parentNode.children).indexOf(row);
const field = input.classList[0].split('-')[1];
cmdList[index][field.charAt(0).toUpperCase() + field.slice(1)] = input.value;
this.currentVersion.CmdList = cmdList;
cmdListInput.value = JSON.stringify(cmdList);
} catch (e) {
console.error('更新指令失败:', e);
alert('更新指令失败,请重试');
}
});
});
} catch (e) {
console.error('渲染指令列表失败:', e);
cmdListBody.innerHTML = '<tr><td colspan="4" style="text-align: center; padding: 20px; color: #e53e3e;">指令列表格式错误</td></tr>';
}
};
// 添加指令按钮事件
addCmdBtn.addEventListener('click', () => {
try {
let cmdList = Array.isArray(this.currentVersion.CmdList) ?
this.currentVersion.CmdList :
JSON.parse(cmdListInput.value);
// 添加新的指令
cmdList.push({ Name: '', Description: '', Call: '' });
// 更新 currentVersion 和输入框的值
this.currentVersion.CmdList = cmdList;
cmdListInput.value = JSON.stringify(cmdList);
// 重新渲染表格
renderCmdListRows();
} catch (e) {
console.error('添加指令失败:', e);
alert('添加指令失败,请检查指令列表格式');
2025-04-28 12:25:20 +08:00
}
});
// 初始渲染表格
renderCmdListRows();
2025-04-28 12:25:20 +08:00
}
/**
* 显示日期时间选择对话框
* @param {HTMLInputElement} inputElement - 输入元素
*/
showDateTimeDialog(inputElement) {
// 获取当前系统时间
const now = new Date();
// 解析当前日期和时间
let currentDate = now;
let currentTime = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}:${String(now.getSeconds()).padStart(2, '0')}`;
// 如果输入框有值,尝试解析
if (inputElement.value && inputElement.value.trim() !== '') {
try {
const parts = inputElement.value.split(' ');
if (parts.length >= 1) {
const dateParts = parts[0].split('-');
if (dateParts.length === 3) {
const year = parseInt(dateParts[0]);
const month = parseInt(dateParts[1]) - 1; // 月份从0开始
const day = parseInt(dateParts[2]);
const parsedDate = new Date(year, month, day);
// 验证日期是否有效
if (!isNaN(parsedDate.getTime())) {
currentDate = parsedDate;
}
}
}
if (parts.length >= 2) {
// 验证时间格式
const timeRegex = /^([01]\d|2[0-3]):([0-5]\d):([0-5]\d)$/;
if (timeRegex.test(parts[1])) {
currentTime = parts[1];
}
}
} catch (error) {
console.error('解析日期时间失败:', error);
// 使用系统当前时间(已在初始化时设置)
}
}
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'dateTimeModal';
modal.style.cssText = 'position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); display: flex; justify-content: center; align-items: center;';
// 获取年、月、日
const year = currentDate.getFullYear();
const month = currentDate.getMonth(); // 0-11
const day = currentDate.getDate();
// 生成日历
const daysInMonth = new Date(year, month + 1, 0).getDate();
const firstDay = new Date(year, month, 1).getDay(); // 0-60表示周日
// 生成月份选项
let monthOptions = '';
const monthNames = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'];
monthNames.forEach((name, idx) => {
monthOptions += `<option value="${idx}" ${idx === month ? 'selected' : ''}>${name}</option>`;
});
// 生成年份选项
let yearOptions = '';
const currentYear = new Date().getFullYear();
for (let y = currentYear - 10; y <= currentYear + 10; y++) {
yearOptions += `<option value="${y}" ${y === year ? 'selected' : ''}>${y}</option>`;
}
// 生成日历表格
let calendarRows = '';
let dayCount = 1;
// 添加表头
calendarRows += '<tr>';
['日', '一', '二', '三', '四', '五', '六'].forEach(dayName => {
calendarRows += `<th>${dayName}</th>`;
});
calendarRows += '</tr>';
// 计算行数
const totalCells = firstDay + daysInMonth;
const rowCount = Math.ceil(totalCells / 7);
// 添加日期行
for (let i = 0; i < rowCount; i++) {
calendarRows += '<tr>';
for (let j = 0; j < 7; j++) {
if ((i === 0 && j < firstDay) || dayCount > daysInMonth) {
calendarRows += '<td></td>';
} else {
const isToday = dayCount === day;
calendarRows += `<td class="calendar-day ${isToday ? 'selected' : ''}" data-day="${dayCount}">${dayCount}</td>`;
dayCount++;
}
}
calendarRows += '</tr>';
}
// 解析当前时间为时、分、秒
let hours = '00', minutes = '00', seconds = '00';
if (currentTime) {
const timeParts = currentTime.split(':');
if (timeParts.length >= 1) hours = timeParts[0].padStart(2, '0');
if (timeParts.length >= 2) minutes = timeParts[1].padStart(2, '0');
if (timeParts.length >= 3) seconds = timeParts[2].padStart(2, '0');
}
modal.innerHTML = `
<div class="modal-content" style="background-color: white; margin: auto; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-width: 400px; width: 100%;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div class="modal-title" style="font-size: 18px; font-weight: 600; color: #2d3748;">选择日期和时间</div>
<span class="close" style="font-size: 24px; color: #a0aec0; cursor: pointer;">&times;</span>
</div>
<div class="modal-body">
<div class="calendar-container" style="width: 100%;">
<div class="calendar-header" style="display: flex; justify-content: space-between; margin-bottom: 10px;">
<select id="calendarMonth" style="padding: 5px; border: 1px solid #e2e8f0; border-radius: 4px; flex: 1; margin-right: 10px;">${monthOptions}</select>
<select id="calendarYear" style="padding: 5px; border: 1px solid #e2e8f0; border-radius: 4px; flex: 1;">${yearOptions}</select>
</div>
<table class="calendar-table" style="width: 100%; border-collapse: collapse;">
<thead>
${calendarRows}
</thead>
</table>
</div>
<div class="time-container" style="margin-top: 15px;">
<label style="display: block; margin-bottom: 5px; color: #4a5568;">时间:</label>
<div style="display: flex; gap: 5px;">
<div style="flex: 1;">
<label for="hourInput" style="display: block; text-align: center; font-size: 12px; color: #718096;"></label>
<input type="number" id="hourInput" min="0" max="23" value="${hours}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
<div style="display: flex; align-items: center; padding-top: 15px;">:</div>
<div style="flex: 1;">
<label for="minuteInput" style="display: block; text-align: center; font-size: 12px; color: #718096;"></label>
<input type="number" id="minuteInput" min="0" max="59" value="${minutes}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
<div style="display: flex; align-items: center; padding-top: 15px;">:</div>
<div style="flex: 1;">
<label for="secondInput" style="display: block; text-align: center; font-size: 12px; color: #718096;"></label>
<input type="number" id="secondInput" min="0" max="59" value="${seconds}" style="width: 100%; padding: 8px; border: 1px solid #e2e8f0; border-radius: 4px; text-align: center;">
</div>
</div>
</div>
</div>
<div class="modal-footer" style="margin-top: 20px; text-align: right;">
<button id="cancelDateTime" style="background-color: #e2e8f0; color: #4a5568; border: none; border-radius: 4px; padding: 8px 16px; margin-right: 10px; cursor: pointer;">取消</button>
<button id="confirmDateTime" style="background-color: #4c6ef5; color: white; border: none; border-radius: 4px; padding: 8px 16px; cursor: pointer;">确定</button>
</div>
</div>
`;
// 添加样式
const style = document.createElement('style');
style.textContent = `
.calendar-day {
cursor: pointer;
padding: 8px;
text-align: center;
border: 1px solid #e2e8f0;
}
.calendar-day:hover {
background-color: #ebf4ff;
}
.calendar-day.selected {
background-color: #4c6ef5;
color: white;
}
.calendar-table th {
padding: 8px;
text-align: center;
border: 1px solid #e2e8f0;
color: #4a5568;
}
`;
// 获取shadowRoot
const shadowRoot = this.shadowRoot;
// 添加到DOM
shadowRoot.appendChild(style);
shadowRoot.appendChild(modal);
// 事件处理
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelDateTime');
const confirmBtn = modal.querySelector('#confirmDateTime');
const calendarMonth = modal.querySelector('#calendarMonth');
const calendarYear = modal.querySelector('#calendarYear');
const calendarDays = modal.querySelectorAll('.calendar-day');
const hourInput = modal.querySelector('#hourInput');
const minuteInput = modal.querySelector('#minuteInput');
const secondInput = modal.querySelector('#secondInput');
// 验证时间输入
const validateTimeInput = (input, min, max) => {
let value = parseInt(input.value);
if (isNaN(value)) value = 0;
if (value < min) value = min;
if (value > max) value = max;
input.value = value.toString().padStart(2, '0');
};
// 添加输入验证
hourInput.addEventListener('change', () => validateTimeInput(hourInput, 0, 23));
minuteInput.addEventListener('change', () => validateTimeInput(minuteInput, 0, 59));
secondInput.addEventListener('change', () => validateTimeInput(secondInput, 0, 59));
// 选择日期事件
calendarDays.forEach(cell => {
cell.addEventListener('click', (e) => {
// 移除所有选中状态
calendarDays.forEach(day => day.classList.remove('selected'));
// 添加新选中状态
e.target.classList.add('selected');
});
});
// 月份和年份变化时重新渲染日历
const updateCalendar = () => {
const selectedYear = parseInt(calendarYear.value);
const selectedMonth = parseInt(calendarMonth.value);
// 关闭当前对话框
closeModal();
// 更新日期参数并重新显示对话框
currentDate = new Date(selectedYear, selectedMonth, 1);
this.showDateTimeDialog(inputElement);
};
calendarMonth.addEventListener('change', updateCalendar);
calendarYear.addEventListener('change', updateCalendar);
const closeModal = () => {
if (modal.parentNode === shadowRoot) {
shadowRoot.removeChild(modal);
}
if (style.parentNode === shadowRoot) {
shadowRoot.removeChild(style);
}
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
confirmBtn.addEventListener('click', () => {
// 获取选中的日期
const selectedDay = modal.querySelector('.calendar-day.selected');
if (!selectedDay) {
closeModal();
return;
}
const day = selectedDay.dataset.day;
const month = parseInt(calendarMonth.value) + 1; // 月份从0开始显示时+1
const year = calendarYear.value;
// 获取时间值并确保两位数
const hours = hourInput.value.padStart(2, '0');
const minutes = minuteInput.value.padStart(2, '0');
const seconds = secondInput.value.padStart(2, '0');
// 格式化日期
const formattedDate = `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`;
// 格式化时间
const formattedTime = `${hours}:${minutes}:${seconds}`;
// 更新输入框值为标准格式
inputElement.value = `${formattedDate} ${formattedTime}`;
closeModal();
});
}
// 创建新版本的方法
createNewVersion() {
// 创建一个新的版本对象,设置默认值
const newVersion = {
ClassName: this.currentService.className,
Name: this.currentService.name,
Version: this.getNextVersionNumber(),
Author: '',
Description: '',
CreatTime: this.getCurrentDateTime(),
ChangeTime: this.getCurrentDateTime(),
CodePath: '',
CmdList: [], // 初始化为空数组
OtherParam: '{}' // 初始化为空对象的 JSON 字符串
2025-04-28 12:25:20 +08:00
};
// 打开编辑界面
this.showVersionEditor(newVersion);
}
// 获取下一个版本号
getNextVersionNumber() {
if (!this.currentServiceVersions || this.currentServiceVersions.length === 0) {
return "1.0.0.0";
}
// 找出最高版本号
let highestVersion = null;
this.currentServiceVersions.forEach(version => {
if (!version.Version) return;
if (!highestVersion || this.compareVersions(version.Version, highestVersion) > 0) {
highestVersion = version.Version;
}
});
if (!highestVersion) return "1.0.0.0";
// 解析版本号
const versionParts = highestVersion.toString().split('.').map(Number);
// 确保版本号有4位
while (versionParts.length < 4) {
versionParts.push(0);
}
// 增加最后一位
versionParts[versionParts.length - 1]++;
// 处理进位
for (let i = versionParts.length - 1; i > 0; i--) {
if (versionParts[i] > 9) {
versionParts[i] = 0;
versionParts[i - 1]++;
}
}
return versionParts.join('.');
}
// 获取当前日期时间格式化字符串
getCurrentDateTime() {
const now = new Date();
// 格式化日期YYYY-MM-DD
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const date = `${year}-${month}-${day}`;
// 格式化时间HH:MM:SS
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const time = `${hours}:${minutes}:${seconds}`;
return `${date} ${time}`;
}
// 显示服务创建对话框
showServiceCreationDialog() {
// 创建模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'serviceCreationModal';
modal.style.cssText = 'position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.4); display: flex; justify-content: center; align-items: center;';
modal.innerHTML = `
<div class="modal-content" style="background-color: white; margin: auto; padding: 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); max-width: 500px; width: 100%;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">
<div class="modal-title" style="font-size: 20px; font-weight: 600; color: #2d3748;">添加新服务</div>
<span class="close" style="font-size: 24px; color: #a0aec0; cursor: pointer;">&times;</span>
</div>
<div class="modal-body">
<form id="serviceCreationForm">
<div class="form-group" style="margin-bottom: 15px;">
<label for="serviceClassName" style="display: block; margin-bottom: 5px; font-weight: 500; color: #4a5568;">服务类名 (ClassName)*</label>
<input type="text" id="serviceClassName" required style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px;">
<small style="display: block; margin-top: 5px; color: #718096; font-size: 12px;">必须以XN开头且是唯一的类名: XNUserService</small>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label for="serviceName" style="display: block; margin-bottom: 5px; font-weight: 500; color: #4a5568;">服务名称 (ServiceName)*</label>
<input type="text" id="serviceName" required style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px;">
<small style="display: block; margin-top: 5px; color: #718096; font-size: 12px;">服务的英文名称建议使用英文</small>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label for="serviceNameCN" style="display: block; margin-bottom: 5px; font-weight: 500; color: #4a5568;">中文名称 (ServiceName_CN)*</label>
<input type="text" id="serviceNameCN" required style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px;">
<small style="display: block; margin-top: 5px; color: #718096; font-size: 12px;">服务的中文显示名称</small>
</div>
<div class="form-group" style="margin-bottom: 15px;">
<label for="serviceDescription" style="display: block; margin-bottom: 5px; font-weight: 500; color: #4a5568;">服务描述 (Description)</label>
<textarea id="serviceDescription" style="width: 100%; padding: 10px; border: 1px solid #e2e8f0; border-radius: 4px; font-size: 14px; min-height: 100px; resize: vertical;"></textarea>
</div>
</form>
</div>
<div class="modal-footer" style="margin-top: 20px; text-align: right;">
<button id="cancelService" style="background-color: #e2e8f0; color: #4a5568; border: none; border-radius: 4px; padding: 10px 16px; margin-right: 10px; cursor: pointer; font-weight: 500;">取消</button>
<button id="confirmService" style="background-color: #4c6ef5; color: white; border: none; border-radius: 4px; padding: 10px 16px; cursor: pointer; font-weight: 500;">创建服务</button>
</div>
</div>
`;
// 获取shadowRoot
const shadowRoot = this.shadowRoot;
// 添加到DOM
shadowRoot.appendChild(modal);
// 获取DOM元素
const closeBtn = modal.querySelector('.close');
const cancelBtn = modal.querySelector('#cancelService');
const confirmBtn = modal.querySelector('#confirmService');
const form = modal.querySelector('#serviceCreationForm');
// 关闭对话框
const closeModal = () => {
if (modal.parentNode === shadowRoot) {
shadowRoot.removeChild(modal);
}
};
// 关闭按钮事件
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
// 确认按钮事件
confirmBtn.addEventListener('click', async () => {
// 表单验证
const serviceClassName = modal.querySelector('#serviceClassName').value.trim();
const serviceName = modal.querySelector('#serviceName').value.trim();
const serviceNameCN = modal.querySelector('#serviceNameCN').value.trim();
const serviceDescription = modal.querySelector('#serviceDescription').value.trim();
if (!serviceClassName || !serviceName || !serviceNameCN) {
alert('服务类名、服务名称和中文名称是必填项!');
return;
}
// 验证类名格式 - 必须以XN开头
const classNameRegex = /^XN[A-Za-z0-9]*$/;
if (!classNameRegex.test(serviceClassName)) {
alert('服务类名必须以XN开头并且只能包含字母和数字');
return;
}
try {
// 禁用确认按钮,显示加载状态
confirmBtn.disabled = true;
confirmBtn.textContent = '创建中...';
// 构建服务数据
const serviceData = {
ClassName: serviceClassName,
ServiceName: serviceName,
ServiceName_CN: serviceNameCN,
Description: serviceDescription
};
// 调用API创建服务
await this.createService(serviceData);
// 关闭对话框
closeModal();
// 刷新服务列表
this.init();
// 显示成功提示
alert('服务创建成功!');
} catch (error) {
alert(`创建服务失败: ${error.message}`);
// 恢复按钮状态
confirmBtn.disabled = false;
confirmBtn.textContent = '创建服务';
}
});
}
// 创建服务的API
async createService(serviceData) {
try {
// 确保所有必要字段都存在
const requiredData = {
ClassName: serviceData.ClassName,
ServiceName: serviceData.ServiceName,
ServiceName_CN: serviceData.ServiceName_CN,
Description: serviceData.Description || ''
};
const response = await fetch('/api/services', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requiredData)
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `HTTP错误! 状态码: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error('创建服务失败:', error);
throw error;
}
}
}
customElements.define('service-development', ServiceDevelopment);