feat(browser): 受管浏览器代理设置(profile 导入已有实现,本轮补全代理)

This commit is contained in:
Rick Ye
2026-09-11 17:02:11 +08:00
parent 6507948f01
commit b6e6031415
7 changed files with 263 additions and 7 deletions
@@ -6,11 +6,12 @@ import {
LuCircleAlert,
LuDownload,
LuGlobe,
LuNetwork,
LuPlus,
LuRotateCw,
LuX
} from 'react-icons/lu'
import type { BrowserImportResult, BrowserSource, BrowserUiState } from '@shared/types'
import type { BrowserImportResult, BrowserProxySettings, BrowserSource, BrowserUiState } from '@shared/types'
import { BrowserSlot } from './BrowserSlot'
/**
@@ -43,6 +44,7 @@ export function BrowserPanel(props: BrowserPanelProps): JSX.Element {
const { state, suspended } = props
const [address, setAddress] = useState('')
const [importOpen, setImportOpen] = useState(false)
const [proxyOpen, setProxyOpen] = useState(false)
const [tabListOpen, setTabListOpen] = useState(false)
const [tabMenu, setTabMenu] = useState<{ x: number; y: number; tabId: string } | null>(null)
@@ -80,7 +82,7 @@ export function BrowserPanel(props: BrowserPanelProps): JSX.Element {
const riskBanner = state && !state.riskAcknowledged
// 下拉/右键菜单会伸到页面区域上方,原生视图必须暂时让位(原生层级永远在 DOM 之上)
const overlayOpen = importOpen || tabListOpen || tabMenu !== null
const overlayOpen = importOpen || proxyOpen || tabListOpen || tabMenu !== null
// 需要暂时隐藏原生视图的原因(宿主全屏浮层 + 本面板菜单)
const hideCause = suspended || overlayOpen
@@ -362,6 +364,15 @@ export function BrowserPanel(props: BrowserPanelProps): JSX.Element {
>
<LuDownload size={13} />
</button>
<button
type="button"
onClick={() => setProxyOpen(true)}
title="代理设置"
aria-expanded={proxyOpen}
className="flex size-7 items-center justify-center rounded text-ink-3 transition-colors hover:bg-hover hover:text-ink-2"
>
<LuNetwork size={13} />
</button>
</div>
{/* 风险告知(首次使用门禁,Agent 与用户操作都会被拦截直到确认) */}
@@ -419,6 +430,9 @@ export function BrowserPanel(props: BrowserPanelProps): JSX.Element {
onImportProfile={props.onImportProfile}
/>
)}
{/* 代理设置浮层(覆盖面板,出现时原生视图已隐藏) */}
{proxyOpen && <ProxyOverlay state={state} onClose={() => setProxyOpen(false)} />}
</div>
)
}
@@ -560,3 +574,139 @@ function ImportOverlay({
</div>
)
}
// ---- 代理设置浮层 ----
const PROXY_MODES: Array<{ id: BrowserProxySettings['mode']; label: string }> = [
{ id: 'system', label: '跟随系统' },
{ id: 'direct', label: '直连' },
{ id: 'fixed', label: '手动代理' }
]
function ProxyOverlay({ state, onClose }: { state: BrowserUiState | null; onClose: () => void }): JSX.Element {
const current = state?.proxy ?? { mode: 'system' as const }
const [mode, setMode] = useState<BrowserProxySettings['mode']>(current.mode)
const [rules, setRules] = useState(current.mode === 'fixed' ? (current.rules ?? '') : '')
const [bypass, setBypass] = useState(current.bypass ?? '')
const [busy, setBusy] = useState(false)
const [error, setError] = useState<string | null>(null)
const [saved, setSaved] = useState(false)
// 主进程广播回来(保存成功/其他入口改动)→ 表单跟随当前生效值
useEffect(() => {
setMode(current.mode)
setRules(current.mode === 'fixed' ? (current.rules ?? '') : '')
setBypass(current.bypass ?? '')
}, [current.mode, current.rules, current.bypass])
const save = (): void => {
setBusy(true)
setError(null)
setSaved(false)
window.api
.browserSetProxy({ mode, rules: rules.trim(), bypass: bypass.trim() })
.then((p) => {
setMode(p.mode)
setRules(p.rules ?? '')
setBypass(p.bypass ?? '')
setSaved(true)
})
.catch((err) => setError(err instanceof Error ? err.message : String(err)))
.finally(() => setBusy(false))
}
const inputCls =
'w-full rounded border border-line bg-inset px-2.5 py-1.5 text-[12px] text-ink outline-none placeholder:text-ink-3 focus:border-accent focus:bg-surface'
return (
<div className="absolute inset-0 z-20 flex flex-col bg-surface/95 backdrop-blur-[2px]">
<div className="flex h-10 shrink-0 items-center justify-between border-b border-line px-3">
<div className="text-[12.5px] font-medium text-ink-2">代理设置</div>
<button
type="button"
aria-label="关闭"
onClick={onClose}
className="flex size-6 items-center justify-center rounded text-ink-3 hover:bg-hover hover:text-ink-2"
>
<LuX size={13} />
</button>
</div>
<div className="min-h-0 flex-1 overflow-y-auto p-3 text-[12px] text-ink-2">
<p className="mb-3 leading-relaxed text-ink-3">
代理只作用于内置受管浏览器(不影响应用其他部分与系统设置),保存后立即生效。
</p>
<div className="flex w-fit items-center rounded-control border border-line bg-hover p-0.5">
{PROXY_MODES.map((m) => (
<button
key={m.id}
type="button"
onClick={() => setMode(m.id)}
className={`h-7 rounded-[6px] px-3 text-[12px] transition-colors ${
mode === m.id ? 'bg-surface font-medium text-ink shadow-card' : 'text-ink-3 hover:text-ink-2'
}`}
>
{m.label}
</button>
))}
</div>
{mode === 'fixed' && (
<div className="mt-3 flex flex-col gap-2.5">
<label className="flex flex-col gap-1">
<span className="text-[11px] text-ink-3">代理规则(多协议用分号分隔)</span>
<input
value={rules}
onChange={(e) => setRules(e.target.value)}
placeholder="127.0.0.1:7890 或 http=127.0.0.1:7890;https=127.0.0.1:7890"
spellCheck={false}
className={inputCls}
/>
</label>
<label className="flex flex-col gap-1">
<span className="text-[11px] text-ink-3">绕过列表(逗号分隔,可留空)</span>
<input
value={bypass}
onChange={(e) => setBypass(e.target.value)}
placeholder="<local>,*.example.com,127.0.0.1"
spellCheck={false}
className={inputCls}
/>
</label>
<p className="text-[10.5px] leading-4 text-ink-3">
规则语法与 Chromium 一致:host:port 应用于全部协议;也可按协议指定(http=…;https=…;socks=…)。
</p>
</div>
)}
{error && (
<div className="mt-3 rounded border border-red/40 bg-red-tint px-2.5 py-2 text-[11.5px] text-red">{error}</div>
)}
{saved && !error && (
<div className="mt-3 rounded border border-green-200 bg-green-tint px-2.5 py-2 text-[11.5px] text-green">
已保存并立即生效(在途连接已重置)。
</div>
)}
</div>
<div className="flex shrink-0 items-center justify-end gap-2 border-t border-line px-3 py-2.5">
<button
type="button"
onClick={onClose}
className="rounded px-3 py-1.5 text-[12px] text-ink-3 transition-colors hover:bg-hover hover:text-ink-2"
>
关闭
</button>
<button
type="button"
disabled={busy || (mode === 'fixed' && !rules.trim())}
onClick={save}
className="rounded bg-accent px-3.5 py-1.5 text-[12px] font-medium text-page transition-opacity hover:opacity-90 disabled:opacity-40"
>
{busy ? '应用中…' : '保存并应用'}
</button>
</div>
</div>
)
}
+3
View File
@@ -3,6 +3,7 @@ import type {
AppSettings,
AttachmentInfo,
BrowserImportResult,
BrowserProxySettings,
BrowserSource,
BrowserUiEvent,
BrowserUiState,
@@ -115,6 +116,8 @@ declare global {
}) => Promise<void>
browserDetectSources: () => Promise<BrowserSource[]>
browserImportProfile: (sourceId: string, profileDir: string) => Promise<BrowserImportResult>
browserGetProxy: () => Promise<BrowserProxySettings>
browserSetProxy: (input: BrowserProxySettings) => Promise<BrowserProxySettings>
onBrowserEvent: (callback: (event: BrowserUiEvent) => void) => () => void
// ---- 插件系统 ----
pluginsList: () => Promise<PluginInfo[]>