ui v0.0.2-beta

This commit is contained in:
go0p
2025-03-04 16:07:14 +08:00
parent 1149cfeb4d
commit 1eee8217ec
9 changed files with 529 additions and 116 deletions
@@ -0,0 +1,65 @@
/* 表格样式 */
.proxy-table {
background: white;
border-radius: 8px;
}
/* 表头样式 */
.proxy-table .ant-table-thead > tr > th {
background: white !important;
color: #333;
font-weight: 500;
border-bottom: 1px solid var(--border-color);
padding: 12px 16px;
}
/* 斑马纹样式 */
.proxy-table .ant-table-tbody > tr:nth-child(even) {
background-color: #fafafa;
}
/* 单元格样式 */
.proxy-table .ant-table-tbody > tr > td {
border-bottom: 1px solid var(--border-color);
padding: 12px 16px;
}
/* 操作列图标样式 */
.action-icon {
font-size: 16px;
cursor: pointer;
color: #666;
transition: all 0.3s;
}
.action-icon:hover {
color: var(--yakit-primary);
transform: scale(1.1);
}
.action-icon.enabled {
color: var(--yakit-primary);
}
.action-icon.delete:hover {
color: #ff4d4f;
}
/* 间距调整 */
.ant-space-middle {
gap: 16px !important;
}
/* 添加按钮样式 */
.add-proxy-btn {
background-color: var(--yakit-primary);
color: white;
border: none;
border-radius: 4px;
margin-bottom: 16px;
}
.add-proxy-btn:hover {
background-color: var(--yakit-primary-hover) !important;
color: white !important;
}
@@ -1,18 +1,32 @@
import React, { useRef } from 'react';
import { Card, Input, Space, Button, Select, InputNumber, Form, Table, Tooltip, Popover } from 'antd';
import React, { useRef, useState, useEffect, useCallback } from 'react';
import { Card, Input, Space, Button, Select, InputNumber, Form, Table, Tooltip, Popover, Modal, message } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import { DeleteOutlined, PlusOutlined, EditOutlined, CheckOutlined } from '@ant-design/icons';
import { ProxyConfig } from '@/types/proxy';
import './index.css';
interface ProxySettingsProps {
proxyConfigs: ProxyConfig[];
onAdd: () => void;
onChange: (configId: string, field: keyof ProxyConfig, value: any) => void;
onAdd: (config: ProxyConfig) => void;
onChange: (configId: string, field: keyof ProxyConfig | 'config', value: any) => void;
onDelete: (configId: string) => void;
onApply: (configId: string) => Promise<void>;
onClear: (configId: string) => Promise<void>;
}
// 添加编辑表单的接口
interface EditFormData {
name: string;
proxyType: "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect";
scheme?: "http" | "https" | "socks4" | "socks5";
host?: string;
port?: number;
pacScript?: string;
}
// 或者更好的方式是创建一个专门的类型
type ProxyConfigField = keyof ProxyConfig | 'config';
export const ProxySettings: React.FC<ProxySettingsProps> = ({
proxyConfigs,
onAdd,
@@ -21,120 +35,141 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
onApply,
onClear
}) => {
const [editingConfig, setEditingConfig] = useState<ProxyConfig | null>(null);
const [editModalVisible, setEditModalVisible] = useState(false);
const [form] = Form.useForm<EditFormData>();
// 添加 useEffect 来监听表单值变化
useEffect(() => {
if (editModalVisible && editingConfig) {
form.setFieldsValue({
name: editingConfig.name,
proxyType: editingConfig.proxyType,
scheme: editingConfig.scheme,
host: editingConfig.host,
port: editingConfig.port,
pacScript: editingConfig.pacScript
});
}
}, [editModalVisible, editingConfig, form]);
// 处理添加按钮点击
const handleAdd = useCallback(() => {
setEditingConfig({
id: Date.now().toString(),
name: '',
proxyType: 'fixed_servers',
scheme: 'http' as "http" | "https" | "socks4" | "socks5",
host: '127.0.0.1',
port: 8080,
enabled: false
});
setEditModalVisible(true);
}, []);
// 处理编辑按钮点击
const handleEdit = (record: ProxyConfig) => {
setEditingConfig(record);
setEditModalVisible(true);
};
// 处理编辑保存
const handleEditSave = async () => {
try {
const values = await form.validateFields();
if (editingConfig) {
if (editingConfig.enabled) {
await onClear(editingConfig.id);
}
const updatedConfig: ProxyConfig = {
...editingConfig,
...values,
proxyType: values.proxyType as "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect",
scheme: values.scheme as "http" | "https" | "socks4" | "socks5"
};
if (!proxyConfigs.find(config => config.id === editingConfig.id)) {
await onAdd(updatedConfig);
} else {
await onChange(editingConfig.id, 'config', updatedConfig);
}
setEditModalVisible(false);
setEditingConfig(null);
form.resetFields();
}
} catch (error) {
console.error('Validate Failed:', error);
message.error('保存失败,请检查表单');
}
};
// 处理模态框关闭
const handleModalClose = () => {
form.resetFields();
setEditModalVisible(false);
setEditingConfig(null);
};
const columns: ColumnsType<ProxyConfig> = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
render: (text: string, record: ProxyConfig) => (
<Input
value={text}
onChange={e => onChange(record.id, 'name', e.target.value)}
disabled={record.id === 'direct'}
/>
)
render: (text: string) => text
},
{
title: '类型',
dataIndex: 'proxyType',
key: 'proxyType',
render: (text: string, record: ProxyConfig) => (
<Select
value={text}
onChange={value => onChange(record.id, 'proxyType', value)}
style={{ width: 120 }}
disabled={record.id === 'direct'}
options={[
{ label: '直接连接', value: 'direct' },
{ label: '代理服务器', value: 'fixed_servers' },
{ label: 'PAC 脚本', value: 'pac_script' }
]}
/>
)
render: (text: string) => {
const typeMap = {
direct: '直接连接',
fixed_servers: '代理服务器',
pac_script: 'PAC 脚本'
};
return typeMap[text as keyof typeof typeMap] || text;
}
},
{
title: '协议',
dataIndex: 'scheme',
key: 'scheme',
render: (text: string, record: ProxyConfig) => (
record.proxyType === 'fixed_servers' && (
<Select
value={text}
onChange={value => onChange(record.id, 'scheme', value)}
style={{ width: 100 }}
options={[
{ label: 'HTTP', value: 'http' },
{ label: 'HTTPS', value: 'https' },
{ label: 'SOCKS4', value: 'socks4' },
{ label: 'SOCKS5', value: 'socks5' }
]}
/>
)
)
key: 'scheme'
},
{
title: '主机',
dataIndex: 'host',
key: 'host',
render: (text: string, record: ProxyConfig) => (
record.proxyType === 'fixed_servers' && (
<Input
value={text}
onChange={e => onChange(record.id, 'host', e.target.value)}
placeholder="127.0.0.1"
/>
)
)
key: 'host'
},
{
title: '端口',
dataIndex: 'port',
key: 'port',
render: (text: number, record: ProxyConfig) => (
record.proxyType === 'fixed_servers' && (
<InputNumber
value={text}
onChange={value => onChange(record.id, 'port', value)}
min={1}
max={65535}
/>
)
)
},
{
title: 'PAC 脚本',
dataIndex: 'pacScript',
key: 'pacScript',
render: (text: string, record: ProxyConfig) => (
record.proxyType === 'pac_script' && (
<Input.TextArea
value={text}
onChange={e => onChange(record.id, 'pacScript', e.target.value)}
rows={4}
placeholder="输入 PAC 脚本"
/>
)
)
key: 'port'
},
{
title: '操作',
key: 'action',
width: 120,
render: (_, record: ProxyConfig) => (
<Space>
<Button
type={record.enabled ? "primary" : "default"}
danger={record.enabled}
onClick={() => record.enabled ?
onClear(record.id) :
onApply(record.id)
}
>
{record.enabled ? '取消应用' : '应用选项'}
</Button>
<Space size="middle">
<CheckOutlined
className={`action-icon ${record.enabled ? 'enabled' : ''}`}
onClick={async () => {
if (record.enabled) {
await onClear(record.id);
} else {
await onApply(record.id);
}
}}
/>
<EditOutlined
className="action-icon"
onClick={() => handleEdit(record)}
/>
{record.id !== 'direct' && (
<Button
danger
icon={<DeleteOutlined />}
<DeleteOutlined
className="action-icon delete"
onClick={() => onDelete(record.id)}
/>
)}
@@ -145,21 +180,129 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
const buttonRef = useRef(null);
// 过滤掉固定模式的代理
const filteredProxyConfigs = proxyConfigs.filter(
config => !['direct', 'system'].includes(config.id)
);
return (
<div>
<Space style={{ marginBottom: 16, justifyContent: 'flex-end', width: '100%' }}>
<Popover content="添加代理" trigger="hover">
<Button ref={buttonRef} onClick={onAdd}>
添加代理
</Button>
</Popover>
<Button
className="add-proxy-btn"
onClick={handleAdd}
icon={<PlusOutlined />}
>
添加代理
</Button>
</Space>
<Table
dataSource={proxyConfigs}
className="proxy-table"
dataSource={filteredProxyConfigs}
columns={columns}
rowKey="id"
pagination={false}
bordered={false}
size="middle"
/>
<Modal
title="编辑代理配置"
open={editModalVisible}
onOk={handleEditSave}
onCancel={handleModalClose}
destroyOnClose
>
<Form
form={form}
layout="vertical"
preserve={false}
>
<Form.Item
name="name"
label="名称"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="proxyType"
label="类型"
rules={[{ required: true }]}
>
<Select
options={[
{ label: '直接连接', value: 'direct' },
{ label: '代理服务器', value: 'fixed_servers' },
{ label: 'PAC 脚本', value: 'pac_script' }
]}
onChange={(value) => {
// 当类型改变时,清除相关字段
if (value !== 'fixed_servers') {
form.setFieldsValue({
scheme: undefined,
host: undefined,
port: undefined
});
}
}}
/>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}
>
{({ getFieldValue }) => {
const proxyType = getFieldValue('proxyType');
return proxyType === 'fixed_servers' ? (
<>
<Form.Item
name="scheme"
label="协议"
rules={[{ required: true, message: '请选择协议' }]}
>
<Select
options={[
{ label: 'HTTP', value: 'http' },
{ label: 'HTTPS', value: 'https' },
{ label: 'SOCKS4', value: 'socks4' },
{ label: 'SOCKS5', value: 'socks5' }
]}
/>
</Form.Item>
<Form.Item
name="host"
label="主机"
rules={[{ required: true, message: '请输入主机地址' }]}
>
<Input placeholder="127.0.0.1" />
</Form.Item>
<Form.Item
name="port"
label="端口"
rules={[{ required: true, message: '请输入端口号' }]}
>
<InputNumber
min={1}
max={65535}
placeholder="8080"
style={{ width: '100%' }}
/>
</Form.Item>
</>
) : proxyType === 'pac_script' ? (
<Form.Item
name="pacScript"
label="PAC 脚本"
rules={[{ required: true, message: '请输入 PAC 脚本' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
) : null;
}}
</Form.Item>
</Form>
</Modal>
</div>
);
);
};
+83 -14
View File
@@ -1,35 +1,104 @@
import React from 'react';
import { Layout, Tabs } from 'antd';
import React, { useState, useEffect, useRef } from 'react';
import { Layout, Tabs, message } from 'antd';
import { ProxySettings } from './components/ProxySettings';
import { ProxyLogs } from './components/ProxyLogs';
import { useProxyConfigs } from './hooks/useProxyConfigs';
import { useProxyLogs } from './hooks/useProxyLogs';
import { ProxyConfig } from '@/types/proxy';
import { ProxyActionType } from '@/types/action';
const { Content } = Layout;
interface ProxySettingsProps {
proxyConfigs: ProxyConfig[];
onAdd: (config: ProxyConfig) => void;
onChange: (configId: string, field: keyof ProxyConfig | 'config', value: any) => void;
onDelete: (configId: string) => void;
onApply: (configId: string) => Promise<void>;
onClear: (configId: string) => Promise<void>;
}
export const OptionsPage: React.FC = () => {
const {
proxyConfigs,
handleAddProxy,
handleConfigChange,
handleConfigChange: handleConfigChangeHook,
handleDeleteProxy,
handleApplyConfig,
handleClearProxy
} = useProxyConfigs();
const { proxyLogs, handleClearLogs } = useProxyLogs();
const [proxyConfigsState, setProxyConfigs] = useState<ProxyConfig[]>([]);
const handleAdd = () => {
const newConfig: ProxyConfig = {
id: Date.now().toString(),
name: '新建代理',
proxyType: 'fixed_servers',
scheme: 'http',
host: '127.0.0.1',
port: 8080,
enabled: false
useEffect(() => {
setProxyConfigs(proxyConfigs);
}, [proxyConfigs]);
// 通知 background 页面已准备就绪
useEffect(() => {
chrome.runtime.sendMessage({ action: 'OPTIONS_PAGE_READY' });
const messageListener = (
message: any,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void
) => {
if (message.action === 'TRIGGER_ADD_PROXY') {
const proxySettingsElement = document.querySelector('.add-proxy-btn');
if (proxySettingsElement) {
(proxySettingsElement as HTMLElement).click();
}
}
sendResponse();
};
handleAddProxy(newConfig);
chrome.runtime.onMessage.addListener(messageListener);
return () => {
chrome.runtime.onMessage.removeListener(messageListener);
};
}, []);
const handleAdd = async (config: ProxyConfig) => {
try {
await handleAddProxy(config);
} catch (error) {
console.error('Failed to add proxy:', error);
message.error('添加代理失败');
}
};
const handleConfigChange = async (configId: string, field: keyof ProxyConfig | 'config', value: any) => {
try {
const updatedConfigs = proxyConfigsState.map(config => {
if (config.id === configId) {
if (field === 'config') {
// 如果是整个配置更新
return value;
} else {
// 如果是单个字段更新
return {
...config,
[field]: value
};
}
}
return config;
});
// 更新 IndexedDB
await chrome.runtime.sendMessage({
action: ProxyActionType.UPDATE_PROXY_CONFIG,
configs: updatedConfigs
});
// 更新本地状态
setProxyConfigs(updatedConfigs);
message.success('更新配置成功');
} catch (error) {
console.error('Failed to update config:', error);
message.error('更新配置失败');
}
};
return (
@@ -43,7 +112,7 @@ export const OptionsPage: React.FC = () => {
label: '代理设置',
children: (
<ProxySettings
proxyConfigs={proxyConfigs}
proxyConfigs={proxyConfigsState}
onAdd={handleAdd}
onChange={handleConfigChange}
onDelete={handleDeleteProxy}