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 = `
-
+
+
+ ${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 {
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
从ICD导入
+
+
+
+
+ 支持从Excel文件(.xlsx/.xls)导入接口数据。
+ 请确保Excel文件格式正确,包含所有必要的字段。
+ 导入过程中请勿关闭页面。
+
+
+
+
从IDL导入
+
+
+
+
+ 支持从IDL文件(.idl)导入接口数据。
+ 请确保IDL文件格式正确,包含所有必要的接口定义。
+ 导入过程中请勿关闭页面。
+
+
+
+
-
-
-
-
-
`;
}
+
+ 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 = `
-