feat: 添加部门管理功能、主题切换和多语言支持
refactor(dashboard): 重构用户管理页面和路由结构 feat(server): 实现部门管理API和RBAC增强功能 docs: 更新用户手册和管理员指南文档 style: 统一图标使用和组件命名规范 test: 添加部门服务和数据隔离测试用例 chore: 更新依赖和配置文件
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
useLocale,
|
||||
useState,
|
||||
useEffect,
|
||||
Card,
|
||||
@@ -107,6 +108,7 @@ const riskColors: Record<string, string> = {
|
||||
};
|
||||
|
||||
const StrategyMarketplacePage: FC = () => {
|
||||
const { t } = useLocale();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activeTab, setActiveTab] = useState('marketplace');
|
||||
const [categoryFilter, setCategoryFilter] = useState<string>('all');
|
||||
@@ -244,15 +246,15 @@ const StrategyMarketplacePage: FC = () => {
|
||||
<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>}
|
||||
{strategy.is_featured && <Tag icon={<StarOutlined />} color="gold">{t('strategyMarketplace.featured')}</Tag>}
|
||||
</Space>
|
||||
<Space>
|
||||
<Tag color={categoryColors[strategy.category]} icon={categoryIcons[strategy.category]}>
|
||||
{strategy.category.replace('_', ' ')}
|
||||
{t(`strategyMarketplace.categories.${strategy.category.toLowerCase()}`)}
|
||||
</Tag>
|
||||
<Tag color={riskColors[strategy.risk_level]}>{strategy.risk_level} Risk</Tag>
|
||||
<Tag color={riskColors[strategy.risk_level]}>{t(`strategyMarketplace.risk.${strategy.risk_level.toLowerCase()}`)}</Tag>
|
||||
{strategy.price === 0 ? (
|
||||
<Tag color="green">FREE</Tag>
|
||||
<Tag color="green">{t('strategyMarketplace.price.free')}</Tag>
|
||||
) : (
|
||||
<Tag color="blue">${strategy.price.toFixed(2)}</Tag>
|
||||
)}
|
||||
@@ -262,15 +264,15 @@ const StrategyMarketplacePage: FC = () => {
|
||||
</Text>
|
||||
<Space>
|
||||
<Text type="secondary">
|
||||
<PercentageOutlined /> Avg ROI: {(strategy.avg_roi * 100).toFixed(1)}%
|
||||
<PercentageOutlined /> {t('strategyMarketplace.stats.avgRoi')}: {(strategy.avg_roi * 100).toFixed(1)}%
|
||||
</Text>
|
||||
<Divider type="vertical" />
|
||||
<Text type="secondary">
|
||||
<CheckCircleOutlined /> Success: {strategy.success_rate}%
|
||||
<CheckCircleOutlined /> {t('strategyMarketplace.stats.success')}: {strategy.success_rate}%
|
||||
</Text>
|
||||
<Divider type="vertical" />
|
||||
<Text type="secondary">
|
||||
<BarChartOutlined /> Used: {strategy.usage_count}
|
||||
<BarChartOutlined /> {t('strategyMarketplace.stats.used')}: {strategy.usage_count}
|
||||
</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
@@ -285,7 +287,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
handleActivateStrategy(strategy.id);
|
||||
}}
|
||||
>
|
||||
Activate
|
||||
{t('strategyMarketplace.actions.activate')}
|
||||
</Button>
|
||||
</Col>
|
||||
)}
|
||||
@@ -305,9 +307,9 @@ const StrategyMarketplacePage: FC = () => {
|
||||
<Space>
|
||||
<Text strong style={{ fontSize: 16 }}>{rec.strategy.name}</Text>
|
||||
<Tag color={rec.priority === 'HIGH' ? 'red' : rec.priority === 'MEDIUM' ? 'orange' : 'blue'}>
|
||||
{rec.priority} Priority
|
||||
{t(`strategyMarketplace.priority.${rec.priority.toLowerCase()}`)}
|
||||
</Tag>
|
||||
<Badge count={`Score: ${rec.score.toFixed(0)}`} style={{ backgroundColor: '#52c41a' }} />
|
||||
<Badge count={`${t('strategyMarketplace.score')}: ${rec.score.toFixed(0)}`} style={{ backgroundColor: '#52c41a' }} />
|
||||
</Space>
|
||||
<Space wrap>
|
||||
{rec.reasons.map((reason, idx) => (
|
||||
@@ -316,11 +318,11 @@ const StrategyMarketplacePage: FC = () => {
|
||||
</Space>
|
||||
<Space>
|
||||
<Text type="secondary">
|
||||
Expected ROI: <Text strong style={{ color: '#52c41a' }}>{(rec.expectedRoi * 100).toFixed(1)}%</Text>
|
||||
{t('strategyMarketplace.expectedRoi')}: <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' }} />
|
||||
{t('strategyMarketplace.confidence')}: <Progress percent={rec.confidence * 100} size="small" style={{ width: 80, display: 'inline-block' }} />
|
||||
</Text>
|
||||
</Space>
|
||||
</Space>
|
||||
@@ -334,7 +336,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
handleActivateStrategy(rec.strategy.id);
|
||||
}}
|
||||
>
|
||||
Activate
|
||||
{t('strategyMarketplace.actions.activate')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -343,20 +345,20 @@ const StrategyMarketplacePage: FC = () => {
|
||||
|
||||
const myStrategiesColumns: ColumnsType<MerchantStrategy> = [
|
||||
{
|
||||
title: 'Strategy',
|
||||
title: t('strategyMarketplace.columns.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('_', ' ')}
|
||||
{t(`strategyMarketplace.categories.${record.strategy.category.toLowerCase()}`)}
|
||||
</Tag>
|
||||
<Text strong>{name}</Text>
|
||||
</Space>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Status',
|
||||
title: t('strategyMarketplace.columns.status'),
|
||||
dataIndex: 'status',
|
||||
key: 'status',
|
||||
render: (status: string) => {
|
||||
@@ -367,11 +369,11 @@ const StrategyMarketplacePage: FC = () => {
|
||||
FAILED: { color: 'error', icon: <CloseCircleOutlined /> }
|
||||
};
|
||||
const config = statusConfig[status];
|
||||
return <Tag color={config.color} icon={config.icon}>{status}</Tag>;
|
||||
return <Tag color={config.color} icon={config.icon}>{t(`strategyMarketplace.status.${status.toLowerCase()}`)}</Tag>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'ROI Achieved',
|
||||
title: t('strategyMarketplace.columns.roiAchieved'),
|
||||
dataIndex: 'roi_achieved',
|
||||
key: 'roi',
|
||||
render: (roi: number) => (
|
||||
@@ -381,19 +383,19 @@ const StrategyMarketplacePage: FC = () => {
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Revenue Generated',
|
||||
title: t('strategyMarketplace.columns.revenueGenerated'),
|
||||
dataIndex: 'revenue_generated',
|
||||
key: 'revenue',
|
||||
render: (revenue: number) => `$${revenue.toFixed(2)}`
|
||||
},
|
||||
{
|
||||
title: 'Activated',
|
||||
title: t('strategyMarketplace.columns.activated'),
|
||||
dataIndex: 'activated_at',
|
||||
key: 'activated_at',
|
||||
render: (date: string) => new Date(date).toLocaleDateString()
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
title: t('strategyMarketplace.columns.actions'),
|
||||
key: 'actions',
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
@@ -403,7 +405,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
icon={<PauseCircleOutlined />}
|
||||
onClick={() => handlePauseStrategy(record.id)}
|
||||
>
|
||||
Pause
|
||||
{t('strategyMarketplace.actions.pause')}
|
||||
</Button>
|
||||
)}
|
||||
{record.status === 'PAUSED' && (
|
||||
@@ -413,7 +415,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={() => handleResumeStrategy(record.id)}
|
||||
>
|
||||
Resume
|
||||
{t('strategyMarketplace.actions.resume')}
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
@@ -426,7 +428,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
title={
|
||||
<Space>
|
||||
{selectedStrategy?.name}
|
||||
{selectedStrategy?.is_featured && <Tag icon={<StarOutlined />} color="gold">Featured</Tag>}
|
||||
{selectedStrategy?.is_featured && <Tag icon={<StarOutlined />} color="gold">{t('strategyMarketplace.featured')}</Tag>}
|
||||
</Space>
|
||||
}
|
||||
open={detailModalVisible}
|
||||
@@ -434,7 +436,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
width={700}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={() => setDetailModalVisible(false)}>
|
||||
Cancel
|
||||
{t('strategyMarketplace.actions.cancel')}
|
||||
</Button>,
|
||||
<Button
|
||||
key="activate"
|
||||
@@ -442,7 +444,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
icon={<PlayCircleOutlined />}
|
||||
onClick={() => handleActivateStrategy(selectedStrategy?.id || '')}
|
||||
>
|
||||
Activate Strategy
|
||||
{t('strategyMarketplace.actions.activateStrategy')}
|
||||
</Button>
|
||||
]}
|
||||
>
|
||||
@@ -453,7 +455,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
<Row gutter={16}>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title="Average ROI"
|
||||
title={t('strategyMarketplace.stats.averageRoi')}
|
||||
value={(selectedStrategy.avg_roi * 100).toFixed(1)}
|
||||
suffix="%"
|
||||
valueStyle={{ color: '#52c41a' }}
|
||||
@@ -461,14 +463,14 @@ const StrategyMarketplacePage: FC = () => {
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title="Success Rate"
|
||||
title={t('strategyMarketplace.stats.successRate')}
|
||||
value={selectedStrategy.success_rate}
|
||||
suffix="%"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Statistic
|
||||
title="Usage Count"
|
||||
title={t('strategyMarketplace.stats.usageCount')}
|
||||
value={selectedStrategy.usage_count}
|
||||
/>
|
||||
</Col>
|
||||
@@ -478,35 +480,35 @@ const StrategyMarketplacePage: FC = () => {
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Text type="secondary">Category: </Text>
|
||||
<Text type="secondary">{t('strategyMarketplace.category')}: </Text>
|
||||
<Tag color={categoryColors[selectedStrategy.category]} icon={categoryIcons[selectedStrategy.category]}>
|
||||
{selectedStrategy.category.replace('_', ' ')}
|
||||
{t(`strategyMarketplace.categories.${selectedStrategy.category.toLowerCase()}`)}
|
||||
</Tag>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Text type="secondary">Risk Level: </Text>
|
||||
<Text type="secondary">{t('strategyMarketplace.riskLevel')}: </Text>
|
||||
<Tag color={riskColors[selectedStrategy.risk_level]}>
|
||||
{selectedStrategy.risk_level}
|
||||
{t(`strategyMarketplace.risk.${selectedStrategy.risk_level.toLowerCase()}`)}
|
||||
</Tag>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={16}>
|
||||
<Col span={12}>
|
||||
<Text type="secondary">Price: </Text>
|
||||
<Text type="secondary">{t('strategyMarketplace.price.title')}: </Text>
|
||||
{selectedStrategy.price === 0 ? (
|
||||
<Tag color="green">FREE</Tag>
|
||||
<Tag color="green">{t('strategyMarketplace.price.free')}</Tag>
|
||||
) : (
|
||||
<Text strong>${selectedStrategy.price.toFixed(2)}</Text>
|
||||
)}
|
||||
<Text type="secondary"> ({selectedStrategy.billing_type})</Text>
|
||||
<Text type="secondary"> ({t(`strategyMarketplace.billingType.${selectedStrategy.billing_type.toLowerCase()}`)})</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Divider />
|
||||
|
||||
<div>
|
||||
<Text type="secondary">Tags: </Text>
|
||||
<Text type="secondary">{t('strategyMarketplace.tags')}: </Text>
|
||||
<Space wrap style={{ marginTop: 8 }}>
|
||||
{selectedStrategy.tags.map((tag, idx) => (
|
||||
<Tag key={idx} icon={<TagOutlined />}>{tag}</Tag>
|
||||
@@ -534,15 +536,15 @@ const StrategyMarketplacePage: FC = () => {
|
||||
<Row gutter={16} align="middle">
|
||||
<Col flex="auto">
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
<ShopOutlined /> Strategy Marketplace
|
||||
<ShopOutlined /> {t('strategyMarketplace.title')}
|
||||
</Title>
|
||||
<Text type="secondary">
|
||||
Discover and activate AI-powered strategies to grow your business
|
||||
{t('strategyMarketplace.description')}
|
||||
</Text>
|
||||
</Col>
|
||||
<Col>
|
||||
<Search
|
||||
placeholder="Search strategies..."
|
||||
placeholder={t('strategyMarketplace.searchPlaceholder')}
|
||||
allowClear
|
||||
enterButton={<SearchOutlined />}
|
||||
style={{ width: 300 }}
|
||||
@@ -558,7 +560,7 @@ const StrategyMarketplacePage: FC = () => {
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={[
|
||||
{ key: 'marketplace', label: <span><ShopOutlined /> Marketplace</span>, children: (
|
||||
{ key: 'marketplace', label: <span><ShopOutlined /> {t('strategyMarketplace.tabs.marketplace')}</span>, children: (
|
||||
<>
|
||||
<Row gutter={16}>
|
||||
<Col span={6}>
|
||||
@@ -567,46 +569,46 @@ const StrategyMarketplacePage: FC = () => {
|
||||
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>
|
||||
<Option value="all">{t('strategyMarketplace.categories.all')}</Option>
|
||||
<Option value="PRICING">{t('strategyMarketplace.categories.pricing')}</Option>
|
||||
<Option value="ADVERTISING">{t('strategyMarketplace.categories.advertising')}</Option>
|
||||
<Option value="PRODUCT_SELECTION">{t('strategyMarketplace.categories.productSelection')}</Option>
|
||||
<Option value="INVENTORY">{t('strategyMarketplace.categories.inventory')}</Option>
|
||||
<Option value="LOGISTICS">{t('strategyMarketplace.categories.logistics')}</Option>
|
||||
<Option value="MARKETING">{t('strategyMarketplace.categories.marketing')}</Option>
|
||||
</Select>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{filteredStrategies.length === 0 ? (
|
||||
<Empty description="No strategies found" />
|
||||
<Empty description={t('strategyMarketplace.noStrategies')} />
|
||||
) : (
|
||||
filteredStrategies.map(strategy => renderStrategyCard(strategy))
|
||||
)}
|
||||
</>
|
||||
)},
|
||||
{ key: 'featured', label: <span><StarOutlined /> Featured</span>, children: (
|
||||
{ key: 'featured', label: <span><StarOutlined /> {t('strategyMarketplace.tabs.featured')}</span>, children: (
|
||||
featuredStrategies.length === 0 ? (
|
||||
<Empty description="No featured strategies" />
|
||||
<Empty description={t('strategyMarketplace.noFeaturedStrategies')} />
|
||||
) : (
|
||||
featuredStrategies.map(strategy => renderStrategyCard(strategy))
|
||||
)
|
||||
)},
|
||||
{ key: 'trending', label: <span><FireOutlined /> Trending</span>, children: (
|
||||
{ key: 'trending', label: <span><FireOutlined /> {t('strategyMarketplace.tabs.trending')}</span>, children: (
|
||||
trendingStrategies.length === 0 ? (
|
||||
<Empty description="No trending strategies" />
|
||||
<Empty description={t('strategyMarketplace.noTrendingStrategies')} />
|
||||
) : (
|
||||
trendingStrategies.map(strategy => renderStrategyCard(strategy))
|
||||
)
|
||||
)},
|
||||
{ key: 'recommendations', label: <span><BulbOutlined /> Recommendations</span>, children: (
|
||||
{ key: 'recommendations', label: <span><BulbOutlined /> {t('strategyMarketplace.tabs.recommendations')}</span>, children: (
|
||||
recommendations.length === 0 ? (
|
||||
<Empty description="No recommendations available" />
|
||||
<Empty description={t('strategyMarketplace.noRecommendations')} />
|
||||
) : (
|
||||
recommendations.map(rec => renderRecommendationCard(rec))
|
||||
)
|
||||
)},
|
||||
{ key: 'my-strategies', label: <span><SettingOutlined /> My Strategies</span>, children: (
|
||||
{ key: 'my-strategies', label: <span><SettingOutlined /> {t('strategyMarketplace.tabs.myStrategies')}</span>, children: (
|
||||
<Table
|
||||
columns={myStrategiesColumns}
|
||||
dataSource={myStrategies}
|
||||
|
||||
Reference in New Issue
Block a user