feat(browser): 受管浏览器代理设置(profile 导入已有实现,本轮补全代理)
This commit is contained in:
@@ -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,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[]>
|
||||
|
||||
Reference in New Issue
Block a user