新增tabs功能,每个tab对应一个webview
This commit is contained in:
parent
cc8a8e142e
commit
24cb194937
11
index.html
11
index.html
@ -28,7 +28,16 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="content-area">
|
<div class="content-area">
|
||||||
<webview id="webview" class="page-content" autosize="on" src="about:blank" webpreferences="contextIsolation=yes, nodeIntegration=no"></webview>
|
<div class="tabs-container">
|
||||||
|
<div class="tabs-header">
|
||||||
|
<ul id="tabsList" class="tabs-list">
|
||||||
|
<!-- Tabs will be added here dynamically -->
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="tabsContent" class="tabs-content">
|
||||||
|
<!-- Webviews will be added here dynamically -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -171,3 +171,88 @@ body {
|
|||||||
.close-btn:hover {
|
.close-btn:hover {
|
||||||
background-color: #2980b9;
|
background-color: #2980b9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Tabs styles */
|
||||||
|
.tabs-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-header {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-list {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 15px;
|
||||||
|
height: 100%;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
background-color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item.active {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 2px solid #1890ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item .tab-title {
|
||||||
|
margin-right: 8px;
|
||||||
|
max-width: 150px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item .tab-close {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
line-height: 16px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #999;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-item .tab-close:hover {
|
||||||
|
background-color: #e6e6e6;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-content {
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-pane webview {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
146
src/index.ts
146
src/index.ts
@ -1,4 +1,4 @@
|
|||||||
import { WebviewTag } from "electron";
|
import { ipcRenderer, WebviewTag } from "electron";
|
||||||
|
|
||||||
// 菜单项
|
// 菜单项
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
@ -28,6 +28,121 @@ interface ApiResponse<T> {
|
|||||||
data: T;
|
data: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tab management
|
||||||
|
interface Tab {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
webview: WebviewTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
let tabs: Tab[] = [];
|
||||||
|
let activeTabId: string | null = null;
|
||||||
|
|
||||||
|
// Create a new tab
|
||||||
|
function createTab(title: string, url: string): Tab {
|
||||||
|
const id = `tab-${Date.now()}`;
|
||||||
|
const tabPane = document.createElement('div');
|
||||||
|
tabPane.className = 'tab-pane';
|
||||||
|
tabPane.id = `pane-${id}`;
|
||||||
|
|
||||||
|
const webview = document.createElement('webview');
|
||||||
|
webview.className = 'page-content';
|
||||||
|
webview.setAttribute('autosize', 'on');
|
||||||
|
webview.setAttribute('allowpopups', 'true');
|
||||||
|
webview.setAttribute('webpreferences', 'contextIsolation=yes, nodeIntegration=no');
|
||||||
|
webview.src = url;
|
||||||
|
|
||||||
|
tabPane.appendChild(webview);
|
||||||
|
document.getElementById('tabsContent')?.appendChild(tabPane);
|
||||||
|
|
||||||
|
const tab: Tab = {
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
webview
|
||||||
|
};
|
||||||
|
|
||||||
|
tabs.push(tab);
|
||||||
|
return tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create tab header
|
||||||
|
function createTabHeader(tab: Tab): HTMLLIElement {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'tab-item';
|
||||||
|
li.dataset.tabId = tab.id;
|
||||||
|
|
||||||
|
const titleSpan = document.createElement('span');
|
||||||
|
titleSpan.className = 'tab-title';
|
||||||
|
titleSpan.textContent = tab.title;
|
||||||
|
|
||||||
|
const closeButton = document.createElement('span');
|
||||||
|
closeButton.className = 'tab-close';
|
||||||
|
closeButton.textContent = '×';
|
||||||
|
closeButton.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
closeTab(tab.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
li.appendChild(titleSpan);
|
||||||
|
li.appendChild(closeButton);
|
||||||
|
|
||||||
|
li.addEventListener('click', () => {
|
||||||
|
activateTab(tab.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate a tab
|
||||||
|
function activateTab(tabId: string) {
|
||||||
|
const tab = tabs.find(t => t.id === tabId);
|
||||||
|
if (!tab) return;
|
||||||
|
|
||||||
|
// Update active states
|
||||||
|
document.querySelectorAll('.tab-item').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.tab-pane').forEach(pane => {
|
||||||
|
pane.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Activate the selected tab
|
||||||
|
const tabElement = document.querySelector(`.tab-item[data-tab-id="${tabId}"]`);
|
||||||
|
const paneElement = document.getElementById(`pane-${tabId}`);
|
||||||
|
if (tabElement && paneElement) {
|
||||||
|
tabElement.classList.add('active');
|
||||||
|
paneElement.classList.add('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
activeTabId = tabId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close a tab
|
||||||
|
function closeTab(tabId: string) {
|
||||||
|
const tabIndex = tabs.findIndex(t => t.id === tabId);
|
||||||
|
if (tabIndex === -1) return;
|
||||||
|
|
||||||
|
const tab = tabs[tabIndex];
|
||||||
|
const tabElement = document.querySelector(`.tab-item[data-tab-id="${tabId}"]`);
|
||||||
|
const paneElement = document.getElementById(`pane-${tabId}`);
|
||||||
|
|
||||||
|
if (tabElement) tabElement.remove();
|
||||||
|
if (paneElement) paneElement.remove();
|
||||||
|
|
||||||
|
tabs.splice(tabIndex, 1);
|
||||||
|
|
||||||
|
// If we closed the active tab, activate another one
|
||||||
|
if (activeTabId === tabId) {
|
||||||
|
if (tabs.length > 0) {
|
||||||
|
activateTab(tabs[Math.min(tabIndex, tabs.length - 1)].id);
|
||||||
|
} else {
|
||||||
|
activeTabId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
function checkLoginStatus() {
|
function checkLoginStatus() {
|
||||||
const cookie = window.electronAPI.getSessionStorage('cookie');
|
const cookie = window.electronAPI.getSessionStorage('cookie');
|
||||||
@ -71,7 +186,7 @@ async function getMenuList(): Promise<MenuItem[]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建菜单项
|
// Modify the createMenuItem function to handle new tab creation
|
||||||
function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'menu-item';
|
li.className = 'menu-item';
|
||||||
@ -89,7 +204,7 @@ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
|||||||
|
|
||||||
if (item.Url) {
|
if (item.Url) {
|
||||||
li.addEventListener('click', async () => {
|
li.addEventListener('click', async () => {
|
||||||
// 移除其他菜单项的选中状态和图标
|
// Remove active state from other menu items
|
||||||
document.querySelectorAll('.menu-item').forEach(menuItem => {
|
document.querySelectorAll('.menu-item').forEach(menuItem => {
|
||||||
menuItem.classList.remove('active');
|
menuItem.classList.remove('active');
|
||||||
const menuIcon = menuItem.querySelector('.menu-icon') as HTMLImageElement;
|
const menuIcon = menuItem.querySelector('.menu-icon') as HTMLImageElement;
|
||||||
@ -101,7 +216,7 @@ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加当前菜单项的选中状态和图标
|
// Add active state to current menu item
|
||||||
li.classList.add('active');
|
li.classList.add('active');
|
||||||
icon.src = item.IconConfig._1x.Selected;
|
icon.src = item.IconConfig._1x.Selected;
|
||||||
|
|
||||||
@ -111,7 +226,12 @@ function createMenuItem(item: MenuItem, menuList: MenuItem[]): HTMLLIElement {
|
|||||||
console.log('✅ URL 可访问:', result.status);
|
console.log('✅ URL 可访问:', result.status);
|
||||||
const cookies: string = window.electronAPI.getSessionStorage('cookie');
|
const cookies: string = window.electronAPI.getSessionStorage('cookie');
|
||||||
await window.electronAPI.setWebviewCookie(url, cookies);
|
await window.electronAPI.setWebviewCookie(url, cookies);
|
||||||
(document.querySelector("webview") as WebviewTag).src = url;
|
|
||||||
|
// Create new tab
|
||||||
|
const tab = createTab(item.ShowName, url);
|
||||||
|
const tabHeader = createTabHeader(tab);
|
||||||
|
document.getElementById('tabsList')?.appendChild(tabHeader);
|
||||||
|
activateTab(tab.id);
|
||||||
} else {
|
} else {
|
||||||
console.warn('❌ URL 不可访问:', result.error ?? `status ${result.status}`);
|
console.warn('❌ URL 不可访问:', result.error ?? `status ${result.status}`);
|
||||||
showErrorModal(`无法访问 ${url}\r\n异常原因:${result.error ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
|
showErrorModal(`无法访问 ${url}\r\n异常原因:${result.error ?? `status ${result.status}`}\r\n请联系10000技术支持。`);
|
||||||
@ -151,16 +271,22 @@ function showErrorModal(message: string) {
|
|||||||
errorModal.style.display = 'block';
|
errorModal.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化
|
// Modify the initialize function to create the first tab
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
// 检查登录状态
|
// Check login status
|
||||||
checkLoginStatus();
|
checkLoginStatus();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const menuList = await getMenuList();
|
const menuList = await getMenuList();
|
||||||
renderMenu(menuList);
|
renderMenu(menuList);
|
||||||
|
|
||||||
// 绑定退出登录事件
|
// Create initial tab
|
||||||
|
const initialTab = createTab('首页', 'about:blank');
|
||||||
|
const tabHeader = createTabHeader(initialTab);
|
||||||
|
document.getElementById('tabsList')?.appendChild(tabHeader);
|
||||||
|
activateTab(initialTab.id);
|
||||||
|
|
||||||
|
// Bind logout event
|
||||||
const logoutBtn = document.getElementById('btnLogout');
|
const logoutBtn = document.getElementById('btnLogout');
|
||||||
if (logoutBtn) {
|
if (logoutBtn) {
|
||||||
logoutBtn.addEventListener('click', handleLogout);
|
logoutBtn.addEventListener('click', handleLogout);
|
||||||
@ -168,12 +294,12 @@ async function initialize() {
|
|||||||
|
|
||||||
const errorModal = document.getElementById('errorModal') as HTMLDivElement;
|
const errorModal = document.getElementById('errorModal') as HTMLDivElement;
|
||||||
const closeErrorModal = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
const closeErrorModal = document.getElementById('closeErrorModal') as HTMLButtonElement;
|
||||||
// 关闭按钮点击事件
|
// Close button click event
|
||||||
closeErrorModal.addEventListener('click', (event) => {
|
closeErrorModal.addEventListener('click', (event) => {
|
||||||
errorModal.style.display = 'none';
|
errorModal.style.display = 'none';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 点击窗口外部关闭
|
// Click outside to close
|
||||||
window.addEventListener('click', (event) => {
|
window.addEventListener('click', (event) => {
|
||||||
if (event.target === errorModal) {
|
if (event.target === errorModal) {
|
||||||
errorModal.style.display = 'none';
|
errorModal.style.display = 'none';
|
||||||
|
10
src/main.ts
10
src/main.ts
@ -109,6 +109,11 @@ const createWindow = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 隐藏顶部菜单栏
|
||||||
|
win.setMenuBarVisibility(false);
|
||||||
|
win.setAutoHideMenuBar(true);
|
||||||
|
win.setMenu(null);
|
||||||
|
|
||||||
// // 配置 webview 的权限
|
// // 配置 webview 的权限
|
||||||
// win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
// win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
|
||||||
// callback({
|
// callback({
|
||||||
@ -131,11 +136,6 @@ const createWindow = () => {
|
|||||||
// });
|
// });
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// 隐藏顶部菜单栏
|
|
||||||
win.setMenuBarVisibility(false);
|
|
||||||
win.setAutoHideMenuBar(true);
|
|
||||||
win.setMenu(null);
|
|
||||||
|
|
||||||
// 设置session
|
// 设置session
|
||||||
win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
win.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||||
callback(true);
|
callback(true);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user