1052 lines
32 KiB
TypeScript
1052 lines
32 KiB
TypeScript
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<DocumentDetailProps> = () => {
|
||
const { id } = useParams<{ id: string }>();
|
||
const navigate = useNavigate();
|
||
|
||
const [document, setDocument] = useState<DocumentTranslation | null>(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 (
|
||
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||
<Spin size="large" />
|
||
<div style={{ marginTop: '16px' }}>加载文档详情...</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
if (!document) {
|
||
return (
|
||
<div style={{ textAlign: 'center', padding: '50px' }}>
|
||
<div>文档记录不存在</div>
|
||
<Button type="primary" onClick={() => navigate('/documents')} style={{ marginTop: '16px' }}>
|
||
返回文档列表
|
||
</Button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div style={{ padding: '24px' }}>
|
||
{/* 头部导航 */}
|
||
<div style={{ marginBottom: '24px', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||
<div>
|
||
<Button
|
||
icon={<ArrowLeftOutlined />}
|
||
onClick={() => navigate('/documents')}
|
||
style={{ marginRight: '16px' }}
|
||
>
|
||
返回
|
||
</Button>
|
||
<Title level={2} style={{ display: 'inline-block', margin: 0 }}>
|
||
文档详情 #{document.id}
|
||
</Title>
|
||
</div>
|
||
|
||
{/* 管理员操作按钮 */}
|
||
<Space>
|
||
<Button
|
||
icon={<EditOutlined />}
|
||
onClick={() => setEditModalVisible(true)}
|
||
>
|
||
编辑信息
|
||
</Button>
|
||
<Button
|
||
icon={<SettingOutlined />}
|
||
onClick={() => setStatusModalVisible(true)}
|
||
>
|
||
更改状态
|
||
</Button>
|
||
<Button
|
||
icon={<UserOutlined />}
|
||
onClick={() => setReassignModalVisible(true)}
|
||
disabled={document.status === 'completed'}
|
||
>
|
||
重新分配
|
||
</Button>
|
||
<Button
|
||
icon={<ReloadOutlined />}
|
||
onClick={() => setRetranslateModalVisible(true)}
|
||
disabled={document.status !== 'completed'}
|
||
>
|
||
重新翻译
|
||
</Button>
|
||
<Button
|
||
icon={<DollarOutlined />}
|
||
onClick={() => setRefundModalVisible(true)}
|
||
disabled={document.paymentStatus !== 'paid'}
|
||
>
|
||
处理退款
|
||
</Button>
|
||
<Button
|
||
icon={<MessageOutlined />}
|
||
onClick={() => setAdminNoteModalVisible(true)}
|
||
>
|
||
添加备注
|
||
</Button>
|
||
</Space>
|
||
</div>
|
||
|
||
{/* 系统状态提醒 */}
|
||
{document.issues && document.issues.length > 0 && (
|
||
<Alert
|
||
message="系统检测到问题"
|
||
description={document.issues.join(', ')}
|
||
type="warning"
|
||
showIcon
|
||
style={{ marginBottom: '24px' }}
|
||
/>
|
||
)}
|
||
|
||
{/* 基本信息卡片 */}
|
||
<Card title="文档信息" style={{ marginBottom: '24px' }}>
|
||
<Descriptions column={3} bordered>
|
||
<Descriptions.Item label="文件名" span={2}>
|
||
<Space>
|
||
<FileOutlined />
|
||
{document.fileName}
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="文件大小" span={1}>
|
||
{formatFileSize(document.originalSize)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="状态" span={1}>
|
||
<Tag color={getStatusColor(document.status)}>
|
||
{getStatusText(document.status)}
|
||
</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="支付状态" span={1}>
|
||
<Tag color={getPaymentStatusColor(document.paymentStatus)}>
|
||
{getPaymentStatusText(document.paymentStatus)}
|
||
</Tag>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="翻译进度" span={1}>
|
||
<Progress percent={document.progress} size="small" />
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="客户姓名" span={1}>
|
||
<Space>
|
||
<UserOutlined />
|
||
{document.clientName}
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="客户邮箱" span={1}>
|
||
{document.clientEmail}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="客户电话" span={1}>
|
||
{document.clientPhone}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="源语言" span={1}>
|
||
{document.sourceLanguage}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="目标语言" span={1}>
|
||
{document.targetLanguage}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="翻译质量" span={1}>
|
||
{getQualityText(document.quality)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="紧急程度" span={1}>
|
||
{getUrgencyText(document.urgency)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="译员" span={1}>
|
||
<Space>
|
||
<UserOutlined />
|
||
{document.translatorName}
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="重译次数" span={1}>
|
||
<Text type={(document.retranslationCount || 0) > 0 ? 'warning' : 'secondary'}>
|
||
{document.retranslationCount || 0} 次
|
||
</Text>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="创建时间" span={1}>
|
||
<Space>
|
||
<CalendarOutlined />
|
||
{new Date(document.createdAt).toLocaleString()}
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="完成时间" span={1}>
|
||
<Space>
|
||
<CalendarOutlined />
|
||
{document.completedAt ? new Date(document.completedAt).toLocaleString() : '-'}
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="预计时间" span={1}>
|
||
{document.estimatedTime} 分钟
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="实际时间" span={1}>
|
||
{document.actualTime || '-'} 分钟
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="费用" span={1}>
|
||
<Space>
|
||
<DollarOutlined />
|
||
<Text strong>¥{document.cost.toFixed(2)}</Text>
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="退款金额" span={1}>
|
||
<Space>
|
||
<DollarOutlined />
|
||
<Text type={document.refundAmount > 0 ? 'danger' : 'secondary'}>
|
||
¥{document.refundAmount.toFixed(2)}
|
||
</Text>
|
||
</Space>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="质量评分" span={1}>
|
||
<Space>
|
||
<AuditOutlined />
|
||
<Text strong style={{ color: document.qualityScore >= 90 ? '#52c41a' : document.qualityScore >= 70 ? '#faad14' : '#ff4d4f' }}>
|
||
{document.qualityScore}/100
|
||
</Text>
|
||
</Space>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
|
||
{document.adminNotes && (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<Text strong>管理员备注:</Text>
|
||
<Paragraph style={{ marginTop: '8px', background: '#f6f6f6', padding: '12px', borderRadius: '6px' }}>
|
||
{document.adminNotes}
|
||
</Paragraph>
|
||
</div>
|
||
)}
|
||
</Card>
|
||
|
||
{/* 文件操作卡片 */}
|
||
<Card
|
||
title={
|
||
<Space>
|
||
<FileTextOutlined />
|
||
文件管理
|
||
</Space>
|
||
}
|
||
style={{ marginBottom: '24px' }}
|
||
>
|
||
<div style={{ display: 'flex', justifyContent: 'space-around', textAlign: 'center', padding: '20px' }}>
|
||
<div>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => message.success('预览原文件...')}
|
||
style={{ marginBottom: '8px' }}
|
||
>
|
||
预览原文件
|
||
</Button>
|
||
<div>
|
||
<Text type="secondary">原始文档</Text>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Button
|
||
size="large"
|
||
icon={<DownloadOutlined />}
|
||
onClick={() => message.success('下载原文件中...')}
|
||
style={{ marginBottom: '8px' }}
|
||
>
|
||
下载原文件
|
||
</Button>
|
||
<div>
|
||
<Text type="secondary">{formatFileSize(document.originalSize)}</Text>
|
||
</div>
|
||
</div>
|
||
|
||
{document.translatedFileUrl && (
|
||
<>
|
||
<div>
|
||
<Button
|
||
type="primary"
|
||
size="large"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => message.success('预览译文...')}
|
||
style={{ marginBottom: '8px' }}
|
||
>
|
||
预览译文
|
||
</Button>
|
||
<div>
|
||
<Text type="secondary">翻译文档</Text>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<Button
|
||
size="large"
|
||
icon={<DownloadOutlined />}
|
||
onClick={() => message.success('下载译文中...')}
|
||
style={{ marginBottom: '8px' }}
|
||
>
|
||
下载译文
|
||
</Button>
|
||
<div>
|
||
<Text type="secondary">已完成</Text>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
</Card>
|
||
|
||
{/* 详细内容标签页 */}
|
||
<Card>
|
||
<Tabs defaultActiveKey="progress">
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<ClockCircleOutlined />
|
||
进度追踪
|
||
</Space>
|
||
}
|
||
key="progress"
|
||
>
|
||
<div style={{ padding: '20px' }}>
|
||
<Timeline>
|
||
<Timeline.Item color="green">
|
||
<div>
|
||
<Text strong>文档上传</Text>
|
||
<div style={{ color: '#999', fontSize: '12px' }}>
|
||
{new Date(document.createdAt).toLocaleString()}
|
||
</div>
|
||
</div>
|
||
</Timeline.Item>
|
||
<Timeline.Item color="blue">
|
||
<div>
|
||
<Text strong>分配译员</Text>
|
||
<div style={{ color: '#999', fontSize: '12px' }}>
|
||
译员:{document.translatorName}
|
||
</div>
|
||
</div>
|
||
</Timeline.Item>
|
||
<Timeline.Item color={document.status === 'completed' ? 'green' : 'blue'}>
|
||
<div>
|
||
<Text strong>翻译进行中</Text>
|
||
<div style={{ color: '#999', fontSize: '12px' }}>
|
||
进度:{document.progress}%
|
||
</div>
|
||
</div>
|
||
</Timeline.Item>
|
||
{document.status === 'completed' && (
|
||
<Timeline.Item color="green">
|
||
<div>
|
||
<Text strong>翻译完成</Text>
|
||
<div style={{ color: '#999', fontSize: '12px' }}>
|
||
{document.completedAt && new Date(document.completedAt).toLocaleString()}
|
||
</div>
|
||
</div>
|
||
</Timeline.Item>
|
||
)}
|
||
</Timeline>
|
||
</div>
|
||
</TabPane>
|
||
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<StarOutlined />
|
||
用户评价
|
||
</Space>
|
||
}
|
||
key="rating"
|
||
>
|
||
<div style={{ padding: '20px' }}>
|
||
{document.rating ? (
|
||
<>
|
||
<div style={{ marginBottom: '20px' }}>
|
||
<Text strong>服务评分:</Text>
|
||
<Rate disabled value={document.rating} style={{ marginLeft: '8px' }} />
|
||
<Text style={{ marginLeft: '8px' }}>
|
||
({document.rating}/5 分)
|
||
</Text>
|
||
</div>
|
||
|
||
{document.feedback && (
|
||
<div>
|
||
<Text strong>用户反馈:</Text>
|
||
<Paragraph style={{ marginTop: '8px' }}>
|
||
{document.feedback}
|
||
</Paragraph>
|
||
</div>
|
||
)}
|
||
</>
|
||
) : (
|
||
<div style={{ textAlign: 'center', color: '#999', padding: '50px' }}>
|
||
暂无用户评价
|
||
</div>
|
||
)}
|
||
</div>
|
||
</TabPane>
|
||
|
||
<TabPane
|
||
tab={
|
||
<Space>
|
||
<AuditOutlined />
|
||
质量分析
|
||
</Space>
|
||
}
|
||
key="quality"
|
||
>
|
||
<div style={{ padding: '20px' }}>
|
||
<Descriptions column={2}>
|
||
<Descriptions.Item label="质量评分">
|
||
<Progress
|
||
type="circle"
|
||
percent={document.qualityScore}
|
||
width={80}
|
||
strokeColor={document.qualityScore >= 90 ? '#52c41a' : document.qualityScore >= 70 ? '#faad14' : '#ff4d4f'}
|
||
/>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="系统检测">
|
||
{document.issues && document.issues.length > 0 ? (
|
||
<div>
|
||
{document.issues.map((issue, index) => (
|
||
<Tag key={index} color="red" style={{ marginBottom: '4px' }}>
|
||
{issue}
|
||
</Tag>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<Tag color="green">无异常</Tag>
|
||
)}
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="时间效率">
|
||
<Statistic
|
||
title="实际用时/预计用时"
|
||
value={document.actualTime ? (document.actualTime / document.estimatedTime) * 100 : 0}
|
||
precision={1}
|
||
suffix="%"
|
||
valueStyle={{ color: (document.actualTime || 0) <= document.estimatedTime ? '#3f8600' : '#cf1322' }}
|
||
/>
|
||
</Descriptions.Item>
|
||
<Descriptions.Item label="重译情况">
|
||
<Text type={(document.retranslationCount || 0) > 0 ? 'warning' : 'secondary'}>
|
||
{document.retranslationCount || 0} 次重译
|
||
</Text>
|
||
</Descriptions.Item>
|
||
</Descriptions>
|
||
</div>
|
||
</TabPane>
|
||
</Tabs>
|
||
</Card>
|
||
|
||
{/* 编辑信息弹窗 */}
|
||
<Modal
|
||
title="编辑文档信息"
|
||
visible={editModalVisible}
|
||
onCancel={() => setEditModalVisible(false)}
|
||
footer={null}
|
||
width={600}
|
||
>
|
||
<Form
|
||
form={form}
|
||
layout="vertical"
|
||
onFinish={handleEdit}
|
||
>
|
||
<Form.Item
|
||
name="fileName"
|
||
label="文件名"
|
||
rules={[{ required: true, message: '请输入文件名' }]}
|
||
>
|
||
<Input />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="quality"
|
||
label="翻译质量"
|
||
rules={[{ required: true, message: '请选择翻译质量' }]}
|
||
>
|
||
<Select>
|
||
<Option value="basic">基础版</Option>
|
||
<Option value="professional">专业版</Option>
|
||
<Option value="premium">高级版</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="urgency"
|
||
label="紧急程度"
|
||
rules={[{ required: true, message: '请选择紧急程度' }]}
|
||
>
|
||
<Select>
|
||
<Option value="normal">普通</Option>
|
||
<Option value="urgent">加急</Option>
|
||
<Option value="emergency">特急</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="cost"
|
||
label="费用"
|
||
rules={[{ required: true, message: '请输入费用' }]}
|
||
>
|
||
<Input type="number" addonAfter="元" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="translatorName"
|
||
label="译员姓名"
|
||
>
|
||
<Input />
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setEditModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" htmlType="submit">
|
||
保存
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 更改状态弹窗 */}
|
||
<Modal
|
||
title="更改文档状态"
|
||
visible={statusModalVisible}
|
||
onCancel={() => setStatusModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={statusForm}
|
||
layout="vertical"
|
||
onFinish={handleStatusChange}
|
||
>
|
||
<Form.Item
|
||
name="status"
|
||
label="新状态"
|
||
rules={[{ required: true, message: '请选择状态' }]}
|
||
>
|
||
<Select>
|
||
<Option value="pending">等待处理</Option>
|
||
<Option value="in_progress">翻译中</Option>
|
||
<Option value="completed">已完成</Option>
|
||
<Option value="cancelled">已取消</Option>
|
||
<Option value="failed">翻译失败</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setStatusModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" htmlType="submit">
|
||
更新状态
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 重新分配译员弹窗 */}
|
||
<Modal
|
||
title="重新分配译员"
|
||
visible={reassignModalVisible}
|
||
onCancel={() => setReassignModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={reassignForm}
|
||
layout="vertical"
|
||
onFinish={handleReassign}
|
||
>
|
||
<Form.Item
|
||
name="translatorId"
|
||
label="选择译员"
|
||
rules={[{ required: true, message: '请选择译员' }]}
|
||
>
|
||
<Select>
|
||
<Option value="translator_1">李翻译 - 商务专家</Option>
|
||
<Option value="translator_2">王译员 - 技术专家</Option>
|
||
<Option value="translator_3">张翻译 - 法律专家</Option>
|
||
</Select>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="translatorName"
|
||
label="译员姓名"
|
||
rules={[{ required: true, message: '请输入译员姓名' }]}
|
||
>
|
||
<Input />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="reason"
|
||
label="重新分配原因"
|
||
rules={[{ required: true, message: '请输入原因' }]}
|
||
>
|
||
<TextArea rows={3} placeholder="请输入重新分配原因..." />
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setReassignModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" htmlType="submit">
|
||
确认分配
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 重新翻译弹窗 */}
|
||
<Modal
|
||
title="重新翻译"
|
||
visible={retranslateModalVisible}
|
||
onCancel={() => setRetranslateModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={retranslateForm}
|
||
layout="vertical"
|
||
onFinish={handleRetranslate}
|
||
>
|
||
<Alert
|
||
message="重新翻译提醒"
|
||
description="重新翻译将重置翻译进度,请确认操作"
|
||
type="warning"
|
||
style={{ marginBottom: '16px' }}
|
||
/>
|
||
|
||
<Form.Item
|
||
name="reason"
|
||
label="重新翻译原因"
|
||
rules={[{ required: true, message: '请输入重新翻译原因' }]}
|
||
>
|
||
<TextArea rows={3} placeholder="请输入重新翻译原因..." />
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setRetranslateModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" danger htmlType="submit">
|
||
确认重新翻译
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 退款处理弹窗 */}
|
||
<Modal
|
||
title="处理退款"
|
||
visible={refundModalVisible}
|
||
onCancel={() => setRefundModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={refundForm}
|
||
layout="vertical"
|
||
onFinish={handleRefund}
|
||
initialValues={{ amount: document.cost }}
|
||
>
|
||
<Alert
|
||
message="退款提醒"
|
||
description={`原支付金额:¥${document.cost.toFixed(2)}`}
|
||
type="info"
|
||
style={{ marginBottom: '16px' }}
|
||
/>
|
||
|
||
<Form.Item
|
||
name="amount"
|
||
label="退款金额"
|
||
rules={[
|
||
{ required: true, message: '请输入退款金额' },
|
||
{ type: 'number', min: 0, max: document.cost, message: `退款金额不能超过¥${document.cost.toFixed(2)}` }
|
||
]}
|
||
>
|
||
<Input type="number" addonAfter="元" />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
name="reason"
|
||
label="退款原因"
|
||
rules={[{ required: true, message: '请输入退款原因' }]}
|
||
>
|
||
<TextArea rows={3} placeholder="请输入退款原因..." />
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setRefundModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" danger htmlType="submit">
|
||
确认退款
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
{/* 添加管理员备注弹窗 */}
|
||
<Modal
|
||
title="添加管理员备注"
|
||
visible={adminNoteModalVisible}
|
||
onCancel={() => setAdminNoteModalVisible(false)}
|
||
footer={null}
|
||
>
|
||
<Form
|
||
form={noteForm}
|
||
layout="vertical"
|
||
onFinish={handleAddAdminNote}
|
||
initialValues={{ note: document.adminNotes }}
|
||
>
|
||
<Form.Item
|
||
name="note"
|
||
label="备注内容"
|
||
rules={[{ required: true, message: '请输入备注内容' }]}
|
||
>
|
||
<TextArea rows={4} placeholder="请输入管理员备注..." />
|
||
</Form.Item>
|
||
|
||
<Form.Item style={{ marginBottom: 0, textAlign: 'right' }}>
|
||
<Space>
|
||
<Button onClick={() => setAdminNoteModalVisible(false)}>
|
||
取消
|
||
</Button>
|
||
<Button type="primary" htmlType="submit">
|
||
保存备注
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default DocumentDetail;
|