import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Card, Descriptions, Button, Tag, Typography, Space, Modal, Input, message, Spin, Progress, Tabs, Image, Select, Form, Alert, Divider, Upload, Rate, Timeline, Statistic, Row, Col, Avatar, } from 'antd'; import { ArrowLeftOutlined, FileTextOutlined, DownloadOutlined, EyeOutlined, EditOutlined, CheckCircleOutlined, ClockCircleOutlined, ExclamationCircleOutlined, FileOutlined, TranslationOutlined, UserOutlined, CalendarOutlined, DollarOutlined, StarOutlined, SettingOutlined, MessageOutlined, ReloadOutlined, DeleteOutlined, AuditOutlined, UploadOutlined, SwapOutlined, } from '@ant-design/icons'; import { DocumentTranslation } from '../../types'; import { database } from '../../utils/database'; import { api } from '../../utils/api'; const { Title, Text, Paragraph } = Typography; const { TextArea } = Input; const { TabPane } = Tabs; const { Option } = Select; interface DocumentDetailProps {} const DocumentDetail: React.FC = () => { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [document, setDocument] = useState(null); const [loading, setLoading] = useState(true); const [editModalVisible, setEditModalVisible] = useState(false); const [statusModalVisible, setStatusModalVisible] = useState(false); const [reassignModalVisible, setReassignModalVisible] = useState(false); const [refundModalVisible, setRefundModalVisible] = useState(false); const [adminNoteModalVisible, setAdminNoteModalVisible] = useState(false); const [retranslateModalVisible, setRetranslateModalVisible] = useState(false); const [form] = Form.useForm(); const [statusForm] = Form.useForm(); const [reassignForm] = Form.useForm(); const [refundForm] = Form.useForm(); const [noteForm] = Form.useForm(); const [retranslateForm] = Form.useForm(); useEffect(() => { if (id) { loadDocumentDetails(); } }, [id]); const loadDocumentDetails = async () => { try { setLoading(true); await database.connect(); // 模拟获取文档详情(管理员视角) const mockDocument: DocumentTranslation = { id: id!, userId: 'user_1', fileName: 'business_contract.pdf', originalSize: 2048576, fileUrl: '/documents/business_contract.pdf', translatedFileUrl: '/documents/business_contract_translated.pdf', sourceLanguage: 'zh-CN', targetLanguage: 'en-US', status: 'completed', progress: 100, quality: 'professional', urgency: 'normal', estimatedTime: 120, actualTime: 105, cost: 128.50, translatorId: 'translator_2', translatorName: '王译员', rating: 4, feedback: '翻译质量很好,术语准确,但有个别地方可以更流畅。', createdAt: '2024-01-15T09:00:00Z', completedAt: '2024-01-15T10:45:00Z', // 管理员相关字段 adminNotes: '客户对翻译质量满意,已完成交付', paymentStatus: 'paid', refundAmount: 0, qualityScore: 92, issues: [], retranslationCount: 0, clientName: '李先生', clientEmail: 'li@example.com', clientPhone: '+86 138 0013 8000', }; setDocument(mockDocument); // 填充表单数据 form.setFieldsValue({ fileName: mockDocument.fileName, quality: mockDocument.quality, urgency: mockDocument.urgency, cost: mockDocument.cost, translatorName: mockDocument.translatorName, }); statusForm.setFieldsValue({ status: mockDocument.status, }); } catch (error) { console.error('加载文档详情失败:', error); message.error('加载文档详情失败'); } finally { setLoading(false); } }; const handleEdit = async (values: any) => { if (!document) return; try { const updatedDocument = { ...document, ...values, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setEditModalVisible(false); message.success('文档信息更新成功'); } catch (error) { message.error('更新文档信息失败'); } }; const handleStatusChange = async (values: any) => { if (!document) return; try { const updatedDocument = { ...document, status: values.status, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setStatusModalVisible(false); message.success('状态更新成功'); } catch (error) { message.error('更新状态失败'); } }; const handleReassign = async (values: any) => { if (!document) return; try { const updatedDocument = { ...document, translatorId: values.translatorId, translatorName: values.translatorName, status: 'in_progress' as const, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setReassignModalVisible(false); message.success('译员重新分配成功'); } catch (error) { message.error('重新分配译员失败'); } }; const handleRefund = async (values: any) => { if (!document) return; try { const refundAmount = values.amount || document.cost; // 模拟退款API调用 await api.refundPayment(`payment_${document.id}`, refundAmount); const updatedDocument = { ...document, refundAmount: refundAmount, paymentStatus: 'refunded' as const, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setRefundModalVisible(false); message.success('退款处理成功'); } catch (error) { message.error('退款处理失败'); } }; const handleAddAdminNote = async (values: any) => { if (!document) return; try { const updatedDocument = { ...document, adminNotes: values.note, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setAdminNoteModalVisible(false); message.success('管理员备注添加成功'); } catch (error) { message.error('添加备注失败'); } }; const handleRetranslate = async (values: any) => { if (!document) return; try { const updatedDocument = { ...document, retranslationCount: (document.retranslationCount || 0) + 1, status: 'in_progress' as const, progress: 0, adminNotes: `${document.adminNotes || ''}\n重新翻译原因: ${values.reason}`, updatedAt: new Date().toISOString(), }; setDocument(updatedDocument); setRetranslateModalVisible(false); message.success('重新翻译任务已创建'); } catch (error) { message.error('创建重新翻译任务失败'); } }; const getStatusColor = (status: string) => { const colors = { pending: 'orange', in_progress: 'blue', completed: 'green', cancelled: 'red', failed: 'red', }; return colors[status as keyof typeof colors] || 'default'; }; const getStatusText = (status: string) => { const texts = { pending: '等待处理', in_progress: '翻译中', completed: '已完成', cancelled: '已取消', failed: '翻译失败', }; return texts[status as keyof typeof texts] || status; }; const getPaymentStatusColor = (status: string) => { const colors = { pending: 'orange', paid: 'green', refunded: 'purple', failed: 'red', }; return colors[status as keyof typeof colors] || 'default'; }; const getPaymentStatusText = (status: string) => { const texts = { pending: '待支付', paid: '已支付', refunded: '已退款', failed: '支付失败', }; return texts[status as keyof typeof texts] || status; }; const getQualityText = (quality: string) => { const texts = { basic: '基础版', professional: '专业版', premium: '高级版', }; return texts[quality as keyof typeof texts] || quality; }; const getUrgencyText = (urgency: string) => { const texts = { normal: '普通', urgent: '加急', emergency: '特急', }; return texts[urgency as keyof typeof texts] || urgency; }; const formatFileSize = (bytes: number) => { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; }; if (loading) { return (
加载文档详情...
); } if (!document) { return (
文档记录不存在
); } return (
{/* 头部导航 */}
文档详情 #{document.id}
{/* 管理员操作按钮 */}
{/* 系统状态提醒 */} {document.issues && document.issues.length > 0 && ( )} {/* 基本信息卡片 */} {document.fileName} {formatFileSize(document.originalSize)} {getStatusText(document.status)} {getPaymentStatusText(document.paymentStatus)} {document.clientName} {document.clientEmail} {document.clientPhone} {document.sourceLanguage} {document.targetLanguage} {getQualityText(document.quality)} {getUrgencyText(document.urgency)} {document.translatorName} 0 ? 'warning' : 'secondary'}> {document.retranslationCount || 0} 次 {new Date(document.createdAt).toLocaleString()} {document.completedAt ? new Date(document.completedAt).toLocaleString() : '-'} {document.estimatedTime} 分钟 {document.actualTime || '-'} 分钟 ¥{document.cost.toFixed(2)} 0 ? 'danger' : 'secondary'}> ¥{document.refundAmount.toFixed(2)} = 90 ? '#52c41a' : document.qualityScore >= 70 ? '#faad14' : '#ff4d4f' }}> {document.qualityScore}/100 {document.adminNotes && (
管理员备注: {document.adminNotes}
)}
{/* 文件操作卡片 */} 文件管理 } style={{ marginBottom: '24px' }} >
原始文档
{formatFileSize(document.originalSize)}
{document.translatedFileUrl && ( <>
翻译文档
已完成
)}
{/* 详细内容标签页 */} 进度追踪 } key="progress" >
文档上传
{new Date(document.createdAt).toLocaleString()}
分配译员
译员:{document.translatorName}
翻译进行中
进度:{document.progress}%
{document.status === 'completed' && (
翻译完成
{document.completedAt && new Date(document.completedAt).toLocaleString()}
)}
用户评价 } key="rating" >
{document.rating ? ( <>
服务评分: ({document.rating}/5 分)
{document.feedback && (
用户反馈: {document.feedback}
)} ) : (
暂无用户评价
)}
质量分析 } key="quality" >
= 90 ? '#52c41a' : document.qualityScore >= 70 ? '#faad14' : '#ff4d4f'} /> {document.issues && document.issues.length > 0 ? (
{document.issues.map((issue, index) => ( {issue} ))}
) : ( 无异常 )}
0 ? 'warning' : 'secondary'}> {document.retranslationCount || 0} 次重译
{/* 编辑信息弹窗 */} setEditModalVisible(false)} footer={null} width={600} >
{/* 更改状态弹窗 */} setStatusModalVisible(false)} footer={null} >
{/* 重新分配译员弹窗 */} setReassignModalVisible(false)} footer={null} >