- 更新 DashboardLayout 组件,统一使用演示模式布局 - 实现仪表盘页面的完整演示数据和功能 - 完成用户管理页面的演示模式,包含搜索、过滤、分页等功能 - 实现通话记录页面的演示数据和录音播放功能 - 完成翻译员管理页面的演示模式 - 实现订单管理页面的完整功能 - 完成发票管理页面的演示数据 - 更新文档管理页面 - 添加 utils.ts 工具函数库 - 完善 API 路由和数据库结构 - 修复各种 TypeScript 类型错误 - 统一界面风格和用户体验
683 lines
29 KiB
TypeScript
683 lines
29 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import DashboardLayout from '../../components/Layout/DashboardLayout';
|
||
import {
|
||
BuildingOfficeIcon,
|
||
MagnifyingGlassIcon,
|
||
PlusIcon,
|
||
EyeIcon,
|
||
PencilIcon,
|
||
TrashIcon,
|
||
ClockIcon,
|
||
CheckCircleIcon,
|
||
XCircleIcon,
|
||
ChevronLeftIcon,
|
||
ChevronRightIcon,
|
||
UserGroupIcon,
|
||
CalendarIcon,
|
||
CurrencyDollarIcon,
|
||
DocumentTextIcon,
|
||
StarIcon,
|
||
PhoneIcon,
|
||
EnvelopeIcon
|
||
} from '@heroicons/react/24/outline';
|
||
|
||
interface Enterprise {
|
||
id: string;
|
||
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;
|
||
}
|
||
|
||
export default function Enterprise() {
|
||
const [enterprises, setEnterprises] = useState<Enterprise[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [searchTerm, setSearchTerm] = useState('');
|
||
const [filterPackage, setFilterPackage] = useState<'all' | 'basic' | 'standard' | 'premium' | 'custom'>('all');
|
||
const [filterStatus, setFilterStatus] = useState<'all' | 'active' | 'expired' | 'pending' | 'cancelled'>('all');
|
||
const [currentPage, setCurrentPage] = useState(1);
|
||
const [selectedEnterprises, setSelectedEnterprises] = useState<string[]>([]);
|
||
const enterprisesPerPage = 10;
|
||
|
||
useEffect(() => {
|
||
loadEnterprises();
|
||
}, [searchTerm, filterPackage, filterStatus, currentPage]);
|
||
|
||
const loadEnterprises = async () => {
|
||
try {
|
||
setLoading(true);
|
||
|
||
// 模拟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;
|
||
|
||
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)
|
||
);
|
||
}
|
||
|
||
if (filterPackage !== 'all') {
|
||
filteredData = filteredData.filter(enterprise => enterprise.servicePackage === filterPackage);
|
||
}
|
||
|
||
if (filterStatus !== 'all') {
|
||
filteredData = filteredData.filter(enterprise => enterprise.contractStatus === filterStatus);
|
||
}
|
||
|
||
setEnterprises(filteredData);
|
||
setLoading(false);
|
||
}, 1000);
|
||
} catch (error) {
|
||
console.error('加载企业数据失败:', error);
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleSelectEnterprise = (enterpriseId: string) => {
|
||
setSelectedEnterprises(prev =>
|
||
prev.includes(enterpriseId)
|
||
? prev.filter(id => id !== enterpriseId)
|
||
: [...prev, enterpriseId]
|
||
);
|
||
};
|
||
|
||
const handleSelectAll = () => {
|
||
if (selectedEnterprises.length === enterprises.length) {
|
||
setSelectedEnterprises([]);
|
||
} else {
|
||
setSelectedEnterprises(enterprises.map(enterprise => enterprise.id));
|
||
}
|
||
};
|
||
|
||
const getPackageText = (packageType: string) => {
|
||
switch (packageType) {
|
||
case 'basic': return '基础版';
|
||
case 'standard': return '标准版';
|
||
case 'premium': return '高级版';
|
||
case 'custom': return '定制版';
|
||
default: return packageType;
|
||
}
|
||
};
|
||
|
||
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';
|
||
}
|
||
};
|
||
|
||
const getStatusText = (status: string) => {
|
||
switch (status) {
|
||
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';
|
||
}
|
||
};
|
||
|
||
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" />;
|
||
}
|
||
};
|
||
|
||
const getCompanySizeText = (size: string) => {
|
||
switch (size) {
|
||
case 'small': return '小型企业';
|
||
case 'medium': return '中型企业';
|
||
case 'large': return '大型企业';
|
||
case 'enterprise': return '集团企业';
|
||
default: return size;
|
||
}
|
||
};
|
||
|
||
const formatCurrency = (amount: number) => {
|
||
return new Intl.NumberFormat('zh-CN', {
|
||
style: 'currency',
|
||
currency: 'CNY'
|
||
}).format(amount);
|
||
};
|
||
|
||
const totalPages = Math.ceil(enterprises.length / enterprisesPerPage);
|
||
const currentEnterprises = enterprises.slice(
|
||
(currentPage - 1) * enterprisesPerPage,
|
||
currentPage * enterprisesPerPage
|
||
);
|
||
|
||
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>
|
||
</div>
|
||
</div>
|
||
</DashboardLayout>
|
||
);
|
||
}
|
||
|
||
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>
|
||
</div>
|
||
<div className="mt-4 sm:mt-0">
|
||
<button
|
||
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"
|
||
>
|
||
<PlusIcon className="h-4 w-4 mr-2" />
|
||
添加企业客户
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 统计卡片 */}
|
||
<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" />
|
||
</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.length}</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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" />
|
||
</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.filter(e => e.contractStatus === 'active').length}
|
||
</dd>
|
||
</dl>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
</div>
|
||
</div>
|
||
|
||
<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>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 搜索和过滤 */}
|
||
<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>
|
||
|
||
{/* 服务套餐过滤 */}
|
||
<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>
|
||
|
||
{/* 合同状态过滤 */}
|
||
<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>
|
||
</div>
|
||
|
||
{/* 批量操作 */}
|
||
{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} 个企业客户
|
||
</span>
|
||
<div className="flex space-x-2">
|
||
<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">
|
||
续约提醒
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 企业列表 */}
|
||
<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">
|
||
<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={enterprises.length > 0 && selectedEnterprises.length === enterprises.length}
|
||
onChange={handleSelectAll}
|
||
/>
|
||
</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>
|
||
|
||
{/* 分页 */}
|
||
{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">
|
||
<button
|
||
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"
|
||
>
|
||
上一页
|
||
</button>
|
||
<button
|
||
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"
|
||
>
|
||
下一页
|
||
</button>
|
||
</div>
|
||
<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>
|
||
</div>
|
||
<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) => (
|
||
<button
|
||
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'
|
||
}`}
|
||
>
|
||
{page}
|
||
</button>
|
||
))}
|
||
<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>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</DashboardLayout>
|
||
);
|
||
}
|