diff --git a/public/manifest.json b/public/manifest.json index b8fc865..26299b3 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -8,7 +8,7 @@ "open_in_tab": true }, "action": { - "default_title": "Click to open panel", + "default_popup": "index.html", "default_icon": { "16": "/images/icon16.png", "48": "/images/icon48.png", diff --git a/public/proxy/proxy-settings.js b/public/proxy/proxy-settings.js index 4261b05..932dd62 100644 --- a/public/proxy/proxy-settings.js +++ b/public/proxy/proxy-settings.js @@ -27,12 +27,20 @@ export class ProxySettings { const configs = await proxyStore.getProxyConfigs(); if (!configs || configs.length === 0) { // 设置默认的直接连接配置 - await proxyStore.saveProxyConfigs([{ - id: 'direct', - name: '直接连接', - proxyType: 'direct', - enabled: false - }]); + await proxyStore.saveProxyConfigs([ + { + id: 'direct', + name: '直接连接', + proxyType: 'direct', + enabled: false + }, + { + id: 'system', + name: '系统代理', + proxyType: 'system', + enabled: false + } + ]); } // 确保日志存储已初始化 const logs = await proxyStore.getLogs(); // 使用 getLogs 而不是 getProxyLogs diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 6f8aece..0000000 --- a/src/App.css +++ /dev/null @@ -1,44 +0,0 @@ -.App { - width: 420px; - border-radius: 0px 0px 4px 4px; - border-right: 1px solid #EAECF3; - border-bottom: 1px solid #EAECF3; - border-left: 1px solid #EAECF3; - background: #fff; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 36d25d2..627befd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ import React from "react"; -import "./App.css"; +// import "./App.css"; import {ConfigProvider} from "antd"; import {Contro} from "@components/Contro"; import {Proxifier} from "@components/Proxifier"; import {EvalInTab} from "@components/EvalInTab"; import {ProxySwitch} from "@components/ProxySwitch"; +import './styles/global.css'; function App() { return ( diff --git a/src/components/ProxySwitch/index.css b/src/components/ProxySwitch/index.css index 6b12f83..9c92859 100644 --- a/src/components/ProxySwitch/index.css +++ b/src/components/ProxySwitch/index.css @@ -1,68 +1,180 @@ -.proxy-switch { - padding: 16px; - background: #fff; - border-radius: 4px; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +.proxy-container { + min-width: 200px; + background: white; } -.proxy-switch-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 16px; +.proxy-menu { + border: none !important; + box-shadow: none !important; } -.proxy-switch .ant-select { - margin-right: 8px; +.menu-item { + height: 40px !important; + line-height: 40px !important; + margin: 0 !important; + padding: 0 16px !important; } -.proxy-switch .ant-space { - gap: 8px; +.menu-item .anticon { + font-size: 16px; + color: var(--yakit-primary); + margin-right: 8px; } -.proxy-switch-title { - display: flex; - align-items: center; +.menu-item-label { + font-size: 14px; + color: #333; } -.proxy-switch-title-text { - color: #31343F; - font-size: 14px; - font-weight: 600; - margin-right: 8px; +.menu-item:hover { + background-color: var(--yakit-primary-5) !important; } -.proxy-switch-content { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - background: #F8F8F8; - border-radius: 4px; +.menu-item:hover .anticon, +.menu-item:hover .menu-item-label { + color: var(--yakit-primary) !important; } -.proxy-switch-info { - display: flex; - flex-direction: column; - gap: 4px; +/* 选中状态 */ +.menu-item.ant-menu-item-selected { + background-color: var(--yakit-primary) !important; } -.proxy-switch-status { - color: #85899E; - font-size: 12px; +.menu-item.ant-menu-item-selected .anticon, +.menu-item.ant-menu-item-selected .menu-item-label { + color: white !important; } -.proxy-switch-proxy { - color: #31343F; - font-size: 13px; +.menu-item.ant-menu-item-selected:hover { + background-color: var(--yakit-primary-hover) !important; } -.proxy-switch-status-active { - color: var(--yakit-primary); +/* 分隔线 */ +.ant-menu-item-divider { + margin: 4px 0 !important; + border-color: #EAECF3 !important; } -.proxy-switch-actions { - display: flex; - align-items: center; - gap: 8px; +/* 设置选项 */ +.menu-item-setting { + border-top: 1px solid #EAECF3; + margin-top: 4px !important; +} + +.menu-item-setting .anticon { + color: #666; +} + +.menu-item-setting:hover { + background-color: var(--yakit-primary-5) !important; +} + +.menu-item-setting:hover .anticon, +.menu-item-setting:hover .menu-item-label { + color: var(--yakit-primary) !important; +} + +/* 调整图标大小和对齐 */ +.anticon { + font-size: 16px; +} + +/* 添加以下样式来确保下拉菜单显示在正确的位置 */ +.ant-dropdown { + position: absolute !important; + top: 100% !important; + left: 0 !important; + width: 100% !important; + min-width: 200px !important; +} + +.dropdown-content { + background: white; + border-radius: 4px; + box-shadow: 0 3px 6px -4px rgba(0,0,0,0.12), + 0 6px 16px 0 rgba(0,0,0,0.08), + 0 9px 28px 8px rgba(0,0,0,0.05); + padding: 4px; +} + +/* 确保容器不会限制弹出层 */ +.proxy-container { + min-width: 200px; + background: white; + border-radius: 4px; + position: static; +} + +/* 添加这个样式来确保下拉菜单显示在正确的位置 */ +body { + position: relative; +} + +.ant-menu { + border: none !important; + box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important; + padding: 4px 0 !important; + width: 180px !important; + background: white !important; +} + +.ant-menu-item { + height: 28px !important; + line-height: 28px !important; + margin: 0 !important; + padding: 0 16px !important; +} + +.ant-menu-item:hover { + background-color: #f5f5f5 !important; +} + +.menu-icon { + margin-right: 8px; + display: inline-flex; + align-items: center; +} + +.menu-item-selected { + background-color: #e6f7ff !important; +} + +.ant-menu-item-divider { + margin: 4px 0 !important; + height: 1px !important; + background-color: #f0f0f0 !important; +} + +.ant-menu-item:last-child { + margin-top: 4px !important; + border-top: 1px solid #f0f0f0; +} + +/* 移除多余的样式 */ +.ant-menu-root { + background: transparent !important; +} + +/* 调整整体容器大小 */ +.ant-menu-root { + width: 180px !important; + min-height: auto !important; +} + +/* 添加代理按钮样式 */ +.menu-item-add { + color: #666 !important; +} + +.menu-item-add:hover { + background-color: #f5f5f5 !important; + color: var(--yakit-primary) !important; +} + +.menu-item-add .anticon { + color: #666; +} + +.menu-item-add:hover .anticon { + color: var(--yakit-primary); } \ No newline at end of file diff --git a/src/components/ProxySwitch/index.tsx b/src/components/ProxySwitch/index.tsx index 41d119a..b08ed56 100644 --- a/src/components/ProxySwitch/index.tsx +++ b/src/components/ProxySwitch/index.tsx @@ -1,206 +1,54 @@ import React, {useEffect, useState} from "react"; -import {Select, Button, Tooltip, Modal, Form, Input, Radio, Space, message, Switch} from "antd"; -import {PlusOutlined, SettingOutlined} from "@ant-design/icons"; -import {ProxyConfig} from "@/types/proxy"; -import {StorageChanges} from "@/types/chrome"; +import {Menu} from "antd"; +import {GlobalOutlined, DisconnectOutlined, SettingOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons"; import {ProxyActionType} from '@/types/action'; import "./index.css"; +import type { MenuProps } from 'antd'; +import type { ProxyConfig } from '@/types/proxy'; -interface ProxySwitchProps { - onChange?: (checked: boolean) => void; +// 固定的代理模式 +const FIXED_MODES = [ + { + key: 'direct', + name: '[直接连接]', + icon: , + color: '#666', + config: { + id: 'direct', + name: '[直接连接]', + proxyType: 'direct' + } + }, + { + key: 'system', + name: '[系统代理]', + icon: , + color: '#666', + config: { + id: 'system', + name: '[系统代理]', + proxyType: 'system' + } + } +]; + +interface CustomProxy { + key: string; + name: string; + color: string; + config: ProxyConfig; + enabled?: boolean; } -export const ProxySwitch: React.FC = ({ onChange }) => { - const [currentMode, setCurrentMode] = useState("direct"); - const [proxyConfigs, setProxyConfigs] = useState([]); - const [isModalVisible, setIsModalVisible] = useState(false); - const [form] = Form.useForm(); - const [proxyHost, setProxyHost] = useState(''); - const [proxyPort, setProxyPort] = useState(''); - const [enabled, setEnabled] = useState(false); - const [loading, setLoading] = useState(true); - - useEffect(() => { - loadConfigs().catch(error => { - console.error("Failed to load configs:", error); - // 可以在这里添加错误提示 - }); - }, []); - - useEffect(() => { - if (proxyConfigs.length > 0) { - applyProxySettings(currentMode).catch(error => { - console.error("Failed to apply proxy settings:", error); - // 可以在这里添加错误提示 - }); - } - }, [proxyConfigs, currentMode]); - - useEffect(() => { - const handleStorageChange = (changes: StorageChanges) => { - if (changes.proxyConfigs) { - const newConfigs = changes.proxyConfigs.newValue; - setProxyConfigs(newConfigs); - const currentConfig = newConfigs.find((c: ProxyConfig) => c.id === currentMode); - if (!currentConfig || !currentConfig.enabled) { - setCurrentMode("direct"); - } - } - }; - chrome.storage.onChanged.addListener(handleStorageChange); - return () => chrome.storage.onChanged.removeListener(handleStorageChange); - }, [currentMode]); +export const ProxySwitch: React.FC = () => { + const [currentMode, setCurrentMode] = useState('direct'); + const [customProxies, setCustomProxies] = useState([]); useEffect(() => { loadProxyStatus(); + loadCustomProxies(); }, []); - const loadConfigs = async () => { - try { - const result = await chrome.storage.local.get('proxyConfigs'); - if (!result.proxyConfigs) { - const defaultConfigs: ProxyConfig[] = [ - { - id: "direct", - name: "直接连接", - proxyType: "direct" as const, - enabled: true - }, - { - id: "default", - name: "默认代理", - proxyType: "fixed_server" as const, - host: "127.0.0.1", - port: 8080, - scheme: "http", - enabled: false - } - ]; - await chrome.storage.local.set({proxyConfigs: defaultConfigs}); - setProxyConfigs(defaultConfigs); - setCurrentMode("direct"); - } else { - setProxyConfigs(result.proxyConfigs); - const currentConfig = result.proxyConfigs.find((c: ProxyConfig) => c.id === currentMode); - if (!currentConfig || !currentConfig.enabled) { - setCurrentMode("direct"); - } - } - return true; - } catch (error) { - console.error("Error in loadConfigs:", error); - throw error; - } - }; - - const applyProxySettings = async (modeId: string) => { - try { - const config = proxyConfigs.find((c: ProxyConfig) => c.id === modeId); - if (!config || !config.enabled) return false; - - if (config.proxyType === "direct") { - chrome.proxy.settings.clear({scope: 'regular'}); - return true; - } else if (config.proxyType === "fixed_server") { - chrome.proxy.settings.set({ - value: { - mode: "fixed_servers", - rules: { - singleProxy: { - scheme: config.scheme, - host: config.host, - port: Number(config.port) - }, - bypassList: ["<-loopback>"] - } - }, - scope: "regular" - }); - return true; - } else if (config.proxyType === "pac_script") { - chrome.proxy.settings.set({ - value: { - mode: "pac_script", - pacScript: { - data: config.pacScript - } - }, - scope: "regular" - }); - return true; - } - return false; - } catch (error) { - console.error("Failed to apply proxy settings:", error); - throw error; - } - }; - - const handleAddProxy = () => { - form.resetFields(); - setIsModalVisible(true); - }; - - const handleModalOk = () => { - form.validateFields().then(async values => { - try { - const newConfig: ProxyConfig = { - id: Date.now().toString(), - name: values.name, - proxyType: values.proxyType as "direct" | "fixed_server" | "pac_script" | "auto_detect", - host: values.host, - port: values.port ? parseInt(values.port) : undefined, - scheme: values.scheme, - enabled: true, - }; - const updatedConfigs = [...proxyConfigs, newConfig]; - await chrome.storage.local.set({proxyConfigs: updatedConfigs}); - setProxyConfigs(updatedConfigs); - setIsModalVisible(false); - } catch (error) { - console.error("Failed to save new proxy config:", error); - // 可以在这里添加错误提示 - } - }); - }; - - const handleSettingClick = () => { - if (chrome.runtime.openOptionsPage) { - chrome.runtime.openOptionsPage(); - } else { - chrome.tabs.create({ - url: chrome.runtime.getURL('proxy/options.html') - }); - } - }; - - const handleApplyProxy = async () => { - try { - const response = await chrome.runtime.sendMessage({ - action: 'SET_PROXY_CONFIG', - config: { - host: proxyHost, - port: parseInt(proxyPort), - scheme: 'http', - proxyType: 'fixed_server', - id: 'default', - name: '默认代理' - } - }); - - if (response.success) { - message.success('代理设置已应用'); - } else { - message.error(response.error || '代理设置失败'); - } - } catch (error) { - console.error('Error applying proxy:', error); - message.error('应用代理设置时发生错误'); - } - }; - - const proxyType: ProxyConfig['proxyType'] = proxyConfigs.find((c: ProxyConfig) => c.id === currentMode)?.proxyType || 'direct'; - const loadProxyStatus = async () => { try { const response = await chrome.runtime.sendMessage({ @@ -208,157 +56,124 @@ export const ProxySwitch: React.FC = ({ onChange }) => { }); if (response.success) { - setEnabled(response.data.enabled); + const activeMode = response.data.mode; + setCurrentMode(activeMode); + + if (FIXED_MODES.some(mode => mode.key === activeMode)) { + setCurrentMode(activeMode); + } } - setLoading(false); } catch (error) { console.error('Error loading proxy status:', error); - setLoading(false); } }; - const handleChange = async (checked: boolean) => { + const loadCustomProxies = async () => { try { - if (checked) { - // 获取配置列表 - const configResponse = await chrome.runtime.sendMessage({ - action: ProxyActionType.GET_PROXY_CONFIGS - }); - - if (!configResponse.success) { - throw new Error(configResponse.error || '获取代理配置失败'); - } + const response = await chrome.runtime.sendMessage({ + action: ProxyActionType.GET_PROXY_CONFIGS + }); + + if (response.success && response.data) { + const proxies = response.data + .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id)) + .map((proxy: ProxyConfig): CustomProxy => ({ + key: proxy.id, + name: proxy.name, + color: '#1890ff', + config: proxy, + enabled: proxy.enabled + })); + setCustomProxies(proxies); - const configs = configResponse.data || []; - const defaultConfig = configs.find((c: ProxyConfig) => c.id === 'direct'); - - if (defaultConfig) { - const response = await chrome.runtime.sendMessage({ - action: ProxyActionType.SET_PROXY_CONFIG, - config: defaultConfig - }); - - if (response.success) { - setEnabled(true); - onChange?.(true); - } - } - } else { - const response = await chrome.runtime.sendMessage({ - action: ProxyActionType.CLEAR_PROXY_CONFIG - }); - - if (response.success) { - setEnabled(false); - onChange?.(false); + const enabledProxy = proxies.find((proxy: ProxyConfig) => proxy.enabled); + if (enabledProxy) { + setCurrentMode(enabledProxy.key); } } } catch (error) { - console.error('Error toggling proxy:', error); + console.error('Error loading custom proxies:', error); } }; + const handleApplyConfig = async (mode: string) => { + try { + const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode); + const customProxy = customProxies.find(proxy => proxy.key === mode); + + const config = fixedMode?.config || customProxy?.config; + + if (!config) { + console.error('No config found for mode:', mode); + return; + } + + const response = await chrome.runtime.sendMessage({ + action: ProxyActionType.SET_PROXY_CONFIG, + config + }); + + if (response.success) { + setCurrentMode(mode); + await loadCustomProxies(); + } + } catch (error) { + console.error('Failed to apply proxy config:', error); + } + }; + + const handleModeChange = async (mode: string) => { + if (mode === 'setting') { + chrome.runtime.openOptionsPage?.(); + return; + } + + if (mode === 'add') { + chrome.runtime.openOptionsPage?.(); + return; + } + + try { + await handleApplyConfig(mode); + } catch (error) { + console.error('Failed to change proxy mode:', error); + } + }; + + const menuItems: MenuProps['items'] = [ + ...FIXED_MODES.map(mode => ({ + key: mode.key, + icon: {mode.icon}, + label: mode.name, + className: currentMode === mode.key ? 'menu-item-selected' : '' + })), + { type: 'divider' }, + ...customProxies.map(proxy => ({ + key: proxy.key, + icon: , + label: {proxy.name}, + className: proxy.enabled ? 'menu-item-selected' : '' + })), + { + key: 'add', + icon: , + label: '添加代理...', + className: 'menu-item-add' + }, + { type: 'divider' }, + { + key: 'setting', + icon: , + label: '选项' + } + ]; + return ( - - - config.enabled).map((config: ProxyConfig) => ({ - label: config.name, - value: config.id - }))} - /> - - - } - onClick={handleAddProxy} - /> - - - } - onClick={handleSettingClick} - /> - - - - - setIsModalVisible(false)} - > - - - - - - - 直接连接 - 代理服务器 - PAC 脚本 - - - prev.proxyType !== curr.proxyType} - > - {({getFieldValue}) => - getFieldValue('proxyType') === 'fixed_server' && ( - <> - - - HTTP - HTTPS - SOCKS4 - SOCKS5 - - - - - - - - - > - ) - } - - - - - - + handleModeChange(key)} + style={{ width: 180 }} + /> ); }; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 332c1a3..78b3afd 100644 --- a/src/index.css +++ b/src/index.css @@ -4,11 +4,13 @@ html:root { body { margin: 0; + padding: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + overflow: hidden; } *, @@ -17,23 +19,9 @@ body { box-sizing: border-box; } -::-webkit-scrollbar { - /*滚动条整体样式*/ - width: 8px; - /*高宽分别对应横竖滚动条的尺寸*/ - height: 8px; -} - -::-webkit-scrollbar-thumb { - /*滚动条里面小方块*/ - border-radius: 10px; - box-shadow: inset 0 0 5px rgba(193, 193, 193); - background: #c1c1c1; -} - -::-webkit-scrollbar-track { - /*滚动条里面轨道*/ - box-shadow: inset 0 0 5px rgba(193, 193, 193); - border-radius: 10px; - background: #ededed; +#root { + width: 180px; + height: fit-content; + background: white; + overflow: hidden; } diff --git a/src/styles/global.css b/src/styles/global.css index 320e3ce..46ccb10 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -2,8 +2,18 @@ --yakit-primary: #F28B44; --yakit-primary-hover: #f4a061; --yakit-primary-active: #e87633; - --yakit-primary-5: rgba(242, 139, 68, 0.05); + --yakit-primary-5: #fff5eb; --yakit-primary-10: rgba(242, 139, 68, 0.1); + + /* 添加其他全局变量 */ + --border-color: #f0f0f0; + --text-color: #333; + --icon-color: #666; + + /* 菜单相关变量 */ + --menu-item-height: 28px; + --menu-padding: 4px; + --menu-width: 180px; } .ant-btn-primary { @@ -34,4 +44,15 @@ .ant-btn:not(.ant-btn-primary):hover { color: var(--yakit-primary) !important; border-color: var(--yakit-primary) !important; +} + +/* 移除所有滚动条 */ +::-webkit-scrollbar { + display: none; +} + +/* 确保所有内容都在视口内 */ +html, body { + overflow: hidden; + height: fit-content; } \ No newline at end of file