349 lines
12 KiB
JavaScript
349 lines
12 KiB
JavaScript
class TabsContainer extends HTMLElement {
|
||
constructor() {
|
||
super();
|
||
this.attachShadow({ mode: 'open' });
|
||
this.openTabs = new Set();
|
||
this.currentActiveTabId = null; // 跟踪当前激活的标签页ID
|
||
}
|
||
|
||
connectedCallback() {
|
||
this.render();
|
||
this.setupEventListeners();
|
||
}
|
||
|
||
render() {
|
||
this.shadowRoot.innerHTML = `
|
||
<style>
|
||
:host {
|
||
display: block;
|
||
}
|
||
|
||
.tabs-container {
|
||
background-color: white;
|
||
border-bottom: 1px solid #e0e0e0;
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 4px;
|
||
height: 41px;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.tabs-header {
|
||
display: flex;
|
||
padding: 0 32px;
|
||
overflow-x: auto;
|
||
overflow-y: hidden;
|
||
height: 40px;
|
||
flex: 1;
|
||
scroll-behavior: smooth;
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
}
|
||
|
||
.tabs-header::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.tab {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 16px;
|
||
height: 100%;
|
||
background-color: #f5f5f5;
|
||
border: 1px solid #e0e0e0;
|
||
border-bottom: none;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
white-space: nowrap;
|
||
flex: 0 0 auto;
|
||
gap: 8px;
|
||
margin-right: -1px;
|
||
margin-top: 4px;
|
||
position: relative;
|
||
border-radius: 4px 4px 0 0;
|
||
}
|
||
|
||
.tab:hover {
|
||
background-color: #fafafa;
|
||
}
|
||
|
||
.tab span {
|
||
margin: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
font-size: 15px;
|
||
color: #666;
|
||
line-height: 1.2;
|
||
display: inline-block;
|
||
max-width: 200px;
|
||
}
|
||
|
||
.tab.active {
|
||
background-color: white;
|
||
border-bottom: none;
|
||
margin-top: 0;
|
||
height: calc(100% + 1px);
|
||
z-index: 1;
|
||
box-shadow: 0 -2px 5px rgba(0,0,0,0.05);
|
||
}
|
||
|
||
.tab.active span {
|
||
color: #8B6DB3;
|
||
font-weight: 800;
|
||
}
|
||
|
||
.tab.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background-color: #1890ff;
|
||
}
|
||
|
||
.icon-small {
|
||
width: 18px;
|
||
height: 18px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.close-tab {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 16px;
|
||
height: 16px;
|
||
opacity: 0.5;
|
||
transition: opacity 0.3s;
|
||
margin-left: 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.close-tab:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
.close-tab img {
|
||
width: 12px;
|
||
height: 12px;
|
||
}
|
||
|
||
.tabs-scroll-button {
|
||
width: 28px;
|
||
height: 28px;
|
||
background-color: #7986E7;
|
||
border-radius: 50%;
|
||
display: none;
|
||
justify-content: center;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
position: absolute;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
z-index: 10;
|
||
transition: all 0.3s ease;
|
||
border: none;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.tabs-scroll-button:hover {
|
||
background-color: rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.tabs-scroll-button.left {
|
||
left: 4px;
|
||
}
|
||
|
||
.tabs-scroll-button.right {
|
||
right: 4px;
|
||
}
|
||
|
||
.tabs-scroll-button.show {
|
||
display: flex;
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
</style>
|
||
<div class="tabs-container">
|
||
<div class="tabs-scroll-button left" id="scrollLeft">
|
||
<img src="assets/icons/png/chevron-dleft_b.png" alt="向左滚动" class="icon-small">
|
||
</div>
|
||
<div class="tabs-header"></div>
|
||
<div class="tabs-scroll-button right" id="scrollRight">
|
||
<img src="assets/icons/png/chevron-dright_b.png" alt="向右滚动" class="icon-small">
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
setupEventListeners() {
|
||
const tabsHeader = this.shadowRoot.querySelector('.tabs-header');
|
||
const scrollLeftBtn = this.shadowRoot.getElementById('scrollLeft');
|
||
const scrollRightBtn = this.shadowRoot.getElementById('scrollRight');
|
||
|
||
// 检查是否需要显示滚动按钮
|
||
const checkScrollButtons = () => {
|
||
const { scrollLeft, scrollWidth, clientWidth } = tabsHeader;
|
||
const hasScroll = scrollWidth > clientWidth;
|
||
|
||
scrollLeftBtn.classList.toggle('show', hasScroll && scrollLeft > 0);
|
||
scrollRightBtn.classList.toggle('show', hasScroll && scrollLeft < scrollWidth - clientWidth - 1);
|
||
};
|
||
|
||
// 添加滚动事件监听
|
||
tabsHeader.addEventListener('scroll', checkScrollButtons);
|
||
window.addEventListener('resize', checkScrollButtons);
|
||
|
||
// 滚动按钮点击事件
|
||
scrollLeftBtn.addEventListener('click', () => {
|
||
tabsHeader.scrollBy({ left: -200, behavior: 'smooth' });
|
||
});
|
||
|
||
scrollRightBtn.addEventListener('click', () => {
|
||
tabsHeader.scrollBy({ left: 200, behavior: 'smooth' });
|
||
});
|
||
|
||
// 添加 MutationObserver 监听标签页的变化
|
||
const observer = new MutationObserver(checkScrollButtons);
|
||
observer.observe(tabsHeader, {
|
||
childList: true,
|
||
subtree: true,
|
||
attributes: true,
|
||
attributeFilter: ['class']
|
||
});
|
||
|
||
// 初始检查滚动按钮状态
|
||
checkScrollButtons();
|
||
}
|
||
|
||
activateTab(id) {
|
||
const activeTab = this.shadowRoot.querySelector(`.tab[data-tab="${id}"]`);
|
||
if (!activeTab) return;
|
||
|
||
// 检查是否是当前已激活的标签页,如果是则不触发事件
|
||
if (this.currentActiveTabId === id) {
|
||
return;
|
||
}
|
||
|
||
// 记录新的激活标签页ID
|
||
const previousTabId = this.currentActiveTabId;
|
||
this.currentActiveTabId = id;
|
||
|
||
this.shadowRoot.querySelectorAll('.tab').forEach(tab => {
|
||
tab.classList.toggle('active', tab.getAttribute('data-tab') === id);
|
||
});
|
||
|
||
const title = activeTab.getAttribute('data-title') || activeTab.querySelector('span').textContent;
|
||
const parentText = activeTab.getAttribute('data-parent-text') || '主页';
|
||
const parentTool = activeTab.getAttribute('data-parent-tool');
|
||
|
||
// 触发标签页激活事件,并传递前一个标签页ID
|
||
this.dispatchEvent(new CustomEvent('tab-activated', {
|
||
detail: {
|
||
id,
|
||
title,
|
||
parentText,
|
||
parentTool,
|
||
previousTabId, // 添加前一个标签页ID
|
||
isTabSwitch: true // 标记为真正的标签切换
|
||
}
|
||
}));
|
||
|
||
// 同步左侧子工具栏选中状态
|
||
if (parentTool) {
|
||
// 先选中主工具栏
|
||
const mainToolbar = document.querySelector('main-toolbar');
|
||
const mainToolItem = mainToolbar.shadowRoot.querySelector(`.tool-item[data-tool="${parentTool}"]`);
|
||
if (mainToolItem) {
|
||
// 移除所有工具项的激活状态
|
||
mainToolbar.shadowRoot.querySelectorAll('.tool-item').forEach(t => t.classList.remove('active'));
|
||
// 激活当前工具项
|
||
mainToolItem.classList.add('active');
|
||
}
|
||
|
||
// 再同步子工具栏
|
||
const subToolbar = document.querySelector('sub-toolbar');
|
||
subToolbar.setAttribute('current-tool', parentTool);
|
||
|
||
// 找到并点击对应的子菜单项
|
||
setTimeout(() => {
|
||
const subMenuItems = subToolbar.shadowRoot.querySelectorAll('.sub-item');
|
||
const targetSubItem = Array.from(subMenuItems).find(item =>
|
||
item.textContent.trim() === title &&
|
||
item.closest('.sub-menu').getAttribute('data-parent') === parentTool
|
||
);
|
||
|
||
if (targetSubItem) {
|
||
// 移除所有子菜单项的激活状态
|
||
subMenuItems.forEach(item => item.classList.remove('active'));
|
||
// 激活当前子菜单项
|
||
targetSubItem.classList.add('active');
|
||
}
|
||
}, 0); // 使用setTimeout确保DOM已更新
|
||
}
|
||
}
|
||
|
||
createTab(id, title, icon, parentText, parentTool) {
|
||
// 对于其他标签页,如果已经打开则激活
|
||
if (this.openTabs.has(id)) {
|
||
this.activateTab(id);
|
||
return;
|
||
}
|
||
|
||
const tab = document.createElement('div');
|
||
tab.className = 'tab';
|
||
tab.setAttribute('data-tab', id);
|
||
tab.setAttribute('data-parent-text', parentText || '主页');
|
||
tab.setAttribute('data-parent-tool', parentTool || 'home');
|
||
tab.setAttribute('data-title', title);
|
||
tab.innerHTML = `
|
||
<img src="assets/icons/png/${icon}_b.png" alt="${title}" class="icon-small">
|
||
<span>${title}</span>
|
||
${id !== 'overview' ? '<div class="close-tab"><img src="assets/icons/png/close_b.png" alt="关闭"></div>' : ''}
|
||
`;
|
||
|
||
this.shadowRoot.querySelector('.tabs-header').appendChild(tab);
|
||
|
||
tab.addEventListener('click', () => {
|
||
this.activateTab(id);
|
||
});
|
||
|
||
if (id !== 'overview') {
|
||
const closeBtn = tab.querySelector('.close-tab');
|
||
closeBtn.addEventListener('click', (e) => {
|
||
e.stopPropagation();
|
||
this.closeTab(id);
|
||
});
|
||
}
|
||
|
||
this.openTabs.add(id);
|
||
this.activateTab(id);
|
||
}
|
||
|
||
closeTab(id) {
|
||
if (id === 'overview') return;
|
||
|
||
const tab = this.shadowRoot.querySelector(`.tab[data-tab="${id}"]`);
|
||
|
||
if (tab.classList.contains('active')) {
|
||
const prevTab = tab.previousElementSibling || tab.nextElementSibling;
|
||
if (prevTab) {
|
||
this.activateTab(prevTab.getAttribute('data-tab'));
|
||
}
|
||
}
|
||
|
||
tab.remove();
|
||
this.openTabs.delete(id);
|
||
|
||
// 触发标签页关闭事件
|
||
this.dispatchEvent(new CustomEvent('tab-closed', {
|
||
detail: { id }
|
||
}));
|
||
}
|
||
}
|
||
|
||
customElements.define('tabs-container', TabsContainer);
|