refactor: 重构代码结构和类型定义,优化类型安全性和代码可维护性
- 添加类型定义文件和类型引用 - 删除废弃的页面模块和导出文件 - 新增聚合管理模块和插件系统 - 修复类型错误和潜在运行时问题 - 更新API基础URL和配置 - 优化组件类型定义和事件处理 - 重构数据源接口和实现 - 完善文档和开发进度记录
This commit is contained in:
429
dashboard/src/pages/Settings/SubscriptionManage.tsx
Normal file
429
dashboard/src/pages/Settings/SubscriptionManage.tsx
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import ProfileSettings from './ProfileSettings';
|
||||
import TenantSettings from './TenantSettings';
|
||||
import UserManagement from './UserManagement';
|
||||
|
||||
export {
|
||||
ProfileSettings,
|
||||
TenantSettings,
|
||||
UserManagement,
|
||||
};
|
||||
@@ -41,7 +41,6 @@ import {
|
||||
EditOutlined,
|
||||
DeleteOutlined,
|
||||
PlusOutlined,
|
||||
KeyOutlined,
|
||||
MailOutlined,
|
||||
MobileOutlined,
|
||||
DesktopOutlined,
|
||||
|
||||
Reference in New Issue
Block a user