XNSim/XNSimHtml/components/tabs-container.js

349 lines
12 KiB
JavaScript
Raw Permalink Normal View History

2025-04-28 12:25:20 +08:00
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);