Explorar el Código

V1.1.7可关闭

liuq hace 3 semanas
padre
commit
bc8f8ffd84
Se han modificado 2 ficheros con 106 adiciones y 1 borrados
  1. 51 0
      src/main/index.ts
  2. 55 1
      src/renderer/src/BrowserWindow.tsx

+ 51 - 0
src/main/index.ts

@@ -697,6 +697,50 @@ class ViewManager {
     this.activeTabId = id
   }
 
+  closeTab(id: string): { closed: boolean; activeTabId: string | null } {
+    const tab = this.tabs.get(id)
+    if (!tab || this.tabs.size <= 1) {
+      return { closed: false, activeTabId: this.activeTabId }
+    }
+
+    const tabIds = Array.from(this.tabs.keys())
+    const closingActiveTab = this.activeTabId === id
+    let nextActiveTabId = this.activeTabId
+
+    if (closingActiveTab) {
+      const tabIndex = tabIds.indexOf(id)
+      nextActiveTabId = tabIds[tabIndex + 1] ?? tabIds[tabIndex - 1] ?? null
+    }
+
+    try {
+      this.window.contentView.removeChildView(tab.view)
+    } catch {
+      // ignore
+    }
+
+    this.tabs.delete(id)
+    if (tab.appId && this.appIdToTabId.get(tab.appId) === id) {
+      this.appIdToTabId.delete(tab.appId)
+    }
+
+    if (closingActiveTab) {
+      this.activeTabId = null
+      if (nextActiveTabId) {
+        this.switchTab(nextActiveTabId)
+      }
+    }
+
+    try {
+      if (!tab.view.webContents.isDestroyed()) {
+        tab.view.webContents.close()
+      }
+    } catch {
+      // ignore
+    }
+
+    return { closed: true, activeTabId: this.activeTabId }
+  }
+
   setBounds(bounds: Electron.Rectangle) {
     this.bounds = bounds
     if (this.activeTabId) {
@@ -1333,6 +1377,13 @@ ipcMain.on('browser-action', (event, action) => {
       case 'switch-tab':
         viewManager.switchTab(action.id)
         break
+      case 'close-tab': {
+        const result = viewManager.closeTab(action.id)
+        if (result.closed) {
+          event.reply('tab-closed', action.id, result.activeTabId)
+        }
+        break
+      }
       case 'go-back':
         viewManager.goBack()
         break

+ 55 - 1
src/renderer/src/BrowserWindow.tsx

@@ -1,4 +1,5 @@
 import React, { useState, useEffect, useRef, useCallback } from 'react'
+import { BsX } from 'react-icons/bs'
 
 interface Tab {
   id: string
@@ -27,6 +28,11 @@ const BrowserWindow: React.FC = () => {
     setActiveTabId(id)
   }, [])
 
+  const handleTabClosed = useCallback((_event: unknown, id: string, nextActiveTabId: string | null) => {
+    setTabs((prev) => prev.filter((tab) => tab.id !== id))
+    setActiveTabId((prev) => nextActiveTabId ?? (prev === id ? null : prev))
+  }, [])
+
   const handleTabsSync = useCallback(
     (_event: unknown, snapshots: Array<{ id: string; url: string; title: string }>) => {
       if (!snapshots?.length) return
@@ -70,6 +76,7 @@ const BrowserWindow: React.FC = () => {
     ipc.on('tab-created', handleTabCreated)
     ipc.on('tab-update', handleTabUpdate)
     ipc.on('tab-activate', handleTabActivate)
+    ipc.on('tab-closed', handleTabClosed)
     ipc.on('browser-tabs-sync', handleTabsSync)
     ipc.send('browser-action', { type: 'browser-ui-ready' })
 
@@ -80,10 +87,11 @@ const BrowserWindow: React.FC = () => {
       ipc.removeListener('tab-created', handleTabCreated)
       ipc.removeListener('tab-update', handleTabUpdate)
       ipc.removeListener('tab-activate', handleTabActivate)
+      ipc.removeListener('tab-closed', handleTabClosed)
       ipc.removeListener('browser-tabs-sync', handleTabsSync)
       window.removeEventListener('resize', updateViewBounds)
     }
-  }, [handleTabActivate, handleTabCreated, handleTabUpdate, handleTabsSync, updateViewBounds])
+  }, [handleTabActivate, handleTabClosed, handleTabCreated, handleTabUpdate, handleTabsSync, updateViewBounds])
   
   // 当 tabs 或 activeTabId 变化导致布局变化时,调整 BrowserView 的 bounds
   useEffect(() => {
@@ -95,6 +103,14 @@ const BrowserWindow: React.FC = () => {
       window.electron.ipcRenderer.send('browser-action', { type: 'switch-tab', id })
   }
 
+  const handleCloseTab = (event: React.MouseEvent<HTMLButtonElement>, id: string) => {
+      event.stopPropagation()
+      if (tabs.length <= 1) return
+      window.electron.ipcRenderer.send('browser-action', { type: 'close-tab', id })
+  }
+
+  const canCloseTabs = tabs.length > 1
+
   return (
     <div className="browser-window" style={{ display: 'flex', flexDirection: 'column', height: '100vh', width: '100vw', backgroundColor: 'var(--im-bg)', overflow: 'hidden' }}>
       {/* Title Bar / Tab Bar */}
@@ -128,6 +144,20 @@ const BrowserWindow: React.FC = () => {
               <div style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
                 {tab.title || 'Loading...'}
               </div>
+              <button
+                type="button"
+                className="browser-tab-close"
+                onClick={(event) => handleCloseTab(event, tab.id)}
+                disabled={!canCloseTabs}
+                aria-label="关闭标签页"
+                title={canCloseTabs ? '关闭标签页' : '最后一个标签页不能关闭,请关闭窗口'}
+                style={{
+                  color: activeTabId === tab.id ? 'var(--im-text-muted)' : '#94a3b8',
+                  visibility: canCloseTabs ? 'visible' : 'hidden'
+                }}
+              >
+                <BsX size={14} />
+              </button>
             </div>
           ))}
         </div>
@@ -153,6 +183,30 @@ const BrowserWindow: React.FC = () => {
           0% { transform: rotate(0deg); }
           100% { transform: rotate(360deg); }
         }
+
+        .browser-tab-close {
+          width: 20px;
+          height: 20px;
+          margin-left: 6px;
+          border: none;
+          border-radius: 4px;
+          background: transparent;
+          display: flex;
+          align-items: center;
+          justify-content: center;
+          flex-shrink: 0;
+          padding: 0;
+          cursor: pointer;
+        }
+
+        .browser-tab-close:hover:not(:disabled) {
+          background: rgba(15, 23, 42, 0.08);
+          color: var(--im-text);
+        }
+
+        .browser-tab-close:disabled {
+          cursor: default;
+        }
       `}</style>
     </div>
   )