基本完成接口配置页面
This commit is contained in:
parent
28ac4d236f
commit
1e07a82611
@ -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;
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
Binary file not shown.
@ -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 文件导入的功能
|
||||
|
@ -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', () => {
|
||||
@ -85,19 +89,16 @@ 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];
|
||||
toolbar.addEventListener('import-data', async (e) => {
|
||||
const { file, type } = e.detail;
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('type', type);
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/interface/import', {
|
||||
const response = await fetch('/api/icd/import', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
@ -108,41 +109,63 @@ class InterfaceConfig extends HTMLElement {
|
||||
|
||||
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}`);
|
||||
importDialog.show(result.data);
|
||||
} else {
|
||||
throw new Error(result.error || '导入失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导入数据时出错:', error);
|
||||
alert('导入数据失败');
|
||||
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);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
});
|
||||
|
||||
// 表格编辑按钮事件
|
||||
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 = `
|
||||
<style>
|
||||
@ -238,6 +367,7 @@ class InterfaceConfig extends HTMLElement {
|
||||
<interface-toolbar></interface-toolbar>
|
||||
<interface-data-table></interface-data-table>
|
||||
<variable-form></variable-form>
|
||||
<import-dialog></import-dialog>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
@ -6,182 +6,366 @@ class InterfaceDataTable extends HTMLElement {
|
||||
this.filteredData = [];
|
||||
this.selectedRows = new Set();
|
||||
this.searchKeyword = '';
|
||||
this.filters = {
|
||||
productName: '',
|
||||
ataName: '',
|
||||
structName: ''
|
||||
};
|
||||
this.pageSize = 10;
|
||||
this.currentPage = 1;
|
||||
this.isLoading = false;
|
||||
this.filterTimeout = null;
|
||||
this.eventListeners = new Map(); // 存储事件监听器
|
||||
this.debouncedFilter = this.debounce(this.filterData.bind(this), 300);
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
this.render();
|
||||
this.addEventListeners();
|
||||
this.updateProductFilter();
|
||||
}
|
||||
|
||||
setData(data) {
|
||||
this.data = data;
|
||||
this.updateProductFilter();
|
||||
this.renderTable();
|
||||
}
|
||||
addEventListeners() {
|
||||
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');
|
||||
|
||||
setSearchKeyword(keyword) {
|
||||
this.searchKeyword = keyword.toLowerCase();
|
||||
this.updateProductFilter();
|
||||
this.renderTable();
|
||||
}
|
||||
|
||||
updateProductFilter() {
|
||||
const headerTools = document.querySelector('header-tools');
|
||||
if (headerTools) {
|
||||
const selectedProduct = headerTools.selectedProduct;
|
||||
this.filterData(selectedProduct);
|
||||
selectAllCheckbox.addEventListener('change', (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 {
|
||||
// 如果没有找到 header-tools 组件,显示所有数据
|
||||
this.selectedRows.delete(index);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
rowCheckboxes.forEach(checkbox => {
|
||||
checkbox.addEventListener('change', (e) => {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
if (e.target.checked) {
|
||||
this.selectedRows.add(index);
|
||||
} else {
|
||||
this.selectedRows.delete(index);
|
||||
}
|
||||
selectAllCheckbox.checked = this.selectedRows.size === rowCheckboxes.length;
|
||||
});
|
||||
});
|
||||
|
||||
editButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
const item = this.filteredData[index];
|
||||
this.dispatchEvent(new CustomEvent('edit', {
|
||||
detail: item,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
deleteButtons.forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.target.dataset.index);
|
||||
const item = this.filteredData[index];
|
||||
if (confirm(`确定要删除接口 "${item.InterfaceName}" 吗?`)) {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: item,
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 监听搜索事件
|
||||
this.addEventListener('search', async (e) => {
|
||||
this.searchKeyword = e.detail.keyword.toLowerCase();
|
||||
await this.filterData();
|
||||
});
|
||||
|
||||
// 监听重置搜索事件
|
||||
this.addEventListener('reset-search', async () => {
|
||||
// 重置所有筛选条件
|
||||
this.searchKeyword = '';
|
||||
this.filters = {
|
||||
productName: '',
|
||||
ataName: '',
|
||||
structName: ''
|
||||
};
|
||||
// 恢复原始数据
|
||||
this.filteredData = [...this.data];
|
||||
// 重新渲染表格
|
||||
this.render();
|
||||
});
|
||||
|
||||
// 监听高级搜索事件
|
||||
this.addEventListener('advanced-search', async (e) => {
|
||||
this.filters = e.detail;
|
||||
await this.filterData();
|
||||
});
|
||||
|
||||
// 添加分页事件监听器
|
||||
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) {
|
||||
firstPageBtn.addEventListener('click', () => this.setPage(1));
|
||||
}
|
||||
if (prevPageBtn) {
|
||||
prevPageBtn.addEventListener('click', () => this.setPage(this.currentPage - 1));
|
||||
}
|
||||
if (nextPageBtn) {
|
||||
nextPageBtn.addEventListener('click', () => this.setPage(this.currentPage + 1));
|
||||
}
|
||||
if (lastPageBtn) {
|
||||
lastPageBtn.addEventListener('click', () => this.setPage(this.getTotalPages()));
|
||||
}
|
||||
if (pageSizeSelect) {
|
||||
pageSizeSelect.addEventListener('change', (e) => {
|
||||
this.pageSize = parseInt(e.target.value);
|
||||
this.currentPage = 1; // 重置到第一页
|
||||
this.render();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
filterData(selectedProduct) {
|
||||
// 首先根据构型过滤
|
||||
let filteredByProduct = selectedProduct && selectedProduct !== ''
|
||||
? this.data.filter(item => item.ProductName === selectedProduct)
|
||||
: [...this.data];
|
||||
// 防抖函数
|
||||
debounce(func, wait) {
|
||||
return (...args) => {
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
this.filterTimeout = null;
|
||||
}, wait);
|
||||
};
|
||||
}
|
||||
|
||||
async filterData() {
|
||||
if (this.isLoading) return;
|
||||
this.setLoading(true);
|
||||
|
||||
try {
|
||||
await new Promise(resolve => {
|
||||
if (this.filterTimeout) {
|
||||
clearTimeout(this.filterTimeout);
|
||||
}
|
||||
|
||||
this.filterTimeout = setTimeout(() => {
|
||||
requestAnimationFrame(() => {
|
||||
// 首先根据高级搜索条件过滤
|
||||
let filtered = [...this.data];
|
||||
|
||||
if (this.filters.productName) {
|
||||
filtered = filtered.filter(item =>
|
||||
item.ProductName === this.filters.productName
|
||||
);
|
||||
}
|
||||
if (this.filters.ataName) {
|
||||
filtered = filtered.filter(item =>
|
||||
item.ATAName === this.filters.ataName
|
||||
);
|
||||
}
|
||||
if (this.filters.structName) {
|
||||
filtered = filtered.filter(item =>
|
||||
item.ModelStructName === this.filters.structName
|
||||
);
|
||||
}
|
||||
|
||||
// 然后根据搜索关键词过滤
|
||||
if (this.searchKeyword) {
|
||||
this.filteredData = filteredByProduct.filter(item =>
|
||||
item.ATAName.toLowerCase().includes(this.searchKeyword) ||
|
||||
item.ModelStructName.toLowerCase().includes(this.searchKeyword) ||
|
||||
this.filteredData = filtered.filter(item =>
|
||||
item.InterfaceName.toLowerCase().includes(this.searchKeyword)
|
||||
);
|
||||
} else {
|
||||
this.filteredData = filteredByProduct;
|
||||
this.filteredData = filtered;
|
||||
}
|
||||
|
||||
this.currentPage = 1; // 重置到第一页
|
||||
this.render();
|
||||
resolve();
|
||||
});
|
||||
}, 300);
|
||||
});
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async setData(data) {
|
||||
this.setLoading(true);
|
||||
try {
|
||||
// 清理旧数据
|
||||
this.data = [];
|
||||
this.filteredData = [];
|
||||
this.selectedRows.clear();
|
||||
|
||||
// 设置新数据
|
||||
this.data = data;
|
||||
this.filteredData = [...data];
|
||||
this.currentPage = 1;
|
||||
this.render();
|
||||
} finally {
|
||||
this.setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
getSelectedRows() {
|
||||
return Array.from(this.selectedRows);
|
||||
return Array.from(this.selectedRows).map(index => this.filteredData[index]);
|
||||
}
|
||||
|
||||
addEventListeners() {
|
||||
this.shadowRoot.addEventListener('click', (e) => {
|
||||
const row = e.target.closest('tr');
|
||||
if (!row) return;
|
||||
|
||||
if (e.target.type === 'checkbox') {
|
||||
const id = row.dataset.id;
|
||||
if (e.target.checked) {
|
||||
this.selectedRows.add(id);
|
||||
} else {
|
||||
this.selectedRows.delete(id);
|
||||
formatArraySize(item) {
|
||||
if (item.InterfaceIsArray === 1) {
|
||||
if (item.InterfaceArraySize_2 && item.InterfaceArraySize_2 > 1) {
|
||||
return `${item.InterfaceArraySize_1}×${item.InterfaceArraySize_2}`;
|
||||
}
|
||||
this.updateSelectedRows();
|
||||
return `${item.InterfaceArraySize_1}`;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听构型变化事件
|
||||
document.addEventListener('product-change', () => {
|
||||
this.updateProductFilter();
|
||||
this.renderTable();
|
||||
});
|
||||
|
||||
// 监听搜索事件
|
||||
document.addEventListener('search', (e) => {
|
||||
this.setSearchKeyword(e.detail.keyword);
|
||||
});
|
||||
return '1';
|
||||
}
|
||||
|
||||
updateSelectedRows() {
|
||||
const checkboxes = this.shadowRoot.querySelectorAll('input[type="checkbox"]');
|
||||
checkboxes.forEach(checkbox => {
|
||||
checkbox.checked = this.selectedRows.has(checkbox.closest('tr').dataset.id);
|
||||
});
|
||||
getTotalPages() {
|
||||
return Math.ceil(this.filteredData.length / this.pageSize);
|
||||
}
|
||||
|
||||
renderTable() {
|
||||
const tbody = this.shadowRoot.querySelector('tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
this.filteredData.forEach(item => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.dataset.id = `${item.ProductName}-${item.ATAName}-${item.ModelStructName}-${item.InterfaceName}`;
|
||||
|
||||
tr.innerHTML = `
|
||||
<td><input type="checkbox"></td>
|
||||
<td>${item.ProductName}</td>
|
||||
<td>${item.ATAName}</td>
|
||||
<td>${item.ModelStructName}</td>
|
||||
<td>${item.InterfaceName}</td>
|
||||
<td>${item.InterfaceType}</td>
|
||||
<td>${item.InterfaceOption === 1 ? '是' : '否'}</td>
|
||||
<td>${item.InterfaceIsArray === 1 ? '是' : '否'}</td>
|
||||
<td>${item.InterfaceArraySize_1 || ''}</td>
|
||||
<td>${item.InterfaceArraySize_2 || ''}</td>
|
||||
<td>${item.InterfaceNotes || ''}</td>
|
||||
<td>
|
||||
<button class="edit-btn">编辑</button>
|
||||
<button class="delete-btn">删除</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tr.querySelector('.edit-btn').addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('edit', {
|
||||
detail: {
|
||||
ProductName: item.ProductName,
|
||||
ATAName: item.ATAName,
|
||||
ModelStructName: item.ModelStructName,
|
||||
InterfaceName: item.InterfaceName
|
||||
}
|
||||
getCurrentPageData() {
|
||||
const start = (this.currentPage - 1) * this.pageSize;
|
||||
const end = start + this.pageSize;
|
||||
return this.filteredData.slice(start, end).map((item, index) => ({
|
||||
...item,
|
||||
_displayIndex: start + index
|
||||
}));
|
||||
});
|
||||
|
||||
tr.querySelector('.delete-btn').addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('delete', {
|
||||
detail: {
|
||||
ProductName: item.ProductName,
|
||||
ATAName: item.ATAName,
|
||||
ModelStructName: item.ModelStructName,
|
||||
InterfaceName: item.InterfaceName
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
setPage(page) {
|
||||
if (page < 1 || page > this.getTotalPages()) return;
|
||||
this.currentPage = page;
|
||||
this.render();
|
||||
}
|
||||
|
||||
setLoading(loading) {
|
||||
this.isLoading = loading;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
table-layout: fixed;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
th, td {
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #3498db;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
th {
|
||||
.data-table th {
|
||||
background-color: #f5f5f5;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
.data-table tr:nth-child(even) {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
.edit-btn, .delete-btn {
|
||||
.data-table tr:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.checkbox-cell {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.product-cell {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.ata-cell {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.struct-cell {
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.interface-cell {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.type-cell {
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
.size-cell {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.notes-cell {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.action-cell {
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 4px 8px;
|
||||
margin: 0 4px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
@ -194,43 +378,290 @@ class InterfaceDataTable extends HTMLElement {
|
||||
color: white;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
.action-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.action-btn:focus {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.action-btn:active {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.pagination button {
|
||||
padding: 5px 10px;
|
||||
border: 1px solid #ddd;
|
||||
background-color: white;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pagination button:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.pagination button:disabled {
|
||||
background-color: #f5f5f5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.pagination .page-info {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.pagination .page-size-select {
|
||||
margin-left: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
<table>
|
||||
<div style="position: relative;">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><input type="checkbox" id="select-all"></th>
|
||||
<th>构型</th>
|
||||
<th>ATA章节</th>
|
||||
<th>模型结构体名</th>
|
||||
<th>接口</th>
|
||||
<th>接口类型</th>
|
||||
<th>是否可选</th>
|
||||
<th>是否为数组</th>
|
||||
<th>第一维大小</th>
|
||||
<th>第二维大小</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
<th class="checkbox-cell">
|
||||
<input type="checkbox" id="selectAll">
|
||||
</th>
|
||||
<th class="product-cell">构型</th>
|
||||
<th class="ata-cell">ATA章节</th>
|
||||
<th class="struct-cell">接口结构体名</th>
|
||||
<th class="interface-cell">接口名称</th>
|
||||
<th class="type-cell">数据类型</th>
|
||||
<th class="size-cell">数据大小</th>
|
||||
<th class="notes-cell">备注</th>
|
||||
<th class="action-cell">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
<tbody>
|
||||
${this.getCurrentPageData().map((item) => `
|
||||
<tr>
|
||||
<td class="checkbox-cell">
|
||||
<input type="checkbox" class="row-checkbox" data-index="${item._displayIndex}">
|
||||
</td>
|
||||
<td>${item.ProductName || ''}</td>
|
||||
<td>${item.ATAName || ''}</td>
|
||||
<td>${item.ModelStructName || ''}</td>
|
||||
<td>${item.InterfaceName || ''}</td>
|
||||
<td>${item.InterfaceType || ''}</td>
|
||||
<td>${this.formatArraySize(item)}</td>
|
||||
<td>${item.InterfaceNotes || ''}</td>
|
||||
<td>
|
||||
<button type="button" class="action-btn edit-btn" data-index="${item._displayIndex}">编辑</button>
|
||||
<button type="button" class="action-btn delete-btn" data-index="${item._displayIndex}">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
${this.isLoading ? `
|
||||
<div class="loading-overlay">
|
||||
<div class="loading-spinner"></div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="pagination">
|
||||
<button id="firstPage" ${this.currentPage === 1 ? 'disabled' : ''}>首页</button>
|
||||
<button id="prevPage" ${this.currentPage === 1 ? 'disabled' : ''}>上一页</button>
|
||||
<span class="page-info">第 ${this.currentPage} 页 / 共 ${this.getTotalPages()} 页</span>
|
||||
<button id="nextPage" ${this.currentPage === this.getTotalPages() ? 'disabled' : ''}>下一页</button>
|
||||
<button id="lastPage" ${this.currentPage === this.getTotalPages() ? 'disabled' : ''}>末页</button>
|
||||
<select class="page-size-select" id="pageSize">
|
||||
<option value="10" ${this.pageSize === 10 ? 'selected' : ''}>10条/页</option>
|
||||
<option value="20" ${this.pageSize === 20 ? 'selected' : ''}>20条/页</option>
|
||||
<option value="50" ${this.pageSize === 50 ? 'selected' : ''}>50条/页</option>
|
||||
<option value="100" ${this.pageSize === 100 ? 'selected' : ''}>100条/页</option>
|
||||
</select>
|
||||
</div>
|
||||
`;
|
||||
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);
|
||||
// 修改事件监听器的添加方式
|
||||
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 {
|
||||
this.selectedRows.delete(id);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
321
XNSimHtml/components/interface-config/import-dialog.js
Normal file
321
XNSimHtml/components/interface-config/import-dialog.js
Normal file
@ -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 = `
|
||||
<option value="">请选择构型</option>
|
||||
${this.products.map(product => `
|
||||
<option value="${product}">${product}</option>
|
||||
`).join('')}
|
||||
`;
|
||||
}
|
||||
|
||||
updateAtaOptions() {
|
||||
const select = this.shadowRoot.querySelector('#ata-select');
|
||||
select.innerHTML = `
|
||||
<option value="">请选择ATA章节</option>
|
||||
${this.atas.map(ata => `
|
||||
<option value="${ata}">${ata}</option>
|
||||
`).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 => `
|
||||
<tr>
|
||||
<td>${shouldLowercase ? item.name.toLowerCase() : item.name}</td>
|
||||
<td>${item.variableType || '-'}</td>
|
||||
<td>${item.structName}</td>
|
||||
<td>${item.isArray ? '是' : '否'}</td>
|
||||
<td>${item.arraySize1 || '-'}</td>
|
||||
<td>${item.arraySize2 || '-'}</td>
|
||||
<td>${item.description}</td>
|
||||
</tr>
|
||||
`).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 = `
|
||||
<style>
|
||||
:host {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
min-width: 800px;
|
||||
max-width: 90%;
|
||||
max-height: 90vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5em;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.options {
|
||||
margin-bottom: 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.option-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.option-group label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
select, input[type="text"] {
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f5f5f5;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
<div class="dialog">
|
||||
<div class="header">
|
||||
<div class="title">导入数据预览</div>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="options">
|
||||
<div class="option-group">
|
||||
<label for="product-select">构型</label>
|
||||
<select id="product-select">
|
||||
<option value="">请选择构型</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="option-group">
|
||||
<label for="ata-select">ATA章节</label>
|
||||
<select id="ata-select">
|
||||
<option value="">请选择ATA章节</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="option-group">
|
||||
<label for="model-input">模型名称</label>
|
||||
<input type="text" id="model-input" placeholder="请输入模型名称">
|
||||
</div>
|
||||
<div class="option-group">
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="lowercase-checkbox">
|
||||
<label for="lowercase-checkbox">小写所有变量名</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>变量名称</th>
|
||||
<th>变量类型</th>
|
||||
<th>结构体</th>
|
||||
<th>是否数组</th>
|
||||
<th>第一维大小</th>
|
||||
<th>第二维大小</th>
|
||||
<th>描述</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button class="confirm-btn">确认导入</button>
|
||||
<button class="cancel-btn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('import-dialog', ImportDialog);
|
@ -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');
|
||||
});
|
||||
|
||||
// 监听高级搜索的筛选条件变化
|
||||
[productSelect, ataSelect, structSelect].forEach(select => {
|
||||
select.addEventListener('change', () => {
|
||||
this.dispatchEvent(new CustomEvent('advanced-search', {
|
||||
detail: {
|
||||
productName: productSelect.value,
|
||||
ataName: ataSelect.value,
|
||||
structName: structSelect.value
|
||||
}
|
||||
});
|
||||
|
||||
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 }
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -67,65 +125,320 @@ class Toolbar extends HTMLElement {
|
||||
<style>
|
||||
.toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
.toolbar-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-group.left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.toolbar-group.center {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toolbar-group.right {
|
||||
flex: 1;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.search-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 6px 12px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
button {
|
||||
.btn {
|
||||
padding: 6px 12px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background-color: #fff;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
.btn:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.add-btn:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background-color: #da190b;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
.import-btn {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.import-btn:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
padding: 6px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: none;
|
||||
color: #666;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon-btn img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.advanced-search-btn {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.advanced-search-btn.active {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
|
||||
.advanced-search-panel {
|
||||
display: none;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
background-color: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.advanced-search-panel select {
|
||||
padding: 6px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 800px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.import-section {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.import-section h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.import-btn-section {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.import-description {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
margin-top: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cancel-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background-color: #f5f5f5;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel-btn:hover {
|
||||
background-color: #e0e0e0;
|
||||
}
|
||||
</style>
|
||||
<div class="toolbar">
|
||||
<div class="search-container">
|
||||
<input type="text" class="search-input" placeholder="搜索...">
|
||||
<button class="search-btn">搜索</button>
|
||||
<button class="reset-btn">重置</button>
|
||||
<div class="toolbar-group left">
|
||||
<button class="btn add-btn">
|
||||
<i class="fas fa-plus"></i>
|
||||
添加变量
|
||||
</button>
|
||||
<button class="btn delete-btn">
|
||||
<i class="fas fa-trash"></i>
|
||||
删除变量
|
||||
</button>
|
||||
</div>
|
||||
<div class="toolbar-group center">
|
||||
<button class="btn import-btn">
|
||||
<i class="fas fa-upload"></i>
|
||||
导入数据
|
||||
</button>
|
||||
</div>
|
||||
<div class="toolbar-group right">
|
||||
<div class="search-group">
|
||||
<input type="text" class="search-input" placeholder="搜索接口名称...">
|
||||
<button class="icon-btn search-btn" title="搜索">
|
||||
<img src="assets/icons/png/search_b.png" alt="搜索">
|
||||
</button>
|
||||
<button class="icon-btn reset-btn" title="重置">
|
||||
<img src="assets/icons/png/refresh_b.png" alt="重置">
|
||||
</button>
|
||||
<button class="advanced-search-btn">高级搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="advanced-search-panel">
|
||||
<select id="filterProduct">
|
||||
<option value="">选择构型</option>
|
||||
</select>
|
||||
<select id="filterAta">
|
||||
<option value="">选择ATA章节</option>
|
||||
</select>
|
||||
<select id="filterStruct">
|
||||
<option value="">选择接口结构体</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="modal" id="importModal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title">导入数据</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="import-section">
|
||||
<h3>从ICD导入</h3>
|
||||
<div class="import-btn-section">
|
||||
<button class="btn import-btn" id="importExcelBtn">
|
||||
<i class="fas fa-file-excel"></i>
|
||||
选择Excel文件
|
||||
</button>
|
||||
</div>
|
||||
<div class="import-description">
|
||||
支持从Excel文件(.xlsx/.xls)导入接口数据。<br>
|
||||
请确保Excel文件格式正确,包含所有必要的字段。<br>
|
||||
导入过程中请勿关闭页面。
|
||||
</div>
|
||||
</div>
|
||||
<div class="import-section">
|
||||
<h3>从IDL导入</h3>
|
||||
<div class="import-btn-section">
|
||||
<button class="btn import-btn" id="importIdlBtn">
|
||||
<i class="fas fa-file-code"></i>
|
||||
选择IDL文件
|
||||
</button>
|
||||
</div>
|
||||
<div class="import-description">
|
||||
支持从IDL文件(.idl)导入接口数据。<br>
|
||||
请确保IDL文件格式正确,包含所有必要的接口定义。<br>
|
||||
导入过程中请勿关闭页面。
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="cancel-btn" id="cancelImportBtn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
<button class="add-btn">添加变量</button>
|
||||
<button class="delete-btn">删除变量</button>
|
||||
<button class="download-btn">下载模板</button>
|
||||
<button class="import-btn">导入数据</button>
|
||||
<input type="file" class="file-input" accept=".xlsx,.xls,.csv">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
updateFilterOptions(options) {
|
||||
const productSelect = this.shadowRoot.querySelector('#filterProduct');
|
||||
const ataSelect = this.shadowRoot.querySelector('#filterAta');
|
||||
const structSelect = this.shadowRoot.querySelector('#filterStruct');
|
||||
|
||||
// 更新构型选项
|
||||
productSelect.innerHTML = '<option value="">选择构型</option>' +
|
||||
options.products.map(product => `<option value="${product}">${product}</option>`).join('');
|
||||
|
||||
// 更新ATA章节选项
|
||||
ataSelect.innerHTML = '<option value="">选择ATA章节</option>' +
|
||||
options.atas.map(ata => `<option value="${ata}">${ata}</option>`).join('');
|
||||
|
||||
// 更新接口结构体选项
|
||||
structSelect.innerHTML = '<option value="">选择接口结构体</option>' +
|
||||
options.structs.map(struct => `<option value="${struct}">${struct}</option>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('interface-toolbar', Toolbar);
|
||||
customElements.define('interface-toolbar', InterfaceToolbar);
|
@ -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 = `
|
||||
<option value="">请选择构型</option>
|
||||
${this.products.map(product => `
|
||||
<option value="${product}" ${this.interfaceData?.ProductName === product ? 'selected' : ''}>
|
||||
${product}
|
||||
</option>
|
||||
`).join('')}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
updateAtaOptions() {
|
||||
const select = this.shadowRoot.querySelector('#ataName');
|
||||
if (select) {
|
||||
select.innerHTML = `
|
||||
<option value="">请选择ATA章节</option>
|
||||
${this.ataChapters.map(chapter => `
|
||||
<option value="${chapter}" ${this.interfaceData?.ATAName === chapter ? 'selected' : ''}>
|
||||
${chapter}
|
||||
</option>
|
||||
`).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 = `
|
||||
<option value="">请选择模型结构体</option>
|
||||
${this.modelStructs.map(struct => `
|
||||
<option value="${struct}" ${this.interfaceData?.ModelStructName === struct ? 'selected' : ''}>
|
||||
${struct}
|
||||
</option>
|
||||
`).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 = `
|
||||
<style>
|
||||
.form-dialog {
|
||||
.form-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
@ -96,38 +270,26 @@ class VariableForm extends HTMLElement {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
position: absolute;
|
||||
.form-container {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
width: 500px;
|
||||
max-height: 90vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
@ -138,10 +300,7 @@ class VariableForm extends HTMLElement {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
input[type="text"],
|
||||
input[type="number"],
|
||||
select,
|
||||
textarea {
|
||||
select, input[type="text"], input[type="number"] {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ddd;
|
||||
@ -149,20 +308,24 @@ class VariableForm extends HTMLElement {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 100px;
|
||||
resize: vertical;
|
||||
.checkbox-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.form-footer {
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.save-btn,
|
||||
.cancel-btn {
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
@ -178,67 +341,109 @@ class VariableForm extends HTMLElement {
|
||||
background-color: #f44336;
|
||||
color: white;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.array-size-group {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.array-size-group input[type="number"] {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
||||
<div class="form-dialog">
|
||||
<div class="form-content">
|
||||
<div class="form-header">
|
||||
<div class="form-title">${this.mode === 'add' ? '添加接口' : '编辑接口'}</div>
|
||||
<button class="close-btn">×</button>
|
||||
</div>
|
||||
<div class="form-overlay">
|
||||
<div class="form-container">
|
||||
<h2 class="form-title">${this.mode === 'add' ? '添加接口' : '编辑接口'}</h2>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label>系统名称</label>
|
||||
<input type="text" name="systemName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>产品名称</label>
|
||||
<input type="text" name="productName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>ATA章节</label>
|
||||
<input type="text" name="ataName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>模型结构名</label>
|
||||
<input type="text" name="modelStructName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>接口名</label>
|
||||
<input type="text" name="interfaceName" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>接口类型</label>
|
||||
<select name="interfaceType" required>
|
||||
<option value="int">int</option>
|
||||
<option value="float">float</option>
|
||||
<option value="double">double</option>
|
||||
<option value="bool">bool</option>
|
||||
<option value="string">string</option>
|
||||
<label for="productName">构型</label>
|
||||
<select id="productName" name="productName" required>
|
||||
<option value="">请选择构型</option>
|
||||
${this.products.map(product => `
|
||||
<option value="${product}" ${this.interfaceData?.ProductName === product ? 'selected' : ''}>
|
||||
${product}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>接口选项</label>
|
||||
<input type="text" name="interfaceOption" required>
|
||||
<label for="ataName">ATA章节</label>
|
||||
<select id="ataName" name="ataName" required>
|
||||
<option value="">请选择ATA章节</option>
|
||||
${this.ataChapters.map(chapter => `
|
||||
<option value="${chapter}" ${this.interfaceData?.ATAName === chapter ? 'selected' : ''}>
|
||||
${chapter}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<input type="checkbox" name="interfaceIsArray">
|
||||
是否为数组
|
||||
</label>
|
||||
<label for="modelStructName">接口结构体名</label>
|
||||
<select id="modelStructName" name="modelStructName" required>
|
||||
<option value="">请选择接口结构体</option>
|
||||
${this.modelStructs.map(struct => `
|
||||
<option value="${struct}" ${this.interfaceData?.ModelStructName === struct ? 'selected' : ''}>
|
||||
${struct}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数组大小1</label>
|
||||
<input type="number" name="interfaceArraySize_1">
|
||||
<label for="interfaceName">接口名称</label>
|
||||
<input type="text" id="interfaceName" name="interfaceName" required
|
||||
value="${this.interfaceData?.InterfaceName || ''}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数组大小2</label>
|
||||
<input type="number" name="interfaceArraySize_2">
|
||||
<label for="interfaceType">数据类型</label>
|
||||
<select id="interfaceType" name="interfaceType" required>
|
||||
<option value="">请选择数据类型</option>
|
||||
${interfaceTypes.map(type => `
|
||||
<option value="${type}" ${this.interfaceData?.InterfaceType === type ? 'selected' : ''}>
|
||||
${type}
|
||||
</option>
|
||||
`).join('')}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>备注</label>
|
||||
<textarea name="interfaceNotes"></textarea>
|
||||
<div class="checkbox-group">
|
||||
<input type="checkbox" id="interfaceIsArray" name="interfaceIsArray"
|
||||
${this.interfaceData?.InterfaceIsArray === 1 ? 'checked' : ''}>
|
||||
<label for="interfaceIsArray">是否为数组</label>
|
||||
</div>
|
||||
<div class="form-footer">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>数组大小</label>
|
||||
<div class="array-size-group">
|
||||
<input type="number" id="interfaceArraySize_1" name="interfaceArraySize_1"
|
||||
value="${this.interfaceData?.InterfaceArraySize_1 || ''}"
|
||||
${this.interfaceData?.InterfaceIsArray !== 1 ? 'disabled' : ''}
|
||||
placeholder="第一维">
|
||||
<span>×</span>
|
||||
<input type="number" id="interfaceArraySize_2" name="interfaceArraySize_2"
|
||||
value="${this.interfaceData?.InterfaceArraySize_2 || ''}"
|
||||
${this.interfaceData?.InterfaceIsArray !== 1 ? 'disabled' : ''}
|
||||
placeholder="第二维">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="interfaceNotes">备注</label>
|
||||
<input type="text" id="interfaceNotes" name="interfaceNotes"
|
||||
value="${this.interfaceData?.InterfaceNotes || ''}">
|
||||
</div>
|
||||
|
||||
<div class="button-group">
|
||||
<button type="button" class="cancel-btn">取消</button>
|
||||
<button type="submit" class="save-btn">保存</button>
|
||||
</div>
|
||||
|
69
XNSimHtml/routes/icd-import.js
Normal file
69
XNSimHtml/routes/icd-import.js
Normal file
@ -0,0 +1,69 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { parseICDFile } = require('../utils/icd-parser');
|
||||
|
||||
// 确保uploads目录存在
|
||||
const uploadsDir = path.join(__dirname, '..', 'uploads');
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 配置文件上传
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
cb(null, uploadsDir);
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
cb(null, Date.now() + path.extname(file.originalname));
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
fileFilter: function (req, file, cb) {
|
||||
// 检查文件扩展名
|
||||
const ext = path.extname(file.originalname).toLowerCase();
|
||||
if (ext !== '.xlsx') {
|
||||
return cb(new Error('只允许上传.xlsx文件'));
|
||||
}
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
// 导入ICD文件
|
||||
router.post('/import', upload.single('file'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: '未找到上传的文件'
|
||||
});
|
||||
}
|
||||
|
||||
const result = await parseICDFile(req.file.path);
|
||||
|
||||
// 删除临时文件
|
||||
fs.unlinkSync(req.file.path);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: result
|
||||
});
|
||||
} catch (error) {
|
||||
// 如果文件存在,删除它
|
||||
if (req.file && fs.existsSync(req.file.path)) {
|
||||
fs.unlinkSync(req.file.path);
|
||||
}
|
||||
|
||||
console.error('导入ICD文件时出错:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
@ -3,7 +3,16 @@ const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const xlsx = require('xlsx');
|
||||
const multer = require('multer');
|
||||
const { getDataInterfaces, addDataInterface, updateDataInterface, deleteDataInterface } = require('../utils/db-utils');
|
||||
const {
|
||||
getDataInterfaces,
|
||||
addDataInterface,
|
||||
updateDataInterface,
|
||||
deleteDataInterface,
|
||||
getDataInterfaceStructs,
|
||||
addDataInterfaceStruct,
|
||||
updateDataInterfaceStruct,
|
||||
deleteDataInterfaceStruct
|
||||
} = require('../utils/db-utils');
|
||||
|
||||
const router = express.Router();
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
@ -87,6 +96,52 @@ router.delete('/delete', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 获取接口结构体列表
|
||||
router.get('/struct/list', async (req, res) => {
|
||||
try {
|
||||
const { systemName, productName, ataName } = req.query;
|
||||
const structs = await getDataInterfaceStructs(systemName, productName, ataName);
|
||||
res.json(structs);
|
||||
} catch (error) {
|
||||
console.error('获取接口结构体列表失败:', error);
|
||||
res.status(500).json({ error: '获取接口结构体列表失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 添加接口结构体
|
||||
router.post('/struct/add', async (req, res) => {
|
||||
try {
|
||||
const result = await addDataInterfaceStruct(req.body);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('添加接口结构体失败:', error);
|
||||
res.status(500).json({ error: error.message || '添加接口结构体失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 更新接口结构体
|
||||
router.put('/struct/update', async (req, res) => {
|
||||
try {
|
||||
const result = await updateDataInterfaceStruct(req.body);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('更新接口结构体失败:', error);
|
||||
res.status(500).json({ error: error.message || '更新接口结构体失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 删除接口结构体
|
||||
router.delete('/struct/delete', async (req, res) => {
|
||||
try {
|
||||
const { systemName, productName, ataName, modelStructName } = req.query;
|
||||
const result = await deleteDataInterfaceStruct(systemName, productName, ataName, modelStructName);
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('删除接口结构体失败:', error);
|
||||
res.status(500).json({ error: error.message || '删除接口结构体失败' });
|
||||
}
|
||||
});
|
||||
|
||||
// 下载模板
|
||||
router.get('/template', async (req, res) => {
|
||||
try {
|
||||
@ -120,57 +175,40 @@ router.get('/template', async (req, res) => {
|
||||
});
|
||||
|
||||
// 导入数据
|
||||
router.post('/import', upload.single('file'), async (req, res) => {
|
||||
router.post('/import', async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ error: '未上传文件' });
|
||||
const importData = req.body;
|
||||
if (!Array.isArray(importData)) {
|
||||
return res.status(400).json({ error: '导入数据格式错误' });
|
||||
}
|
||||
|
||||
const workbook = xlsx.read(req.file.buffer);
|
||||
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||
const data = xlsx.utils.sheet_to_json(worksheet);
|
||||
|
||||
const results = [];
|
||||
for (const item of data) {
|
||||
for (const item of importData) {
|
||||
try {
|
||||
// 验证所有必填字段
|
||||
const requiredFields = {
|
||||
'系统名称': 'SystemName',
|
||||
'产品名称': 'ProductName',
|
||||
'ATA章节': 'ATAName',
|
||||
'模型结构名': 'ModelStructName',
|
||||
'接口名': 'InterfaceName',
|
||||
'接口类型': 'InterfaceType',
|
||||
'接口选项': 'InterfaceOption',
|
||||
'是否为数组': 'InterfaceIsArray',
|
||||
'数组大小1': 'InterfaceArraySize_1',
|
||||
'数组大小2': 'InterfaceArraySize_2',
|
||||
'备注': 'InterfaceNotes'
|
||||
};
|
||||
const requiredFields = [
|
||||
'SystemName',
|
||||
'ProductName',
|
||||
'ATAName',
|
||||
'ModelStructName',
|
||||
'InterfaceName',
|
||||
'InterfaceType',
|
||||
'InterfaceOption',
|
||||
'InterfaceIsArray',
|
||||
'InterfaceArraySize_1',
|
||||
'InterfaceArraySize_2',
|
||||
'InterfaceNotes'
|
||||
];
|
||||
|
||||
// 检查所有必填字段是否存在
|
||||
for (const [excelField, dbField] of Object.entries(requiredFields)) {
|
||||
if (item[excelField] === undefined || item[excelField] === null || item[excelField] === '') {
|
||||
throw new Error(`字段 "${excelField}" 是必填字段`);
|
||||
for (const field of requiredFields) {
|
||||
if (item[field] === undefined || item[field] === null || item[field] === '') {
|
||||
throw new Error(`字段 "${field}" 是必填字段`);
|
||||
}
|
||||
}
|
||||
|
||||
const interfaceData = {
|
||||
SystemName: item['系统名称'],
|
||||
ProductName: item['产品名称'],
|
||||
ATAName: item['ATA章节'],
|
||||
ModelStructName: item['模型结构名'],
|
||||
InterfaceName: item['接口名'],
|
||||
InterfaceType: item['接口类型'],
|
||||
InterfaceOption: item['接口选项'],
|
||||
InterfaceIsArray: item['是否为数组'] === '是' ? 1 : 0,
|
||||
InterfaceArraySize_1: item['数组大小1'],
|
||||
InterfaceArraySize_2: item['数组大小2'],
|
||||
InterfaceNotes: item['备注']
|
||||
};
|
||||
|
||||
const result = await addDataInterface(interfaceData);
|
||||
results.push({ success: true, data: interfaceData });
|
||||
const result = await addDataInterface(item);
|
||||
results.push({ success: true, data: item });
|
||||
} catch (error) {
|
||||
results.push({ success: false, error: error.message, data: item });
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ const simulationRoutes = require('./routes/run-simulation');
|
||||
const udpMonitorRoutes = require('./routes/udp-monitor');
|
||||
const productsRoutes = require('./routes/products');
|
||||
const interfaceRoutes = require('./routes/interface-config');
|
||||
const icdImportRoutes = require('./routes/icd-import');
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3000;
|
||||
@ -70,6 +71,7 @@ app.use('/api', simulationRoutes);
|
||||
app.use('/api/udp-monitor', udpMonitorRoutes);
|
||||
app.use('/api', productsRoutes);
|
||||
app.use('/api/interface', interfaceRoutes);
|
||||
app.use('/api/icd', icdImportRoutes);
|
||||
|
||||
// 主页路由
|
||||
app.get('/', (req, res) => {
|
||||
|
@ -791,8 +791,14 @@ function updateDataInterface(interfaceData) {
|
||||
'InterfaceArraySize_2'
|
||||
];
|
||||
|
||||
// 检查数据结构
|
||||
if (!interfaceData.currentData || !interfaceData.originalData) {
|
||||
throw new Error('数据格式错误:缺少 currentData 或 originalData');
|
||||
}
|
||||
|
||||
// 验证 currentData 中的必填字段
|
||||
for (const field of requiredFields) {
|
||||
if (interfaceData[field] === undefined || interfaceData[field] === null) {
|
||||
if (interfaceData.currentData[field] === undefined || interfaceData.currentData[field] === null) {
|
||||
throw new Error(`${field} 是必填字段`);
|
||||
}
|
||||
}
|
||||
@ -810,10 +816,33 @@ function updateDataInterface(interfaceData) {
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// 更新接口
|
||||
// 首先检查记录是否存在
|
||||
const existingRecord = db.prepare(`
|
||||
SELECT COUNT(*) as count FROM 'DataInterface'
|
||||
WHERE SystemName = ? AND ProductName = ? AND ATAName = ?
|
||||
AND ModelStructName = ? AND InterfaceName = ?
|
||||
`).get(
|
||||
interfaceData.originalData.SystemName,
|
||||
interfaceData.originalData.ProductName,
|
||||
interfaceData.originalData.ATAName,
|
||||
interfaceData.originalData.ModelStructName,
|
||||
interfaceData.originalData.InterfaceName
|
||||
);
|
||||
|
||||
if (existingRecord.count === 0) {
|
||||
db.close();
|
||||
throw new Error('要更新的记录不存在');
|
||||
}
|
||||
|
||||
// 更新接口,包括所有字段
|
||||
const updateResult = db.prepare(`
|
||||
UPDATE 'DataInterface'
|
||||
SET InterfaceType = ?,
|
||||
SET SystemName = ?,
|
||||
ProductName = ?,
|
||||
ATAName = ?,
|
||||
ModelStructName = ?,
|
||||
InterfaceName = ?,
|
||||
InterfaceType = ?,
|
||||
InterfaceOption = ?,
|
||||
InterfaceIsArray = ?,
|
||||
InterfaceArraySize_1 = ?,
|
||||
@ -822,21 +851,30 @@ function updateDataInterface(interfaceData) {
|
||||
WHERE SystemName = ? AND ProductName = ? AND ATAName = ?
|
||||
AND ModelStructName = ? AND InterfaceName = ?
|
||||
`).run(
|
||||
interfaceData.InterfaceType,
|
||||
interfaceData.InterfaceOption,
|
||||
interfaceData.InterfaceIsArray,
|
||||
interfaceData.InterfaceArraySize_1,
|
||||
interfaceData.InterfaceArraySize_2,
|
||||
interfaceData.InterfaceNotes || null,
|
||||
interfaceData.SystemName,
|
||||
interfaceData.ProductName,
|
||||
interfaceData.ATAName,
|
||||
interfaceData.ModelStructName,
|
||||
interfaceData.InterfaceName
|
||||
interfaceData.currentData.SystemName,
|
||||
interfaceData.currentData.ProductName,
|
||||
interfaceData.currentData.ATAName,
|
||||
interfaceData.currentData.ModelStructName,
|
||||
interfaceData.currentData.InterfaceName,
|
||||
interfaceData.currentData.InterfaceType,
|
||||
interfaceData.currentData.InterfaceOption,
|
||||
interfaceData.currentData.InterfaceIsArray,
|
||||
interfaceData.currentData.InterfaceArraySize_1,
|
||||
interfaceData.currentData.InterfaceArraySize_2,
|
||||
interfaceData.currentData.InterfaceNotes || null,
|
||||
interfaceData.originalData.SystemName,
|
||||
interfaceData.originalData.ProductName,
|
||||
interfaceData.originalData.ATAName,
|
||||
interfaceData.originalData.ModelStructName,
|
||||
interfaceData.originalData.InterfaceName
|
||||
);
|
||||
|
||||
db.close();
|
||||
|
||||
if (updateResult.changes === 0) {
|
||||
throw new Error('更新失败:没有记录被修改');
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
changes: updateResult.changes,
|
||||
@ -890,6 +928,62 @@ function deleteDataInterface(systemName, productName, ataName, modelStructName,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取接口结构体列表
|
||||
function getDataInterfaceStructs(systemName = 'XNSim', productName, ataName) {
|
||||
try {
|
||||
const xnCorePath = getXNCorePath();
|
||||
if (!xnCorePath) {
|
||||
throw new Error('XNCore环境变量未设置,无法获取数据库路径');
|
||||
}
|
||||
|
||||
const dbPath = xnCorePath + '/database/XNSim.db';
|
||||
if (!dbPath) {
|
||||
throw new Error('无法找到数据库文件');
|
||||
}
|
||||
|
||||
// 打开数据库连接
|
||||
const db = new Database(dbPath, { readonly: true });
|
||||
|
||||
// 根据是否传入 productName 构建不同的查询
|
||||
let query;
|
||||
let params;
|
||||
|
||||
if (!productName || productName === '') {
|
||||
query = `
|
||||
SELECT SystemName, ProductName, ATAName, ModelStructName
|
||||
FROM 'DataInterface'
|
||||
WHERE SystemName = ?
|
||||
GROUP BY ModelStructName
|
||||
ORDER BY ProductName, ATAName, ModelStructName
|
||||
`;
|
||||
params = [systemName];
|
||||
} else {
|
||||
query = `
|
||||
SELECT SystemName, ProductName, ATAName, ModelStructName
|
||||
FROM 'DataInterface'
|
||||
WHERE SystemName = ? AND ProductName = ?
|
||||
GROUP BY ModelStructName
|
||||
ORDER BY ATAName, ModelStructName
|
||||
`;
|
||||
params = [systemName, productName];
|
||||
}
|
||||
|
||||
if (ataName) {
|
||||
query = query.replace('GROUP BY', 'AND ATAName = ? GROUP BY');
|
||||
params.push(ataName);
|
||||
}
|
||||
|
||||
const structs = db.prepare(query).all(...params);
|
||||
|
||||
db.close();
|
||||
|
||||
return structs;
|
||||
} catch (error) {
|
||||
console.error('获取接口结构体列表数据失败:', error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getATAChapters,
|
||||
getModelsByChapterId,
|
||||
@ -903,5 +997,6 @@ module.exports = {
|
||||
getDataInterfaces,
|
||||
addDataInterface,
|
||||
updateDataInterface,
|
||||
deleteDataInterface
|
||||
deleteDataInterface,
|
||||
getDataInterfaceStructs
|
||||
};
|
98
XNSimHtml/utils/icd-parser.js
Normal file
98
XNSimHtml/utils/icd-parser.js
Normal file
@ -0,0 +1,98 @@
|
||||
const XLSX = require('xlsx');
|
||||
|
||||
/**
|
||||
* 解析数组维度字符串
|
||||
* @param {string|number} dimensions - 数组维度字符串或数字,如 "10" 或 "[10]" 或 "[3][2]"
|
||||
* @returns {Object} 包含数组维度的对象
|
||||
*/
|
||||
function parseArrayDimensions(dimensions) {
|
||||
// 处理空值或1的情况
|
||||
if (!dimensions || dimensions === '1' || dimensions === 1) {
|
||||
return { isArray: false, arraySize1: 0, arraySize2: 0 };
|
||||
}
|
||||
|
||||
// 确保dimensions是字符串
|
||||
const dimStr = String(dimensions).trim();
|
||||
|
||||
// 尝试匹配形如 [10] 或 [3][2] 的格式
|
||||
const bracketMatches = dimStr.match(/\[(\d+)\](?:\[(\d+)\])?/);
|
||||
if (bracketMatches) {
|
||||
const size1 = parseInt(bracketMatches[1]);
|
||||
const size2 = bracketMatches[2] ? parseInt(bracketMatches[2]) : 0;
|
||||
return {
|
||||
isArray: true,
|
||||
arraySize1: size1,
|
||||
arraySize2: size2
|
||||
};
|
||||
}
|
||||
|
||||
// 尝试匹配纯数字格式
|
||||
const numberMatch = dimStr.match(/^\d+$/);
|
||||
if (numberMatch) {
|
||||
const size = parseInt(numberMatch[0]);
|
||||
return {
|
||||
isArray: true,
|
||||
arraySize1: size,
|
||||
arraySize2: 0
|
||||
};
|
||||
}
|
||||
|
||||
// 如果都不匹配,则不是数组
|
||||
return { isArray: false, arraySize1: 0, arraySize2: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个工作表
|
||||
* @param {Object} worksheet - Excel工作表对象
|
||||
* @param {string} structName - 结构体名称(input/output)
|
||||
* @returns {Array} 解析后的数据数组
|
||||
*/
|
||||
function parseWorksheet(worksheet, structName) {
|
||||
const data = XLSX.utils.sheet_to_json(worksheet);
|
||||
|
||||
return data.map(row => {
|
||||
const dimensions = parseArrayDimensions(row['Variable Dimensions']);
|
||||
|
||||
return {
|
||||
name: row['Input Variable Name'] || row['Output Variable Name'] || '',
|
||||
description: row['Description'] || '',
|
||||
structName: structName,
|
||||
isArray: dimensions.isArray,
|
||||
arraySize1: dimensions.arraySize1,
|
||||
arraySize2: dimensions.arraySize2,
|
||||
variableType: row['Variable Type'] || ''
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析ICD Excel文件
|
||||
* @param {string} filePath - Excel文件路径
|
||||
* @returns {Promise<Array>} 解析后的数据数组
|
||||
*/
|
||||
async function parseICDFile(filePath) {
|
||||
try {
|
||||
const workbook = XLSX.readFile(filePath);
|
||||
|
||||
// 检查必要的工作表是否存在
|
||||
if (!workbook.SheetNames.includes('Inputs') || !workbook.SheetNames.includes('Outputs')) {
|
||||
throw new Error('ICD文件必须包含Inputs和Outputs两个工作表');
|
||||
}
|
||||
|
||||
// 解析input工作表
|
||||
const inputData = parseWorksheet(workbook.Sheets['Inputs'], 'input');
|
||||
|
||||
// 解析output工作表
|
||||
const outputData = parseWorksheet(workbook.Sheets['Outputs'], 'output');
|
||||
|
||||
// 合并两个工作表的数据
|
||||
return [...inputData, ...outputData];
|
||||
} catch (error) {
|
||||
console.error('解析ICD文件时出错:', error);
|
||||
throw new Error('解析ICD文件失败: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseICDFile
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user