XNSim/XNSimHtml/components/tabs-container.js
2025-04-28 12:25:20 +08:00

349 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);