667 lines
24 KiB
JavaScript
667 lines
24 KiB
JavaScript
class InterfaceDataTable extends HTMLElement {
|
||
constructor() {
|
||
super();
|
||
this.attachShadow({ mode: 'open' });
|
||
this.data = [];
|
||
this.filteredData = [];
|
||
this.selectedRows = new Set();
|
||
this.searchKeyword = '';
|
||
this.filters = {
|
||
planeName: '',
|
||
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();
|
||
}
|
||
|
||
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');
|
||
|
||
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 {
|
||
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 = {
|
||
planeName: '',
|
||
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();
|
||
});
|
||
}
|
||
}
|
||
|
||
// 防抖函数
|
||
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.planeName) {
|
||
filtered = filtered.filter(item =>
|
||
item.PlaneName === this.filters.planeName
|
||
);
|
||
}
|
||
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 = filtered.filter(item =>
|
||
item.InterfaceName.toLowerCase().includes(this.searchKeyword)
|
||
);
|
||
} else {
|
||
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.map((item, index) => ({
|
||
...item,
|
||
_displayIndex: index
|
||
}));
|
||
this.filteredData = [...this.data];
|
||
this.currentPage = 1;
|
||
this.render();
|
||
} finally {
|
||
this.setLoading(false);
|
||
}
|
||
}
|
||
|
||
getSelectedRows() {
|
||
return Array.from(this.selectedRows).map(index => this.filteredData[index]);
|
||
}
|
||
|
||
formatArraySize(item) {
|
||
if (item.InterfaceIsArray === 1) {
|
||
if (item.InterfaceArraySize_2 && item.InterfaceArraySize_2 > 1) {
|
||
return `${item.InterfaceArraySize_1}×${item.InterfaceArraySize_2}`;
|
||
}
|
||
return `${item.InterfaceArraySize_1}`;
|
||
}
|
||
return '1';
|
||
}
|
||
|
||
getTotalPages() {
|
||
return Math.ceil(this.filteredData.length / this.pageSize);
|
||
}
|
||
|
||
getCurrentPageData() {
|
||
const start = (this.currentPage - 1) * this.pageSize;
|
||
return this.filteredData.slice(start, start + this.pageSize);
|
||
}
|
||
|
||
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>
|
||
.data-table {
|
||
width: 100%;
|
||
border-collapse: collapse;
|
||
margin-top: 10px;
|
||
table-layout: fixed;
|
||
position: relative;
|
||
}
|
||
|
||
.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;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.data-table th {
|
||
background-color: #f5f5f5;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.data-table tr:nth-child(even) {
|
||
background-color: #f9f9f9;
|
||
}
|
||
|
||
.data-table tr:hover {
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.checkbox-cell {
|
||
width: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.plane-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 {
|
||
background-color: #4CAF50;
|
||
color: white;
|
||
}
|
||
|
||
.delete-btn {
|
||
background-color: #f44336;
|
||
color: white;
|
||
}
|
||
|
||
.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>
|
||
<div style="position: relative;">
|
||
<table class="data-table">
|
||
<thead>
|
||
<tr>
|
||
<th class="checkbox-cell">
|
||
<input type="checkbox" id="selectAll">
|
||
</th>
|
||
<th class="plane-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>
|
||
${this.getCurrentPageData().map((item) => `
|
||
<tr>
|
||
<td class="checkbox-cell">
|
||
<input type="checkbox" class="row-checkbox" data-index="${item._displayIndex}">
|
||
</td>
|
||
<td>${item.PlaneName || ''}</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 class="action-cell">
|
||
<button class="action-btn edit-btn" data-index="${item._displayIndex}">编辑</button>
|
||
<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 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>
|
||
</div>
|
||
`;
|
||
this.bindEventListeners();
|
||
}
|
||
|
||
// 修改事件监听器的添加方式
|
||
addEventListenerWithCleanup(element, event, handler) {
|
||
if (!this.eventListeners.has(element)) {
|
||
this.eventListeners.set(element, new Map());
|
||
}
|
||
const elementListeners = this.eventListeners.get(element);
|
||
|
||
// 如果已经存在相同的事件监听器,先移除
|
||
if (elementListeners.has(event)) {
|
||
const handlers = elementListeners.get(event);
|
||
handlers.forEach(h => element.removeEventListener(event, h));
|
||
handlers.clear();
|
||
} else {
|
||
elementListeners.set(event, new Set());
|
||
}
|
||
|
||
elementListeners.get(event).add(handler);
|
||
element.addEventListener(event, handler);
|
||
}
|
||
|
||
// 清理事件监听器
|
||
cleanupEventListeners() {
|
||
this.eventListeners.forEach((elementListeners, element) => {
|
||
elementListeners.forEach((handlers, event) => {
|
||
handlers.forEach(handler => {
|
||
element.removeEventListener(event, handler);
|
||
});
|
||
});
|
||
});
|
||
this.eventListeners.clear();
|
||
}
|
||
|
||
bindEventListeners() {
|
||
// 清理之前的事件监听器
|
||
this.cleanupEventListeners();
|
||
|
||
const selectAllCheckbox = this.shadowRoot.querySelector('#selectAll');
|
||
const rowCheckboxes = this.shadowRoot.querySelectorAll('.row-checkbox');
|
||
const editButtons = this.shadowRoot.querySelectorAll('.edit-btn');
|
||
const deleteButtons = this.shadowRoot.querySelectorAll('.delete-btn');
|
||
|
||
// 全选复选框事件
|
||
if (selectAllCheckbox) {
|
||
const selectAllHandler = (e) => {
|
||
const isChecked = e.target.checked;
|
||
rowCheckboxes.forEach(checkbox => {
|
||
checkbox.checked = isChecked;
|
||
const index = parseInt(checkbox.dataset.index);
|
||
if (isChecked) {
|
||
this.selectedRows.add(index);
|
||
} else {
|
||
this.selectedRows.delete(index);
|
||
}
|
||
});
|
||
};
|
||
this.addEventListenerWithCleanup(selectAllCheckbox, 'change', selectAllHandler);
|
||
}
|
||
|
||
// 行复选框事件
|
||
rowCheckboxes.forEach(checkbox => {
|
||
const checkboxHandler = (e) => {
|
||
const index = parseInt(e.target.dataset.index);
|
||
if (e.target.checked) {
|
||
this.selectedRows.add(index);
|
||
} else {
|
||
this.selectedRows.delete(index);
|
||
}
|
||
if (selectAllCheckbox) {
|
||
selectAllCheckbox.checked = this.selectedRows.size === rowCheckboxes.length;
|
||
}
|
||
};
|
||
this.addEventListenerWithCleanup(checkbox, 'change', checkboxHandler);
|
||
});
|
||
|
||
// 编辑按钮事件
|
||
editButtons.forEach(button => {
|
||
const editHandler = (e) => {
|
||
const index = parseInt(e.target.dataset.index);
|
||
const item = this.filteredData[index];
|
||
if (item) {
|
||
this.dispatchEvent(new CustomEvent('edit', {
|
||
detail: { ...item },
|
||
bubbles: true,
|
||
composed: true
|
||
}));
|
||
}
|
||
};
|
||
this.addEventListenerWithCleanup(button, 'click', editHandler);
|
||
});
|
||
|
||
// 删除按钮事件
|
||
deleteButtons.forEach(button => {
|
||
const deleteHandler = (e) => {
|
||
const index = parseInt(e.target.dataset.index);
|
||
const item = this.filteredData[index];
|
||
if (item && confirm(`确定要删除接口 "${item.InterfaceName}" 吗?`)) {
|
||
this.dispatchEvent(new CustomEvent('delete', {
|
||
detail: { ...item },
|
||
bubbles: true,
|
||
composed: true
|
||
}));
|
||
}
|
||
};
|
||
this.addEventListenerWithCleanup(button, 'click', deleteHandler);
|
||
});
|
||
|
||
// 搜索事件
|
||
const searchHandler = async (e) => {
|
||
this.searchKeyword = e.detail.keyword.toLowerCase();
|
||
this.setLoading(true);
|
||
await this.debouncedFilter();
|
||
};
|
||
this.addEventListenerWithCleanup(this, 'search', searchHandler);
|
||
|
||
// 重置搜索事件
|
||
const resetSearchHandler = async () => {
|
||
this.searchKeyword = '';
|
||
this.filters = {
|
||
planeName: '',
|
||
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();
|
||
}
|
||
}
|
||
|
||
customElements.define('interface-data-table', InterfaceDataTable);
|