import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react';
|
import { useCaseData } from '../../contexts/CaseDataContext';
|
import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator';
|
import ProcessAPIService from '../../services/ProcessAPIService';
|
import EvidenceAPIService from '../../services/EvidenceAPIService';
|
import MediationAgreementAPIService from '../../services/MediationAgreementAPIService';
|
import { getMergedParams } from '../../utils/urlParams';
|
import { message, Spin, Tag, Modal, Button, Input, Image } from 'antd';
|
|
const { TextArea } = Input;
|
|
/**
|
* 选项卡容器组件 - 4个选项卡
|
* 通过 forwardRef 暴露 switchTab 方法供父组件调用
|
*/
|
const TabContainer = forwardRef((props, ref) => {
|
const [activeTab, setActiveTab] = useState('mediation-data-board');
|
// 证据材料汇总Tab的审核状态badge
|
const [evidenceBadge, setEvidenceBadge] = useState(null);
|
|
// 暴露 switchTab 方法给父组件
|
useImperativeHandle(ref, () => ({
|
switchTab: (tabKey) => {
|
setActiveTab(tabKey);
|
}
|
}));
|
|
const tabs = [
|
{ key: 'mediation-data-board', label: '调解分析', icon: 'fa-chart-line' },
|
{ key: 'mediation-board', label: 'AI调解实时看板', icon: 'fa-tv' },
|
{ key: 'evidence-board', label: '证据材料汇总', icon: 'fa-file-alt', badge: evidenceBadge },
|
{ key: 'agreement-section', label: '调解协议', icon: 'fa-file-contract', badge: '待确认' },
|
];
|
|
// 更新证据材料汇总Tab的badge状态
|
const handleEvidenceStatusChange = (status) => {
|
setEvidenceBadge(status);
|
};
|
|
return (
|
<div className="tab-container">
|
<div className="tab-header">
|
{tabs.map((tab) => (
|
<button
|
key={tab.key}
|
className={`tab-btn ${activeTab === tab.key ? 'active' : ''}`}
|
onClick={() => setActiveTab(tab.key)}
|
>
|
<i className={`fas ${tab.icon}`}></i>
|
{tab.label}
|
{tab.badge && <span className="tab-badge">{tab.badge}</span>}
|
</button>
|
))}
|
</div>
|
|
<div className="tab-content">
|
{/* 调解数据看板 */}
|
<div className={`tab-pane ${activeTab === 'mediation-data-board' ? 'active' : ''}`}>
|
<div className="tab-content-area">
|
<MediationDataBoard />
|
</div>
|
</div>
|
|
{/* AI调解实时看板 */}
|
<div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}>
|
<div className="tab-content-area">
|
<MediationBoard activeTab={activeTab} />
|
</div>
|
</div>
|
|
{/* 证据材料汇总 */}
|
<div className={`tab-pane ${activeTab === 'evidence-board' ? 'active' : ''}`}>
|
<div className="tab-content-area">
|
<EvidenceBoard onStatusChange={handleEvidenceStatusChange} />
|
</div>
|
</div>
|
|
{/* 调解协议 */}
|
<div className={`tab-pane ${activeTab === 'agreement-section' ? 'active' : ''}`}>
|
<div className="tab-content-area">
|
<AgreementSection />
|
</div>
|
</div>
|
</div>
|
</div>
|
);
|
});
|
|
/**
|
* 调解数据看板
|
*/
|
const MediationDataBoard = () => {
|
const { caseData } = useCaseData();
|
const timeline = caseData || {};
|
|
// 从 timeline 获取数据
|
const gapContent = timeline.result || '暂无分歧分析';
|
const updateTime = formatDuration(timeline.before_duration);
|
const successRate = formatSuccessRate(timeline.mediation?.success_rate);
|
const roundCount = formatRoundCount(timeline.mediation?.mediation_count);
|
|
return (
|
<div className="mediation-metrics">
|
{/* 左侧:诉求差距分析 */}
|
<div className="metric-card">
|
<div className="metric-title">
|
<i className="fas fa-exclamation-circle"></i>
|
<span>诉求差距分析</span>
|
</div>
|
<div className="metric-content">
|
<div className="gap-analysis">
|
<div className="gap-title">
|
<i className="fas fa-exclamation-circle"></i>
|
主要分歧点
|
</div>
|
<div className="gap-update-time">{updateTime}</div>
|
<div className="gap-content">
|
{gapContent}
|
</div>
|
</div>
|
</div>
|
</div>
|
|
{/* 右侧:调解数据 */}
|
<div className="metric-card">
|
<div className="metric-title">
|
<i className="fas fa-exchange-alt"></i>
|
<span>调解数据</span>
|
</div>
|
<div className="metric-content">
|
<div className="success-metric">
|
<div className="success-value">{successRate}</div>
|
<div className="success-label">预计调解成功概率</div>
|
<div className="success-change">
|
|
{/* <i className="fas fa-arrow-up"></i><span>较{updateTime} +8%</span> */}
|
</div>
|
<div style={{ marginTop: 15, fontSize: '0.9rem', color: 'var(--gray-color)' }}>
|
协商沟通:<span style={{ color: 'var(--dark-color)', fontWeight: 600 }}>{roundCount}</span>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
);
|
};
|
|
/**
|
* AI调解实时看板
|
*/
|
const MediationBoard = ({ activeTab }) => {
|
// 状态管理
|
const [records, setRecords] = useState([]);
|
const [loading, setLoading] = useState(false);
|
const [error, setError] = useState(null);
|
|
// 获取案件数据
|
const { caseData } = useCaseData();
|
const timeline = caseData || {};
|
|
// person_type到avatar类型的映射
|
const getAvatarType = (personType) => {
|
const typeMap = {
|
'1': 'ai',
|
'2': 'applicant',
|
'3': 'respondent',
|
'4': 'mediator'
|
};
|
return typeMap[personType] || 'ai';
|
};
|
|
// tag_style到UI标签样式的映射
|
const getTagStyleType = (tagStyle) => {
|
const styleMap = {
|
'red': 'tag-type-5',
|
'blue': 'tag-type-1',
|
'green': 'tag-type-2',
|
'orange': 'tag-type-3'
|
};
|
return styleMap[tagStyle] || 'tag-type-1';
|
};
|
|
// 获取角色显示名称
|
const getRoleDisplayName = (personType, creatorName) => {
|
const roleMap = {
|
'1': 'AI调解员',
|
'2': `申请人(${creatorName})`,
|
'3': `被申请人(${creatorName})`,
|
'4': `调解员(${creatorName})`
|
};
|
return roleMap[personType] || creatorName;
|
};
|
|
// 数据格式化函数
|
const formatRecordData = (apiRecords) => {
|
return apiRecords.map(record => ({
|
avatar: getAvatarType(record.person_type),
|
name: getRoleDisplayName(record.person_type, record.creator),
|
avatarText: record.creator?.charAt(0) || '', // 头像显示名字第一个字
|
time: record.create_time,
|
content: record.result,
|
tags: record.tagList?.map(tag => ({
|
text: tag.tag_name,
|
type: getTagStyleType(tag.tag_style)
|
})) || []
|
}));
|
};
|
|
// 获取调解记录数据
|
const loadMediationRecords = async () => {
|
setLoading(true);
|
setError(null);
|
|
try {
|
// 从timeline中获取mediation_id
|
const mediationId = timeline.mediation?.id;
|
if (!mediationId) {
|
throw new Error('未找到调解ID');
|
}
|
|
// 调用API获取记录列表
|
const response = await ProcessAPIService.getProcessRecords({
|
mediation_id: mediationId
|
});
|
|
// 格式化数据
|
const formattedRecords = formatRecordData(response.data || []);
|
setRecords(formattedRecords);
|
|
} catch (err) {
|
setError(err.message);
|
console.error('获取调解记录失败:', err);
|
message.error(`获取调解记录失败: ${err.message}`);
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 监听Tab切换
|
useEffect(() => {
|
if (activeTab === 'mediation-board') {
|
loadMediationRecords();
|
}
|
}, [activeTab]);
|
|
// 如果还在加载中,显示Loading状态
|
if (loading) {
|
return (
|
<div style={{
|
display: 'flex',
|
justifyContent: 'center',
|
alignItems: 'center',
|
height: '400px'
|
}}>
|
<Spin size="large" tip="正在加载调解记录..." />
|
</div>
|
);
|
}
|
|
// 如果有错误,显示错误信息
|
if (error) {
|
return (
|
<div style={{
|
textAlign: 'center',
|
padding: '40px',
|
color: '#ff4d4f'
|
}}>
|
<div style={{ fontSize: '1.2rem', marginBottom: '10px' }}>
|
<i className="fas fa-exclamation-circle"></i>
|
数据加载失败
|
</div>
|
<div>{error}</div>
|
<button
|
onClick={loadMediationRecords}
|
style={{
|
marginTop: '15px',
|
padding: '8px 16px',
|
backgroundColor: '#1890ff',
|
color: 'white',
|
border: 'none',
|
borderRadius: '4px',
|
cursor: 'pointer'
|
}}
|
>
|
重新加载
|
</button>
|
</div>
|
);
|
}
|
|
// 使用动态数据或默认mock数据
|
const displayRecords = records.length > 0 ? records : [
|
{
|
avatar: 'ai',
|
name: 'AI调解员',
|
avatarText: '',
|
time: '09:30:05',
|
content: '已分别联系李晓明(申请人)和广东好又多贸易有限公司(被申请人)。双方均表示愿意接受调解,希望通过协商解决劳动争议/拖欠、克扣工资纠纷。',
|
tags: [{ text: '意愿调查', type: 'tag-type-1' }, { text: '初步接触', type: 'tag-type-2' }],
|
},
|
{
|
avatar: 'applicant',
|
name: '申请人(李晓明)',
|
avatarText: '李',
|
time: '09:35:22',
|
content: '我在好又多公司担任销售经理3年,公司拖欠我3个月工资共¥42,000,还克扣了季度绩效奖金¥10,800。另外,公司单方面解除劳动合同,应支付经济补偿金¥12,000。我要求公司支付总共¥52,800。',
|
tags: [{ text: '诉求表达', type: 'tag-type-3' }, { text: '情绪激动', type: 'tag-type-5' }],
|
},
|
{
|
avatar: 'respondent',
|
name: '被申请人(好又多公司)',
|
avatarText: '好',
|
time: '09:40:15',
|
content: '公司确实遇到了资金周转困难,拖欠工资问题承认。但李晓明主张的金额有误:1) 3个月工资应为¥35,000(含请假扣款);2) 绩效奖金因未完成KPI指标不应发放;3) 是李晓明主动提出辞职,不应支付经济补偿金。公司最多支付¥38,500。',
|
tags: [{ text: '诉求表达', type: 'tag-type-3' }],
|
},
|
];
|
|
// 从timeline获取沟通情况总结
|
const communicationSummary = timeline.mediation?.summary || '暂无沟通情况总结';
|
|
const getAvatarClass = (avatar) => {
|
const map = {
|
ai: 'avatar-ai',
|
applicant: 'avatar-applicant',
|
respondent: 'avatar-respondent',
|
mediator: 'avatar-mediator',
|
};
|
return map[avatar] || 'avatar-ai';
|
};
|
|
const getContentClass = (avatar) => {
|
const map = {
|
ai: 'content-ai',
|
applicant: 'content-applicant',
|
respondent: 'content-respondent',
|
mediator: 'content-mediator',
|
};
|
return map[avatar] || 'content-ai';
|
};
|
|
const getAvatarContent = (avatar, avatarText) => {
|
if (avatar === 'ai') return <i className="fas fa-robot"></i>;
|
// 显示名字的第一个字
|
return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
|
};
|
|
return (
|
<>
|
<div className="mediation-summary" style={{
|
background: '#f0f7ff',
|
borderRadius: 'var(--border-radius)',
|
padding: '12px 16px',
|
marginBottom: 15,
|
borderLeft: '4px solid var(--primary-color)',
|
fontSize: '0.9rem',
|
color: 'var(--dark-color)',
|
}}>
|
<div style={{ fontWeight: 600, color: 'var(--secondary-color)', marginBottom: 5, display: 'flex', alignItems: 'center', gap: 8 }}>
|
<i className="fas fa-chart-bar"></i>
|
沟通情况总结
|
</div>
|
<div style={{ lineHeight: 1.5 }}>
|
{communicationSummary}
|
</div>
|
</div>
|
|
<div className="board-container" style={{ maxHeight: 450, overflowY: 'auto' }}>
|
{displayRecords.map((item, index) => (
|
<div key={index} className="board-item" style={{
|
background: '#f8f9fa',
|
borderRadius: 'var(--border-radius)',
|
padding: 16,
|
marginBottom: 12,
|
border: '1px solid #e9ecef',
|
}}>
|
<div className="item-header" style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
|
<div className={`item-avatar ${getAvatarClass(item.avatar)}`} style={{
|
width: 32,
|
height: 32,
|
borderRadius: '50%',
|
display: 'flex',
|
justifyContent: 'center',
|
alignItems: 'center',
|
color: 'white',
|
fontWeight: 600,
|
fontSize: '0.9rem',
|
background: item.avatar === 'ai' ? 'linear-gradient(135deg, var(--primary-color), var(--secondary-color))' :
|
item.avatar === 'applicant' ? 'linear-gradient(135deg, var(--primary-color), #0d4a8a)' :
|
item.avatar === 'respondent' ? 'linear-gradient(135deg, #e9c46a, #e76f51)' :
|
'linear-gradient(135deg, #7209b7, #3a0ca3)',
|
}}>
|
{getAvatarContent(item.avatar, item.avatarText)}
|
</div>
|
<div className="item-source">
|
<div style={{ fontWeight: 600, fontSize: '0.95rem', color: 'var(--dark-color)', marginBottom: 2 }}>{item.name}</div>
|
<div style={{ fontSize: '0.8rem', color: 'var(--gray-color)', display: 'flex', alignItems: 'center', gap: 6 }}>
|
<i className="far fa-clock"></i>
|
<span>{item.time}</span>
|
</div>
|
</div>
|
</div>
|
<div className={`item-content ${getContentClass(item.avatar)}`} style={{
|
background: 'white',
|
borderRadius: 8,
|
padding: 12,
|
marginBottom: 10,
|
lineHeight: 1.5,
|
fontSize: '0.9rem',
|
borderLeft: item.avatar === 'respondent' ? '3px solid #e9c46a' : '3px solid var(--primary-color)',
|
}}>
|
{item.content}
|
</div>
|
<div className="item-tags" style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
|
{item.tags.map((tag, i) => (
|
<span key={i} className={`item-tag ${tag.type}`} style={{
|
display: 'inline-block',
|
padding: '3px 8px',
|
borderRadius: 20,
|
fontSize: '0.75rem',
|
fontWeight: 600,
|
background: tag.type === 'tag-type-1' ? '#e3f2fd' :
|
tag.type === 'tag-type-2' ? '#e8f5e9' :
|
tag.type === 'tag-type-3' ? '#fff3e0' :
|
tag.type === 'tag-type-5' ? '#ffebee' : '#e3f2fd',
|
color: tag.type === 'tag-type-1' ? '#1565c0' :
|
tag.type === 'tag-type-2' ? '#2e7d32' :
|
tag.type === 'tag-type-3' ? '#ef6c00' :
|
tag.type === 'tag-type-5' ? '#c62828' : '#1565c0',
|
border: `1px solid ${tag.type === 'tag-type-1' ? '#bbdefb' :
|
tag.type === 'tag-type-2' ? '#c8e6c9' :
|
tag.type === 'tag-type-3' ? '#ffe0b2' :
|
tag.type === 'tag-type-5' ? '#ffcdd2' : '#bbdefb'}`,
|
}}>
|
{tag.text}
|
</span>
|
))}
|
</div>
|
</div>
|
))}
|
</div>
|
</>
|
);
|
};
|
|
/**
|
* 证据材料汇总
|
*/
|
const EvidenceBoard = ({ onStatusChange }) => {
|
const [loading, setLoading] = useState(false);
|
const [error, setError] = useState(null);
|
// 审核弹窗状态
|
const [auditModalVisible, setAuditModalVisible] = useState(false);
|
const [currentAuditItem, setCurrentAuditItem] = useState(null);
|
const [auditRemark, setAuditRemark] = useState('');
|
const [auditLoading, setAuditLoading] = useState(false);
|
const [returnModalVisible, setReturnModalVisible] = useState(false);
|
const [applicantMaterials, setApplicantMaterials] = useState([]);
|
const [respondentMaterials, setRespondentMaterials] = useState([]);
|
// 弹窗数据状态
|
const [modalDataLoading, setModalDataLoading] = useState(false);
|
const [personInfo, setPersonInfo] = useState(null);
|
const [evidenceImages, setEvidenceImages] = useState([]);
|
const [platformUrl, setPlatformUrl] = useState('');
|
|
// 计算整体审核状态(综合申请人和被申请人所有材料)
|
const calculateOverallTabStatus = (applicantList, respondentList) => {
|
const allMaterials = [...applicantList, ...respondentList];
|
if (!allMaterials || allMaterials.length === 0) return null;
|
|
// 存在驳回状态 -> 显示"驳回"
|
const hasRejected = allMaterials.some(m => m.audit_state === -2);
|
if (hasRejected) return '驳回';
|
|
// 存在待审核状态(0或-2) -> 显示"待审核"
|
const hasPending = allMaterials.some(m => m.audit_state === 0 || m.audit_state === -2);
|
if (hasPending) return '待审核';
|
|
// 所有材料都已审核 -> 显示"已审核"
|
return '已审核';
|
};
|
|
// 加载数据
|
const loadData = async () => {
|
// 使用getMergedParams获取参数(URL参数优先,默认值兜底)
|
const params = getMergedParams();
|
const caseId = params.caseId;
|
const caseType = params.caseTypeFirst;
|
const platformCode = params.platform_code;
|
|
console.log('EvidenceBoard loadData params:', { caseId, caseType, platformCode });
|
|
setLoading(true);
|
setError(null);
|
|
try {
|
// 调用API获取数据
|
const response = await EvidenceAPIService.getEvidenceList({
|
case_id: caseId,
|
case_type: caseType,
|
platform_code: platformCode
|
});
|
|
console.log('EvidenceBoard API response:', response);
|
|
const responseData = response.data || [];
|
|
// 分离申请人和被申请人材料
|
const applicantData = responseData.find(item => item.per_type === '15_020008-1');
|
const respondentData = responseData.find(item => item.per_type === '15_020008-2');
|
|
const applicantList = applicantData?.file_list?.slice(0, applicantData.file_count) || [];
|
const respondentList = respondentData?.file_list?.slice(0, respondentData.file_count) || [];
|
|
setApplicantMaterials(applicantList);
|
setRespondentMaterials(respondentList);
|
|
// 计算并通知Tab标题的整体审核状态
|
const overallStatus = calculateOverallTabStatus(applicantList, respondentList);
|
if (onStatusChange) {
|
onStatusChange(overallStatus);
|
}
|
|
} catch (err) {
|
console.error('加载证据材料失败:', err);
|
setError('数据加载失败,请稍后重试');
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 组件挂载时加载数据
|
useEffect(() => {
|
loadData();
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
// 计算区域内整体审核状态(用于卡片标题旁的Tag显示)
|
const calculateOverallStatus = (materials) => {
|
if (!materials || materials.length === 0) return { text: '无数据', color: 'default' };
|
const hasRejected = materials.some(m => m.audit_state === -2);
|
if (hasRejected) return { text: '驳回', color: 'red' };
|
const hasPending = materials.some(m => m.audit_state === 0 || m.audit_state === -2);
|
if (hasPending) return { text: '待审核', color: 'orange' };
|
return { text: '已审核', color: 'green' };
|
};
|
|
const applicantStatus = calculateOverallStatus(applicantMaterials);
|
const respondentStatus = calculateOverallStatus(respondentMaterials);
|
|
// Loading状态
|
if (loading) {
|
return (
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 300 }}>
|
<Spin size="large" tip="加载证据材料中..." />
|
</div>
|
);
|
}
|
|
// 错误状态
|
if (error) {
|
return (
|
<div style={{ textAlign: 'center', padding: 40, color: '#ff4d4f' }}>
|
<div style={{ fontSize: '1.2rem', marginBottom: 10 }}>
|
<i className="fas fa-exclamation-circle"></i> 数据加载失败
|
</div>
|
<div>{error}</div>
|
<button onClick={loadData} style={{
|
marginTop: 15, padding: '8px 16px', backgroundColor: '#1890ff',
|
color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer'
|
}}>重新加载</button>
|
</div>
|
);
|
}
|
|
// 获取材料审核状态样式
|
const getAuditStatusStyle = (auditState) => {
|
if (auditState === 1) {
|
// 已审核 - 绿色边框
|
return {
|
color: '#52c41a',
|
background: '#f6ffed',
|
border: '1px solid #b7eb8f',
|
borderRadius: 4,
|
padding: '2px 10px',
|
fontSize: '0.8rem',
|
fontWeight: 500,
|
};
|
}
|
// 待审核 - 橙色边框
|
return {
|
color: '#fa8c16',
|
background: '#fff7e6',
|
border: '1px solid #ffd591',
|
borderRadius: 4,
|
padding: '2px 10px',
|
fontSize: '0.8rem',
|
fontWeight: 500,
|
};
|
};
|
|
// 打开审核弹窗
|
const handleOpenAuditModal = async (item, type) => {
|
// 获取参数
|
const params = getMergedParams();
|
const caseId = params.caseId;
|
|
// 设置当前审核项
|
const auditItem = {
|
...item,
|
personType: type,
|
per_type: type === 'applicant' ? '15_020008-1' : '15_020008-2'
|
};
|
setCurrentAuditItem(auditItem);
|
setAuditRemark('');
|
setAuditModalVisible(true);
|
|
// 获取platform_url
|
try {
|
const caseDataTimeline = JSON.parse(localStorage.getItem('case_data_timeline') || '{}');
|
const pUrl = caseDataTimeline?.process_config?.platform_url || '';
|
setPlatformUrl(pUrl);
|
} catch (e) {
|
console.warn('获取platform_url失败:', e);
|
}
|
|
// 调用API获取弹窗数据
|
setModalDataLoading(true);
|
setPersonInfo(null);
|
setEvidenceImages([]);
|
|
try {
|
// 并行调用两个API
|
const [personInfoRes, evidenceListRes] = await Promise.all([
|
EvidenceAPIService.getPersonInfo({
|
case_id: caseId,
|
evidence_type: item.evidence_type,
|
per_type: auditItem.per_type
|
}),
|
EvidenceAPIService.getEvidenceListByPerson({
|
case_id: caseId,
|
evidence_type: item.evidence_type,
|
per_type: auditItem.per_type,
|
person_id: item.person_id
|
})
|
]);
|
|
console.log('getPersonInfo response:', personInfoRes);
|
console.log('getEvidenceListByPerson response:', evidenceListRes);
|
|
// 设置当事人信息
|
if (personInfoRes?.data) {
|
setPersonInfo(personInfoRes.data);
|
}
|
|
// 设置图片列表
|
if (evidenceListRes?.data && Array.isArray(evidenceListRes.data)) {
|
setEvidenceImages(evidenceListRes.data);
|
}
|
} catch (err) {
|
console.error('加载弹窗数据失败:', err);
|
message.error('加载材料详情失败');
|
} finally {
|
setModalDataLoading(false);
|
}
|
};
|
|
// 关闭审核弹窗
|
const handleCloseAuditModal = () => {
|
setAuditModalVisible(false);
|
setCurrentAuditItem(null);
|
setAuditRemark('');
|
setPersonInfo(null);
|
setEvidenceImages([]);
|
};
|
|
// 审核通过
|
const handleAuditPass = async () => {
|
if (!currentAuditItem) return;
|
|
const params = getMergedParams();
|
const caseId = params.caseId;
|
|
setAuditLoading(true);
|
try {
|
// 获取文件ID列表
|
const fileIdList = evidenceImages.map(img => img.file_id || img.id).filter(Boolean);
|
|
await EvidenceAPIService.auditEvidence({
|
case_id: caseId,
|
evidence_type: currentAuditItem.evidence_type,
|
person_id: currentAuditItem.person_id,
|
file_id_list: fileIdList,
|
audit_user: '当前用户', // TODO: 从登录信息获取
|
audit_state: 1,
|
audit_remark: ''
|
});
|
|
message.success('审核通过!');
|
handleCloseAuditModal();
|
// 重新加载数据
|
loadData();
|
} catch (err) {
|
console.error('审核失败:', err);
|
message.error('审核失败,请重试');
|
} finally {
|
setAuditLoading(false);
|
}
|
};
|
|
// 退回补充
|
const handleAuditReturn = async () => {
|
if (!currentAuditItem) return;
|
if (!auditRemark.trim()) {
|
message.warning('请填写退回意见');
|
return;
|
}
|
|
const params = getMergedParams();
|
const caseId = params.caseId;
|
|
setAuditLoading(true);
|
try {
|
// 获取文件ID列表
|
const fileIdList = evidenceImages.map(img => img.file_id || img.id).filter(Boolean);
|
|
await EvidenceAPIService.auditEvidence({
|
case_id: caseId,
|
evidence_type: currentAuditItem.evidence_type,
|
person_id: currentAuditItem.person_id,
|
file_id_list: fileIdList,
|
audit_user: '当前用户', // TODO: 从登录信息获取
|
audit_state: 2,
|
audit_remark: auditRemark.trim()
|
});
|
|
message.success('材料已退回,等待补充提交');
|
setReturnModalVisible(false);
|
handleCloseAuditModal();
|
loadData();
|
} catch (err) {
|
console.error('退回失败:', err);
|
message.error('退回失败,请重试');
|
} finally {
|
setAuditLoading(false);
|
}
|
};
|
|
// 打开退回弹窗
|
const handleOpenReturnModal = () => {
|
setAuditRemark('');
|
setReturnModalVisible(true);
|
};
|
|
// 关闭退回弹窗
|
const handleCloseReturnModal = () => {
|
setReturnModalVisible(false);
|
setAuditRemark('');
|
};
|
|
const renderEvidenceItem = (item, type) => (
|
<div key={item.name} className="evidence-item" style={{
|
background: 'white',
|
borderRadius: 6,
|
padding: 12,
|
borderLeft: type === 'applicant' ? '3px solid var(--primary-color)' : '3px solid #e9c46a',
|
display: 'flex',
|
justifyContent: 'space-between',
|
alignItems: 'flex-start',
|
marginBottom: 10,
|
position: 'relative',
|
}}>
|
<div style={{ flex: 1, paddingRight: 70 }}>
|
<div style={{ fontWeight: 600, fontSize: '0.9rem', marginBottom: 4, color: 'var(--dark-color)' }}>{item.name}</div>
|
<div style={{ fontSize: '0.8rem', color: 'var(--gray-color)', lineHeight: 1.4 }}>{item.result}</div>
|
<div style={{ fontSize: '0.75rem', color: 'var(--gray-color)', marginTop: 4, display: 'flex', alignItems: 'center', gap: 5 }}>
|
<i className="far fa-clock"></i>
|
<span>上传时间:{item.update_time || item.create_time}</span>
|
</div>
|
{/* 待审核时显示审核按钮 */}
|
{item.audit_state !== 1 && (
|
<button
|
onClick={() => handleOpenAuditModal(item, type)}
|
style={{
|
marginTop: 8,
|
padding: '4px 12px',
|
fontSize: '0.75rem',
|
background: '#1A6FB8',
|
color: 'white',
|
border: 'none',
|
borderRadius: 4,
|
cursor: 'pointer',
|
fontWeight: 600,
|
transition: 'all 0.2s ease',
|
}}
|
onMouseOver={(e) => {
|
e.target.style.background = '#0d4a8a';
|
e.target.style.transform = 'translateY(-1px)';
|
}}
|
onMouseOut={(e) => {
|
e.target.style.background = '#1A6FB8';
|
e.target.style.transform = 'translateY(0)';
|
}}
|
>
|
审核
|
</button>
|
)}
|
</div>
|
{/* 右上角审核状态标签 */}
|
<div style={{
|
position: 'absolute',
|
top: 12,
|
right: 12,
|
...getAuditStatusStyle(item.audit_state)
|
}}>
|
{item.audit_state === 1 ? '已审核' : '待审核'}
|
</div>
|
</div>
|
);
|
|
return (
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
|
{/* 申请人证据材料 */}
|
<div style={{
|
background: '#f8f9fa',
|
borderRadius: 'var(--border-radius)',
|
padding: 18,
|
borderTop: '4px solid var(--primary-color)',
|
minHeight: 300,
|
}}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
<div style={{ fontWeight: 600, fontSize: '1.05rem', color: 'var(--dark-color)' }}>
|
<i className="fas fa-user-tie" style={{ color: 'var(--primary-color)', marginRight: 8 }}></i>
|
申请人材料
|
</div>
|
<Tag color={applicantStatus.color}>{applicantStatus.text}</Tag>
|
<div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{applicantMaterials.length}份材料</div>
|
</div>
|
<div style={{ maxHeight: 400, overflowY: 'auto' }}>
|
{applicantMaterials.length > 0 ? (
|
applicantMaterials.map((item) => renderEvidenceItem(item, 'applicant'))
|
) : (
|
<div style={{ textAlign: 'center', padding: 40, color: 'var(--gray-color)' }}>暂无材料</div>
|
)}
|
</div>
|
</div>
|
|
{/* 被申请人证据材料 */}
|
<div style={{
|
background: '#f8f9fa',
|
borderRadius: 'var(--border-radius)',
|
padding: 18,
|
borderTop: '4px solid #e9c46a',
|
minHeight: 300,
|
}}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
|
<div style={{ fontWeight: 600, fontSize: '1.05rem', color: 'var(--dark-color)' }}>
|
<i className="fas fa-building" style={{ color: '#e9c46a', marginRight: 8 }}></i>
|
被申请人材料
|
</div>
|
<Tag color={respondentStatus.color}>{respondentStatus.text}</Tag>
|
<div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{respondentMaterials.length}份材料</div>
|
</div>
|
<div style={{ maxHeight: 400, overflowY: 'auto' }}>
|
{respondentMaterials.length > 0 ? (
|
respondentMaterials.map((item) => renderEvidenceItem(item, 'respondent'))
|
) : (
|
<div style={{ textAlign: 'center', padding: 40, color: 'var(--gray-color)' }}>暂无材料</div>
|
)}
|
</div>
|
</div>
|
|
{/* 证据材料审查弹窗 */}
|
<Modal
|
title={null}
|
visible={auditModalVisible}
|
onCancel={handleCloseAuditModal}
|
footer={null}
|
width={900}
|
centered
|
destroyOnClose
|
bodyStyle={{ padding: 0 }}
|
className="doc-audit-modal"
|
>
|
{currentAuditItem && (
|
<div style={{ background: '#f5f7fa' }}>
|
{/* 头部 - 蓝色渐变 */}
|
<div style={{
|
background: 'linear-gradient(135deg, #1A6FB8 0%, #0d4a8a 100%)',
|
color: 'white',
|
padding: '20px 30px',
|
}}>
|
<div style={{ fontSize: 20, fontWeight: 600, marginBottom: 8 }}>证据材料审查</div>
|
<div style={{ fontSize: 14, opacity: 0.9 }}>
|
{currentAuditItem.personType === 'applicant' ? '申请人' : '被申请人'}提交的证据材料
|
</div>
|
</div>
|
|
{/* 主体内容 */}
|
<div style={{ padding: '20px 30px', background: 'white', margin: 0 }}>
|
{/* 审核说明 tip-box */}
|
<div style={{
|
background: '#f0f8ff',
|
borderRadius: 8,
|
padding: 16,
|
marginBottom: 24,
|
border: '1px solid #d9eafb',
|
}}>
|
<div style={{ color: '#1A6FB8', marginBottom: 8, fontSize: 15, fontWeight: 600 }}>审核说明</div>
|
<div style={{ color: '#555', fontSize: 14 }}>
|
请仔细核对申请人提交的材料,确保材料真实、完整、有效。如有疑问或需要补充材料,请使用"退回补充"功能。
|
</div>
|
</div>
|
|
{/* 材料基本信息 */}
|
<div style={{ marginBottom: 24 }}>
|
<div style={{
|
color: '#1A6FB8',
|
fontSize: 16,
|
fontWeight: 600,
|
marginBottom: 16,
|
display: 'flex',
|
alignItems: 'center',
|
}}>
|
<span style={{
|
display: 'inline-block',
|
width: 4,
|
height: 16,
|
background: '#1A6FB8',
|
marginRight: 10,
|
borderRadius: 2,
|
}}></span>
|
材料基本信息
|
</div>
|
{modalDataLoading ? (
|
<div style={{ textAlign: 'center', padding: 20 }}>
|
<Spin tip="加载中..." />
|
</div>
|
) : (
|
<div style={{
|
display: 'grid',
|
gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))',
|
gap: 16,
|
}}>
|
<div style={{ display: 'flex', marginBottom: 12 }}>
|
<span style={{ fontWeight: 500, color: '#666', minWidth: 80 }}>提交人:</span>
|
<span style={{ color: '#222', fontWeight: 500 }}>
|
{personInfo
|
? `${personInfo.per_class_name || (currentAuditItem.personType === 'applicant' ? '申请人' : '被申请人')}-${personInfo.true_name || ''}`
|
: (currentAuditItem.personType === 'applicant' ? '申请方' : '被申请方')}
|
</span>
|
</div>
|
<div style={{ display: 'flex', marginBottom: 12 }}>
|
<span style={{ fontWeight: 500, color: '#666', minWidth: 80 }}>材料类型:</span>
|
<span style={{ color: '#222', fontWeight: 500 }}>
|
{currentAuditItem.evidence_type_name || currentAuditItem.name || '证据材料'}
|
</span>
|
</div>
|
<div style={{ display: 'flex', marginBottom: 12 }}>
|
<span style={{ fontWeight: 500, color: '#666', minWidth: 80 }}>材料数量:</span>
|
<span style={{ color: '#222', fontWeight: 500 }}>
|
{personInfo?.total_count ? `${personInfo.total_count}份` : `${evidenceImages.length || 1}份`}
|
</span>
|
</div>
|
<div style={{ display: 'flex', marginBottom: 12 }}>
|
<span style={{ fontWeight: 500, color: '#666', minWidth: 80 }}>提交时间:</span>
|
<span style={{ color: '#222', fontWeight: 500 }}>
|
{personInfo?.submit_time || currentAuditItem.update_time || currentAuditItem.create_time}
|
</span>
|
</div>
|
</div>
|
)}
|
</div>
|
|
{/* 材料说明 */}
|
<div style={{ marginBottom: 24 }}>
|
<div style={{
|
color: '#1A6FB8',
|
fontSize: 16,
|
fontWeight: 600,
|
marginBottom: 16,
|
display: 'flex',
|
alignItems: 'center',
|
}}>
|
<span style={{
|
display: 'inline-block',
|
width: 4,
|
height: 16,
|
background: '#1A6FB8',
|
marginRight: 10,
|
borderRadius: 2,
|
}}></span>
|
材料说明
|
</div>
|
<div style={{
|
border: '1px solid #e8e8e8',
|
borderRadius: 6,
|
padding: 16,
|
background: '#fafafa',
|
minHeight: 100,
|
fontSize: 14,
|
lineHeight: 1.7,
|
}}>
|
<p style={{ marginBottom: 8 }}>
|
<strong>{currentAuditItem.name}</strong>
|
</p>
|
<p style={{ margin: 0, color: '#555' }}>
|
{currentAuditItem.result || '暂无详细说明'}
|
</p>
|
</div>
|
</div>
|
|
{/* 材料清单 */}
|
<div style={{ marginBottom: 24 }}>
|
<div style={{
|
color: '#1A6FB8',
|
fontSize: 16,
|
fontWeight: 600,
|
marginBottom: 16,
|
display: 'flex',
|
alignItems: 'center',
|
}}>
|
<span style={{
|
display: 'inline-block',
|
width: 4,
|
height: 16,
|
background: '#1A6FB8',
|
marginRight: 10,
|
borderRadius: 2,
|
}}></span>
|
材料清单
|
</div>
|
<table style={{
|
width: '100%',
|
borderCollapse: 'collapse',
|
borderRadius: 6,
|
overflow: 'hidden',
|
boxShadow: '0 1px 2px 0 rgba(0, 0, 0, 0.03)',
|
border: '1px solid #f0f0f0',
|
}}>
|
<thead>
|
<tr>
|
<th style={{
|
background: '#fafafa',
|
color: '#333',
|
fontWeight: 600,
|
textAlign: 'left',
|
padding: 16,
|
borderBottom: '1px solid #f0f0f0',
|
fontSize: 14,
|
width: '40%',
|
}}>材料信息</th>
|
<th style={{
|
background: '#fafafa',
|
color: '#333',
|
fontWeight: 600,
|
textAlign: 'left',
|
padding: 16,
|
borderBottom: '1px solid #f0f0f0',
|
fontSize: 14,
|
width: '60%',
|
}}>材料预览</th>
|
</tr>
|
</thead>
|
<tbody>
|
<tr>
|
<td style={{ padding: 16, borderBottom: '1px solid #f5f5f5', verticalAlign: 'top', fontSize: 14 }}>
|
<div style={{ fontWeight: 600, color: '#333', marginBottom: 6 }}>{currentAuditItem.name}</div>
|
<div style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>
|
材料类型:{currentAuditItem.evidence_type_name || '证据材料'}
|
</div>
|
<div style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>
|
文件格式:{personInfo?.suffix || 'PDF/图片'}
|
</div>
|
<div style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>
|
上传时间:{personInfo?.submit_time || currentAuditItem.update_time || currentAuditItem.create_time}
|
</div>
|
{personInfo?.total_file_size && (
|
<div style={{ fontSize: 13, color: '#666', marginBottom: 4 }}>
|
文件大小:{personInfo.total_file_size}MB
|
</div>
|
)}
|
</td>
|
<td style={{ padding: 16, borderBottom: '1px solid #f5f5f5', verticalAlign: 'top', fontSize: 14 }}>
|
{modalDataLoading ? (
|
<div style={{ textAlign: 'center', padding: 20 }}>
|
<Spin size="small" />
|
</div>
|
) : evidenceImages.length > 0 ? (
|
<>
|
<Image.PreviewGroup>
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
{evidenceImages.map((img, index) => {
|
const imgUrl = img.show_url
|
? (platformUrl ? `${platformUrl}/${img.show_url}` : img.show_url)
|
: '';
|
return (
|
<div
|
key={img.file_id || img.id || index}
|
style={{
|
width: 100,
|
height: 80,
|
borderRadius: 4,
|
overflow: 'hidden',
|
border: '1px solid #e8e8e8',
|
cursor: 'pointer',
|
transition: 'all 0.2s',
|
}}
|
>
|
<Image
|
src={imgUrl}
|
alt={img.file_name || `材料${index + 1}`}
|
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
|
fallback="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iODAiIGZpbGw9IiNmNWY1ZjUiLz48dGV4dCB4PSI1MCIgeT0iNDAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iIzk5OSI+5Zu+54mH6aKE6KeIPC90ZXh0Pjwvc3ZnPg=="
|
preview={{
|
mask: <span style={{ fontSize: 12 }}>预览</span>
|
}}
|
/>
|
</div>
|
);
|
})}
|
</div>
|
</Image.PreviewGroup>
|
<div style={{ fontSize: 13, color: '#666', marginTop: 8 }}>共{evidenceImages.length}个文件</div>
|
</>
|
) : (
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
<div style={{
|
width: 100,
|
height: 80,
|
borderRadius: 4,
|
overflow: 'hidden',
|
border: '1px solid #e8e8e8',
|
background: 'linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%)',
|
display: 'flex',
|
alignItems: 'center',
|
justifyContent: 'center',
|
color: '#999',
|
fontSize: 13,
|
fontWeight: 500,
|
}}>
|
暂无图片
|
</div>
|
</div>
|
)}
|
</td>
|
</tr>
|
</tbody>
|
</table>
|
</div>
|
|
{/* 操作按钮 */}
|
<div style={{
|
display: 'flex',
|
justifyContent: 'center',
|
gap: 16,
|
marginTop: 32,
|
paddingTop: 24,
|
borderTop: '1px solid #f0f0f0',
|
}}>
|
<Button
|
type="primary"
|
size="large"
|
loading={auditLoading}
|
onClick={handleAuditPass}
|
style={{
|
background: '#1A6FB8',
|
borderColor: '#1A6FB8',
|
minWidth: 120,
|
height: 40,
|
fontWeight: 500,
|
boxShadow: '0 2px 0 rgba(0, 0, 0, 0.045)',
|
}}
|
>
|
审核通过
|
</Button>
|
<Button
|
size="large"
|
loading={auditLoading}
|
onClick={handleOpenReturnModal}
|
style={{
|
background: 'white',
|
color: '#1A6FB8',
|
borderColor: '#1A6FB8',
|
minWidth: 120,
|
height: 40,
|
fontWeight: 500,
|
boxShadow: '0 2px 0 rgba(0, 0, 0, 0.015)',
|
}}
|
>
|
退回补充
|
</Button>
|
</div>
|
</div>
|
</div>
|
)}
|
</Modal>
|
|
{/* 退回补充子弹窗 */}
|
<Modal
|
title={
|
<div style={{ background: '#1A6FB8', margin: '-20px -24px', padding: '16px 24px', color: 'white' }}>
|
<span style={{ fontSize: 16, fontWeight: 600 }}>退回补充材料</span>
|
</div>
|
}
|
visible={returnModalVisible}
|
onCancel={handleCloseReturnModal}
|
footer={null}
|
width={480}
|
centered
|
destroyOnClose
|
bodyStyle={{ paddingTop: 24 }}
|
>
|
<div style={{ marginBottom: 20 }}>
|
<label style={{ display: 'block', fontWeight: 500, color: '#333', marginBottom: 8, fontSize: 14 }}>
|
退回意见:
|
</label>
|
<TextArea
|
value={auditRemark}
|
onChange={(e) => setAuditRemark(e.target.value)}
|
placeholder="请详细说明需要补充的材料内容及原因..."
|
rows={5}
|
style={{
|
borderRadius: 4,
|
resize: 'vertical',
|
}}
|
/>
|
</div>
|
<div style={{
|
background: '#fafafa',
|
margin: '0 -24px -24px',
|
padding: '16px 24px',
|
display: 'flex',
|
justifyContent: 'flex-end',
|
gap: 12,
|
}}>
|
<Button onClick={handleCloseReturnModal} style={{ padding: '6px 16px' }}>
|
取消
|
</Button>
|
<Button
|
type="primary"
|
loading={auditLoading}
|
onClick={handleAuditReturn}
|
style={{ background: '#1A6FB8', borderColor: '#1A6FB8', padding: '6px 16px' }}
|
>
|
确认退回
|
</Button>
|
</div>
|
</Modal>
|
</div>
|
);
|
};
|
|
/**
|
* 调解协议
|
*/
|
const AgreementSection = () => {
|
const { caseData } = useCaseData();
|
const [agreementContent, setAgreementContent] = useState('');
|
const [loading, setLoading] = useState(false);
|
const [error, setError] = useState(null);
|
const [editModalVisible, setEditModalVisible] = useState(false);
|
const [editContent, setEditContent] = useState('');
|
const [editLoading, setEditLoading] = useState(false);
|
const [actionLoading, setActionLoading] = useState({
|
confirm: false,
|
download: false,
|
regenerate: false,
|
});
|
const loadedRef = useRef(false);
|
|
// 获取 caseId
|
const caseId = caseData?.caseId || getMergedParams().caseId;
|
|
// 处理协议内容展示(纯文本,处理换行)
|
const renderAgreementContent = (content) => {
|
if (!content) return null;
|
// 处理 \n 换行,简单过滤 Markdown 符号
|
const lines = content.split('\n');
|
return lines.map((line, index) => {
|
// 简单过滤 ** 符号,保留文本
|
const cleanLine = line.replace(/\*\*/g, '');
|
if (!cleanLine.trim()) {
|
return <br key={index} />;
|
}
|
return (
|
<p key={index} style={{ margin: '8px 0', lineHeight: 1.6 }}>
|
{cleanLine}
|
</p>
|
);
|
});
|
};
|
|
// 首次加载协议内容
|
const loadAgreement = async () => {
|
if (!caseId) {
|
setError('缺少案件ID,无法加载协议');
|
return;
|
}
|
setLoading(true);
|
setError(null);
|
try {
|
const response = await MediationAgreementAPIService.generateAgreement(caseId);
|
if (response?.data?.agreeContent) {
|
setAgreementContent(response.data.agreeContent);
|
} else {
|
setError('协议内容为空');
|
}
|
} catch (err) {
|
console.error('加载协议失败:', err);
|
setError('加载协议失败,请稍后重试');
|
} finally {
|
setLoading(false);
|
}
|
};
|
|
// 组件挂载时加载协议
|
useEffect(() => {
|
if (caseId && !loadedRef.current) {
|
loadedRef.current = true;
|
loadAgreement();
|
}
|
}, [caseId]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
// 确认协议
|
const handleConfirmAgreement = async () => {
|
if (!caseId) return;
|
setActionLoading(prev => ({ ...prev, confirm: true }));
|
try {
|
await MediationAgreementAPIService.confirmAgreement(caseId, 'mediator');
|
message.success('协议确认成功!');
|
} catch (err) {
|
console.error('确认协议失败:', err);
|
message.error('确认协议失败,请稍后重试');
|
} finally {
|
setActionLoading(prev => ({ ...prev, confirm: false }));
|
}
|
};
|
|
// 下载协议
|
const handleDownloadAgreement = async () => {
|
if (!caseId) return;
|
setActionLoading(prev => ({ ...prev, download: true }));
|
try {
|
await MediationAgreementAPIService.downloadAgreement(caseId);
|
message.success('协议下载成功!');
|
} catch (err) {
|
console.error('下载协议失败:', err);
|
message.error('下载协议失败,请稍后重试');
|
} finally {
|
setActionLoading(prev => ({ ...prev, download: false }));
|
}
|
};
|
|
// 重新生成协议
|
const handleRegenerateAgreement = async () => {
|
if (!caseId) return;
|
setActionLoading(prev => ({ ...prev, regenerate: true }));
|
try {
|
const response = await MediationAgreementAPIService.generateAgreement(caseId);
|
if (response?.data?.agreeContent) {
|
setAgreementContent(response.data.agreeContent);
|
message.success('协议重新生成成功!');
|
}
|
} catch (err) {
|
console.error('重新生成协议失败:', err);
|
message.error('重新生成协议失败,请稍后重试');
|
} finally {
|
setActionLoading(prev => ({ ...prev, regenerate: false }));
|
}
|
};
|
|
// 打开修改弹窗
|
const handleOpenEditModal = async () => {
|
if (!caseId) return;
|
setEditModalVisible(true);
|
setEditLoading(true);
|
try {
|
const response = await MediationAgreementAPIService.getAgreementDetail(caseId);
|
if (response?.data?.agreeContent) {
|
setEditContent(response.data.agreeContent);
|
}
|
} catch (err) {
|
console.error('获取协议内容失败:', err);
|
message.error('获取协议内容失败');
|
} finally {
|
setEditLoading(false);
|
}
|
};
|
|
// 关闭修改弹窗
|
const handleCloseEditModal = () => {
|
setEditModalVisible(false);
|
setEditContent('');
|
};
|
|
// 保存修改
|
const handleSaveEdit = async () => {
|
if (!caseId || !editContent.trim()) {
|
message.warning('协议内容不能为空');
|
return;
|
}
|
setEditLoading(true);
|
try {
|
await MediationAgreementAPIService.updateAgreement(caseId, editContent);
|
message.success('协议修改保存成功!');
|
handleCloseEditModal();
|
// 刷新父页面协议内容
|
const response = await MediationAgreementAPIService.generateAgreement(caseId);
|
if (response?.data?.agreeContent) {
|
setAgreementContent(response.data.agreeContent);
|
}
|
} catch (err) {
|
console.error('保存协议失败:', err);
|
message.error('保存协议失败,请稍后重试');
|
} finally {
|
setEditLoading(false);
|
}
|
};
|
|
// Loading 状态
|
if (loading) {
|
return (
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 300 }}>
|
<Spin size="large" tip="正在生成调解协议..." />
|
</div>
|
);
|
}
|
|
// 错误状态
|
if (error) {
|
return (
|
<div style={{ textAlign: 'center', padding: 40, color: '#ff4d4f' }}>
|
<div style={{ fontSize: '1.2rem', marginBottom: 10 }}>
|
<i className="fas fa-exclamation-circle"></i> {error}
|
</div>
|
<button onClick={loadAgreement} style={{
|
marginTop: 15, padding: '8px 16px', backgroundColor: '#1890ff',
|
color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer'
|
}}>重新加载</button>
|
</div>
|
);
|
}
|
|
return (
|
<div style={{
|
background: '#f8f9fa',
|
borderRadius: 'var(--border-radius)',
|
padding: 18,
|
maxHeight: 450,
|
overflowY: 'auto',
|
}}>
|
{/* 协议内容展示区域 */}
|
<div style={{ lineHeight: 1.5, fontSize: '0.9rem' }}>
|
{agreementContent ? (
|
renderAgreementContent(agreementContent)
|
) : (
|
<div style={{ textAlign: 'center', color: 'var(--gray-color)', padding: 40 }}>
|
暂无协议内容
|
</div>
|
)}
|
</div>
|
|
{/* 操作按钮 */}
|
<div style={{
|
position: 'sticky',
|
bottom: 0,
|
background: 'rgba(255, 255, 255, 0.95)',
|
padding: '15px 0',
|
marginTop: 20,
|
borderTop: '1px solid var(--border-color)',
|
display: 'flex',
|
justifyContent: 'center',
|
gap: 12,
|
}}>
|
<button
|
onClick={handleConfirmAgreement}
|
disabled={actionLoading.confirm}
|
style={{
|
padding: '10px 20px',
|
border: '2px solid #c3e6cb',
|
borderRadius: 'var(--border-radius)',
|
fontWeight: 600,
|
fontSize: '0.9rem',
|
cursor: actionLoading.confirm ? 'not-allowed' : 'pointer',
|
display: 'flex',
|
alignItems: 'center',
|
gap: 6,
|
background: '#d4edda',
|
color: '#155724',
|
opacity: actionLoading.confirm ? 0.6 : 1,
|
}}>
|
<i className={actionLoading.confirm ? "fas fa-spinner fa-spin" : "fas fa-check-circle"}></i>
|
确认协议
|
</button>
|
<button
|
onClick={handleOpenEditModal}
|
style={{
|
padding: '10px 20px',
|
border: '2px solid #ffeaa7',
|
borderRadius: 'var(--border-radius)',
|
fontWeight: 600,
|
fontSize: '0.9rem',
|
cursor: 'pointer',
|
display: 'flex',
|
alignItems: 'center',
|
gap: 6,
|
background: '#fff3cd',
|
color: '#856404',
|
}}>
|
<i className="fas fa-edit"></i>
|
修改协议
|
</button>
|
<button
|
onClick={handleDownloadAgreement}
|
disabled={actionLoading.download}
|
style={{
|
padding: '10px 20px',
|
border: '2px solid #bbdefb',
|
borderRadius: 'var(--border-radius)',
|
fontWeight: 600,
|
fontSize: '0.9rem',
|
cursor: actionLoading.download ? 'not-allowed' : 'pointer',
|
display: 'flex',
|
alignItems: 'center',
|
gap: 6,
|
background: '#e3f2fd',
|
color: '#1565c0',
|
opacity: actionLoading.download ? 0.6 : 1,
|
}}>
|
<i className={actionLoading.download ? "fas fa-spinner fa-spin" : "fas fa-download"}></i>
|
下载协议
|
</button>
|
<button
|
onClick={handleRegenerateAgreement}
|
disabled={actionLoading.regenerate}
|
style={{
|
padding: '10px 20px',
|
border: '2px solid #bee5eb',
|
borderRadius: 'var(--border-radius)',
|
fontWeight: 600,
|
fontSize: '0.9rem',
|
cursor: actionLoading.regenerate ? 'not-allowed' : 'pointer',
|
display: 'flex',
|
alignItems: 'center',
|
gap: 6,
|
background: '#d1ecf1',
|
color: '#0c5460',
|
opacity: actionLoading.regenerate ? 0.6 : 1,
|
}}>
|
<i className={actionLoading.regenerate ? "fas fa-spinner fa-spin" : "fas fa-redo"}></i>
|
重新生成
|
</button>
|
</div>
|
|
{/* 修改调解协议书弹窗 */}
|
<Modal
|
title={null}
|
visible={editModalVisible}
|
onCancel={handleCloseEditModal}
|
footer={null}
|
width={1000}
|
centered
|
destroyOnClose
|
bodyStyle={{ padding: 0 }}
|
>
|
<div style={{ background: '#f5f7fa' }}>
|
{/* 头部 */}
|
<div style={{
|
background: '#1A6FB8',
|
color: 'white',
|
padding: '25px 30px',
|
borderBottom: '5px solid #0d4a8a',
|
}}>
|
<div style={{ fontSize: 24, fontWeight: 600, marginBottom: 10 }}>在线修改调解协议书</div>
|
<div style={{ fontSize: 16, opacity: 0.9 }}>编辑协议内容</div>
|
</div>
|
|
{/* 主体内容 */}
|
<div style={{ padding: '25px 30px', background: 'white' }}>
|
{/* 编辑提示 */}
|
<div style={{
|
fontSize: 14,
|
color: '#666',
|
marginBottom: 15,
|
padding: 10,
|
background: '#f0f8ff',
|
borderRadius: 5,
|
borderLeft: '3px solid #1A6FB8',
|
}}>
|
<strong>编辑提示:</strong>下方文本框中的协议内容可直接编辑修改。编辑完成后,请点击底部的"保存修改"按钮。
|
</div>
|
|
{/* 协议编辑区域 */}
|
<div style={{ marginBottom: 25 }}>
|
{editLoading ? (
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }}>
|
<Spin size="large" tip="加载协议内容..." />
|
</div>
|
) : (
|
<TextArea
|
value={editContent}
|
onChange={(e) => setEditContent(e.target.value)}
|
style={{
|
border: '1px solid #d0e3ff',
|
borderRadius: 8,
|
minHeight: 500,
|
padding: 20,
|
fontSize: 16,
|
lineHeight: 1.8,
|
background: '#f9fafc',
|
}}
|
placeholder="请输入协议内容..."
|
/>
|
)}
|
</div>
|
|
{/* 操作按钮 */}
|
<div style={{
|
display: 'flex',
|
justifyContent: 'flex-end',
|
marginTop: 30,
|
paddingTop: 20,
|
borderTop: '1px solid #eaeaea',
|
}}>
|
<Button
|
type="primary"
|
size="large"
|
loading={editLoading}
|
onClick={handleSaveEdit}
|
style={{
|
padding: '12px 36px',
|
height: 'auto',
|
fontWeight: 600,
|
fontSize: 16,
|
background: '#1A6FB8',
|
borderColor: '#1A6FB8',
|
}}
|
>
|
保存修改
|
</Button>
|
</div>
|
</div>
|
</div>
|
</Modal>
|
</div>
|
);
|
};
|
|
export default TabContainer;
|