refactor: 重构代码结构和类型定义,优化类型安全性和代码可维护性

- 添加类型定义文件和类型引用
- 删除废弃的页面模块和导出文件
- 新增聚合管理模块和插件系统
- 修复类型错误和潜在运行时问题
- 更新API基础URL和配置
- 优化组件类型定义和事件处理
- 重构数据源接口和实现
- 完善文档和开发进度记录
This commit is contained in:
2026-03-22 11:25:28 +08:00
parent 15ee1758f5
commit a037843851
88 changed files with 42703 additions and 6395 deletions

View File

@@ -0,0 +1,429 @@
import React, { useState } from 'react';
import { Card, Row, Col, Switch, Tag, Typography, Space, Button, Modal, Progress, Divider, Alert, Tabs, Table, message } from 'antd';
import {
CrownOutlined,
RobotOutlined,
ShopOutlined,
GlobalOutlined,
LineChartOutlined,
ApiOutlined,
CheckCircleOutlined,
CloseCircleOutlined,
ThunderboltOutlined,
StarOutlined,
} from '@ant-design/icons';
import { useUser, FEATURES, ROLE_CONFIG } from '@/contexts/UserContext';
const { Title, Text, Paragraph } = Typography;
interface FeatureConfig {
key: string;
name: string;
description: string;
icon: React.ReactNode;
requiredPlan: string[];
benefits: string[];
limitations?: string[];
}
const FEATURE_CONFIGS: FeatureConfig[] = [
{
key: FEATURES.AI_OPERATIONS,
name: 'AI运营中心',
description: '智能运营助手,自动化执行日常运营任务',
icon: <RobotOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['basic', 'pro', 'enterprise'],
benefits: [
'自动选品推荐',
'智能定价策略',
'自动上下架',
'AI决策日志追踪',
'策略市场访问',
],
limitations: [
'免费版: 每日仅3次AI调用',
'基础版: 每日100次AI调用',
'专业版: 无限AI调用',
],
},
{
key: FEATURES.AUTO_PRICING,
name: '自动定价',
description: '基于市场数据和竞争分析的智能定价系统',
icon: <ThunderboltOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['pro', 'enterprise'],
benefits: [
'实时竞品价格监控',
'动态定价策略',
'利润率自动优化',
'价格预警通知',
],
limitations: [
'基础版: 仅查看定价建议',
'专业版: 自动执行定价',
],
},
{
key: FEATURES.MULTI_SHOP,
name: '多店铺管理',
description: '统一管理多个电商平台的店铺',
icon: <ShopOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['basic', 'pro', 'enterprise'],
benefits: [
'跨平台商品同步',
'统一库存管理',
'多店铺数据报表',
'批量操作支持',
],
limitations: [
'免费版: 仅支持1个店铺',
'基础版: 最多3个店铺',
'专业版: 最多10个店铺',
'企业版: 无限店铺',
],
},
{
key: FEATURES.B2B_TRADE,
name: 'B2B贸易',
description: '企业级批发贸易管理功能',
icon: <ShopOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['basic', 'pro', 'enterprise'],
benefits: [
'供应商管理',
'批量订单处理',
'B2B专属定价',
'合同管理',
],
},
{
key: FEATURES.INDEPENDENT_SITE,
name: '独立站',
description: '创建和管理独立电商网站',
icon: <GlobalOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['pro', 'enterprise'],
benefits: [
'自定义品牌站点',
'独立域名绑定',
'SEO优化工具',
'独立支付集成',
],
},
{
key: FEATURES.ADVANCED_ANALYTICS,
name: '高级数据分析',
description: '深度数据分析和可视化报表',
icon: <LineChartOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['pro', 'enterprise'],
benefits: [
'自定义报表',
'数据导出',
'趋势预测',
'竞品分析报告',
],
limitations: [
'基础版: 仅基础报表',
'专业版: 完整分析功能',
],
},
{
key: FEATURES.API_ACCESS,
name: 'API访问',
description: '开放API接口支持系统集成',
icon: <ApiOutlined style={{ fontSize: 24 }} />,
requiredPlan: ['enterprise'],
benefits: [
'RESTful API',
'Webhook支持',
'SDK下载',
'技术文档',
],
},
];
const PLAN_DETAILS = {
free: {
name: '免费版',
price: 0,
color: 'default',
features: ['基础商品管理', '订单管理', '基础报表'],
limitations: ['1个店铺', '每日3次AI调用', '无API访问'],
},
basic: {
name: '基础版',
price: 99,
color: 'blue',
features: ['多店铺管理(3个)', 'AI运营(100次/日)', 'B2B贸易', '基础定价建议'],
limitations: ['无独立站', '无API访问'],
},
pro: {
name: '专业版',
price: 299,
color: 'gold',
features: ['多店铺管理(10个)', '无限AI调用', '自动定价', '独立站', '高级分析'],
limitations: ['无API访问'],
},
enterprise: {
name: '企业版',
price: 999,
color: 'purple',
features: ['无限店铺', '全部功能', 'API访问', '专属客服', '定制开发'],
limitations: [],
},
};
const SubscriptionManage: React.FC = () => {
const { currentUser, hasFeature, isPaidUser, getPlanLabel } = useUser();
const [previewFeature, setPreviewFeature] = useState<FeatureConfig | null>(null);
const [simulatedPlan, setSimulatedPlan] = useState<string | null>(null);
const currentPlan = currentUser.subscription?.plan || 'free';
const handleFeaturePreview = (feature: FeatureConfig) => {
setPreviewFeature(feature);
};
const handleSimulatePlan = (plan: string) => {
setSimulatedPlan(plan);
message.info(`已切换到${PLAN_DETAILS[plan as keyof typeof PLAN_DETAILS].name}预览模式`);
};
const isFeatureAvailable = (featureKey: string) => {
if (simulatedPlan) {
const feature = FEATURE_CONFIGS.find(f => f.key === featureKey);
return feature?.requiredPlan.includes(simulatedPlan) ?? false;
}
return hasFeature(featureKey);
};
const columns = [
{
title: '功能',
dataIndex: 'name',
key: 'name',
render: (name: string, record: FeatureConfig) => (
<Space>
{record.icon}
<span>{name}</span>
</Space>
),
},
{
title: '免费版',
key: 'free',
render: (_: any, record: FeatureConfig) =>
record.requiredPlan.includes('free') ?
<CheckCircleOutlined style={{ color: '#52c41a' }} /> :
<CloseCircleOutlined style={{ color: '#d9d9d9' }} />,
},
{
title: '基础版',
key: 'basic',
render: (_: any, record: FeatureConfig) =>
record.requiredPlan.includes('basic') ?
<CheckCircleOutlined style={{ color: '#52c41a' }} /> :
<CloseCircleOutlined style={{ color: '#d9d9d9' }} />,
},
{
title: '专业版',
key: 'pro',
render: (_: any, record: FeatureConfig) =>
record.requiredPlan.includes('pro') ?
<CheckCircleOutlined style={{ color: '#52c41a' }} /> :
<CloseCircleOutlined style={{ color: '#d9d9d9' }} />,
},
{
title: '企业版',
key: 'enterprise',
render: (_: any, record: FeatureConfig) =>
record.requiredPlan.includes('enterprise') ?
<CheckCircleOutlined style={{ color: '#52c41a' }} /> :
<CloseCircleOutlined style={{ color: '#d9d9d9' }} />,
},
{
title: '当前状态',
key: 'status',
render: (_: any, record: FeatureConfig) => (
<Tag color={isFeatureAvailable(record.key) ? 'green' : 'default'}>
{isFeatureAvailable(record.key) ? '已开通' : '未开通'}
</Tag>
),
},
{
title: '操作',
key: 'action',
render: (_: any, record: FeatureConfig) => (
<Button type="link" onClick={() => handleFeaturePreview(record)}>
</Button>
),
},
];
return (
<div className="subscription-manage">
<div className="page-header" style={{ marginBottom: 24 }}>
<Title level={2}>
<CrownOutlined style={{ marginRight: 8 }} />
</Title>
<Paragraph type="secondary">
</Paragraph>
</div>
<Alert
message="临时预览模式"
description="点击下方套餐卡片可以预览不同套餐的功能权限,实际功能以订阅为准"
type="info"
showIcon
style={{ marginBottom: 24 }}
/>
<Row gutter={16} style={{ marginBottom: 24 }}>
{Object.entries(PLAN_DETAILS).map(([key, plan]) => (
<Col span={6} key={key}>
<Card
hoverable
style={{
borderColor: currentPlan === key ? '#1890ff' : undefined,
borderWidth: currentPlan === key ? 2 : 1,
}}
onClick={() => handleSimulatePlan(key)}
>
<div style={{ textAlign: 'center' }}>
<Tag color={plan.color} style={{ marginBottom: 8 }}>
{plan.name}
</Tag>
{currentPlan === key && (
<Tag color="green" style={{ marginLeft: 4 }}></Tag>
)}
{simulatedPlan === key && currentPlan !== key && (
<Tag color="blue" style={{ marginLeft: 4 }}></Tag>
)}
<Title level={3} style={{ margin: '8px 0' }}>
¥{plan.price}
<Text type="secondary" style={{ fontSize: 14 }}>/</Text>
</Title>
<Divider style={{ margin: '12px 0' }} />
<div style={{ textAlign: 'left' }}>
{plan.features.map((f, i) => (
<div key={i} style={{ marginBottom: 4 }}>
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: 8 }} />
<Text>{f}</Text>
</div>
))}
{plan.limitations.map((l, i) => (
<div key={i} style={{ marginBottom: 4 }}>
<CloseCircleOutlined style={{ color: '#d9d9d9', marginRight: 8 }} />
<Text type="secondary">{l}</Text>
</div>
))}
</div>
</div>
</Card>
</Col>
))}
</Row>
<Card title="功能对比表" style={{ marginBottom: 24 }}>
<Table
columns={columns}
dataSource={FEATURE_CONFIGS}
rowKey="key"
pagination={false}
/>
</Card>
<Card title="当前订阅状态">
<Row gutter={24}>
<Col span={8}>
<div style={{ textAlign: 'center', padding: 24 }}>
<Tag color={PLAN_DETAILS[currentPlan as keyof typeof PLAN_DETAILS]?.color || 'default'} style={{ fontSize: 16, padding: '4px 12px' }}>
{getPlanLabel()}
</Tag>
<Title level={4} style={{ marginTop: 16 }}></Title>
<Text type="secondary">
{currentUser.subscription?.expiresAt ?
`到期时间: ${currentUser.subscription.expiresAt}` :
'永久有效'}
</Text>
</div>
</Col>
<Col span={8}>
<div style={{ textAlign: 'center', padding: 24 }}>
<Title level={2}>{currentUser.subscription?.features.length || 0}</Title>
<Text type="secondary"></Text>
</div>
</Col>
<Col span={8}>
<div style={{ textAlign: 'center', padding: 24 }}>
<Title level={2}>{FEATURE_CONFIGS.filter(f => !hasFeature(f.key)).length}</Title>
<Text type="secondary"></Text>
</div>
</Col>
</Row>
</Card>
<Modal
title={previewFeature?.name}
open={!!previewFeature}
onCancel={() => setPreviewFeature(null)}
footer={[
<Button key="close" onClick={() => setPreviewFeature(null)}>
</Button>,
!isFeatureAvailable(previewFeature?.key || '') && (
<Button key="upgrade" type="primary">
</Button>
),
]}
width={600}
>
{previewFeature && (
<div>
<Paragraph>{previewFeature.description}</Paragraph>
<Title level={5}></Title>
{previewFeature.benefits.map((b, i) => (
<div key={i} style={{ marginBottom: 8 }}>
<CheckCircleOutlined style={{ color: '#52c41a', marginRight: 8 }} />
{b}
</div>
))}
{previewFeature.limitations && (
<>
<Title level={5} style={{ marginTop: 16 }}></Title>
{previewFeature.limitations.map((l, i) => (
<div key={i} style={{ marginBottom: 8 }}>
<Text type="secondary"> {l}</Text>
</div>
))}
</>
)}
<Divider />
<Title level={5}></Title>
<Space>
{previewFeature.requiredPlan.map(p => (
<Tag key={p} color={PLAN_DETAILS[p as keyof typeof PLAN_DETAILS]?.color}>
{PLAN_DETAILS[p as keyof typeof PLAN_DETAILS]?.name}
</Tag>
))}
</Space>
<div style={{ marginTop: 16 }}>
<Tag color={isFeatureAvailable(previewFeature.key) ? 'green' : 'orange'}>
{isFeatureAvailable(previewFeature.key) ? '✓ 您已拥有此功能' : '✗ 需要升级套餐'}
</Tag>
</div>
</div>
)}
</Modal>
</div>
);
};
export default SubscriptionManage;

View File

@@ -1,15 +1,67 @@
import React, { useState, useEffect } from 'react';
import { Table, Button, Input, Select, message, Card, Modal, Form } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined, ArrowLeftOutlined } from '@ant-design/icons';
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 { settingsDataSource, User } from '@/services/settingsDataSource';
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;
lastLogin?: string;
}
const ALL_PERMISSIONS = [
{ key: PERMISSIONS.PRODUCT_READ, label: '商品查看' },
{ key: PERMISSIONS.PRODUCT_WRITE, label: '商品编辑' },
{ key: PERMISSIONS.PRODUCT_DELETE, label: '商品删除' },
{ key: PERMISSIONS.ORDER_READ, label: '订单查看' },
{ key: PERMISSIONS.ORDER_WRITE, label: '订单编辑' },
{ key: PERMISSIONS.ORDER_APPROVE, label: '订单审批' },
{ key: PERMISSIONS.INVENTORY_READ, label: '库存查看' },
{ key: PERMISSIONS.INVENTORY_WRITE, label: '库存编辑' },
{ key: PERMISSIONS.FINANCE_READ, label: '财务查看' },
{ key: PERMISSIONS.FINANCE_WRITE, label: '财务编辑' },
{ key: PERMISSIONS.FINANCE_APPROVE, label: '财务审批' },
{ key: PERMISSIONS.USER_MANAGE, label: '用户管理' },
{ key: PERMISSIONS.SYSTEM_CONFIG, label: '系统配置' },
{ key: PERMISSIONS.AI_OPERATIONS, label: 'AI操作' },
{ key: PERMISSIONS.B2B_ACCESS, label: 'B2B访问' },
];
const ALL_FEATURES = [
{ key: FEATURES.AI_OPERATIONS, label: 'AI运营中心', description: '智能运营助手' },
{ key: FEATURES.AUTO_PRICING, label: '自动定价', description: '智能定价系统' },
{ key: FEATURES.MULTI_SHOP, label: '多店铺管理', description: '统一管理多平台店铺' },
{ key: FEATURES.B2B_TRADE, label: 'B2B贸易', description: '企业级批发贸易' },
{ key: FEATURES.INDEPENDENT_SITE, label: '独立站', description: '创建独立电商网站' },
{ key: FEATURES.ADVANCED_ANALYTICS, label: '高级分析', description: '深度数据分析' },
{ key: FEATURES.API_ACCESS, label: 'API访问', description: '开放API接口' },
];
const PLAN_OPTIONS = [
{ value: 'free', label: '免费版', color: 'default' },
{ value: 'basic', label: '基础版', color: 'blue' },
{ value: 'pro', label: '专业版', color: 'gold' },
{ value: 'enterprise', label: '企业版', color: 'purple' },
];
const ROLE_PERMISSIONS_PRESET: Record<UserRole, string[]> = {
ADMIN: ['*'],
MANAGER: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.PRODUCT_WRITE, PERMISSIONS.PRODUCT_DELETE, PERMISSIONS.ORDER_READ, PERMISSIONS.ORDER_WRITE, PERMISSIONS.ORDER_APPROVE, PERMISSIONS.INVENTORY_READ, PERMISSIONS.INVENTORY_WRITE, PERMISSIONS.FINANCE_READ, PERMISSIONS.USER_MANAGE],
OPERATOR: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.PRODUCT_WRITE, PERMISSIONS.ORDER_READ, PERMISSIONS.ORDER_WRITE, PERMISSIONS.INVENTORY_READ],
FINANCE: [PERMISSIONS.FINANCE_READ, PERMISSIONS.FINANCE_WRITE, PERMISSIONS.FINANCE_APPROVE, PERMISSIONS.ORDER_READ],
SOURCING: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.INVENTORY_READ, PERMISSIONS.INVENTORY_WRITE, PERMISSIONS.B2B_ACCESS],
LOGISTICS: [PERMISSIONS.INVENTORY_READ, PERMISSIONS.INVENTORY_WRITE, PERMISSIONS.ORDER_READ],
ANALYST: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.ORDER_READ, PERMISSIONS.FINANCE_READ],
};
const UserManagement: React.FC = () => {
const navigate = useNavigate();
const [users, setUsers] = useState<User[]>([]);
const { currentUser, hasPermission } = useUser();
const [users, setUsers] = useState<ManagedUser[]>([]);
const [loading, setLoading] = useState(false);
const [filters, setFilters] = useState({
role: '',
@@ -17,7 +69,12 @@ const UserManagement: React.FC = () => {
search: '',
});
const [isModalVisible, setIsModalVisible] = useState(false);
const [isPermissionModalVisible, setIsPermissionModalVisible] = useState(false);
const [isSubscriptionModalVisible, setIsSubscriptionModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState<ManagedUser | null>(null);
const [form] = Form.useForm();
const [permissionForm] = Form.useForm();
const [subscriptionForm] = Form.useForm();
useEffect(() => {
fetchUsers();
@@ -25,49 +82,233 @@ const UserManagement: React.FC = () => {
const fetchUsers = async () => {
setLoading(true);
try {
const data = await settingsDataSource.fetchUsers();
setUsers(data);
} catch (error) {
message.error('Failed to load users');
} finally {
setTimeout(() => {
const mockUsers: ManagedUser[] = [
{
id: 'user_admin',
name: '张管理员',
email: 'admin@crawlful.com',
role: 'ADMIN',
status: 'active',
permissions: ['*'],
createdAt: '2025-01-01',
lastLogin: '2026-03-21 10:30',
subscription: { plan: 'enterprise', expiresAt: '2027-03-21', features: Object.values(FEATURES) },
},
{
id: 'user_manager',
name: '李主管',
email: 'manager@crawlful.com',
role: 'MANAGER',
status: 'active',
permissions: ROLE_PERMISSIONS_PRESET.MANAGER,
createdAt: '2025-02-15',
lastLogin: '2026-03-20 15:20',
subscription: { plan: 'pro', expiresAt: '2026-06-21', features: [FEATURES.AI_OPERATIONS, FEATURES.AUTO_PRICING, FEATURES.MULTI_SHOP, FEATURES.ADVANCED_ANALYTICS] },
},
{
id: 'user_operator',
name: '王专员',
email: 'operator@crawlful.com',
role: 'OPERATOR',
status: 'active',
permissions: ROLE_PERMISSIONS_PRESET.OPERATOR,
createdAt: '2025-03-10',
lastLogin: '2026-03-19 09:15',
subscription: { plan: 'basic', expiresAt: '2026-04-21', features: [FEATURES.MULTI_SHOP] },
},
{
id: 'user_finance',
name: '赵财务',
email: 'finance@crawlful.com',
role: 'FINANCE',
status: 'active',
permissions: ROLE_PERMISSIONS_PRESET.FINANCE,
createdAt: '2025-01-20',
lastLogin: '2026-03-21 08:45',
subscription: { plan: 'pro', expiresAt: '2026-09-21', features: [FEATURES.AI_OPERATIONS, FEATURES.ADVANCED_ANALYTICS] },
},
{
id: 'user_sourcing',
name: '孙采购',
email: 'sourcing@crawlful.com',
role: 'SOURCING',
status: 'active',
permissions: ROLE_PERMISSIONS_PRESET.SOURCING,
createdAt: '2025-04-05',
lastLogin: '2026-03-18 14:30',
subscription: { plan: 'basic', expiresAt: '2026-05-21', features: [FEATURES.B2B_TRADE] },
},
{
id: 'user_logistics',
name: '周物流',
email: 'logistics@crawlful.com',
role: 'LOGISTICS',
status: 'inactive',
permissions: ROLE_PERMISSIONS_PRESET.LOGISTICS,
createdAt: '2025-05-01',
lastLogin: '2026-02-28 11:00',
subscription: { plan: 'basic', expiresAt: '2026-05-21', features: [] },
},
{
id: 'user_analyst',
name: '吴分析',
email: 'analyst@crawlful.com',
role: 'ANALYST',
status: 'active',
permissions: ROLE_PERMISSIONS_PRESET.ANALYST,
createdAt: '2025-02-28',
lastLogin: '2026-03-20 16:45',
subscription: { plan: 'pro', expiresAt: '2026-08-21', features: [FEATURES.ADVANCED_ANALYTICS, FEATURES.API_ACCESS] },
},
{
id: 'user_free',
name: '免费用户',
email: 'free@crawlful.com',
role: 'OPERATOR',
status: 'active',
permissions: [PERMISSIONS.PRODUCT_READ, PERMISSIONS.ORDER_READ],
createdAt: '2026-03-01',
lastLogin: '2026-03-15 10:00',
subscription: { plan: 'free', features: [] },
},
];
setUsers(mockUsers);
setLoading(false);
}
}, 500);
};
const handleAddUser = () => {
setSelectedUser(null);
form.resetFields();
setIsModalVisible(true);
};
const handleEditUser = (id: string) => {
message.info('编辑用户功能开发中');
const handleEditUser = (user: ManagedUser) => {
setSelectedUser(user);
form.setFieldsValue({
name: user.name,
email: user.email,
role: user.role,
status: user.status,
});
setIsModalVisible(true);
};
const handleManagePermissions = (user: ManagedUser) => {
setSelectedUser(user);
permissionForm.setFieldsValue({
role: user.role,
permissions: user.permissions.filter(p => p !== '*'),
usePreset: true,
});
setIsPermissionModalVisible(true);
};
const handleManageSubscription = (user: ManagedUser) => {
setSelectedUser(user);
subscriptionForm.setFieldsValue({
plan: user.subscription?.plan || 'free',
expiresAt: user.subscription?.expiresAt,
features: user.subscription?.features || [],
});
setIsSubscriptionModalVisible(true);
};
const handleDeleteUser = (id: string) => {
message.success('用户已删除');
fetchUsers();
Modal.confirm({
title: '确认删除',
content: '确定要删除此用户吗?此操作不可恢复。',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: () => {
setUsers(users.filter(u => u.id !== id));
message.success('用户已删除');
},
});
};
const handleSubmit = async (values: any) => {
setLoading(true);
// 模拟API调用
setTimeout(() => {
message.success('用户已添加');
if (selectedUser) {
setUsers(users.map(u =>
u.id === selectedUser.id
? { ...u, ...values, permissions: ROLE_PERMISSIONS_PRESET[values.role as UserRole] || [] }
: u
));
message.success('用户已更新');
} else {
const newUser: ManagedUser = {
id: `user_${Date.now()}`,
...values,
permissions: ROLE_PERMISSIONS_PRESET[values.role as UserRole] || [],
createdAt: new Date().toISOString().split('T')[0],
subscription: { plan: 'free', features: [] },
};
setUsers([...users, newUser]);
message.success('用户已添加');
}
setIsModalVisible(false);
setLoading(false);
fetchUsers();
}, 1000);
}, 500);
};
const handlePermissionSubmit = async (values: any) => {
if (!selectedUser) return;
const permissions = values.usePreset
? ROLE_PERMISSIONS_PRESET[values.role as UserRole] || []
: values.permissions || [];
setUsers(users.map(u =>
u.id === selectedUser.id
? { ...u, role: values.role, permissions }
: u
));
message.success('权限已更新');
setIsPermissionModalVisible(false);
};
const handleSubscriptionSubmit = async (values: any) => {
if (!selectedUser) return;
setUsers(users.map(u =>
u.id === selectedUser.id
? {
...u,
subscription: {
plan: values.plan,
expiresAt: values.expiresAt,
features: values.features || []
}
}
: u
));
message.success('订阅已更新');
setIsSubscriptionModalVisible(false);
};
const handleBack = () => {
navigate('/settings');
navigate('/dashboard/settings');
};
const canManageUsers = hasPermission(PERMISSIONS.USER_MANAGE) || currentUser.role === 'ADMIN';
const columns = [
{
title: '姓名',
dataIndex: 'name',
key: 'name',
render: (name: string, record: ManagedUser) => (
<Space>
<span>{name}</span>
{record.id === currentUser.id && <Tag color="blue"></Tag>}
</Space>
),
},
{
title: '邮箱',
@@ -78,30 +319,30 @@ const UserManagement: React.FC = () => {
title: '角色',
dataIndex: 'role',
key: 'role',
render: (role: string) => {
const roleMap: Record<string, string> = {
ADMIN: '管理员',
MANAGER: '运营主管',
OPERATOR: '运营专员',
FINANCE: '财务主管',
SOURCING: '采购专家',
LOGISTICS: '物流专家',
ANALYST: '数据分析师',
};
return roleMap[role] || role;
render: (role: UserRole) => (
<Tag color={ROLE_CONFIG[role]?.color || 'default'}>
{ROLE_CONFIG[role]?.label || role}
</Tag>
),
},
{
title: '套餐',
dataIndex: ['subscription', 'plan'],
key: 'plan',
render: (plan: string) => {
const planConfig = PLAN_OPTIONS.find(p => p.value === plan);
return <Tag color={planConfig?.color}>{planConfig?.label || plan}</Tag>;
},
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const statusMap: Record<string, string> = {
active: '活跃',
inactive: '非活跃',
};
return statusMap[status] || status;
},
render: (status: string) => (
<Tag color={status === 'active' ? 'green' : 'default'}>
{status === 'active' ? '活跃' : '活跃'}
</Tag>
),
},
{
title: '创建时间',
@@ -112,53 +353,92 @@ const UserManagement: React.FC = () => {
title: '最后登录',
dataIndex: 'lastLogin',
key: 'lastLogin',
render: (login: string) => login || '-',
},
{
title: '操作',
key: 'action',
render: (_: any, record: User) => (
<div>
width: 280,
render: (_: any, record: ManagedUser) => (
<Space size="small" wrap>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEditUser(record.id)}
onClick={() => handleEditUser(record)}
>
</Button>
<Button
type="link"
danger
icon={<DeleteOutlined />}
onClick={() => handleDeleteUser(record.id)}
size="small"
icon={<SafetyOutlined />}
onClick={() => handleManagePermissions(record)}
>
</Button>
</div>
<Button
type="link"
size="small"
icon={<CrownOutlined />}
onClick={() => handleManageSubscription(record)}
>
</Button>
{record.id !== currentUser.id && (
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => handleDeleteUser(record.id)}
>
</Button>
)}
</Space>
),
},
];
if (!canManageUsers) {
return (
<Card>
<Alert
message="权限不足"
description="您没有用户管理权限,请联系管理员。"
type="warning"
showIcon
/>
</Card>
);
}
return (
<div className="user-management">
<div className="page-header">
<h1></h1>
<div style={{ display: 'flex', gap: 16 }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAddUser}
>
<div className="page-header" style={{ marginBottom: 24, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1 style={{ margin: 0 }}>
<SafetyOutlined style={{ marginRight: 8 }} />
</h1>
<Space>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAddUser}>
</Button>
<Button
icon={<ArrowLeftOutlined />}
onClick={handleBack}
>
<Button icon={<ArrowLeftOutlined />} onClick={handleBack}>
</Button>
</div>
</Space>
</div>
<Alert
message="管理员可以在此控制用户的角色、权限和订阅套餐"
description="角色预设权限会自动分配,也可以自定义权限。订阅套餐决定用户可用的付费功能。"
type="info"
showIcon
style={{ marginBottom: 24 }}
/>
<div className="filter-section" style={{ marginBottom: 24, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
<Search
placeholder="搜索用户名或邮箱"
@@ -167,100 +447,218 @@ const UserManagement: React.FC = () => {
/>
<Select
placeholder="角色"
style={{ width: 120 }}
style={{ width: 140 }}
onChange={(value) => setFilters({ ...filters, role: value })}
allowClear
>
<Option value=""></Option>
<Option value="ADMIN"></Option>
<Option value="MANAGER"></Option>
<Option value="OPERATOR"></Option>
<Option value="FINANCE"></Option>
{Object.entries(ROLE_CONFIG).map(([key, config]) => (
<Option key={key} value={key}>
<Tag color={config.color} style={{ margin: 0 }}>{config.label}</Tag>
</Option>
))}
</Select>
<Select
placeholder="状态"
style={{ width: 120 }}
onChange={(value) => setFilters({ ...filters, status: value })}
allowClear
>
<Option value=""></Option>
<Option value="active"></Option>
<Option value="inactive"></Option>
</Select>
<Button
icon={<SearchOutlined />}
onClick={fetchUsers}
>
<Button icon={<SearchOutlined />} onClick={fetchUsers}>
</Button>
</div>
<Card title="用户列表">
<Card>
<Table
columns={columns}
dataSource={users}
dataSource={users.filter(u => {
if (filters.role && u.role !== filters.role) return false;
if (filters.status && u.status !== filters.status) return false;
if (filters.search && !u.name.includes(filters.search) && !u.email.includes(filters.search)) return false;
return true;
})}
loading={loading}
rowKey="id"
pagination={{
pageSize: 10,
showSizeChanger: true,
}}
pagination={{ pageSize: 10, showSizeChanger: true }}
/>
</Card>
<Modal
title="添加用户"
title={selectedUser ? '编辑用户' : '添加用户'}
open={isModalVisible}
onCancel={() => setIsModalVisible(false)}
footer={null}
width={500}
>
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
>
<Form.Item
name="name"
label="姓名"
rules={[{ required: true, message: '请输入姓名' }]}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item name="name" label="姓名" rules={[{ required: true, message: '请输入姓名' }]}>
<Input placeholder="请输入姓名" />
</Form.Item>
<Form.Item
name="email"
label="邮箱"
rules={[{ required: true, message: '请输入邮箱' }]}
>
<Input placeholder="请输入邮箱" />
<Form.Item name="email" label="邮箱" rules={[{ required: true, message: '请输入邮箱' }]}>
<Input placeholder="请输入邮箱" disabled={!!selectedUser} />
</Form.Item>
<Form.Item
name="password"
label="密码"
rules={[{ required: true, message: '请输入密码' }]}
>
<Input.Password placeholder="请输入密码" />
</Form.Item>
<Form.Item
name="role"
label="角色"
rules={[{ required: true, message: '请选择角色' }]}
>
{!selectedUser && (
<Form.Item name="password" label="密码" rules={[{ required: true, message: '请输入密码' }]}>
<Input.Password placeholder="请输入密码" />
</Form.Item>
)}
<Form.Item name="role" label="角色" rules={[{ required: true, message: '请选择角色' }]}>
<Select placeholder="请选择角色">
<Option value="ADMIN"></Option>
<Option value="MANAGER"></Option>
<Option value="OPERATOR"></Option>
<Option value="FINANCE"></Option>
<Option value="SOURCING"></Option>
<Option value="LOGISTICS"></Option>
<Option value="ANALYST"></Option>
{Object.entries(ROLE_CONFIG).map(([key, config]) => (
<Option key={key} value={key}>
<Space>
<Tag color={config.color}>{config.label}</Tag>
<span style={{ color: '#999', fontSize: 12 }}>{config.description}</span>
</Space>
</Option>
))}
</Select>
</Form.Item>
<Form.Item name="status" label="状态" initialValue="active">
<Select>
<Option value="active"></Option>
<Option value="inactive"></Option>
</Select>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
<Space>
<Button type="primary" htmlType="submit" loading={loading}>
</Button>
<Button onClick={() => setIsModalVisible(false)}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title={<><SafetyOutlined style={{ marginRight: 8 }} /> - {selectedUser?.name}</>}
open={isPermissionModalVisible}
onCancel={() => setIsPermissionModalVisible(false)}
footer={null}
width={700}
>
<Alert
message="角色预设权限"
description="选择角色后会自动应用该角色的预设权限,也可以关闭预设自定义权限。"
type="info"
showIcon
style={{ marginBottom: 16 }}
/>
<Form form={permissionForm} layout="vertical" onFinish={handlePermissionSubmit}>
<Form.Item name="role" label="角色" rules={[{ required: true }]}>
<Select>
{Object.entries(ROLE_CONFIG).map(([key, config]) => (
<Option key={key} value={key}>
<Tag color={config.color}>{config.label}</Tag>
</Option>
))}
</Select>
</Form.Item>
<Form.Item name="usePreset" label="使用角色预设权限" valuePropName="checked" initialValue={true}>
<Switch checkedChildren="预设" unCheckedChildren="自定义" />
</Form.Item>
<Form.Item name="permissions" label="自定义权限">
<Select mode="multiple" placeholder="选择权限">
{ALL_PERMISSIONS.map(p => (
<Option key={p.key} value={p.key}>
{p.label}
</Option>
))}
</Select>
</Form.Item>
<Divider />
<Descriptions title="当前权限列表" column={3} size="small">
{(selectedUser?.permissions || []).map(p => {
const perm = ALL_PERMISSIONS.find(ap => ap.key === p);
return (
<Descriptions.Item key={p}>
<Tag color={p === '*' ? 'red' : 'blue'}>
{p === '*' ? '全部权限' : perm?.label || p}
</Tag>
</Descriptions.Item>
);
})}
</Descriptions>
<Divider />
<Form.Item>
<Space>
<Button type="primary" htmlType="submit"></Button>
<Button onClick={() => setIsPermissionModalVisible(false)}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
<Modal
title={<><CrownOutlined style={{ marginRight: 8 }} /> - {selectedUser?.name}</>}
open={isSubscriptionModalVisible}
onCancel={() => setIsSubscriptionModalVisible(false)}
footer={null}
width={700}
>
<Form form={subscriptionForm} layout="vertical" onFinish={handleSubscriptionSubmit}>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="plan" label="套餐" rules={[{ required: true }]}>
<Select>
{PLAN_OPTIONS.map(p => (
<Option key={p.value} value={p.value}>
<Tag color={p.color}>{p.label}</Tag>
</Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="expiresAt" label="到期时间">
<Input type="date" />
</Form.Item>
</Col>
</Row>
<Form.Item name="features" label="付费功能">
<Select mode="multiple" placeholder="选择付费功能">
{ALL_FEATURES.map(f => (
<Option key={f.key} value={f.key}>
<Space direction="vertical" size={0}>
<span>{f.label}</span>
<span style={{ fontSize: 12, color: '#999' }}>{f.description}</span>
</Space>
</Option>
))}
</Select>
</Form.Item>
<Divider />
<Descriptions title="当前订阅状态" column={2} size="small" bordered>
<Descriptions.Item label="当前套餐">
<Tag color={PLAN_OPTIONS.find(p => p.value === selectedUser?.subscription?.plan)?.color}>
{PLAN_OPTIONS.find(p => p.value === selectedUser?.subscription?.plan)?.label}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="到期时间">
{selectedUser?.subscription?.expiresAt || '永久'}
</Descriptions.Item>
<Descriptions.Item label="已开通功能" span={2}>
<Space wrap>
{(selectedUser?.subscription?.features || []).map(f => {
const feature = ALL_FEATURES.find(af => af.key === f);
return <Tag key={f} color="green">{feature?.label || f}</Tag>;
})}
{(!selectedUser?.subscription?.features || selectedUser.subscription.features.length === 0) && (
<span style={{ color: '#999' }}></span>
)}
</Space>
</Descriptions.Item>
</Descriptions>
<Divider />
<Form.Item>
<Space>
<Button type="primary" htmlType="submit"></Button>
<Button onClick={() => setIsSubscriptionModalVisible(false)}></Button>
</Space>
</Form.Item>
</Form>
</Modal>
@@ -268,4 +666,4 @@ const UserManagement: React.FC = () => {
);
};
export default UserManagement;
export default UserManagement;

View File

@@ -1,9 +0,0 @@
import ProfileSettings from './ProfileSettings';
import TenantSettings from './TenantSettings';
import UserManagement from './UserManagement';
export {
ProfileSettings,
TenantSettings,
UserManagement,
};

View File

@@ -41,7 +41,6 @@ import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
KeyOutlined,
MailOutlined,
MobileOutlined,
DesktopOutlined,