feat: 添加部门管理功能、主题切换和多语言支持

refactor(dashboard): 重构用户管理页面和路由结构

feat(server): 实现部门管理API和RBAC增强功能

docs: 更新用户手册和管理员指南文档

style: 统一图标使用和组件命名规范

test: 添加部门服务和数据隔离测试用例

chore: 更新依赖和配置文件
This commit is contained in:
2026-03-28 22:52:12 +08:00
parent 22308fe042
commit d327706087
87 changed files with 21372 additions and 4806 deletions

View File

@@ -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}