feat: 添加MSW模拟服务和数据源集成

refactor: 重构页面组件移除冗余Layout组件

feat: 实现WebSocket和事件总线系统

feat: 添加队列和调度系统

docs: 更新架构文档和服务映射

style: 清理重复接口定义使用数据源

chore: 更新依赖项配置

feat: 添加运行时系统和领域引导

ci: 配置ESLint边界检查规则

build: 添加Redis和WebSocket依赖

test: 添加MSW浏览器环境入口

perf: 优化数据获取逻辑使用统一数据源

fix: 修复类型定义和状态管理问题
This commit is contained in:
2026-03-19 01:39:34 +08:00
parent cd55097dbf
commit 0dac26d781
176 changed files with 47075 additions and 8404 deletions

View File

@@ -0,0 +1,640 @@
import React, { useState, useEffect } from 'react';
import {
Card,
Tabs,
Table,
Tag,
Statistic,
Row,
Col,
Spin,
message,
Select,
Button,
Tooltip,
Badge,
Input,
Modal,
Progress,
Rate,
Empty,
Typography,
Space,
Divider
} from 'antd';
import {
ShopOutlined,
ThunderboltOutlined,
FireOutlined,
StarOutlined,
SearchOutlined,
RocketOutlined,
DollarOutlined,
PercentageOutlined,
BarChartOutlined,
SettingOutlined,
PlayCircleOutlined,
PauseCircleOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
BulbOutlined,
TagOutlined
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
const { TabPane } = Tabs;
const { Option } = Select;
const { Search } = Input;
const { Title, Text, Paragraph } = Typography;
interface Strategy {
id: string;
name: string;
description: string;
category: 'PRICING' | 'ADVERTISING' | 'PRODUCT_SELECTION' | 'INVENTORY' | 'LOGISTICS' | 'MARKETING';
risk_level: 'LOW' | 'MEDIUM' | 'HIGH';
price: number;
billing_type: 'FREE' | 'ONE_TIME' | 'SUBSCRIPTION' | 'USAGE_BASED';
avg_roi: number;
usage_count: number;
success_rate: number;
tags: string[];
is_active: boolean;
is_featured: boolean;
}
interface MerchantStrategy {
id: string;
strategy_id: string;
status: 'ACTIVE' | 'PAUSED' | 'COMPLETED' | 'FAILED';
roi_achieved: number;
revenue_generated: number;
activated_at: string;
strategy: Strategy;
}
interface Recommendation {
strategy: Strategy;
score: number;
reasons: string[];
expectedRoi: number;
confidence: number;
priority: 'HIGH' | 'MEDIUM' | 'LOW';
}
const categoryColors: Record<string, string> = {
PRICING: 'blue',
ADVERTISING: 'orange',
PRODUCT_SELECTION: 'green',
INVENTORY: 'purple',
LOGISTICS: 'cyan',
MARKETING: 'magenta'
};
const categoryIcons: Record<string, React.ReactNode> = {
PRICING: <DollarOutlined />,
ADVERTISING: <ThunderboltOutlined />,
PRODUCT_SELECTION: <ShopOutlined />,
INVENTORY: <BarChartOutlined />,
LOGISTICS: <RocketOutlined />,
MARKETING: <BulbOutlined />
};
const riskColors: Record<string, string> = {
LOW: 'success',
MEDIUM: 'warning',
HIGH: 'error'
};
const StrategyMarketplacePage: React.FC = () => {
const [loading, setLoading] = useState(true);
const [activeTab, setActiveTab] = useState('marketplace');
const [categoryFilter, setCategoryFilter] = useState<string>('all');
const [searchQuery, setSearchQuery] = useState('');
const [strategies, setStrategies] = useState<Strategy[]>([]);
const [featuredStrategies, setFeaturedStrategies] = useState<Strategy[]>([]);
const [trendingStrategies, setTrendingStrategies] = useState<Strategy[]>([]);
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
const [myStrategies, setMyStrategies] = useState<MerchantStrategy[]>([]);
const [selectedStrategy, setSelectedStrategy] = useState<Strategy | null>(null);
const [detailModalVisible, setDetailModalVisible] = useState(false);
useEffect(() => {
fetchInitialData();
}, []);
const fetchInitialData = async () => {
setLoading(true);
try {
const [strategiesRes, featuredRes, trendingRes, recommendationsRes, myStrategiesRes] = await Promise.all([
fetch('/api/v1/strategy/strategies?isActive=true').then(r => r.json()),
fetch('/api/v1/strategy/strategies/featured').then(r => r.json()),
fetch('/api/v1/strategy/strategies/trending').then(r => r.json()),
fetch('/api/v1/strategy/recommendations').then(r => r.json()),
fetch('/api/v1/strategy/my-strategies').then(r => r.json())
]);
if (strategiesRes.success) setStrategies(strategiesRes.data);
if (featuredRes.success) setFeaturedStrategies(featuredRes.data);
if (trendingRes.success) setTrendingStrategies(trendingRes.data);
if (recommendationsRes.success) setRecommendations(recommendationsRes.data);
if (myStrategiesRes.success) setMyStrategies(myStrategiesRes.data);
} catch (error) {
message.error('Failed to load strategies');
} finally {
setLoading(false);
}
};
const handleSearch = async (value: string) => {
if (!value.trim()) {
fetchInitialData();
return;
}
setLoading(true);
try {
const res = await fetch(`/api/v1/strategy/strategies/search?q=${encodeURIComponent(value)}`);
const data = await res.json();
if (data.success) {
setStrategies(data.data);
setActiveTab('marketplace');
}
} catch (error) {
message.error('Search failed');
} finally {
setLoading(false);
}
};
const handleActivateStrategy = async (strategyId: string, config?: Record<string, any>) => {
try {
const res = await fetch(`/api/v1/strategy/strategies/${strategyId}/activate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config })
});
const data = await res.json();
if (data.success) {
message.success('Strategy activated successfully!');
fetchInitialData();
setDetailModalVisible(false);
} else {
message.error(data.message || 'Failed to activate strategy');
}
} catch (error) {
message.error('Failed to activate strategy');
}
};
const handlePauseStrategy = async (merchantStrategyId: string) => {
try {
const res = await fetch(`/api/v1/strategy/my-strategies/${merchantStrategyId}/pause`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
message.success('Strategy paused');
fetchInitialData();
} else {
message.error(data.message || 'Failed to pause strategy');
}
} catch (error) {
message.error('Failed to pause strategy');
}
};
const handleResumeStrategy = async (merchantStrategyId: string) => {
try {
const res = await fetch(`/api/v1/strategy/my-strategies/${merchantStrategyId}/resume`, {
method: 'POST'
});
const data = await res.json();
if (data.success) {
message.success('Strategy resumed');
fetchInitialData();
} else {
message.error(data.message || 'Failed to resume strategy');
}
} catch (error) {
message.error('Failed to resume strategy');
}
};
const showStrategyDetail = (strategy: Strategy) => {
setSelectedStrategy(strategy);
setDetailModalVisible(true);
};
const filteredStrategies = categoryFilter === 'all'
? strategies
: strategies.filter(s => s.category === categoryFilter);
const renderStrategyCard = (strategy: Strategy, showActions: boolean = true) => (
<Card
hoverable
style={{ marginBottom: 16 }}
onClick={() => showStrategyDetail(strategy)}
>
<Row gutter={16} align="middle">
<Col flex="auto">
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Space>
<Text strong style={{ fontSize: 16 }}>{strategy.name}</Text>
{strategy.is_featured && <Tag icon={<StarOutlined />} color="gold">Featured</Tag>}
</Space>
<Space>
<Tag color={categoryColors[strategy.category]} icon={categoryIcons[strategy.category]}>
{strategy.category.replace('_', ' ')}
</Tag>
<Tag color={riskColors[strategy.risk_level]}>{strategy.risk_level} Risk</Tag>
{strategy.price === 0 ? (
<Tag color="green">FREE</Tag>
) : (
<Tag color="blue">${strategy.price.toFixed(2)}</Tag>
)}
</Space>
<Text type="secondary" ellipsis style={{ maxWidth: 400 }}>
{strategy.description}
</Text>
<Space>
<Text type="secondary">
<PercentageOutlined /> Avg ROI: {(strategy.avg_roi * 100).toFixed(1)}%
</Text>
<Divider type="vertical" />
<Text type="secondary">
<CheckCircleOutlined /> Success: {strategy.success_rate}%
</Text>
<Divider type="vertical" />
<Text type="secondary">
<BarChartOutlined /> Used: {strategy.usage_count}
</Text>
</Space>
</Space>
</Col>
{showActions && (
<Col>
<Button
type="primary"
icon={<PlayCircleOutlined />}
onClick={(e) => {
e.stopPropagation();
handleActivateStrategy(strategy.id);
}}
>
Activate
</Button>
</Col>
)}
</Row>
</Card>
);
const renderRecommendationCard = (rec: Recommendation) => (
<Card
hoverable
style={{ marginBottom: 16 }}
onClick={() => showStrategyDetail(rec.strategy)}
>
<Row gutter={16} align="middle">
<Col flex="auto">
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Space>
<Text strong style={{ fontSize: 16 }}>{rec.strategy.name}</Text>
<Tag color={rec.priority === 'HIGH' ? 'red' : rec.priority === 'MEDIUM' ? 'orange' : 'blue'}>
{rec.priority} Priority
</Tag>
<Badge count={`Score: ${rec.score.toFixed(0)}`} style={{ backgroundColor: '#52c41a' }} />
</Space>
<Space wrap>
{rec.reasons.map((reason, idx) => (
<Tag key={idx} icon={<BulbOutlined />} color="processing">{reason}</Tag>
))}
</Space>
<Space>
<Text type="secondary">
Expected ROI: <Text strong style={{ color: '#52c41a' }}>{(rec.expectedRoi * 100).toFixed(1)}%</Text>
</Text>
<Divider type="vertical" />
<Text type="secondary">
Confidence: <Progress percent={rec.confidence * 100} size="small" style={{ width: 80, display: 'inline-block' }} />
</Text>
</Space>
</Space>
</Col>
<Col>
<Button
type="primary"
icon={<PlayCircleOutlined />}
onClick={(e) => {
e.stopPropagation();
handleActivateStrategy(rec.strategy.id);
}}
>
Activate
</Button>
</Col>
</Row>
</Card>
);
const myStrategiesColumns: ColumnsType<MerchantStrategy> = [
{
title: 'Strategy',
dataIndex: ['strategy', 'name'],
key: 'name',
render: (name: string, record) => (
<Space>
<Tag color={categoryColors[record.strategy.category]} icon={categoryIcons[record.strategy.category]}>
{record.strategy.category.replace('_', ' ')}
</Tag>
<Text strong>{name}</Text>
</Space>
)
},
{
title: 'Status',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const statusConfig: Record<string, { color: string; icon: React.ReactNode }> = {
ACTIVE: { color: 'success', icon: <CheckCircleOutlined /> },
PAUSED: { color: 'warning', icon: <PauseCircleOutlined /> },
COMPLETED: { color: 'processing', icon: <CheckCircleOutlined /> },
FAILED: { color: 'error', icon: <CloseCircleOutlined /> }
};
const config = statusConfig[status];
return <Tag color={config.color} icon={config.icon}>{status}</Tag>;
}
},
{
title: 'ROI Achieved',
dataIndex: 'roi_achieved',
key: 'roi',
render: (roi: number) => (
<Text style={{ color: roi > 0 ? '#52c41a' : '#ff4d4f' }}>
{(roi * 100).toFixed(1)}%
</Text>
)
},
{
title: 'Revenue Generated',
dataIndex: 'revenue_generated',
key: 'revenue',
render: (revenue: number) => `$${revenue.toFixed(2)}`
},
{
title: 'Activated',
dataIndex: 'activated_at',
key: 'activated_at',
render: (date: string) => new Date(date).toLocaleDateString()
},
{
title: 'Actions',
key: 'actions',
render: (_, record) => (
<Space>
{record.status === 'ACTIVE' && (
<Button
size="small"
icon={<PauseCircleOutlined />}
onClick={() => handlePauseStrategy(record.id)}
>
Pause
</Button>
)}
{record.status === 'PAUSED' && (
<Button
size="small"
type="primary"
icon={<PlayCircleOutlined />}
onClick={() => handleResumeStrategy(record.id)}
>
Resume
</Button>
)}
</Space>
)
}
];
const renderDetailModal = () => (
<Modal
title={
<Space>
{selectedStrategy?.name}
{selectedStrategy?.is_featured && <Tag icon={<StarOutlined />} color="gold">Featured</Tag>}
</Space>
}
open={detailModalVisible}
onCancel={() => setDetailModalVisible(false)}
width={700}
footer={[
<Button key="cancel" onClick={() => setDetailModalVisible(false)}>
Cancel
</Button>,
<Button
key="activate"
type="primary"
icon={<PlayCircleOutlined />}
onClick={() => handleActivateStrategy(selectedStrategy?.id || '')}
>
Activate Strategy
</Button>
]}
>
{selectedStrategy && (
<Space direction="vertical" size="large" style={{ width: '100%' }}>
<Paragraph>{selectedStrategy.description}</Paragraph>
<Row gutter={16}>
<Col span={8}>
<Statistic
title="Average ROI"
value={(selectedStrategy.avg_roi * 100).toFixed(1)}
suffix="%"
valueStyle={{ color: '#52c41a' }}
/>
</Col>
<Col span={8}>
<Statistic
title="Success Rate"
value={selectedStrategy.success_rate}
suffix="%"
/>
</Col>
<Col span={8}>
<Statistic
title="Usage Count"
value={selectedStrategy.usage_count}
/>
</Col>
</Row>
<Divider />
<Row gutter={16}>
<Col span={12}>
<Text type="secondary">Category: </Text>
<Tag color={categoryColors[selectedStrategy.category]} icon={categoryIcons[selectedStrategy.category]}>
{selectedStrategy.category.replace('_', ' ')}
</Tag>
</Col>
<Col span={12}>
<Text type="secondary">Risk Level: </Text>
<Tag color={riskColors[selectedStrategy.risk_level]}>
{selectedStrategy.risk_level}
</Tag>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Text type="secondary">Price: </Text>
{selectedStrategy.price === 0 ? (
<Tag color="green">FREE</Tag>
) : (
<Text strong>${selectedStrategy.price.toFixed(2)}</Text>
)}
<Text type="secondary"> ({selectedStrategy.billing_type})</Text>
</Col>
</Row>
<Divider />
<div>
<Text type="secondary">Tags: </Text>
<Space wrap style={{ marginTop: 8 }}>
{selectedStrategy.tags.map((tag, idx) => (
<Tag key={idx} icon={<TagOutlined />}>{tag}</Tag>
))}
</Space>
</div>
</Space>
)}
</Modal>
);
if (loading) {
return (
<div style={{ textAlign: 'center', padding: '100px 0' }}>
<Spin size="large" />
</div>
);
}
return (
<div style={{ padding: 24 }}>
<Row gutter={[16, 16]}>
<Col span={24}>
<Card>
<Row gutter={16} align="middle">
<Col flex="auto">
<Title level={3} style={{ margin: 0 }}>
<ShopOutlined /> Strategy Marketplace
</Title>
<Text type="secondary">
Discover and activate AI-powered strategies to grow your business
</Text>
</Col>
<Col>
<Search
placeholder="Search strategies..."
allowClear
enterButton={<SearchOutlined />}
style={{ width: 300 }}
onSearch={handleSearch}
/>
</Col>
</Row>
</Card>
</Col>
<Col span={24}>
<Tabs activeKey={activeTab} onChange={setActiveTab}>
<TabPane
tab={<span><ShopOutlined /> Marketplace</span>}
key="marketplace"
>
<Row gutter={16}>
<Col span={6}>
<Select
style={{ width: '100%', marginBottom: 16 }}
value={categoryFilter}
onChange={setCategoryFilter}
>
<Option value="all">All Categories</Option>
<Option value="PRICING">Pricing</Option>
<Option value="ADVERTISING">Advertising</Option>
<Option value="PRODUCT_SELECTION">Product Selection</Option>
<Option value="INVENTORY">Inventory</Option>
<Option value="LOGISTICS">Logistics</Option>
<Option value="MARKETING">Marketing</Option>
</Select>
</Col>
</Row>
{filteredStrategies.length === 0 ? (
<Empty description="No strategies found" />
) : (
filteredStrategies.map(strategy => renderStrategyCard(strategy))
)}
</TabPane>
<TabPane
tab={<span><StarOutlined /> Featured</span>}
key="featured"
>
{featuredStrategies.length === 0 ? (
<Empty description="No featured strategies" />
) : (
featuredStrategies.map(strategy => renderStrategyCard(strategy))
)}
</TabPane>
<TabPane
tab={<span><FireOutlined /> Trending</span>}
key="trending"
>
{trendingStrategies.length === 0 ? (
<Empty description="No trending strategies" />
) : (
trendingStrategies.map(strategy => renderStrategyCard(strategy))
)}
</TabPane>
<TabPane
tab={<span><BulbOutlined /> Recommendations</span>}
key="recommendations"
>
{recommendations.length === 0 ? (
<Empty description="No recommendations available" />
) : (
recommendations.map(rec => renderRecommendationCard(rec))
)}
</TabPane>
<TabPane
tab={<span><SettingOutlined /> My Strategies</span>}
key="my-strategies"
>
<Table
columns={myStrategiesColumns}
dataSource={myStrategies}
rowKey="id"
pagination={{ pageSize: 10 }}
/>
</TabPane>
</Tabs>
</Col>
</Row>
{renderDetailModal()}
</div>
);
};
export default StrategyMarketplacePage;