`;
}
@@ -247,7 +263,7 @@ function createFloatingPanel() {
position: fixed;
top: 20px;
right: 0;
- width: 200px;
+ width: 180px;
background: white;
border-radius: 8px 0 0 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
@@ -299,21 +315,41 @@ function createFloatingPanel() {
}
.panel-content {
- padding: 12px;
+ padding: 4px;
}
.proxy-item {
+ position: relative;
display: flex;
align-items: center;
- padding: 8px 12px;
+ padding: 4px 10px;
cursor: pointer;
transition: all 0.2s;
color: #666;
border-left: 3px solid transparent;
+ overflow: hidden;
}
- .proxy-item:hover {
- background: #f5f5f5;
+ .proxy-item > span {
+ position: relative;
+ z-index: 1;
+ }
+
+ .watermark-icon {
+ position: absolute;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.3;
+ pointer-events: none;
+ display: none;
+ object-fit: contain;
+ object-position: right center;
+ padding: 4px;
+ }
+
+ .proxy-item.active .watermark-icon {
+ display: block;
}
.proxy-item.active {
@@ -333,6 +369,10 @@ function createFloatingPanel() {
transition: color 0.2s;
}
+ .proxy-item:hover {
+ background: #f5f5f5;
+ }
+
.proxy-item:hover span:first-child {
color: #ff6b00;
}
@@ -340,7 +380,7 @@ function createFloatingPanel() {
.add-proxy {
display: flex;
align-items: center;
- padding: 8px 12px;
+ padding: 4px 10px;
color: #1890ff;
cursor: pointer;
border-top: 1px solid #eee;
@@ -352,7 +392,7 @@ function createFloatingPanel() {
}
.settings {
- padding: 8px 12px;
+ padding: 4px 10px;
color: #666;
cursor: pointer;
border-top: 1px solid #eee;
@@ -362,6 +402,25 @@ function createFloatingPanel() {
.settings:hover {
background: #f5f5f5;
}
+
+ .panel-header .header-content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 4px;
+ }
+
+ .yak-icon {
+ width: 24px;
+ height: 24px;
+ object-fit: contain;
+ }
+
+ .divider {
+ height: 1px;
+ background-color: #eee;
+ margin: 4px 0;
+ }
`;
// 创建面板内容
@@ -370,20 +429,24 @@ function createFloatingPanel() {
panel.innerHTML = `
- 🔴
+ 🟢
[直接连接]
⚙️
[系统代理]
+
➕
- 添加代理...
+ [添加代理...]
👨💻
diff --git a/src/components/ProxySwitch/index.css b/src/components/ProxySwitch/index.css
index f004061..92a75af 100644
--- a/src/components/ProxySwitch/index.css
+++ b/src/components/ProxySwitch/index.css
@@ -200,4 +200,36 @@ body {
.ant-menu-item .menu-icon {
transition: color 0.3s ease;
+}
+
+.proxy-switch-container {
+ position: relative;
+ width: 180px;
+ overflow: hidden;
+}
+
+.panel-watermark {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.03;
+ pointer-events: none;
+ object-fit: contain;
+ object-position: right bottom;
+ z-index: 0;
+}
+
+/* 确保菜单项在水印上层 */
+.ant-menu-item {
+ position: relative;
+ z-index: 1;
+ background: transparent !important;
+}
+
+/* 确保分割线在水印上层 */
+.ant-menu-item-divider {
+ position: relative;
+ z-index: 1;
}
\ No newline at end of file
diff --git a/src/components/ProxySwitch/index.tsx b/src/components/ProxySwitch/index.tsx
index b8b0969..de927bb 100644
--- a/src/components/ProxySwitch/index.tsx
+++ b/src/components/ProxySwitch/index.tsx
@@ -6,6 +6,9 @@ import "./index.css";
import type { MenuProps } from 'antd';
import type { ProxyConfig } from '@/types/proxy';
+// 添加 YAK 图标 URL 常量
+const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg');
+
// 固定的代理模式
const FIXED_MODES = [
{
@@ -48,18 +51,21 @@ interface ProxySwitchProps {
onProxyChange: (config: ProxyConfig) => void;
}
-export const ProxySwitch: React.FC
= ({
- proxyConfigs,
- currentProxy,
- onProxyChange,
-}) => {
- const [currentMode, setCurrentMode] = useState('direct');
+export const ProxySwitch: React.FC = () => {
+ const [initialized, setInitialized] = useState(false);
+ const [currentMode, setCurrentMode] = useState('');
const [customProxies, setCustomProxies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
- loadProxyStatus();
- loadCustomProxies();
+ const init = async () => {
+ await Promise.all([
+ loadProxyStatus(),
+ loadCustomProxies()
+ ]);
+ setInitialized(true);
+ };
+ init();
}, []);
const loadProxyStatus = async () => {
@@ -68,55 +74,154 @@ export const ProxySwitch: React.FC = ({
action: ProxyActionType.GET_PROXY_STATUS
});
+ if (!response) {
+ console.log('No response from background script');
+ return;
+ }
+
if (response.success) {
const activeMode = response.data.mode;
- setCurrentMode(activeMode);
-
if (FIXED_MODES.some(mode => mode.key === activeMode)) {
setCurrentMode(activeMode);
}
}
} catch (error) {
console.error('Error loading proxy status:', error);
+ setCurrentMode('direct');
}
};
const loadCustomProxies = async () => {
try {
- 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);
+ // 首先检查当前是否在 options 页面的上下文中
+ const currentUrl = window.location.href;
+ const isInOptionsContext = currentUrl.includes('chrome-extension://') && currentUrl.includes('options.html');
- const enabledProxy = proxies.find((proxy: ProxyConfig) => proxy.enabled);
- if (enabledProxy) {
- setCurrentMode(enabledProxy.key);
+ if (isInOptionsContext) {
+ // 如果在 options 页面上下文中,直接使用消息通信
+ 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 enabledProxy = response.data.find((proxy: ProxyConfig) => proxy.enabled);
+ if (enabledProxy) {
+ setCurrentMode(enabledProxy.id);
+ } else {
+ setCurrentMode('direct');
+ }
+ return;
}
}
+
+ // 如果不在 options 页面上下文中,使用 IndexedDB
+ const DB_NAME = 'yaklang_extension';
+ const STORE_NAME = 'proxy_configs';
+
+ // 打开数据库
+ const db = await new Promise((resolve, reject) => {
+ const request = indexedDB.open(DB_NAME, 1);
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve(request.result);
+ });
+
+ // 从数据库读取代理配置
+ const configs = await new Promise((resolve, reject) => {
+ try {
+ const transaction = db.transaction([STORE_NAME], 'readonly');
+ const store = transaction.objectStore(STORE_NAME);
+ const request = store.getAll();
+
+ request.onerror = () => reject(request.error);
+ request.onsuccess = () => resolve(request.result || []);
+ } catch (error) {
+ reject(error);
+ }
+ });
+
+ // 处理代理配置
+ const proxies = configs
+ .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 enabledProxy = configs.find((proxy: ProxyConfig) => proxy.enabled);
+ if (enabledProxy) {
+ setCurrentMode(enabledProxy.id);
+ } else {
+ setCurrentMode('direct');
+ }
+
} catch (error) {
console.error('Error loading custom proxies:', error);
+ setCustomProxies([]);
+ setCurrentMode('direct');
}
};
- const handleApplyConfig = async (mode: string) => {
+ const handleModeChange = async (mode: string) => {
+ if (mode === 'setting') {
+ await chrome.runtime.openOptionsPage?.();
+ return;
+ }
+
+ if (mode === 'add') {
+ try {
+ const [activeTab] = await chrome.tabs.query({
+ active: true,
+ currentWindow: true
+ });
+ const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
+
+ if (activeTab?.url === optionsUrl) {
+ chrome.tabs.sendMessage(activeTab.id!, {
+ action: 'TRIGGER_ADD_PROXY'
+ });
+ } else {
+ const tab = await chrome.tabs.create({
+ url: optionsUrl
+ });
+
+ const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
+ if (tabId === tab.id && changeInfo.status === 'complete') {
+ chrome.tabs.onUpdated.removeListener(listener);
+ chrome.tabs.sendMessage(tab.id!, {
+ action: 'TRIGGER_ADD_PROXY'
+ });
+ }
+ };
+
+ chrome.tabs.onUpdated.addListener(listener);
+ }
+ } catch (error) {
+ console.error('Failed to get current tab:', error);
+ }
+ return;
+ }
+
try {
setIsLoading(true);
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;
@@ -149,63 +254,13 @@ export const ProxySwitch: React.FC = ({
}
};
- const handleModeChange = async (mode: string) => {
- if (mode === 'setting') {
- await chrome.runtime.openOptionsPage?.();
- return;
- }
-
- if (mode === 'add') {
- try {
- // 获取当前活动标签页
- const [activeTab] = await chrome.tabs.query({
- active: true,
- currentWindow: true
- });
- const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
-
- if (activeTab?.url === optionsUrl) {
- // 如果当前就在 options 页面,直接发消息触发添加代理
- chrome.tabs.sendMessage(activeTab.id!, {
- action: 'TRIGGER_ADD_PROXY'
- });
- } else {
- // 如果不在 options 页面,创建新的
- const tab = await chrome.tabs.create({
- url: optionsUrl
- });
-
- // 等待页面加载完成
- const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
- if (tabId === tab.id && changeInfo.status === 'complete') {
- chrome.tabs.onUpdated.removeListener(listener);
- chrome.tabs.sendMessage(tab.id!, {
- action: 'TRIGGER_ADD_PROXY'
- });
- }
- };
-
- chrome.tabs.onUpdated.addListener(listener);
- }
- } catch (error) {
- console.error('Failed to get current tab:', error);
- }
- 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' : ''} ${isLoading ? 'menu-item-loading' : ''}`
+ className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
+ title: mode.name.replace(/[\[\]]/g, '')
})),
{ type: 'divider' },
...customProxies.map(proxy => ({
@@ -215,7 +270,8 @@ export const ProxySwitch: React.FC = ({
color: currentMode === proxy.key ? 'var(--yakit-primary)' : 'inherit',
opacity: isLoading ? 0.7 : 1
}}>{proxy.name},
- className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`
+ className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
+ title: `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
})),
{
key: 'add',
@@ -231,13 +287,37 @@ export const ProxySwitch: React.FC = ({
}
];
- return (
-