原生html + js :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dynamic Tabs</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.tab-close {
margin-left: 8px;
cursor: pointer;
padding: 2px 6px;
border-radius: 50%;
}
.tab-close:hover {
background-color: rgba(0,0,0,0.1);
}
.add-tab-button {
cursor: pointer;
padding: 8px 12px;
border: none;
background: none;
}
.nav-tabs .nav-item .nav-link {
padding-right: 35px;
position: relative;
}
.tab-close {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
}
</style>
</head>
<body>
<div class="container mt-4">
<div class="card">
<div class="card-body">
<div class="d-flex align-items-center">
<ul class="nav nav-tabs flex-grow-1" id="myTabs" role="tablist">
</ul>
<button class="add-tab-button" id="addTabBtn">
<i class="fas fa-plus"></i> Add Tab
</button>
</div>
<div class="tab-content mt-3" id="myTabContent">
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
class TabManager {
constructor() {
this.tabCounter = 0;
this.tabs = new Map(); // 存储所有的tab引用
this.init();
}
init() {
// 初始化事件监听
document.getElementById('addTabBtn').addEventListener('click', () => this.addNewTab());
// 添加第一个tab
this.addNewTab();
}
createTabId() {
return `tab${++this.tabCounter}`;
}
createTabElement(tabId, isActive = false) {
const li = document.createElement('li');
li.className = 'nav-item';
li.innerHTML = `
<a class="nav-link ${isActive ? 'active' : ''}"
id="${tabId}-tab"
data-bs-toggle="tab"
href="#${tabId}-content"
role="tab">
Tab ${this.tabCounter}
<span class="tab-close">
<i class="fas fa-times"></i>
</span>
</a>
`;
// 添加关闭按钮事件
const closeBtn = li.querySelector('.tab-close');
closeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
this.removeTab(tabId);
});
return li;
}
createTabContent(tabId, isActive = false) {
const div = document.createElement('div');
div.className = `tab-pane fade ${isActive ? 'show active' : ''}`;
div.id = `${tabId}-content`;
div.innerHTML = `
<form class="mt-3">
<div class="mb-3">
<label for="${tabId}-product-id" class="form-label">Product ID</label>
<input type="text" class="form-control" id="${tabId}-product-id">
</div>
<div class="mb-3">
<label for="${tabId}-app-name" class="form-label">App Name</label>
<input type="text" class="form-control" id="${tabId}-app-name">
</div>
</form>
`;
return div;
}
addNewTab() {
const tabId = this.createTabId();
const isFirstTab = this.tabCounter === 1;
const tabElement = this.createTabElement(tabId, isFirstTab);
const tabContent = this.createTabContent(tabId, isFirstTab);
document.getElementById('myTabs').appendChild(tabElement);
document.getElementById('myTabContent').appendChild(tabContent);
// 存储tab引用
this.tabs.set(tabId, {
element: tabElement,
content: tabContent
});
// 如果是第一个标签,激活它
if (isFirstTab) {
const tabInstance = new bootstrap.Tab(document.getElementById(`${tabId}-tab`));
tabInstance.show();
}
}
removeTab(tabId) {
const tab = this.tabs.get(tabId);
if (!tab) return;
const tabLink = document.getElementById(`${tabId}-tab`);
const isActive = tabLink.classList.contains('active');
// 如果删除的是活动的标签页,需要激活另一个标签页
if (isActive) {
const remainingTabs = Array.from(this.tabs.keys())
.filter(id => id !== tabId);
if (remainingTabs.length > 0) {
const nextTabId = remainingTabs[0];
const nextTab = document.getElementById(`${nextTabId}-tab`);
const bsTab = new bootstrap.Tab(nextTab);
bsTab.show();
}
}
// 使用 requestAnimationFrame 确保在下一帧删除元素
requestAnimationFrame(() => {
tab.element.remove();
tab.content.remove();
this.tabs.delete(tabId);
});
}
}
// 初始化
document.addEventListener('DOMContentLoaded', () => {
window.tabManager = new TabManager();
});
</script>
</body>
</html>