2025-06-30 19:42:43 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import DashboardLayout from '../../components/Layout/DashboardLayout';
|
2025-06-29 16:13:50 +08:00
|
|
|
|
import {
|
|
|
|
|
BuildingOfficeIcon,
|
2025-06-30 19:42:43 +08:00
|
|
|
|
MagnifyingGlassIcon,
|
2025-06-29 16:13:50 +08:00
|
|
|
|
PlusIcon,
|
2025-06-30 19:42:43 +08:00
|
|
|
|
EyeIcon,
|
2025-06-29 16:13:50 +08:00
|
|
|
|
PencilIcon,
|
|
|
|
|
TrashIcon,
|
2025-06-30 19:42:43 +08:00
|
|
|
|
ClockIcon,
|
|
|
|
|
CheckCircleIcon,
|
|
|
|
|
XCircleIcon,
|
2025-06-29 16:13:50 +08:00
|
|
|
|
ChevronLeftIcon,
|
2025-06-30 19:42:43 +08:00
|
|
|
|
ChevronRightIcon,
|
|
|
|
|
UserGroupIcon,
|
|
|
|
|
CalendarIcon,
|
|
|
|
|
CurrencyDollarIcon,
|
|
|
|
|
DocumentTextIcon,
|
|
|
|
|
StarIcon,
|
|
|
|
|
PhoneIcon,
|
|
|
|
|
EnvelopeIcon
|
2025-06-29 16:13:50 +08:00
|
|
|
|
} from '@heroicons/react/24/outline';
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
interface Enterprise {
|
2025-06-29 16:13:50 +08:00
|
|
|
|
id: string;
|
2025-06-30 19:42:43 +08:00
|
|
|
|
companyName: string;
|
|
|
|
|
contactPerson: string;
|
|
|
|
|
contactPhone: string;
|
|
|
|
|
contactEmail: string;
|
|
|
|
|
industry: string;
|
|
|
|
|
companySize: 'small' | 'medium' | 'large' | 'enterprise';
|
|
|
|
|
servicePackage: 'basic' | 'standard' | 'premium' | 'custom';
|
|
|
|
|
contractStatus: 'active' | 'expired' | 'pending' | 'cancelled';
|
|
|
|
|
contractValue: number;
|
|
|
|
|
contractStart: string;
|
|
|
|
|
contractEnd: string;
|
|
|
|
|
monthlyUsage: number;
|
|
|
|
|
totalOrders: number;
|
|
|
|
|
rating: number;
|
|
|
|
|
address: string;
|
|
|
|
|
website?: string;
|
|
|
|
|
notes?: string;
|
|
|
|
|
createdAt: string;
|
|
|
|
|
lastActivity: string;
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
export default function Enterprise() {
|
|
|
|
|
const [enterprises, setEnterprises] = useState<Enterprise[]>([]);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
const [loading, setLoading] = useState(true);
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
|
|
|
const [filterPackage, setFilterPackage] = useState<'all' | 'basic' | 'standard' | 'premium' | 'custom'>('all');
|
|
|
|
|
const [filterStatus, setFilterStatus] = useState<'all' | 'active' | 'expired' | 'pending' | 'cancelled'>('all');
|
2025-06-29 16:13:50 +08:00
|
|
|
|
const [currentPage, setCurrentPage] = useState(1);
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const [selectedEnterprises, setSelectedEnterprises] = useState<string[]>([]);
|
|
|
|
|
const enterprisesPerPage = 10;
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
loadEnterprises();
|
|
|
|
|
}, [searchTerm, filterPackage, filterStatus, currentPage]);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const loadEnterprises = async () => {
|
2025-06-29 16:13:50 +08:00
|
|
|
|
try {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
// 模拟API调用
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
const mockData: Enterprise[] = [
|
|
|
|
|
{
|
|
|
|
|
id: '1',
|
|
|
|
|
companyName: '科技创新有限公司',
|
|
|
|
|
contactPerson: '张总',
|
|
|
|
|
contactPhone: '+86 138-0000-0001',
|
|
|
|
|
contactEmail: 'zhang@tech-innovation.com',
|
|
|
|
|
industry: '科技',
|
|
|
|
|
companySize: 'large',
|
|
|
|
|
servicePackage: 'premium',
|
|
|
|
|
contractStatus: 'active',
|
|
|
|
|
contractValue: 500000,
|
|
|
|
|
contractStart: '2024-01-01',
|
|
|
|
|
contractEnd: '2024-12-31',
|
|
|
|
|
monthlyUsage: 45000,
|
|
|
|
|
totalOrders: 156,
|
|
|
|
|
rating: 4.8,
|
|
|
|
|
address: '北京市朝阳区科技园区A座',
|
|
|
|
|
website: 'https://tech-innovation.com',
|
|
|
|
|
notes: '重要客户,需要优先服务',
|
|
|
|
|
createdAt: '2023-12-15',
|
|
|
|
|
lastActivity: '2024-01-20 15:30'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '2',
|
|
|
|
|
companyName: '国际贸易集团',
|
|
|
|
|
contactPerson: '李经理',
|
|
|
|
|
contactPhone: '+86 139-0000-0002',
|
|
|
|
|
contactEmail: 'li@international-trade.com',
|
|
|
|
|
industry: '贸易',
|
|
|
|
|
companySize: 'enterprise',
|
|
|
|
|
servicePackage: 'custom',
|
|
|
|
|
contractStatus: 'active',
|
|
|
|
|
contractValue: 800000,
|
|
|
|
|
contractStart: '2023-06-01',
|
|
|
|
|
contractEnd: '2025-05-31',
|
|
|
|
|
monthlyUsage: 68000,
|
|
|
|
|
totalOrders: 289,
|
|
|
|
|
rating: 4.9,
|
|
|
|
|
address: '上海市浦东新区金融中心B座',
|
|
|
|
|
website: 'https://international-trade.com',
|
|
|
|
|
notes: '多语言需求,主要涉及欧洲市场',
|
|
|
|
|
createdAt: '2023-05-20',
|
|
|
|
|
lastActivity: '2024-01-21 09:15'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '3',
|
|
|
|
|
companyName: '医疗设备公司',
|
|
|
|
|
contactPerson: '王主任',
|
|
|
|
|
contactPhone: '+86 136-0000-0003',
|
|
|
|
|
contactEmail: 'wang@medical-device.com',
|
|
|
|
|
industry: '医疗',
|
|
|
|
|
companySize: 'medium',
|
|
|
|
|
servicePackage: 'standard',
|
|
|
|
|
contractStatus: 'active',
|
|
|
|
|
contractValue: 200000,
|
|
|
|
|
contractStart: '2024-01-15',
|
|
|
|
|
contractEnd: '2024-07-14',
|
|
|
|
|
monthlyUsage: 15000,
|
|
|
|
|
totalOrders: 67,
|
|
|
|
|
rating: 4.5,
|
|
|
|
|
address: '广州市天河区医疗产业园',
|
|
|
|
|
notes: '专业医疗术语翻译需求',
|
|
|
|
|
createdAt: '2024-01-10',
|
|
|
|
|
lastActivity: '2024-01-19 14:20'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '4',
|
|
|
|
|
companyName: '教育培训机构',
|
|
|
|
|
contactPerson: '陈校长',
|
|
|
|
|
contactPhone: '+86 135-0000-0004',
|
|
|
|
|
contactEmail: 'chen@education.com',
|
|
|
|
|
industry: '教育',
|
|
|
|
|
companySize: 'small',
|
|
|
|
|
servicePackage: 'basic',
|
|
|
|
|
contractStatus: 'expired',
|
|
|
|
|
contractValue: 50000,
|
|
|
|
|
contractStart: '2023-09-01',
|
|
|
|
|
contractEnd: '2023-12-31',
|
|
|
|
|
monthlyUsage: 8000,
|
|
|
|
|
totalOrders: 34,
|
|
|
|
|
rating: 4.2,
|
|
|
|
|
address: '深圳市南山区教育城',
|
|
|
|
|
notes: '合同已到期,需要续约',
|
|
|
|
|
createdAt: '2023-08-25',
|
|
|
|
|
lastActivity: '2024-01-05 10:45'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: '5',
|
|
|
|
|
companyName: '新兴科技公司',
|
|
|
|
|
contactPerson: '刘总监',
|
|
|
|
|
contactPhone: '+86 137-0000-0005',
|
|
|
|
|
contactEmail: 'liu@emerging-tech.com',
|
|
|
|
|
industry: '科技',
|
|
|
|
|
companySize: 'medium',
|
|
|
|
|
servicePackage: 'standard',
|
|
|
|
|
contractStatus: 'pending',
|
|
|
|
|
contractValue: 150000,
|
|
|
|
|
contractStart: '2024-02-01',
|
|
|
|
|
contractEnd: '2024-08-31',
|
|
|
|
|
monthlyUsage: 0,
|
|
|
|
|
totalOrders: 0,
|
|
|
|
|
rating: 0,
|
|
|
|
|
address: '杭州市西湖区创新园',
|
|
|
|
|
website: 'https://emerging-tech.com',
|
|
|
|
|
notes: '新客户,正在洽谈合同',
|
|
|
|
|
createdAt: '2024-01-18',
|
|
|
|
|
lastActivity: '2024-01-21 16:00'
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
// 应用过滤器
|
|
|
|
|
let filteredData = mockData;
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
if (searchTerm) {
|
|
|
|
|
const term = searchTerm.toLowerCase();
|
|
|
|
|
filteredData = filteredData.filter(enterprise =>
|
|
|
|
|
enterprise.companyName.toLowerCase().includes(term) ||
|
|
|
|
|
enterprise.contactPerson.toLowerCase().includes(term) ||
|
|
|
|
|
enterprise.contactEmail.toLowerCase().includes(term) ||
|
|
|
|
|
enterprise.industry.toLowerCase().includes(term)
|
|
|
|
|
);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
2025-06-30 19:42:43 +08:00
|
|
|
|
|
|
|
|
|
if (filterPackage !== 'all') {
|
|
|
|
|
filteredData = filteredData.filter(enterprise => enterprise.servicePackage === filterPackage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (filterStatus !== 'all') {
|
|
|
|
|
filteredData = filteredData.filter(enterprise => enterprise.contractStatus === filterStatus);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
setEnterprises(filteredData);
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}, 1000);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
} catch (error) {
|
2025-06-30 19:42:43 +08:00
|
|
|
|
console.error('加载企业数据失败:', error);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const handleSelectEnterprise = (enterpriseId: string) => {
|
|
|
|
|
setSelectedEnterprises(prev =>
|
|
|
|
|
prev.includes(enterpriseId)
|
|
|
|
|
? prev.filter(id => id !== enterpriseId)
|
|
|
|
|
: [...prev, enterpriseId]
|
|
|
|
|
);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const handleSelectAll = () => {
|
|
|
|
|
if (selectedEnterprises.length === enterprises.length) {
|
|
|
|
|
setSelectedEnterprises([]);
|
|
|
|
|
} else {
|
|
|
|
|
setSelectedEnterprises(enterprises.map(enterprise => enterprise.id));
|
|
|
|
|
}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const getPackageText = (packageType: string) => {
|
|
|
|
|
switch (packageType) {
|
|
|
|
|
case 'basic': return '基础版';
|
|
|
|
|
case 'standard': return '标准版';
|
|
|
|
|
case 'premium': return '高级版';
|
|
|
|
|
case 'custom': return '定制版';
|
|
|
|
|
default: return packageType;
|
|
|
|
|
}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const getPackageColor = (packageType: string) => {
|
|
|
|
|
switch (packageType) {
|
|
|
|
|
case 'basic': return 'bg-gray-100 text-gray-800';
|
|
|
|
|
case 'standard': return 'bg-blue-100 text-blue-800';
|
|
|
|
|
case 'premium': return 'bg-purple-100 text-purple-800';
|
|
|
|
|
case 'custom': return 'bg-green-100 text-green-800';
|
|
|
|
|
default: return 'bg-gray-100 text-gray-800';
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getStatusText = (status: string) => {
|
|
|
|
|
switch (status) {
|
2025-06-30 19:42:43 +08:00
|
|
|
|
case 'active': return '生效中';
|
|
|
|
|
case 'expired': return '已过期';
|
|
|
|
|
case 'pending': return '待生效';
|
|
|
|
|
case 'cancelled': return '已取消';
|
|
|
|
|
default: return status;
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getStatusColor = (status: string) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'active': return 'bg-green-100 text-green-800';
|
|
|
|
|
case 'expired': return 'bg-red-100 text-red-800';
|
|
|
|
|
case 'pending': return 'bg-yellow-100 text-yellow-800';
|
|
|
|
|
case 'cancelled': return 'bg-gray-100 text-gray-800';
|
|
|
|
|
default: return 'bg-gray-100 text-gray-800';
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const getStatusIcon = (status: string) => {
|
|
|
|
|
switch (status) {
|
|
|
|
|
case 'active': return <CheckCircleIcon className="h-4 w-4" />;
|
|
|
|
|
case 'expired': return <XCircleIcon className="h-4 w-4" />;
|
|
|
|
|
case 'pending': return <ClockIcon className="h-4 w-4" />;
|
|
|
|
|
case 'cancelled': return <XCircleIcon className="h-4 w-4" />;
|
|
|
|
|
default: return <ClockIcon className="h-4 w-4" />;
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const getCompanySizeText = (size: string) => {
|
|
|
|
|
switch (size) {
|
|
|
|
|
case 'small': return '小型企业';
|
|
|
|
|
case 'medium': return '中型企业';
|
|
|
|
|
case 'large': return '大型企业';
|
|
|
|
|
case 'enterprise': return '集团企业';
|
|
|
|
|
default: return size;
|
2025-06-29 16:13:50 +08:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const formatCurrency = (amount: number) => {
|
|
|
|
|
return new Intl.NumberFormat('zh-CN', {
|
|
|
|
|
style: 'currency',
|
|
|
|
|
currency: 'CNY'
|
|
|
|
|
}).format(amount);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
};
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
const totalPages = Math.ceil(enterprises.length / enterprisesPerPage);
|
|
|
|
|
const currentEnterprises = enterprises.slice(
|
|
|
|
|
(currentPage - 1) * enterprisesPerPage,
|
|
|
|
|
currentPage * enterprisesPerPage
|
|
|
|
|
);
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
if (loading) {
|
|
|
|
|
return (
|
|
|
|
|
<DashboardLayout title="企业服务">
|
|
|
|
|
<div className="flex items-center justify-center h-64">
|
|
|
|
|
<div className="text-center">
|
|
|
|
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-indigo-600 mx-auto"></div>
|
|
|
|
|
<div className="mt-4 text-lg text-gray-600">加载企业数据中...</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
</DashboardLayout>
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
return (
|
|
|
|
|
<DashboardLayout title="企业服务">
|
|
|
|
|
<div className="space-y-6">
|
|
|
|
|
{/* 页面标题 */}
|
|
|
|
|
<div className="sm:flex sm:items-center sm:justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-2xl font-bold text-gray-900">企业服务</h1>
|
|
|
|
|
<p className="mt-1 text-sm text-gray-600">
|
|
|
|
|
管理企业客户、服务套餐和合同信息,提供专业的企业级翻译服务。
|
|
|
|
|
</p>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="mt-4 sm:mt-0">
|
2025-06-29 16:13:50 +08:00
|
|
|
|
<button
|
2025-06-30 19:42:43 +08:00
|
|
|
|
type="button"
|
|
|
|
|
className="inline-flex items-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
2025-06-29 16:13:50 +08:00
|
|
|
|
>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<PlusIcon className="h-4 w-4 mr-2" />
|
|
|
|
|
添加企业客户
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 统计卡片 */}
|
|
|
|
|
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
|
|
|
<div className="p-5">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
<BuildingOfficeIcon className="h-6 w-6 text-gray-400" />
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="ml-5 w-0 flex-1">
|
|
|
|
|
<dl>
|
|
|
|
|
<dt className="text-sm font-medium text-gray-500 truncate">企业客户总数</dt>
|
|
|
|
|
<dd className="text-lg font-medium text-gray-900">{enterprises.length}</dd>
|
|
|
|
|
</dl>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
|
|
|
<div className="p-5">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
<CheckCircleIcon className="h-6 w-6 text-green-400" />
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="ml-5 w-0 flex-1">
|
|
|
|
|
<dl>
|
|
|
|
|
<dt className="text-sm font-medium text-gray-500 truncate">活跃合同</dt>
|
|
|
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
|
|
|
{enterprises.filter(e => e.contractStatus === 'active').length}
|
|
|
|
|
</dd>
|
|
|
|
|
</dl>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
|
|
|
<div className="p-5">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
<CurrencyDollarIcon className="h-6 w-6 text-green-400" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="ml-5 w-0 flex-1">
|
|
|
|
|
<dl>
|
|
|
|
|
<dt className="text-sm font-medium text-gray-500 truncate">合同总价值</dt>
|
|
|
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
|
|
|
{formatCurrency(enterprises.reduce((sum, e) => sum + e.contractValue, 0))}
|
|
|
|
|
</dd>
|
|
|
|
|
</dl>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="bg-white overflow-hidden shadow rounded-lg">
|
|
|
|
|
<div className="p-5">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex-shrink-0">
|
|
|
|
|
<DocumentTextIcon className="h-6 w-6 text-blue-400" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="ml-5 w-0 flex-1">
|
|
|
|
|
<dl>
|
|
|
|
|
<dt className="text-sm font-medium text-gray-500 truncate">总订单数</dt>
|
|
|
|
|
<dd className="text-lg font-medium text-gray-900">
|
|
|
|
|
{enterprises.reduce((sum, e) => sum + e.totalOrders, 0)}
|
|
|
|
|
</dd>
|
|
|
|
|
</dl>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 搜索和过滤 */}
|
|
|
|
|
<div className="bg-white shadow rounded-lg p-6">
|
|
|
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
|
|
|
|
{/* 搜索框 */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
|
|
|
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" />
|
|
|
|
|
</div>
|
|
|
|
|
<input
|
|
|
|
|
type="text"
|
|
|
|
|
placeholder="搜索企业名称、联系人或行业..."
|
|
|
|
|
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500"
|
|
|
|
|
value={searchTerm}
|
|
|
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 服务套餐过滤 */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<select
|
|
|
|
|
className="block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md"
|
|
|
|
|
value={filterPackage}
|
|
|
|
|
onChange={(e) => setFilterPackage(e.target.value as any)}
|
|
|
|
|
>
|
|
|
|
|
<option value="all">所有套餐</option>
|
|
|
|
|
<option value="basic">基础版</option>
|
|
|
|
|
<option value="standard">标准版</option>
|
|
|
|
|
<option value="premium">高级版</option>
|
|
|
|
|
<option value="custom">定制版</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 合同状态过滤 */}
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<select
|
|
|
|
|
className="block w-full pl-3 pr-10 py-2 text-base border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 rounded-md"
|
|
|
|
|
value={filterStatus}
|
|
|
|
|
onChange={(e) => setFilterStatus(e.target.value as any)}
|
|
|
|
|
>
|
|
|
|
|
<option value="all">所有状态</option>
|
|
|
|
|
<option value="active">生效中</option>
|
|
|
|
|
<option value="expired">已过期</option>
|
|
|
|
|
<option value="pending">待生效</option>
|
|
|
|
|
<option value="cancelled">已取消</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 批量操作 */}
|
|
|
|
|
{selectedEnterprises.length > 0 && (
|
|
|
|
|
<div className="mt-4 flex items-center justify-between bg-gray-50 p-3 rounded-md">
|
|
|
|
|
<span className="text-sm text-gray-700">
|
|
|
|
|
已选择 {selectedEnterprises.length} 个企业客户
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</span>
|
|
|
|
|
<div className="flex space-x-2">
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<button className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500">
|
|
|
|
|
批量导出
|
|
|
|
|
</button>
|
|
|
|
|
<button className="inline-flex items-center px-3 py-1 border border-transparent text-sm leading-4 font-medium rounded-md text-white bg-green-600 hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500">
|
|
|
|
|
续约提醒
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
)}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 企业列表 */}
|
|
|
|
|
<div className="bg-white shadow rounded-lg overflow-hidden">
|
|
|
|
|
<div className="overflow-x-auto">
|
|
|
|
|
<table className="min-w-full divide-y divide-gray-200">
|
|
|
|
|
<thead className="bg-gray-50">
|
|
|
|
|
<tr>
|
|
|
|
|
<th scope="col" className="relative px-6 py-3">
|
2025-06-29 16:13:50 +08:00
|
|
|
|
<input
|
2025-06-30 19:42:43 +08:00
|
|
|
|
type="checkbox"
|
|
|
|
|
className="absolute left-4 top-1/2 -mt-2 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
|
|
|
checked={enterprises.length > 0 && selectedEnterprises.length === enterprises.length}
|
|
|
|
|
onChange={handleSelectAll}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
/>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
企业信息
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
联系信息
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
服务套餐
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
合同状态
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
合同价值
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
使用情况
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
评价
|
|
|
|
|
</th>
|
|
|
|
|
<th scope="col" className="relative px-6 py-3">
|
|
|
|
|
<span className="sr-only">操作</span>
|
|
|
|
|
</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody className="bg-white divide-y divide-gray-200">
|
|
|
|
|
{currentEnterprises.map((enterprise) => (
|
|
|
|
|
<tr key={enterprise.id} className="hover:bg-gray-50">
|
|
|
|
|
<td className="relative px-6 py-4">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
className="absolute left-4 top-1/2 -mt-2 h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
|
|
|
|
|
checked={selectedEnterprises.includes(enterprise.id)}
|
|
|
|
|
onChange={() => handleSelectEnterprise(enterprise.id)}
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<BuildingOfficeIcon className="h-5 w-5 text-gray-400 mr-3" />
|
|
|
|
|
<div>
|
|
|
|
|
<div className="text-sm font-medium text-gray-900">{enterprise.companyName}</div>
|
|
|
|
|
<div className="text-sm text-gray-500">{enterprise.industry} · {getCompanySizeText(enterprise.companySize)}</div>
|
|
|
|
|
{enterprise.website && (
|
|
|
|
|
<div className="text-xs text-blue-500">{enterprise.website}</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="flex items-center text-sm font-medium text-gray-900">
|
|
|
|
|
<UserGroupIcon className="h-4 w-4 mr-1" />
|
|
|
|
|
{enterprise.contactPerson}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center text-sm text-gray-500 mt-1">
|
|
|
|
|
<PhoneIcon className="h-4 w-4 mr-1" />
|
|
|
|
|
{enterprise.contactPhone}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center text-sm text-gray-500 mt-1">
|
|
|
|
|
<EnvelopeIcon className="h-4 w-4 mr-1" />
|
|
|
|
|
{enterprise.contactEmail}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<span className={`inline-flex px-2 py-1 text-xs font-semibold rounded-full ${getPackageColor(enterprise.servicePackage)}`}>
|
|
|
|
|
{getPackageText(enterprise.servicePackage)}
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<div className="space-y-1">
|
|
|
|
|
<span className={`inline-flex items-center px-2 py-1 text-xs font-semibold rounded-full ${getStatusColor(enterprise.contractStatus)}`}>
|
|
|
|
|
{getStatusIcon(enterprise.contractStatus)}
|
|
|
|
|
<span className="ml-1">{getStatusText(enterprise.contractStatus)}</span>
|
|
|
|
|
</span>
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
{enterprise.contractStart} ~ {enterprise.contractEnd}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<div className="text-sm font-medium text-gray-900">
|
|
|
|
|
{formatCurrency(enterprise.contractValue)}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
月使用:{formatCurrency(enterprise.monthlyUsage)}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="text-sm text-gray-900">订单:{enterprise.totalOrders}</div>
|
|
|
|
|
<div className="text-xs text-gray-500">
|
|
|
|
|
最后活动:{enterprise.lastActivity}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap">
|
|
|
|
|
{enterprise.rating > 0 && (
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
<div className="flex items-center">
|
|
|
|
|
{[...Array(5)].map((_, i) => (
|
|
|
|
|
<StarIcon
|
|
|
|
|
key={i}
|
|
|
|
|
className={`h-4 w-4 ${
|
|
|
|
|
i < enterprise.rating ? 'text-yellow-400 fill-current' : 'text-gray-300'
|
|
|
|
|
}`}
|
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</div>
|
|
|
|
|
<span className="ml-2 text-sm text-gray-500">{enterprise.rating}</span>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
|
|
|
|
<div className="flex items-center space-x-2">
|
|
|
|
|
<button className="text-indigo-600 hover:text-indigo-900" title="查看详情">
|
|
|
|
|
<EyeIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
<button className="text-green-600 hover:text-green-900" title="编辑信息">
|
|
|
|
|
<PencilIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
<button className="text-blue-600 hover:text-blue-900" title="查看合同">
|
|
|
|
|
<DocumentTextIcon className="h-4 w-4" />
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{/* 分页 */}
|
|
|
|
|
{totalPages > 1 && (
|
|
|
|
|
<div className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
|
|
|
|
|
<div className="flex-1 flex justify-between sm:hidden">
|
2025-06-29 16:13:50 +08:00
|
|
|
|
<button
|
2025-06-30 19:42:43 +08:00
|
|
|
|
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
|
|
|
|
disabled={currentPage === 1}
|
|
|
|
|
className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
2025-06-29 16:13:50 +08:00
|
|
|
|
>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
上一页
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</button>
|
|
|
|
|
<button
|
2025-06-30 19:42:43 +08:00
|
|
|
|
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
|
|
|
|
|
disabled={currentPage === totalPages}
|
|
|
|
|
className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
2025-06-29 16:13:50 +08:00
|
|
|
|
>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
下一页
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</button>
|
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm text-gray-700">
|
|
|
|
|
显示第 <span className="font-medium">{(currentPage - 1) * enterprisesPerPage + 1}</span> 到{' '}
|
|
|
|
|
<span className="font-medium">{Math.min(currentPage * enterprisesPerPage, enterprises.length)}</span> 条,
|
|
|
|
|
共 <span className="font-medium">{enterprises.length}</span> 条记录
|
|
|
|
|
</p>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
<div>
|
|
|
|
|
<nav className="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
|
|
|
|
disabled={currentPage === 1}
|
|
|
|
|
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
>
|
|
|
|
|
<ChevronLeftIcon className="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
2025-06-29 16:13:50 +08:00
|
|
|
|
<button
|
2025-06-30 19:42:43 +08:00
|
|
|
|
key={page}
|
|
|
|
|
onClick={() => setCurrentPage(page)}
|
|
|
|
|
className={`relative inline-flex items-center px-4 py-2 border text-sm font-medium ${
|
|
|
|
|
page === currentPage
|
|
|
|
|
? 'z-10 bg-indigo-50 border-indigo-500 text-indigo-600'
|
|
|
|
|
: 'bg-white border-gray-300 text-gray-500 hover:bg-gray-50'
|
|
|
|
|
}`}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
{page}
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</button>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
))}
|
|
|
|
|
<button
|
|
|
|
|
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
|
|
|
|
|
disabled={currentPage === totalPages}
|
|
|
|
|
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-500 hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
|
|
|
>
|
|
|
|
|
<ChevronRightIcon className="h-5 w-5" />
|
|
|
|
|
</button>
|
|
|
|
|
</nav>
|
|
|
|
|
</div>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2025-06-30 19:42:43 +08:00
|
|
|
|
</DashboardLayout>
|
2025-06-29 16:13:50 +08:00
|
|
|
|
);
|
|
|
|
|
}
|