refactor: 重构项目结构并优化代码

- 删除无用的文件和错误日志
- 创建统一的 imports 模块集中管理依赖
- 重构组件使用新的 imports 方式
- 修复文档路径大小写问题
- 优化类型定义和接口导出
- 更新依赖版本
- 改进错误处理和API配置
- 统一组件导出方式
This commit is contained in:
2026-03-27 16:56:06 +08:00
parent 2748456d8a
commit 22308fe042
337 changed files with 37060 additions and 57483 deletions

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -20,8 +21,6 @@ import {
Statistic,
Divider,
Collapse,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
@@ -30,14 +29,14 @@ import {
PercentageOutlined,
DollarOutlined,
SettingOutlined,
} from '@ant-design/icons';
Option,
Panel,
TextArea,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import { settingsDataSource, CostTemplate, CostItem } from '@/services/settingsDataSource';
const { Option } = Select;
const { Panel } = Collapse;
const { TextArea } = Input;
const PLATFORM_LIST = ['AMAZON', 'EBAY', 'SHOPIFY', 'SHOPEE', 'LAZADA', 'ALIBABA', 'ALL'];
const CATEGORY_LIST = ['Electronics', 'Clothing', 'Home', 'Industrial', 'General'];
@@ -47,7 +46,7 @@ const COST_TYPE_CONFIG: Record<string, { color: string; text: string; icon: Reac
per_unit: { color: 'orange', text: 'Per Unit', icon: <CalculatorOutlined /> },
};
const CostTemplateConfig: React.FC = () => {
const CostTemplateConfig: FC = () => {
const [loading, setLoading] = useState(false);
const [templates, setTemplates] = useState<CostTemplate[]>([]);
const [modalVisible, setModalVisible] = useState(false);

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -18,8 +19,6 @@ import {
DatePicker,
Alert,
Divider,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
@@ -27,13 +26,13 @@ import {
DollarOutlined,
HistoryOutlined,
LineChartOutlined,
} from '@ant-design/icons';
Option,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import moment from 'moment';
import { settingsDataSource, ExchangeRate } from '@/services/settingsDataSource';
const { Option } = Select;
interface RateHistory {
id: string;
fromCurrency: string;
@@ -62,7 +61,7 @@ const SOURCE_CONFIG: Record<string, { color: string; text: string }> = {
API: { color: 'purple', text: 'API' },
};
const ExchangeRateConfig: React.FC = () => {
const ExchangeRateConfig: FC = () => {
const [loading, setLoading] = useState(false);
const [rates, setRates] = useState<ExchangeRate[]>([]);
const [history, setHistory] = useState<RateHistory[]>([]);

View File

@@ -0,0 +1,543 @@
import {
useState,
useEffect,
Card,
Row,
Col,
Button,
Tag,
Typography,
Space,
Radio,
Select,
Spin,
message,
Steps,
Divider,
Alert,
ConfigProvider,
theme,
SaveOutlined,
CopyOutlined,
DatabaseOutlined,
DownloadOutlined,
DollarOutlined,
CheckCircleOutlined,
CreditCardOutlined,
AimOutlined,
PieChartOutlined,
Title,
Text,
Paragraph,
FC,
} from '@/imports';
import { instanceDataSource, InstanceType, InstanceConfig } from '@/services/instanceDataSource';
import { useUser } from '@/contexts/UserContext';
const InstancePurchase: FC = () => {
const { currentUser } = useUser();
const [currentStep, setCurrentStep] = useState(0);
const [instanceTypes, setInstanceTypes] = useState<InstanceType[]>([]);
const [selectedInstanceType, setSelectedInstanceType] = useState<string>('medium');
const [instanceConfig, setInstanceConfig] = useState<InstanceConfig>({
cpu: 4,
memory: 8,
storage: 200,
bandwidth: 50
});
const [billingCycle, setBillingCycle] = useState<'monthly' | 'yearly'>('monthly');
const [price, setPrice] = useState({ monthly: 0, yearly: 0, discount: 0 });
const [paymentMethod, setPaymentMethod] = useState<string>('creditCard');
const [loading, setLoading] = useState(false);
const [configOptions, setConfigOptions] = useState({
cpuOptions: [] as number[],
memoryOptions: [] as number[],
storageOptions: [] as number[],
bandwidthOptions: [] as number[]
});
// 检查用户是否为 ADMIN
const isAdmin = currentUser?.role === 'ADMIN';
useEffect(() => {
loadInstanceTypes();
loadConfigOptions();
}, []);
useEffect(() => {
if (selectedInstanceType) {
calculatePrice();
}
}, [selectedInstanceType, instanceConfig, billingCycle]);
const loadInstanceTypes = async () => {
setLoading(true);
try {
const types = await instanceDataSource.getInstanceTypes();
setInstanceTypes(types);
if (types.length > 0) {
const defaultType = types.find(t => t.recommended) || types[0];
setSelectedInstanceType(defaultType.id);
setInstanceConfig(defaultType.defaultConfig);
}
} catch (error) {
message.error('Failed to load instance types');
} finally {
setLoading(false);
}
};
const loadConfigOptions = async () => {
try {
const options = await instanceDataSource.getInstanceConfigOptions();
setConfigOptions(options);
} catch (error) {
message.error('Failed to load config options');
}
};
const calculatePrice = async () => {
try {
const priceData = await instanceDataSource.calculatePrice(
selectedInstanceType,
instanceConfig,
billingCycle
);
setPrice(priceData);
} catch (error) {
message.error('Failed to calculate price');
}
};
const handleInstanceTypeChange = (typeId: string) => {
setSelectedInstanceType(typeId);
const instanceType = instanceTypes.find(t => t.id === typeId);
if (instanceType) {
setInstanceConfig(instanceType.defaultConfig);
}
};
const handleConfigChange = (key: keyof InstanceConfig, value: number) => {
setInstanceConfig(prev => ({
...prev,
[key]: value
}));
};
const handleNext = () => {
if (currentStep < 3) {
setCurrentStep(currentStep + 1);
}
};
const handlePrevious = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
const handleSubmit = async () => {
setLoading(true);
try {
const order = await instanceDataSource.createOrder(
selectedInstanceType,
instanceConfig,
billingCycle,
paymentMethod
);
message.success('Order created successfully!');
setCurrentStep(4);
} catch (error) {
message.error('Failed to create order');
} finally {
setLoading(false);
}
};
const renderInstanceTypeSelection = () => (
<Card title={isAdmin ? "选择实例类型" : "选择套餐"}>
<Row gutter={[16, 16]}>
{instanceTypes.map(type => (
<Col xs={24} sm={12} lg={8} key={type.id}>
<Card
hoverable
style={{
borderColor: selectedInstanceType === type.id ? '#1890ff' : undefined,
borderWidth: selectedInstanceType === type.id ? 2 : 1,
borderRadius: '12px',
cursor: 'pointer'
}}
bodyStyle={{ padding: '24px' }}
onClick={() => handleInstanceTypeChange(type.id)}
>
<div style={{ textAlign: 'center' }}>
<div style={{ fontSize: '48px', marginBottom: '16px' }}>
{type.icon}
</div>
<Title level={4} style={{ margin: 0, marginBottom: '8px' }}>
{type.name}
</Title>
<Tag color="blue" style={{ marginBottom: '16px' }}>{type.cupSize}</Tag>
{type.recommended && (
<Tag color="gold" style={{ marginBottom: '16px' }}></Tag>
)}
<Paragraph type="secondary" style={{ marginBottom: '16px' }}>
{type.description}
</Paragraph>
{isAdmin && (
<>
<Divider style={{ margin: '16px 0' }} />
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Text>CPU</Text>
<Text strong>{type.defaultConfig.cpu} </Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Text></Text>
<Text strong>{type.defaultConfig.memory} GB</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Text></Text>
<Text strong>{type.defaultConfig.storage} GB</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Text></Text>
<Text strong>{type.defaultConfig.bandwidth} Mbps</Text>
</div>
</Space>
</>
)}
</div>
</Card>
</Col>
))}
</Row>
</Card>
);
const renderInstanceConfig = () => (
<Card title="配置实例">
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><CpuOutlined /> CPU核心数</Space>}>
<Select
style={{ width: '100%' }}
value={instanceConfig.cpu}
onChange={(value) => handleConfigChange('cpu', value)}
options={configOptions.cpuOptions.map(option => ({
value: option,
label: `${option}`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><ServerOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={instanceConfig.memory}
onChange={(value) => handleConfigChange('memory', value)}
options={configOptions.memoryOptions.map(option => ({
value: option,
label: `${option} GB`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><DatabaseOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={instanceConfig.storage}
onChange={(value) => handleConfigChange('storage', value)}
options={configOptions.storageOptions.map(option => ({
value: option,
label: `${option} GB`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><DownloadOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={instanceConfig.bandwidth}
onChange={(value) => handleConfigChange('bandwidth', value)}
options={configOptions.bandwidthOptions.map(option => ({
value: option,
label: `${option} Mbps`
}))}
/>
</Card>
</Col>
</Row>
<Card style={{ marginTop: '16px' }} title="计费周期">
<Radio.Group
value={billingCycle}
onChange={(e) => setBillingCycle(e.target.value)}
buttonStyle="solid"
size="large"
>
<Radio.Button value="monthly">
<Tag style={{ marginLeft: '8px' }}>¥{price.monthly}</Tag>
</Radio.Button>
<Radio.Button value="yearly">
<Tag color="red" style={{ marginLeft: '8px' }}>¥{price.yearly} ({price.discount}%)</Tag>
</Radio.Button>
</Radio.Group>
<Alert
message="价格说明"
description={`根据您选择的配置,${billingCycle === 'monthly' ? '月付' : '年付'}价格为 ¥${billingCycle === 'monthly' ? price.monthly : price.yearly}`}
type="info"
showIcon
style={{ marginTop: '16px' }}
/>
</Card>
</Card>
);
const renderPaymentMethod = () => (
<Card title="选择支付方式">
<Radio.Group
value={paymentMethod}
onChange={(e) => setPaymentMethod(e.target.value)}
style={{ width: '100%' }}
>
<Card
hoverable
style={{
marginBottom: '16px',
borderColor: paymentMethod === 'creditCard' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'creditCard' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => setPaymentMethod('creditCard')}
>
<Row align="middle">
<Col span={4}>
<CreditCardOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Text strong></Text>
<Paragraph type="secondary">VisaMastercardAmerican Express等</Paragraph>
</Col>
<Col span={4}>
<Radio value="creditCard" />
</Col>
</Row>
</Card>
<Card
hoverable
style={{
marginBottom: '16px',
borderColor: paymentMethod === 'alipay' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'alipay' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => setPaymentMethod('alipay')}
>
<Row align="middle">
<Col span={4}>
<AliPayOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Text strong></Text>
<Paragraph type="secondary">使</Paragraph>
</Col>
<Col span={4}>
<Radio value="alipay" />
</Col>
</Row>
</Card>
<Card
hoverable
style={{
borderColor: paymentMethod === 'wechat' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'wechat' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => setPaymentMethod('wechat')}
>
<Row align="middle">
<Col span={4}>
<WechatOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Text strong></Text>
<Paragraph type="secondary">使</Paragraph>
</Col>
<Col span={4}>
<Radio value="wechat" />
</Col>
</Row>
</Card>
</Radio.Group>
</Card>
);
const renderOrderSummary = () => {
const instanceType = instanceTypes.find(t => t.id === selectedInstanceType);
return (
<Card title="订单摘要">
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>{instanceType?.name}</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text>CPU</Text>
<Text strong>{instanceConfig.cpu} </Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>{instanceConfig.memory} GB</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>{instanceConfig.storage} GB</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>{instanceConfig.bandwidth} Mbps</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>{billingCycle === 'monthly' ? '月付' : '年付'}</Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Text></Text>
<Text strong>
{paymentMethod === 'creditCard' ? '信用卡' :
paymentMethod === 'alipay' ? '支付宝' : '微信支付'}
</Text>
</div>
<Divider />
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0', fontSize: '18px' }}>
<Text strong></Text>
<Text strong style={{ color: '#1890ff' }}>
¥{billingCycle === 'monthly' ? price.monthly : price.yearly}
</Text>
</div>
</Space>
</Card>
);
};
const renderSuccess = () => (
<Card title="购买成功">
<div style={{ textAlign: 'center', padding: '48px 0' }}>
<CheckCircleOutlined style={{ fontSize: '72px', color: '#52c41a', marginBottom: '24px' }} />
<Title level={3} style={{ marginBottom: '16px' }}></Title>
<Paragraph style={{ marginBottom: '24px' }}>
</Paragraph>
<Button type="primary" size="large">
</Button>
</div>
</Card>
);
// 根据用户角色生成步骤
const steps = isAdmin ? [
{ title: '选择实例类型' },
{ title: '配置实例' },
{ title: '选择支付方式' },
{ title: '确认订单' },
{ title: '购买成功' }
] : [
{ title: '选择套餐' },
{ title: '选择支付方式' },
{ title: '确认订单' },
{ title: '购买成功' }
];
// 处理下一步逻辑
const handleNext = () => {
if (isAdmin) {
if (currentStep < 3) {
setCurrentStep(currentStep + 1);
}
} else {
if (currentStep < 2) {
setCurrentStep(currentStep + 1);
}
}
};
// 处理上一步逻辑
const handlePrevious = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1);
}
};
return (
<div className="instance-purchase" style={{ padding: '24px', background: '#f5f5f5', minHeight: '100vh' }}>
<ConfigProvider
theme={{
token: {
colorPrimary: '#1890ff',
borderRadius: 8,
},
}}
>
<div className="page-header" style={{ marginBottom: '24px' }}>
<Title level={2} style={{ margin: 0 }}>
<ServerOutlined style={{ marginRight: '8px' }} />
</Title>
<Paragraph type="secondary">
{isAdmin ? '选择适合您业务需求的实例配置' : '选择适合您业务需求的套餐'}
</Paragraph>
</div>
<Steps
current={currentStep}
items={steps}
style={{ marginBottom: '32px' }}
/>
<Spin spinning={loading}>
{currentStep === 0 && renderInstanceTypeSelection()}
{isAdmin && currentStep === 1 && renderInstanceConfig()}
{currentStep === (isAdmin ? 2 : 1) && renderPaymentMethod()}
{currentStep === (isAdmin ? 3 : 2) && renderOrderSummary()}
{currentStep === (isAdmin ? 4 : 3) && renderSuccess()}
</Spin>
<div style={{ marginTop: '32px', textAlign: 'center' }}>
{currentStep > 0 && currentStep < (isAdmin ? 4 : 3) && (
<Button
style={{ marginRight: '16px' }}
onClick={handlePrevious}
>
</Button>
)}
{currentStep < (isAdmin ? 3 : 2) && (
<Button
type="primary"
onClick={handleNext}
>
</Button>
)}
{currentStep === (isAdmin ? 3 : 2) && (
<Button
type="primary"
onClick={handleSubmit}
loading={loading}
>
</Button>
)}
</div>
</ConfigProvider>
</div>
);
};
export default InstancePurchase;

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -17,8 +18,6 @@ import {
Col,
Statistic,
Badge,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
@@ -28,13 +27,13 @@ import {
ApiOutlined,
ShopOutlined,
LinkOutlined,
} from '@ant-design/icons';
Option,
Password,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import { settingsDataSource, PlatformAccount } from '@/services/settingsDataSource';
const { Option } = Select;
const { Password } = Input;
const PLATFORM_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
// TikTok系列
TIKTOK: { color: 'cyan', text: 'TikTok', icon: <ShopOutlined /> },

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -18,8 +19,6 @@ import {
Divider,
Alert,
Spin,
} from 'antd';
import {
PlusOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
@@ -29,14 +28,15 @@ import {
ApiOutlined,
ReloadOutlined,
DeleteOutlined,
} from '@ant-design/icons';
Title,
Text,
Option,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import { platformAuthDataSource } from '@/services/platformAuthDataSource';
import { PlatformAccount, PlatformAccountStats, Platform } from '@/types/platform';
const { Title, Text } = Typography;
const { Option } = Select;
const PLATFORM_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
// TikTok系列
TIKTOK: { color: 'cyan', text: 'TikTok', icon: <ShopOutlined /> },

View File

@@ -1,9 +1,19 @@
import React, { useState } from 'react';
import { Card, Form, Input, Button, Upload, Avatar, message } from 'antd';
import { UserOutlined, SaveOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
const { TextArea } = Input;
import {
useState,
Card,
Form,
Input,
Button,
Upload,
Avatar,
message,
UserOutlined,
SaveOutlined,
ArrowLeftOutlined,
TextArea,
useNavigate,
FC,
} from '@/imports';
interface ProfileData {
name: string;
@@ -15,7 +25,7 @@ interface ProfileData {
avatar: string;
}
const ProfileSettings: React.FC = () => {
const ProfileSettings: FC = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -18,21 +19,20 @@ import {
Tabs,
Modal,
Progress,
} from 'antd';
import type { TabsProps } from 'antd';
import {
ReloadOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ThunderboltOutlined,
DashboardOutlined,
WarningOutlined,
} from '@ant-design/icons';
Title,
Text,
FC,
} from '@/imports';
import type { TabsProps } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { serviceManagerDataSource, ServiceInfo, ServiceStats, ServicePreset } from '@/services/serviceManagerDataSource';
const { Title, Text } = Typography;
const MEMORY_IMPACT_COLORS: Record<string, string> = {
low: 'green',
medium: 'orange',
@@ -45,7 +45,7 @@ const MEMORY_IMPACT_TEXT: Record<string, string> = {
high: '高',
};
const ServiceManager: React.FC = () => {
const ServiceManager: FC = () => {
const [services, setServices] = useState<ServiceInfo[]>([]);
const [groupedServices, setGroupedServices] = useState<Record<string, ServiceInfo[]>>({});
const [stats, setStats] = useState<ServiceStats | null>(null);

View File

@@ -1,11 +1,30 @@
import React, { useState, useMemo } from 'react';
import {
Card, Row, Col, Switch, Tag, Typography, Space, Button, Modal,
Progress, Divider, Alert, Tabs, Table, message, Statistic,
Timeline, Badge, Tooltip, Radio, Empty, Steps, theme, ConfigProvider
} from 'antd';
import type { ColumnsType } from 'antd/es/table';
import {
useState,
useMemo,
Card,
Row,
Col,
Switch,
Tag,
Typography,
Space,
Button,
Modal,
Progress,
Divider,
Alert,
Tabs,
Table,
message,
Statistic,
Timeline,
Badge,
Tooltip,
Radio,
Empty,
Steps,
theme,
ConfigProvider,
CrownOutlined,
RobotOutlined,
ShopOutlined,
@@ -28,11 +47,16 @@ import {
CheckSquareOutlined,
CloseSquareOutlined,
RightOutlined,
} from '@ant-design/icons';
ServerOutlined,
Title,
Text,
Paragraph,
FC,
} from '@/imports';
import InstancePurchase from './InstancePurchase';
import type { ColumnsType } from 'antd/es/table';
import { useUser, FEATURES, ROLE_CONFIG } from '@/contexts/UserContext';
const { Title, Text, Paragraph } = Typography;
interface FeatureConfig {
key: string;
name: string;
@@ -884,6 +908,16 @@ const SubscriptionManage: React.FC = () => {
),
children: renderUpgradePath(),
},
{
key: 'instance',
label: (
<span>
<ServerOutlined />
</span>
),
children: <InstancePurchase />,
},
]}
/>

View File

@@ -1,13 +1,53 @@
import React, { useState, useEffect } from 'react';
import { Card, Tabs, Table, Button, Form, Input, Select, Modal, message, Space, Tag, Upload, Switch, InputNumber, Row, Col, Timeline, Badge, Popconfirm, Tree, TreeSelect, Tooltip } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SaveOutlined, UploadOutlined, SyncOutlined, CheckCircleOutlined, CloseCircleOutlined, ExclamationCircleOutlined, HistoryOutlined, RollbackOutlined, UserOutlined, FilterOutlined, DownOutlined, UpOutlined, FolderOutlined, FileOutlined } from '@ant-design/icons';
import {
useState,
useEffect,
Card,
Tabs,
Table,
Button,
Form,
Input,
Select,
Modal,
message,
Space,
Tag,
Upload,
Switch,
InputNumber,
Row,
Col,
Timeline,
Badge,
Popconfirm,
Tree,
TreeSelect,
Tooltip,
PlusOutlined,
EditOutlined,
DeleteOutlined,
SaveOutlined,
UploadOutlined,
SyncOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ExclamationCircleOutlined,
HistoryOutlined,
RollbackOutlined,
UserOutlined,
FilterOutlined,
DownOutlined,
UpOutlined,
FolderOutlined,
FileOutlined,
Option,
TextArea,
FC,
} from '@/imports';
import type { UploadProps, TreeProps } from 'antd';
import { settingsDataSource, PlatformAccount, ExchangeRate, CostTemplate, WinNode, ConfigCategory, ConfigChangeLog, Tenant } from '@/services/settingsDataSource';
const { Option } = Select;
const { TextArea } = Input;
const SystemSettings: React.FC = () => {
const SystemSettings: FC = () => {
const [activeTab, setActiveTab] = useState('platform');
const [loading, setLoading] = useState(false);
const [platformAccounts, setPlatformAccounts] = useState<PlatformAccount[]>([]);

View File

@@ -1,9 +1,18 @@
import React, { useState } from 'react';
import { Card, Form, Input, Select, Button, Switch, message } from 'antd';
import { SaveOutlined, ArrowLeftOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
const { Option } = Select;
import {
useState,
Card,
Form,
Input,
Select,
Button,
Switch,
message,
SaveOutlined,
ArrowLeftOutlined,
Option,
useNavigate,
FC,
} from '@/imports';
interface TenantData {
name: string;
@@ -17,7 +26,7 @@ interface TenantData {
enableAnalytics: boolean;
}
const TenantSettings: React.FC = () => {
const TenantSettings: FC = () => {
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();

View File

@@ -1,12 +1,39 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Input, Select, message, Card, Modal, Form, Tag, Space, Divider, Transfer, Switch, Descriptions, Tabs, Row, Col, Alert } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ArrowLeftOutlined, SafetyOutlined, CrownOutlined, LockOutlined } from '@ant-design/icons';
import { useNavigate } from 'react-router-dom';
import {
useState,
useEffect,
Table,
Button,
Input,
Select,
message,
Card,
Modal,
Form,
Tag,
Space,
Divider,
Transfer,
Switch,
Descriptions,
Tabs,
Row,
Col,
Alert,
PlusOutlined,
EditOutlined,
DeleteOutlined,
SearchOutlined,
ArrowLeftOutlined,
SafetyOutlined,
CrownOutlined,
LockOutlined,
Option,
Search,
useNavigate,
FC,
} from '@/imports';
import { useUser, ROLE_CONFIG, FEATURES, PERMISSIONS, UserRole, User } from '@/contexts/UserContext';
const { Option } = Select;
const { Search } = Input;
interface ManagedUser extends User {
status: 'active' | 'inactive';
createdAt: string;
@@ -58,7 +85,7 @@ const ROLE_PERMISSIONS_PRESET: Record<UserRole, string[]> = {
ANALYST: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.ORDER_READ, PERMISSIONS.FINANCE_READ],
};
const UserManagement: React.FC = () => {
const UserManagement: FC = () => {
const navigate = useNavigate();
const { currentUser, hasPermission } = useUser();
const [users, setUsers] = useState<ManagedUser[]>([]);

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
useState,
useEffect,
Card,
Table,
Button,
@@ -21,8 +22,6 @@ import {
Descriptions,
Divider,
Alert,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
@@ -33,13 +32,13 @@ import {
SettingOutlined,
GlobalOutlined,
SafetyOutlined,
} from '@ant-design/icons';
Option,
Password,
FC,
} from '@/imports';
import type { ColumnsType } from 'antd/es/table';
import { settingsDataSource, WinNode } from '@/services/settingsDataSource';
const { Option } = Select;
const { Password } = Input;
const STATUS_CONFIG: Record<string, { color: string; text: string; icon: React.ReactNode }> = {
ONLINE: { color: 'success', text: 'Online', icon: <CheckCircleOutlined /> },
OFFLINE: { color: 'default', text: 'Offline', icon: <CloseCircleOutlined /> },
@@ -54,7 +53,7 @@ const FINGERPRINT_POLICIES = [
'CUSTOM',
];
const WinNodeConfig: React.FC = () => {
const WinNodeConfig: FC = () => {
const [loading, setLoading] = useState(false);
const [nodes, setNodes] = useState<WinNode[]>([]);
const [modalVisible, setModalVisible] = useState(false);

View File

@@ -0,0 +1,92 @@
import {
Card,
Row,
Col,
Select,
Typography,
Space,
FC,
CpuOutlined,
ServerOutlined,
DatabaseOutlined,
DownloadOutlined,
} from '@/imports';
import { InstanceConfig as InstanceConfigType } from '@/services/instanceDataSource';
interface InstanceConfigProps {
config: InstanceConfigType;
onConfigChange: (key: keyof InstanceConfigType, value: number) => void;
configOptions: {
cpuOptions: number[];
memoryOptions: number[];
storageOptions: number[];
bandwidthOptions: number[];
};
}
const InstanceConfig: FC<InstanceConfigProps> = ({
config,
onConfigChange,
configOptions,
}) => {
return (
<Card title="配置实例">
<Row gutter={[16, 16]}>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><CpuOutlined /> CPU核心数</Space>}>
<Select
style={{ width: '100%' }}
value={config.cpu}
onChange={(value) => onConfigChange('cpu', value)}
options={configOptions.cpuOptions.map(option => ({
value: option,
label: `${option}`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><ServerOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={config.memory}
onChange={(value) => onConfigChange('memory', value)}
options={configOptions.memoryOptions.map(option => ({
value: option,
label: `${option} GB`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><DatabaseOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={config.storage}
onChange={(value) => onConfigChange('storage', value)}
options={configOptions.storageOptions.map(option => ({
value: option,
label: `${option} GB`
}))}
/>
</Card>
</Col>
<Col xs={24} sm={12} md={6}>
<Card size="small" title={<Space><DownloadOutlined /> </Space>}>
<Select
style={{ width: '100%' }}
value={config.bandwidth}
onChange={(value) => onConfigChange('bandwidth', value)}
options={configOptions.bandwidthOptions.map(option => ({
value: option,
label: `${option} Mbps`
}))}
/>
</Card>
</Col>
</Row>
</Card>
);
};
export default InstanceConfig;

View File

@@ -0,0 +1,85 @@
import {
Card,
Typography,
Space,
Divider,
FC,
} from '@/imports';
import { InstanceType, InstanceConfig } from '@/services/instanceDataSource';
interface OrderSummaryProps {
instanceType: InstanceType | undefined;
config: InstanceConfig;
billingCycle: 'monthly' | 'yearly';
price: {
monthly: number;
yearly: number;
discount: number;
};
paymentMethod: string;
}
const OrderSummary: FC<OrderSummaryProps> = ({
instanceType,
config,
billingCycle,
price,
paymentMethod,
}) => {
const getPaymentMethodName = (method: string): string => {
switch (method) {
case 'creditCard':
return '信用卡';
case 'alipay':
return '支付宝';
case 'wechat':
return '微信支付';
default:
return '未知';
}
};
return (
<Card title="订单摘要">
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{instanceType?.name}</Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text>CPU</Typography.Text>
<Typography.Text strong>{config.cpu} </Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{config.memory} GB</Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{config.storage} GB</Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{config.bandwidth} Mbps</Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{billingCycle === 'monthly' ? '月付' : '年付'}</Typography.Text>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0' }}>
<Typography.Text></Typography.Text>
<Typography.Text strong>{getPaymentMethodName(paymentMethod)}</Typography.Text>
</div>
<Divider />
<div style={{ display: 'flex', justifyContent: 'space-between', padding: '8px 0', fontSize: '18px' }}>
<Typography.Text strong></Typography.Text>
<Typography.Text strong style={{ color: '#1890ff' }}>
¥{billingCycle === 'monthly' ? price.monthly : price.yearly}
</Typography.Text>
</div>
</Space>
</Card>
);
};
export default OrderSummary;

View File

@@ -0,0 +1,103 @@
import {
Card,
Radio,
Row,
Col,
Typography,
Space,
FC,
CreditCardOutlined,
AliPayOutlined,
WechatOutlined,
} from '@/imports';
interface PaymentFormProps {
paymentMethod: string;
onPaymentMethodChange: (method: string) => void;
}
const PaymentForm: FC<PaymentFormProps> = ({
paymentMethod,
onPaymentMethodChange,
}) => {
return (
<Card title="选择支付方式">
<Radio.Group
value={paymentMethod}
onChange={(e) => onPaymentMethodChange(e.target.value)}
style={{ width: '100%' }}
>
<Card
hoverable
style={{
marginBottom: '16px',
borderColor: paymentMethod === 'creditCard' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'creditCard' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => onPaymentMethodChange('creditCard')}
>
<Row align="middle">
<Col span={4}>
<CreditCardOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Typography.Text strong></Typography.Text>
<Typography.Paragraph type="secondary">VisaMastercardAmerican Express等</Typography.Paragraph>
</Col>
<Col span={4}>
<Radio value="creditCard" />
</Col>
</Row>
</Card>
<Card
hoverable
style={{
marginBottom: '16px',
borderColor: paymentMethod === 'alipay' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'alipay' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => onPaymentMethodChange('alipay')}
>
<Row align="middle">
<Col span={4}>
<AliPayOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Typography.Text strong></Typography.Text>
<Typography.Paragraph type="secondary">使</Typography.Paragraph>
</Col>
<Col span={4}>
<Radio value="alipay" />
</Col>
</Row>
</Card>
<Card
hoverable
style={{
borderColor: paymentMethod === 'wechat' ? '#1890ff' : undefined,
borderWidth: paymentMethod === 'wechat' ? 2 : 1,
cursor: 'pointer'
}}
onClick={() => onPaymentMethodChange('wechat')}
>
<Row align="middle">
<Col span={4}>
<WechatOutlined style={{ fontSize: '24px', color: '#1890ff' }} />
</Col>
<Col span={16}>
<Typography.Text strong></Typography.Text>
<Typography.Paragraph type="secondary">使</Typography.Paragraph>
</Col>
<Col span={4}>
<Radio value="wechat" />
</Col>
</Row>
</Card>
</Radio.Group>
</Card>
);
};
export default PaymentForm;

View File

@@ -1,12 +1,21 @@
import React from 'react';
import { Tabs, Card, Button, Form, Input, Modal, message } from 'antd';
import { SafetyOutlined } from '@ant-design/icons';
import {
useState,
Tabs,
Card,
Button,
Form,
Input,
Modal,
message,
SafetyOutlined,
FC,
} from '@/imports';
const Settings: React.FC = () => {
const [activeTab, setActiveTab] = React.useState('account');
const [passwordModalVisible, setPasswordModalVisible] = React.useState(false);
const [apiKeyModalVisible, setApiKeyModalVisible] = React.useState(false);
const [twoFactorModalVisible, setTwoFactorModalVisible] = React.useState(false);
const Settings: FC = () => {
const [activeTab, setActiveTab] = useState('account');
const [passwordModalVisible, setPasswordModalVisible] = useState(false);
const [apiKeyModalVisible, setApiKeyModalVisible] = useState(false);
const [twoFactorModalVisible, setTwoFactorModalVisible] = useState(false);
const [passwordForm] = Form.useForm();
const [apiKeyForm] = Form.useForm();