diff --git a/Release/IDL/XNAerodynamic1.idl b/Release/IDL/XNAerodynamic1.idl deleted file mode 100644 index a78f0d9..0000000 --- a/Release/IDL/XNAerodynamic1.idl +++ /dev/null @@ -1,57 +0,0 @@ -module XNSim -{ - module ATA04 - { - struct Aerodynamics_input - { - @optional double l_04_i_aerocomac_alpdot_f8; - @optional double l_04_i_aerocomac_beta_f8; - @optional double l_04_i_aerocomac_press_alt_f8; - @optional double l_04_i_aerocomac_tas_f8; - @optional double l_04_i_aerocomac_mach_f8; - @optional double l_04_i_aerocomac_nx_f8; - @optional double l_04_i_aerocomac_ny_f8; - @optional double l_04_i_aerocomac_nz_f8; - @optional double l_04_i_aerocomac_p_f8; - @optional double l_04_i_aerocomac_q_f8; - @optional double l_04_i_aerocomac_r_f8; - @optional double l_04_i_aerocomac_qbar_f8; - @optional double l_04_i_aerocomac_blcg_f8; - @optional double l_04_i_aerocomac_bscg_f8; - @optional double l_04_i_aerocomac_wlcg_f8; - @optional double l_04_i_aerocomac_ail_f8[10]; - @optional double l_04_i_aerocomac_elv_f8[4]; - @optional double l_04_i_aerocomac_rud_f8[2]; - @optional double l_04_i_aerocomac_stab_f8; - @optional double l_04_i_aerocomac_gear_f8[7]; - @optional double l_04_i_aerocomac_flap_f8[10]; - @optional double l_04_i_aerocomac_slat_f8[20]; - @optional double l_04_i_aerocomac_spl_f8[20]; - @optional double l_04_i_aerocomac_tnet_f8[4]; - @optional double l_04_i_aerocomac_kice_f8[20]; - @optional double l_04_i_aerocomac_alt_agl_f8; - }; - struct Aerodynamics_output - { - @optional double l_04_o_aerocomac_fxb_f8; - @optional double l_04_o_aerocomac_fyb_f8; - @optional double l_04_o_aerocomac_fzb_f8; - @optional double l_04_o_aerocomac_mxb_f8; - @optional double l_04_o_aerocomac_myb_f8; - @optional double l_04_o_aerocomac_mzb_f8; - @optional double l_04_o_aerocomac_cls_f8; - @optional double l_04_o_aerocomac_cl_f8; - @optional double l_04_o_aerocomac_cd_f8; - @optional double l_04_o_aerocomac_cm_f8; - @optional double l_04_o_aerocomac_cr_f8; - @optional double l_04_o_aerocomac_cy_f8; - @optional double l_04_o_aerocomac_cn_f8; - }; - struct Aerodynamics_heartbeat - { - long aero_model_heartbeat; - }; - }; - -}; - diff --git a/Release/database/XNSim.db b/Release/database/XNSim.db index 44c1dd5..b86f645 100644 Binary files a/Release/database/XNSim.db and b/Release/database/XNSim.db differ diff --git a/XNSimHtml/README.md b/XNSimHtml/README.md index 3c6b8a9..db6cdbf 100644 --- a/XNSimHtml/README.md +++ b/XNSimHtml/README.md @@ -10,11 +10,17 @@ - Linux: `liblogin.so`(符合 Linux 动态库命名规范) - 支持基于权限级别的用户角色系统 - 使用 Node.js 作为后端服务 +- 接口配置管理功能: + - 支持添加、编辑、删除接口 + - 支持从 Excel 文件导入接口配置 + - 支持按构型、ATA 章节、结构体名等条件筛选 + - 支持分页显示和批量操作 ## 环境要求 - Node.js 14.x 或更高版本 - 环境变量 `XNCore` 指向动态库所在的根目录(系统会在该目录的 lib 子目录中查找动态库) +- MySQL 数据库(用于存储接口配置数据) ## 安装步骤 @@ -42,13 +48,24 @@ $env:XNCore = "C:\path\to\xncore\directory" npm install ``` -4. 启动服务器 +4. 配置数据库连接 + +在项目根目录创建 `.env` 文件,添加以下配置: + +```env +DB_HOST=localhost +DB_USER=your_username +DB_PASSWORD=your_password +DB_NAME=xnsim_db +``` + +5. 启动服务器 ```bash npm start ``` -5. 访问 http://localhost:3000 打开登录页面 +6. 访问 http://localhost:3000 打开登录页面 ## 登录 API @@ -65,6 +82,44 @@ API 响应包含以下信息: - `permissionLevel`: 用户权限级别(整数,仅在登录成功时返回) - `role`: 用户角色描述(字符串,仅在登录成功时返回) +## 接口配置 API + +### 接口列表 + +- 路径: `/api/interface/list` +- 方法: `GET` +- 响应: 返回所有接口配置数据 + +### 添加接口 + +- 路径: `/api/interface/add` +- 方法: `POST` +- 请求体: 包含接口配置信息的 JSON 对象 + +### 更新接口 + +- 路径: `/api/interface/update` +- 方法: `PUT` +- 请求体: 包含当前数据和原始数据的 JSON 对象 + +### 删除接口 + +- 路径: `/api/interface/delete` +- 方法: `DELETE` +- 参数: 通过查询字符串传递接口标识信息 + +### 导入接口 + +- 路径: `/api/interface/import` +- 方法: `POST` +- 请求体: 包含导入数据的 JSON 对象 + +### 获取导入模板 + +- 路径: `/api/interface/template` +- 方法: `GET` +- 响应: 返回 Excel 模板文件 + ## 权限级别 系统支持以下权限级别: @@ -77,12 +132,23 @@ API 响应包含以下信息: ## 技术栈 -- 前端: HTML, CSS, JavaScript +- 前端: HTML, CSS, JavaScript, Web Components - 后端: Node.js, Express -- 外部库: ffi-napi (用于调用 C++动态库) +- 数据库: MySQL +- 外部库: + - ffi-napi (用于调用 C++动态库) + - xlsx (用于处理 Excel 文件) ## 注意事项 - 确保动态库是由 C++编写的,并且包含`Login_validateUser`导出函数(对应 Login 类的 validateUser 静态方法) - `Login_validateUser`函数接受两个字符串参数(用户名和密码),并返回整数表示权限级别(负值表示登录失败) - 系统会根据操作系统类型在`$XNCore/lib`目录下查找相应类型的动态库,并遵循各操作系统的命名规范 +- 接口配置数据需要遵循以下规则: + - 接口名称必须符合 C++ 命名规范 + - 数组大小必须大于 1(第二维可以为 0) + - 必填字段包括:构型、ATA 章节、接口结构体名、接口名称、数据类型 + +## 更新计划 + +1. 添加接口配置从 IDL 文件导入的功能 diff --git a/XNSimHtml/components/interface-config.js b/XNSimHtml/components/interface-config.js index 6564c12..e33be2c 100644 --- a/XNSimHtml/components/interface-config.js +++ b/XNSimHtml/components/interface-config.js @@ -1,24 +1,28 @@ import './interface-config/toolbar.js'; import './interface-config/data-table.js'; import './interface-config/variable-form.js'; +import './interface-config/import-dialog.js'; class InterfaceConfig extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); this.data = []; + this.products = []; // 存储构型列表 + this.atas = []; // 存储ATA章节列表 } connectedCallback() { this.render(); - this.loadData(); this.addEventListeners(); + this.loadData(); } addEventListeners() { const toolbar = this.shadowRoot.querySelector('interface-toolbar'); const dataTable = this.shadowRoot.querySelector('interface-data-table'); const variableForm = this.shadowRoot.querySelector('variable-form'); + const importDialog = this.shadowRoot.querySelector('import-dialog'); // 添加变量事件 toolbar.addEventListener('add-variable', () => { @@ -33,19 +37,19 @@ class InterfaceConfig extends HTMLElement { alert('请选择要删除的接口'); return; } - + if (!confirm(`确定要删除选中的 ${selectedRows.length} 个接口吗?`)) { - return; - } - - try { + return; + } + + try { const deletePromises = selectedRows.map(row => { const { SystemName, ProductName, ATAName, ModelStructName, InterfaceName } = row; return fetch(`/api/interface/delete?systemName=${SystemName}&productName=${ProductName}&ataName=${ATAName}&modelStructName=${ModelStructName}&interfaceName=${InterfaceName}`, { method: 'DELETE' - }); - }); - + }); + }); + const results = await Promise.all(deletePromises); const failed = results.some(result => !result.ok); @@ -85,64 +89,83 @@ class InterfaceConfig extends HTMLElement { }); // 导入数据事件 - toolbar.addEventListener('import-data', () => { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.xlsx,.xls'; - input.onchange = async (e) => { - const file = e.target.files[0]; - if (!file) return; + toolbar.addEventListener('import-data', async (e) => { + const { file, type } = e.detail; + if (!file) return; - const formData = new FormData(); - formData.append('file', file); + const formData = new FormData(); + formData.append('file', file); + formData.append('type', type); - try { - const response = await fetch('/api/interface/import', { - method: 'POST', - body: formData - }); + try { + const response = await fetch('/api/icd/import', { + method: 'POST', + body: formData + }); - if (!response.ok) { - throw new Error('导入失败'); - } - - const result = await response.json(); - if (result.success) { - await this.loadData(); - alert(`导入完成,成功: ${result.results.filter(r => r.success).length}, 失败: ${result.results.filter(r => !r.success).length}`); - } else { - throw new Error(result.error || '导入失败'); - } - } catch (error) { - console.error('导入数据时出错:', error); - alert('导入数据失败'); + if (!response.ok) { + throw new Error('导入失败'); } - }; - input.click(); + + const result = await response.json(); + if (result.success) { + importDialog.show(result.data); + } else { + throw new Error(result.error || '导入失败'); + } + } catch (error) { + console.error('导入数据时出错:', error); + alert('导入数据失败: ' + error.message); + } + }); + + // 确认导入事件 + importDialog.addEventListener('confirm-import', async (e) => { + const importData = e.detail; + try { + console.log('准备导入的数据:', importData); + + const response = await fetch('/api/interface/import', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(importData) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || '保存导入数据失败'); + } + + const result = await response.json(); + if (result.success) { + await this.loadData(); + alert('导入成功'); + } else { + throw new Error(result.error || '保存导入数据失败'); + } + } catch (error) { + console.error('保存导入数据时出错:', error); + alert('保存导入数据失败: ' + error.message); + } }); // 表格编辑按钮事件 dataTable.addEventListener('edit', (e) => { - const { SystemName, ProductName, ATAName, ModelStructName, InterfaceName } = e.detail; - const interfaceData = this.data.find(item => - item.SystemName === SystemName && - item.ProductName === ProductName && - item.ATAName === ATAName && - item.ModelStructName === ModelStructName && - item.InterfaceName === InterfaceName - ); - if (interfaceData) { - variableForm.setMode('edit', interfaceData); + const variableForm = this.shadowRoot.querySelector('variable-form'); + if (variableForm) { + // 确保在设置编辑数据之前先设置选项 + variableForm.setOptions(this.products, this.atas); + // 先显示表单,再设置模式和数据 variableForm.show(); + variableForm.setMode('edit', e.detail); } }); // 表格删除按钮事件 dataTable.addEventListener('delete', async (e) => { const { SystemName, ProductName, ATAName, ModelStructName, InterfaceName } = e.detail; - if (!confirm('确定要删除这个接口吗?')) { - return; - } try { const response = await fetch(`/api/interface/delete?systemName=${SystemName}&productName=${ProductName}&ataName=${ATAName}&modelStructName=${ModelStructName}&interfaceName=${InterfaceName}`, { @@ -168,25 +191,99 @@ class InterfaceConfig extends HTMLElement { const url = interfaceData.mode === 'add' ? '/api/interface/add' : '/api/interface/update'; const method = interfaceData.mode === 'add' ? 'POST' : 'PUT'; + // 如果是编辑模式,需要添加原始数据用于更新 + const requestData = interfaceData.mode === 'edit' ? { + currentData: { + SystemName: 'XNSim', + ProductName: interfaceData.ProductName, + ATAName: interfaceData.ATAName, + ModelStructName: interfaceData.ModelStructName, + InterfaceName: interfaceData.InterfaceName, + InterfaceType: interfaceData.InterfaceType, + InterfaceOption: interfaceData.InterfaceOption || 1, + InterfaceIsArray: interfaceData.InterfaceIsArray, + InterfaceArraySize_1: interfaceData.InterfaceArraySize_1, + InterfaceArraySize_2: interfaceData.InterfaceArraySize_2, + InterfaceNotes: interfaceData.InterfaceNotes + }, + originalData: { + SystemName: 'XNSim', + ProductName: interfaceData.originalProductName || interfaceData.ProductName, + ATAName: interfaceData.originalATAName || interfaceData.ATAName, + ModelStructName: interfaceData.originalModelStructName || interfaceData.ModelStructName, + InterfaceName: interfaceData.originalInterfaceName || interfaceData.InterfaceName + } + } : { + SystemName: 'XNSim', + ProductName: interfaceData.ProductName, + ATAName: interfaceData.ATAName, + ModelStructName: interfaceData.ModelStructName, + InterfaceName: interfaceData.InterfaceName, + InterfaceType: interfaceData.InterfaceType, + InterfaceOption: interfaceData.InterfaceOption || 1, + InterfaceIsArray: interfaceData.InterfaceIsArray, + InterfaceArraySize_1: interfaceData.InterfaceArraySize_1, + InterfaceArraySize_2: interfaceData.InterfaceArraySize_2, + InterfaceNotes: interfaceData.InterfaceNotes + }; + const response = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(interfaceData) + body: JSON.stringify(requestData) }); + + const responseText = await response.text(); + let responseData; + try { + responseData = JSON.parse(responseText); + } catch (e) { + throw new Error('服务器响应格式错误'); + } if (!response.ok) { - throw new Error('保存失败'); + throw new Error(responseData.error || '保存失败'); } await this.loadData(); alert('保存成功'); } catch (error) { console.error('保存接口时出错:', error); - alert('保存接口失败'); + alert('保存接口失败: ' + error.message); } }); + + // 搜索事件 + toolbar.addEventListener('search', (e) => { + const keyword = e.detail.keyword; + dataTable.dispatchEvent(new CustomEvent('search', { + detail: { keyword }, + bubbles: true, + composed: true + })); + }); + + // 重置搜索事件 + toolbar.addEventListener('reset-search', async () => { + // 重新加载数据 + await this.loadData(); + // 触发重置事件 + dataTable.dispatchEvent(new CustomEvent('reset-search', { + bubbles: true, + composed: true + })); + }); + + // 高级搜索事件 + toolbar.addEventListener('advanced-search', (e) => { + dataTable.dispatchEvent(new CustomEvent('advanced-search', { + detail: e.detail, + bubbles: true, + composed: true + })); + }); } async loadData() { @@ -196,7 +293,24 @@ class InterfaceConfig extends HTMLElement { throw new Error('获取数据失败'); } this.data = await response.json(); + + // 从接口数据中提取构型和ATA章节列表 + this.products = [...new Set(this.data.map(item => item.ProductName))]; + this.atas = [...new Set(this.data.map(item => item.ATAName))]; + this.updateTable(); + this.updateAdvancedSearchOptions(); + + // 通知所有子组件数据已更新 + const importDialog = this.shadowRoot.querySelector('import-dialog'); + if (importDialog) { + importDialog.setOptions(this.products, this.atas); + } + + const variableForm = this.shadowRoot.querySelector('variable-form'); + if (variableForm) { + variableForm.setOptions(this.products, this.atas); + } } catch (error) { console.error('加载数据时出错:', error); alert('加载数据失败'); @@ -210,6 +324,21 @@ class InterfaceConfig extends HTMLElement { } } + updateAdvancedSearchOptions() { + const toolbar = this.shadowRoot.querySelector('interface-toolbar'); + if (!toolbar) return; + + // 使用已提取的构型和ATA章节列表 + const structs = [...new Set(this.data.map(item => item.ModelStructName))]; + + // 更新下拉选项 + toolbar.updateFilterOptions({ + products: this.products, + atas: this.atas, + structs + }); + } + render() { this.shadowRoot.innerHTML = ` - - - - - - - - - - - - - - - - - - -
构型ATA章节模型结构体名接口接口类型是否可选是否为数组第一维大小第二维大小备注操作
+
+ + + + + + + + + + + + + + + + ${this.getCurrentPageData().map((item) => ` + + + + + + + + + + + + `).join('')} + +
+ + 构型ATA章节接口结构体名接口名称数据类型数据大小备注操作
+ + ${item.ProductName || ''}${item.ATAName || ''}${item.ModelStructName || ''}${item.InterfaceName || ''}${item.InterfaceType || ''}${this.formatArraySize(item)}${item.InterfaceNotes || ''} + + +
+ ${this.isLoading ? ` +
+
+
+ ` : ''} +
+ `; + this.bindEventListeners(); + } - this.shadowRoot.querySelector('#select-all').addEventListener('change', (e) => { - const checkboxes = this.shadowRoot.querySelectorAll('input[type="checkbox"]:not(#select-all)'); - checkboxes.forEach(checkbox => { - checkbox.checked = e.target.checked; - const id = checkbox.closest('tr').dataset.id; - if (e.target.checked) { - this.selectedRows.add(id); - } else { - this.selectedRows.delete(id); - } + // 修改事件监听器的添加方式 + addEventListenerWithCleanup(element, event, handler) { + if (!this.eventListeners.has(element)) { + this.eventListeners.set(element, new Map()); + } + const elementListeners = this.eventListeners.get(element); + + // 如果已经存在相同的事件监听器,先移除 + if (elementListeners.has(event)) { + const handlers = elementListeners.get(event); + handlers.forEach(h => element.removeEventListener(event, h)); + handlers.clear(); + } else { + elementListeners.set(event, new Set()); + } + + elementListeners.get(event).add(handler); + element.addEventListener(event, handler); + } + + // 清理事件监听器 + cleanupEventListeners() { + this.eventListeners.forEach((elementListeners, element) => { + elementListeners.forEach((handlers, event) => { + handlers.forEach(handler => { + element.removeEventListener(event, handler); + }); }); }); + this.eventListeners.clear(); + } + + bindEventListeners() { + // 清理之前的事件监听器 + this.cleanupEventListeners(); + + const selectAllCheckbox = this.shadowRoot.querySelector('#selectAll'); + const rowCheckboxes = this.shadowRoot.querySelectorAll('.row-checkbox'); + const editButtons = this.shadowRoot.querySelectorAll('.edit-btn'); + const deleteButtons = this.shadowRoot.querySelectorAll('.delete-btn'); + + // 全选复选框事件 + if (selectAllCheckbox) { + const selectAllHandler = (e) => { + const isChecked = e.target.checked; + rowCheckboxes.forEach(checkbox => { + checkbox.checked = isChecked; + const index = parseInt(checkbox.dataset.index); + if (isChecked) { + this.selectedRows.add(index); + } else { + this.selectedRows.delete(index); + } + }); + }; + this.addEventListenerWithCleanup(selectAllCheckbox, 'change', selectAllHandler); + } + + // 行复选框事件 + rowCheckboxes.forEach(checkbox => { + const checkboxHandler = (e) => { + const index = parseInt(e.target.dataset.index); + if (e.target.checked) { + this.selectedRows.add(index); + } else { + this.selectedRows.delete(index); + } + if (selectAllCheckbox) { + selectAllCheckbox.checked = this.selectedRows.size === rowCheckboxes.length; + } + }; + this.addEventListenerWithCleanup(checkbox, 'change', checkboxHandler); + }); + + // 编辑按钮事件 + editButtons.forEach(button => { + const editHandler = (e) => { + const index = parseInt(e.target.dataset.index); + const item = this.filteredData[index]; + if (item) { + this.dispatchEvent(new CustomEvent('edit', { + detail: { ...item }, + bubbles: true, + composed: true + })); + } + }; + this.addEventListenerWithCleanup(button, 'click', editHandler); + }); + + // 删除按钮事件 + deleteButtons.forEach(button => { + const deleteHandler = (e) => { + const index = parseInt(e.target.dataset.index); + const item = this.filteredData[index]; + if (item && confirm(`确定要删除接口 "${item.InterfaceName}" 吗?`)) { + this.dispatchEvent(new CustomEvent('delete', { + detail: { ...item }, + bubbles: true, + composed: true + })); + } + }; + this.addEventListenerWithCleanup(button, 'click', deleteHandler); + }); + + // 搜索事件 + const searchHandler = async (e) => { + this.searchKeyword = e.detail.keyword.toLowerCase(); + this.setLoading(true); + await this.debouncedFilter(); + }; + this.addEventListenerWithCleanup(this, 'search', searchHandler); + + // 重置搜索事件 + const resetSearchHandler = async () => { + this.searchKeyword = ''; + this.filters = { + productName: '', + ataName: '', + structName: '' + }; + this.filteredData = [...this.data]; + this.render(); + }; + this.addEventListenerWithCleanup(this, 'reset-search', resetSearchHandler); + + // 高级搜索事件 + const advancedSearchHandler = async (e) => { + this.filters = e.detail; + this.setLoading(true); + await this.debouncedFilter(); + }; + this.addEventListenerWithCleanup(this, 'advanced-search', advancedSearchHandler); + + // 分页按钮事件 + const firstPageBtn = this.shadowRoot.querySelector('#firstPage'); + const prevPageBtn = this.shadowRoot.querySelector('#prevPage'); + const nextPageBtn = this.shadowRoot.querySelector('#nextPage'); + const lastPageBtn = this.shadowRoot.querySelector('#lastPage'); + const pageSizeSelect = this.shadowRoot.querySelector('#pageSize'); + + if (firstPageBtn) { + this.addEventListenerWithCleanup(firstPageBtn, 'click', () => this.setPage(1)); + } + if (prevPageBtn) { + this.addEventListenerWithCleanup(prevPageBtn, 'click', () => this.setPage(this.currentPage - 1)); + } + if (nextPageBtn) { + this.addEventListenerWithCleanup(nextPageBtn, 'click', () => this.setPage(this.currentPage + 1)); + } + if (lastPageBtn) { + this.addEventListenerWithCleanup(lastPageBtn, 'click', () => this.setPage(this.getTotalPages())); + } + if (pageSizeSelect) { + this.addEventListenerWithCleanup(pageSizeSelect, 'change', (e) => { + this.pageSize = parseInt(e.target.value); + this.currentPage = 1; + this.render(); + }); + } + } + + // 组件销毁时清理 + disconnectedCallback() { + if (this.filterTimeout) { + clearTimeout(this.filterTimeout); + this.filterTimeout = null; + } + this.cleanupEventListeners(); + this.data = []; + this.filteredData = []; + this.selectedRows.clear(); } } diff --git a/XNSimHtml/components/interface-config/import-dialog.js b/XNSimHtml/components/interface-config/import-dialog.js new file mode 100644 index 0000000..300606e --- /dev/null +++ b/XNSimHtml/components/interface-config/import-dialog.js @@ -0,0 +1,321 @@ +class ImportDialog extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + this.data = []; + this.products = []; + this.atas = []; + } + + connectedCallback() { + this.render(); + this.addEventListeners(); + } + + // 接收父组件传递的选项数据 + setOptions(products, atas) { + this.products = products; + this.atas = atas; + this.updateProductOptions(); + this.updateAtaOptions(); + } + + updateProductOptions() { + const select = this.shadowRoot.querySelector('#product-select'); + select.innerHTML = ` + + ${this.products.map(product => ` + + `).join('')} + `; + } + + updateAtaOptions() { + const select = this.shadowRoot.querySelector('#ata-select'); + select.innerHTML = ` + + ${this.atas.map(ata => ` + + `).join('')} + `; + } + + show(data) { + this.data = data; + this.updateTable(); + this.style.display = 'block'; + } + + hide() { + this.style.display = 'none'; + } + + updateTable() { + const tbody = this.shadowRoot.querySelector('tbody'); + const lowercaseCheckbox = this.shadowRoot.querySelector('#lowercase-checkbox'); + const shouldLowercase = lowercaseCheckbox.checked; + + tbody.innerHTML = this.data.map(item => ` + + ${shouldLowercase ? item.name.toLowerCase() : item.name} + ${item.variableType || '-'} + ${item.structName} + ${item.isArray ? '是' : '否'} + ${item.arraySize1 || '-'} + ${item.arraySize2 || '-'} + ${item.description} + + `).join(''); + } + + addEventListeners() { + const closeBtn = this.shadowRoot.querySelector('.close-btn'); + const confirmBtn = this.shadowRoot.querySelector('.confirm-btn'); + const cancelBtn = this.shadowRoot.querySelector('.cancel-btn'); + const lowercaseCheckbox = this.shadowRoot.querySelector('#lowercase-checkbox'); + + closeBtn.addEventListener('click', () => this.hide()); + cancelBtn.addEventListener('click', () => this.hide()); + + // 添加小写复选框的事件监听 + lowercaseCheckbox.addEventListener('change', () => { + this.updateTable(); + }); + + confirmBtn.addEventListener('click', () => { + const productSelect = this.shadowRoot.querySelector('#product-select'); + const ataSelect = this.shadowRoot.querySelector('#ata-select'); + const modelInput = this.shadowRoot.querySelector('#model-input'); + + if (!productSelect.value) { + alert('请选择构型'); + return; + } + if (!ataSelect.value) { + alert('请选择ATA章节'); + return; + } + if (!modelInput.value.trim()) { + alert('请输入模型名称'); + return; + } + + const modelName = modelInput.value.trim(); + + // 处理数据,转换为数据库格式 + const processedData = this.data.map(item => ({ + SystemName: 'XNSim', // 系统名称固定为XNSim + ProductName: productSelect.value, + ATAName: ataSelect.value, + ModelStructName: `${modelName}_${item.structName}`, + InterfaceName: lowercaseCheckbox.checked ? item.name.toLowerCase() : item.name, + InterfaceType: item.variableType || '', + InterfaceOption: 1, + InterfaceIsArray: item.isArray ? 1 : 0, // 确保是数字类型 + InterfaceArraySize_1: item.arraySize1 || 0, // 确保是数字类型 + InterfaceArraySize_2: item.arraySize2 || 0, // 确保是数字类型 + InterfaceNotes: item.description || '' // 确保不是null + })); + + // 验证数据 + const invalidData = processedData.find(item => { + return !item.InterfaceType || + (item.InterfaceIsArray === 1 && (!item.InterfaceArraySize_1 || item.InterfaceArraySize_1 <= 1)); + }); + + if (invalidData) { + alert('数据格式不正确,请检查:\n1. 变量类型不能为空\n2. 如果是数组,第一维大小必须大于1'); + return; + } + + this.dispatchEvent(new CustomEvent('confirm-import', { + detail: processedData, + bubbles: true, + composed: true + })); + this.hide(); + }); + } + + render() { + this.shadowRoot.innerHTML = ` + +
+
+
导入数据预览
+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+
+
+ + + + + + + + + + + + + +
变量名称变量类型结构体是否数组第一维大小第二维大小描述
+
+ +
+ `; + } +} + +customElements.define('import-dialog', ImportDialog); \ No newline at end of file diff --git a/XNSimHtml/components/interface-config/toolbar.js b/XNSimHtml/components/interface-config/toolbar.js index 048654c..ab03186 100644 --- a/XNSimHtml/components/interface-config/toolbar.js +++ b/XNSimHtml/components/interface-config/toolbar.js @@ -1,7 +1,8 @@ -class Toolbar extends HTMLElement { +class InterfaceToolbar extends HTMLElement { constructor() { super(); this.attachShadow({ mode: 'open' }); + this.showAdvancedSearch = false; } connectedCallback() { @@ -10,55 +11,112 @@ class Toolbar extends HTMLElement { } addEventListeners() { - const searchBtn = this.shadowRoot.querySelector('.search-btn'); - const resetBtn = this.shadowRoot.querySelector('.reset-btn'); const addBtn = this.shadowRoot.querySelector('.add-btn'); const deleteBtn = this.shadowRoot.querySelector('.delete-btn'); - const downloadBtn = this.shadowRoot.querySelector('.download-btn'); const importBtn = this.shadowRoot.querySelector('.import-btn'); + const searchInput = this.shadowRoot.querySelector('.search-input'); + const searchBtn = this.shadowRoot.querySelector('.search-btn'); + const resetBtn = this.shadowRoot.querySelector('.reset-btn'); + const advancedSearchBtn = this.shadowRoot.querySelector('.advanced-search-btn'); + const advancedSearchPanel = this.shadowRoot.querySelector('.advanced-search-panel'); + const productSelect = this.shadowRoot.querySelector('#filterProduct'); + const ataSelect = this.shadowRoot.querySelector('#filterAta'); + const structSelect = this.shadowRoot.querySelector('#filterStruct'); + const importModal = this.shadowRoot.querySelector('#importModal'); + const importExcelBtn = this.shadowRoot.querySelector('#importExcelBtn'); + const importIdlBtn = this.shadowRoot.querySelector('#importIdlBtn'); + const cancelImportBtn = this.shadowRoot.querySelector('#cancelImportBtn'); + + addBtn.addEventListener('click', () => { + this.dispatchEvent(new CustomEvent('add-variable')); + }); + + deleteBtn.addEventListener('click', () => { + this.dispatchEvent(new CustomEvent('delete-variable')); + }); + + importBtn.addEventListener('click', () => { + importModal.style.display = 'block'; + }); + + importExcelBtn.addEventListener('click', () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.xlsx,.xls'; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + this.dispatchEvent(new CustomEvent('import-data', { + detail: { file, type: 'excel' } + })); + importModal.style.display = 'none'; + } + }; + input.click(); + }); + + importIdlBtn.addEventListener('click', () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.idl'; + input.onchange = (e) => { + const file = e.target.files[0]; + if (file) { + this.dispatchEvent(new CustomEvent('import-data', { + detail: { file, type: 'idl' } + })); + importModal.style.display = 'none'; + } + }; + input.click(); + }); + + cancelImportBtn.addEventListener('click', () => { + importModal.style.display = 'none'; + }); + + // 点击模态框外部关闭 + importModal.addEventListener('click', (e) => { + if (e.target === importModal) { + importModal.style.display = 'none'; + } + }); searchBtn.addEventListener('click', () => { - const searchInput = this.shadowRoot.querySelector('.search-input'); + const keyword = searchInput.value.trim(); this.dispatchEvent(new CustomEvent('search', { - detail: { keyword: searchInput.value } + detail: { keyword } })); }); resetBtn.addEventListener('click', () => { - const searchInput = this.shadowRoot.querySelector('.search-input'); + // 重置搜索框 searchInput.value = ''; + // 重置高级搜索下拉框 + productSelect.value = ''; + ataSelect.value = ''; + structSelect.value = ''; + // 触发重置事件 this.dispatchEvent(new CustomEvent('reset-search')); }); - addBtn.addEventListener('click', () => { - const form = document.querySelector('variable-form'); - if (form) { - form.setMode('add'); - form.show(); - } + advancedSearchBtn.addEventListener('click', () => { + this.showAdvancedSearch = !this.showAdvancedSearch; + advancedSearchPanel.style.display = this.showAdvancedSearch ? 'flex' : 'none'; + advancedSearchBtn.classList.toggle('active'); }); - deleteBtn.addEventListener('click', () => { - this.dispatchEvent(new CustomEvent('delete-selected')); - }); - - downloadBtn.addEventListener('click', () => { - this.dispatchEvent(new CustomEvent('download-template')); - }); - - importBtn.addEventListener('click', () => { - const fileInput = this.shadowRoot.querySelector('.file-input'); - fileInput.click(); - }); - - const fileInput = this.shadowRoot.querySelector('.file-input'); - fileInput.addEventListener('change', (e) => { - const file = e.target.files[0]; - if (file) { - this.dispatchEvent(new CustomEvent('import-data', { - detail: { file } + // 监听高级搜索的筛选条件变化 + [productSelect, ataSelect, structSelect].forEach(select => { + select.addEventListener('change', () => { + this.dispatchEvent(new CustomEvent('advanced-search', { + detail: { + productName: productSelect.value, + ataName: ataSelect.value, + structName: structSelect.value + } })); - } + }); }); } @@ -67,65 +125,320 @@ class Toolbar extends HTMLElement {
-
- - - +
+ + +
+
+ +
+
+
+ + + + +
+
+
+
+ + + +
+ `; } + + updateFilterOptions(options) { + const productSelect = this.shadowRoot.querySelector('#filterProduct'); + const ataSelect = this.shadowRoot.querySelector('#filterAta'); + const structSelect = this.shadowRoot.querySelector('#filterStruct'); + + // 更新构型选项 + productSelect.innerHTML = '' + + options.products.map(product => ``).join(''); + + // 更新ATA章节选项 + ataSelect.innerHTML = '' + + options.atas.map(ata => ``).join(''); + + // 更新接口结构体选项 + structSelect.innerHTML = '' + + options.structs.map(struct => ``).join(''); + } } -customElements.define('interface-toolbar', Toolbar); \ No newline at end of file +customElements.define('interface-toolbar', InterfaceToolbar); \ No newline at end of file diff --git a/XNSimHtml/components/interface-config/variable-form.js b/XNSimHtml/components/interface-config/variable-form.js index ce98650..ff2b3c4 100644 --- a/XNSimHtml/components/interface-config/variable-form.js +++ b/XNSimHtml/components/interface-config/variable-form.js @@ -4,6 +4,9 @@ class VariableForm extends HTMLElement { this.attachShadow({ mode: 'open' }); this.mode = 'add'; this.interfaceData = null; + this.products = []; + this.ataChapters = []; + this.modelStructs = []; } connectedCallback() { @@ -11,81 +14,252 @@ class VariableForm extends HTMLElement { this.addEventListeners(); } + // 接收父组件传递的选项数据 + setOptions(products, atas) { + this.products = products; + this.ataChapters = atas; + this.updateProductOptions(); + this.updateAtaOptions(); + } + + updateProductOptions() { + const select = this.shadowRoot.querySelector('#productName'); + if (select) { + select.innerHTML = ` + + ${this.products.map(product => ` + + `).join('')} + `; + } + } + + updateAtaOptions() { + const select = this.shadowRoot.querySelector('#ataName'); + if (select) { + select.innerHTML = ` + + ${this.ataChapters.map(chapter => ` + + `).join('')} + `; + } + } + + async loadModelStructs(productName, ataName) { + try { + const response = await fetch(`/api/interface/struct/list?systemName=XNSim&productName=${productName}&ataName=${ataName}`); + if (!response.ok) { + throw new Error('获取模型结构体列表失败'); + } + const data = await response.json(); + this.modelStructs = data.map(item => item.ModelStructName); + this.updateModelStructSelect(); + } catch (error) { + console.error('加载模型结构体列表失败:', error); + alert('加载模型结构体列表失败'); + } + } + + updateModelStructSelect() { + const select = this.shadowRoot.querySelector('#modelStructName'); + if (select) { + select.innerHTML = ` + + ${this.modelStructs.map(struct => ` + + `).join('')} + `; + } + } + setMode(mode, data = null) { this.mode = mode; this.interfaceData = data; - this.updateForm(); + if (data) { + // 保存原始数据 + this.originalData = { + ProductName: data.ProductName, + ATAName: data.ATAName, + ModelStructName: data.ModelStructName, + InterfaceName: data.InterfaceName + }; + // 如果是在编辑模式下,需要先加载模型结构体列表 + this.loadModelStructs(data.ProductName, data.ATAName).then(() => { + this.render(); + this.addEventListeners(); + this.show(); + }); + } else { + this.render(); + this.addEventListeners(); + this.show(); + } } show() { - this.shadowRoot.querySelector('.form-dialog').style.display = 'block'; + const overlay = this.shadowRoot.querySelector('.form-overlay'); + if (overlay) { + overlay.style.display = 'block'; + } } hide() { - this.shadowRoot.querySelector('.form-dialog').style.display = 'none'; - } - - updateForm() { - const form = this.shadowRoot.querySelector('form'); - if (this.mode === 'edit' && this.interfaceData) { - form.querySelector('[name="systemName"]').value = this.interfaceData.SystemName; - form.querySelector('[name="productName"]').value = this.interfaceData.ProductName; - form.querySelector('[name="ataName"]').value = this.interfaceData.ATAName; - form.querySelector('[name="modelStructName"]').value = this.interfaceData.ModelStructName; - form.querySelector('[name="interfaceName"]').value = this.interfaceData.InterfaceName; - form.querySelector('[name="interfaceType"]').value = this.interfaceData.InterfaceType; - form.querySelector('[name="interfaceOption"]').value = this.interfaceData.InterfaceOption; - form.querySelector('[name="interfaceIsArray"]').checked = this.interfaceData.InterfaceIsArray; - form.querySelector('[name="interfaceArraySize_1"]').value = this.interfaceData.InterfaceArraySize_1 || ''; - form.querySelector('[name="interfaceArraySize_2"]').value = this.interfaceData.InterfaceArraySize_2 || ''; - form.querySelector('[name="interfaceNotes"]').value = this.interfaceData.InterfaceNotes || ''; - } else { - form.reset(); - form.querySelector('[name="systemName"]').value = 'XNSim'; - form.querySelector('[name="productName"]').value = 'C909'; + const overlay = this.shadowRoot.querySelector('.form-overlay'); + if (overlay) { + overlay.style.display = 'none'; } } addEventListeners() { const form = this.shadowRoot.querySelector('form'); - const closeBtn = this.shadowRoot.querySelector('.close-btn'); const cancelBtn = this.shadowRoot.querySelector('.cancel-btn'); + const productSelect = this.shadowRoot.querySelector('#productName'); + const ataSelect = this.shadowRoot.querySelector('#ataName'); + const interfaceNameInput = this.shadowRoot.querySelector('#interfaceName'); + const isArrayCheckbox = this.shadowRoot.querySelector('#interfaceIsArray'); + const arraySize1Input = this.shadowRoot.querySelector('#interfaceArraySize_1'); + const arraySize2Input = this.shadowRoot.querySelector('#interfaceArraySize_2'); + + // 监听构型和ATA章节的变化 + productSelect.addEventListener('change', () => { + const productName = productSelect.value; + const ataName = ataSelect.value; + if (productName && ataName) { + this.loadModelStructs(productName, ataName); + } + }); + + ataSelect.addEventListener('change', () => { + const productName = productSelect.value; + const ataName = ataSelect.value; + if (productName && ataName) { + this.loadModelStructs(productName, ataName); + } + }); + + // 验证C++命名规范 + interfaceNameInput.addEventListener('input', (e) => { + const value = e.target.value; + // C++命名规范:以字母或下划线开头,只能包含字母、数字和下划线 + const isValid = /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value); + if (!isValid) { + e.target.setCustomValidity('接口名称必须以字母或下划线开头,只能包含字母、数字和下划线'); + } else { + e.target.setCustomValidity(''); + } + }); + + // 验证数组大小 + arraySize1Input.addEventListener('input', (e) => { + const value = parseInt(e.target.value); + if (value <= 1) { + e.target.setCustomValidity('第一维大小必须大于1'); + } else { + e.target.setCustomValidity(''); + } + }); + + arraySize2Input.addEventListener('input', (e) => { + const value = e.target.value; + if (value && parseInt(value) <= 1 && parseInt(value) !== 0) { + e.target.setCustomValidity('第二维大小必须大于1'); + } else { + e.target.setCustomValidity(''); + } + }); form.addEventListener('submit', (e) => { e.preventDefault(); + const formData = new FormData(form); - const interfaceData = { - SystemName: formData.get('systemName'), + const data = { + SystemName: 'XNSim', ProductName: formData.get('productName'), ATAName: formData.get('ataName'), ModelStructName: formData.get('modelStructName'), InterfaceName: formData.get('interfaceName'), InterfaceType: formData.get('interfaceType'), - InterfaceOption: formData.get('interfaceOption'), - InterfaceIsArray: formData.get('interfaceIsArray') === 'on', - InterfaceArraySize_1: formData.get('interfaceArraySize_1') || null, - InterfaceArraySize_2: formData.get('interfaceArraySize_2') || null, - InterfaceNotes: formData.get('interfaceNotes') || null + InterfaceOption: 1, + InterfaceIsArray: formData.get('interfaceIsArray') ? 1 : 0, + InterfaceArraySize_1: formData.get('interfaceArraySize_1'), + InterfaceArraySize_2: formData.get('interfaceArraySize_2'), + InterfaceNotes: formData.get('interfaceNotes') }; - this.dispatchEvent(new CustomEvent('save-success', { - detail: { - mode: this.mode, - ...interfaceData - } - })); + // 如果是编辑模式,添加原始数据 + if (this.mode === 'edit' && this.originalData) { + data.originalProductName = this.originalData.ProductName; + data.originalATAName = this.originalData.ATAName; + data.originalModelStructName = this.originalData.ModelStructName; + data.originalInterfaceName = this.originalData.InterfaceName; + } + // 验证必填字段 + const requiredFields = [ + 'SystemName', + 'ProductName', + 'ATAName', + 'ModelStructName', + 'InterfaceName', + 'InterfaceType' + ]; + + const missingFields = requiredFields.filter(field => !data[field]); + if (missingFields.length > 0) { + alert(`请填写以下必填字段:${missingFields.join(', ')}`); + return; + } + + // 如果是数组类型,验证数组大小 + if (data.InterfaceIsArray === 1) { + if (!data.InterfaceArraySize_1 || data.InterfaceArraySize_1 <= 1) { + alert('第一维大小必须大于1'); + return; + } + if (data.InterfaceArraySize_2 && data.InterfaceArraySize_2 <= 1 && data.InterfaceArraySize_2 !== 0) { + alert('第二维大小必须大于1或等于0'); + return; + } + } + + this.dispatchEvent(new CustomEvent('save-success', { + detail: { ...data, mode: this.mode } + })); this.hide(); }); - closeBtn.addEventListener('click', () => this.hide()); - cancelBtn.addEventListener('click', () => this.hide()); + cancelBtn.addEventListener('click', () => { + this.hide(); + }); + + // 监听数组大小输入框的变化 + isArrayCheckbox.addEventListener('change', () => { + arraySize1Input.disabled = !isArrayCheckbox.checked; + arraySize2Input.disabled = !isArrayCheckbox.checked; + if (!isArrayCheckbox.checked) { + arraySize1Input.value = ''; + arraySize2Input.value = ''; + } + }); } render() { + const interfaceTypes = [ + 'char', 'octet', 'short', 'unsigned short', + 'long', 'unsigned long', 'long long', 'unsigned long long', + 'float', 'double', 'long double', 'boolean' + ]; + this.shadowRoot.innerHTML = ` -
-
-
-
${this.mode === 'add' ? '添加接口' : '编辑接口'}
- -
+
+
+

${this.mode === 'add' ? '添加接口' : '编辑接口'}

- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - + + ${this.products.map(product => ` + + `).join('')}
+
- - + +
+
- + +
+
- - + +
+
- - + +
+
- - +
+ + +
-