From 2cfe2f7e5c51dc4cb2c312bb8acc4d664779005d Mon Sep 17 00:00:00 2001
From: shimai <shimai@example.com>
Date: Mon, 09 Mar 2026 14:58:05 +0800
Subject: [PATCH] refactor:重构状态等逻辑
---
web-app/src/components/dashboard/TabContainer.jsx | 1506 ++++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 1,353 insertions(+), 153 deletions(-)
diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index c753a92..3f5bd66 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -1,19 +1,41 @@
-import React, { useState } from 'react';
+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 = () => {
+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: '待审核' },
+ { 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">
@@ -42,14 +64,14 @@
{/* AI调解实时看板 */}
<div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}>
<div className="tab-content-area">
- <MediationBoard />
+ <MediationBoard activeTab={activeTab} />
</div>
</div>
{/* 证据材料汇总 */}
<div className={`tab-pane ${activeTab === 'evidence-board' ? 'active' : ''}`}>
<div className="tab-content-area">
- <EvidenceBoard />
+ <EvidenceBoard onStatusChange={handleEvidenceStatusChange} />
</div>
</div>
@@ -62,7 +84,7 @@
</div>
</div>
);
-};
+});
/**
* 调解数据看板
@@ -126,11 +148,152 @@
/**
* AI调解实时看板
*/
-const MediationBoard = () => {
- const boardItems = [
+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' }],
@@ -138,6 +301,7 @@
{
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' }],
@@ -145,12 +309,16 @@
{
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',
@@ -171,12 +339,10 @@
return map[avatar] || 'content-ai';
};
- const getAvatarContent = (avatar) => {
+ const getAvatarContent = (avatar, avatarText) => {
if (avatar === 'ai') return <i className="fas fa-robot"></i>;
- if (avatar === 'applicant') return '李';
- if (avatar === 'respondent') return '好';
- if (avatar === 'mediator') return '调';
- return <i className="fas fa-robot"></i>;
+ // 显示名字的第一个字
+ return avatarText || (avatar === 'applicant' ? '申' : avatar === 'respondent' ? '被' : '调');
};
return (
@@ -195,12 +361,12 @@
沟通情况总结
</div>
<div style={{ lineHeight: 1.5 }}>
- 截至目前,AI调解员已与申请双方进行协商沟通6轮,期间申请人补充材料2次,被申请人补充材料2次,双方对调解方案表现出积极协商态度。
+ {communicationSummary}
</div>
</div>
<div className="board-container" style={{ maxHeight: 450, overflowY: 'auto' }}>
- {boardItems.map((item, index) => (
+ {displayRecords.map((item, index) => (
<div key={index} className="board-item" style={{
background: '#f8f9fa',
borderRadius: 'var(--border-radius)',
@@ -224,7 +390,7 @@
item.avatar === 'respondent' ? 'linear-gradient(135deg, #e9c46a, #e76f51)' :
'linear-gradient(135deg, #7209b7, #3a0ca3)',
}}>
- {getAvatarContent(item.avatar)}
+ {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>
@@ -280,20 +446,316 @@
/**
* 证据材料汇总
*/
-const EvidenceBoard = () => {
- const applicantEvidence = [
- { name: '劳动合同', desc: '2023年8月1日-2026年1月31日,约定月工资¥14,000', time: '2026-01-15 09:35', status: 'verified' },
- { name: '2026年1-3月工资条', desc: '显示工资发放记录,1月后无发放记录', time: '2026-01-15 09:36', status: 'verified' },
- { name: '2026年第一季度考勤记录', desc: '显示全勤,无请假缺勤记录', time: '2026-01-15 09:37', status: 'verified' },
- { name: '绩效考评表', desc: '第一季度绩效评分85分,达到奖金发放标准', time: '2026-01-15 09:38', status: 'verified' },
- { name: '解除劳动合同通知书', desc: '公司2026年1月15日单方面解除合同', time: '2026-01-15 09:45', status: 'pending' },
- ];
+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 respondentEvidence = [
- { name: '2026年1-3月考勤异常记录', desc: '显示李晓明1月有2天事假,应扣款¥1,333', time: '2026-01-15 09:50', status: 'pending' },
- { name: '第一季度销售业绩报表', desc: '显示李晓明未完成销售KPI指标(完成率87%)', time: '2026-01-15 09:52', status: 'pending' },
- { name: '员工离职申请表', desc: '李晓明2026年1月10日提交的辞职申请', time: '2026-01-15 09:55', status: 'pending' },
- ];
+ // 计算整体审核状态(综合申请人和被申请人所有材料)
+ 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.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={{
@@ -305,27 +767,52 @@
justifyContent: 'space-between',
alignItems: 'flex-start',
marginBottom: 10,
+ position: 'relative',
}}>
- <div style={{ flex: 1 }}>
+ <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.desc}</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.time}</span>
+ <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={{ marginLeft: 10 }}>
- <span style={{
- fontSize: '0.75rem',
- padding: '3px 8px',
- borderRadius: 12,
- fontWeight: 600,
- background: item.status === 'verified' ? '#d4edda' : '#fff3cd',
- color: item.status === 'verified' ? '#155724' : '#856404',
- border: `1px solid ${item.status === 'verified' ? '#c3e6cb' : '#ffeaa7'}`,
- }}>
- {item.status === 'verified' ? '已审核' : '待审核'}
- </span>
+ {/* 右上角审核状态标签 */}
+ <div style={{
+ position: 'absolute',
+ top: 12,
+ right: 12,
+ ...getAuditStatusStyle(item.audit_state)
+ }}>
+ {item.audit_state === 1 ? '已审核' : '待审核'}
</div>
</div>
);
@@ -345,10 +832,15 @@
<i className="fas fa-user-tie" style={{ color: 'var(--primary-color)', marginRight: 8 }}></i>
申请人材料
</div>
- <div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{applicantEvidence.length}份材料</div>
+ <Tag color={applicantStatus.color}>{applicantStatus.text}</Tag>
+ <div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{applicantMaterials.length}份材料</div>
</div>
- <div>
- {applicantEvidence.map((item) => renderEvidenceItem(item, 'applicant'))}
+ <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>
@@ -365,12 +857,448 @@
<i className="fas fa-building" style={{ color: '#e9c46a', marginRight: 8 }}></i>
被申请人材料
</div>
- <div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{respondentEvidence.length}份材料</div>
+ <Tag color={respondentStatus.color}>{respondentStatus.text}</Tag>
+ <div style={{ fontSize: '0.85rem', color: 'var(--gray-color)', marginLeft: 'auto' }}>{respondentMaterials.length}份材料</div>
</div>
- <div>
- {respondentEvidence.map((item) => renderEvidenceItem(item, 'respondent'))}
+ <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) => {
+ // 判断 show_url 是否以 http 开头
+ const isFullUrl = img.show_url && img.show_url.startsWith('http');
+ // 如果是完整URL直接使用,否则拼接 platformUrl
+ const imgUrl = img.show_url
+ ? (isFullUrl ? img.show_url : (platformUrl ? `${platformUrl}/${img.show_url}` : img.show_url))
+ : '';
+ // 获取文件名:true_name 为空时取 file_name
+ const rawFileName = img.true_name || img.file_name || `材料${index + 1}`;
+ // 获取文件类型后缀
+ const suffix = img.suffix || '';
+ // 检查文件名是否已包含后缀,避免重复
+ const fileName = suffix && rawFileName.toLowerCase().endsWith(`.${suffix.toLowerCase()}`)
+ ? rawFileName
+ : (suffix ? `${rawFileName}.${suffix}` : rawFileName);
+ const isPdf = suffix.toLowerCase() === 'pdf';
+
+ if (isPdf) {
+ // PDF文件:直接打开PDF链接
+ 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',
+ background: '#f5f5f5',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ }}
+ onClick={() => {
+ // 直接打开PDF
+ window.open(imgUrl, '_blank');
+ }}
+ >
+ <i className="fas fa-file-pdf" style={{ fontSize: 32, color: '#ff4d4f' }} />
+ <span style={{ fontSize: 10, color: '#666', marginTop: 4, maxWidth: 90, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
+ {fileName}
+ </span>
+ </div>
+ );
+ }
+
+ // 图片文件:使用图片预览
+ 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={fileName}
+ style={{ width: '100%', height: '100%', objectFit: 'cover' }}
+ fallback="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjgwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAiIGhlaWdodD0iODAiIGZpbGw9IiNmNWY1ZjUiLz48dGV4dCB4PSI1MCIgeT0iNDAiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMCIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZmlsbD0iIzk5OSI+5Zu+54mH6aKE6KeIPC90ZXh0Pjwvc3ZnPg=="
+ preview={{
+ mask: (
+ <span style={{ fontSize: 12, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%' }}>
+ <span>预览</span>
+ <span style={{ fontSize: 10, marginTop: 2, maxWidth: 80, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
+ {fileName}
+ </span>
+ </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>
);
};
@@ -379,6 +1307,214 @@
* 调解协议
*/
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 {
+ // 调用API获取PDF文件流
+ const response = await MediationAgreementAPIService.downloadAgreement(caseId);
+
+ // 创建Blob对象(PDF格式)
+ const blob = new Blob([response.data], {
+ type: 'application/pdf'
+ });
+
+ // 创建下载链接
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `调解协议_${caseId}.pdf`;
+
+ // 触发下载
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ // 清理URL对象
+ window.URL.revokeObjectURL(url);
+
+ 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.regenerateAgreement(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',
@@ -387,56 +1523,15 @@
maxHeight: 450,
overflowY: 'auto',
}}>
+ {/* 协议内容展示区域 */}
<div style={{ lineHeight: 1.5, fontSize: '0.9rem' }}>
- <div style={{ textAlign: 'center', color: 'var(--secondary-color)', fontSize: '1.2rem', marginBottom: 20, fontWeight: 700 }}>
- 李晓明与广东好又多贸易有限公司劳动争议调解协议书
- </div>
-
- <p><strong>甲方(申请人):</strong>李晓明</p>
- <p><strong>乙方(被申请人):</strong>广东好又多贸易有限公司</p>
-
- <p style={{ textIndent: '2em', marginTop: 15 }}>
- 甲方与乙方因劳动报酬支付问题发生争议,双方于2026年1月15日向"云小调"劳动争议AI调解智能体申请调解。经AI调解员调解,双方本着平等自愿、互谅互让的原则,达成如下调解协议:
- </p>
-
- <h3 style={{ color: 'var(--secondary-color)', margin: '15px 0 8px', fontSize: '1rem' }}>一、争议事项</h3>
- <p style={{ textIndent: '2em' }}>甲方主张乙方拖欠2026年1月至3月共三个月工资、2026年第一季度绩效奖金以及解除劳动合同经济补偿金,合计人民币52,800元。</p>
-
- <h3 style={{ color: 'var(--secondary-color)', margin: '15px 0 8px', fontSize: '1rem' }}>二、调解结果</h3>
- <p style={{ textIndent: '2em' }}>经调解,双方达成一致意见如下:</p>
- <ul style={{ paddingLeft: 30, marginBottom: 12 }}>
- <li style={{ marginBottom: 6 }}>乙方同意向甲方支付劳动报酬共计人民币肆万肆仟元整(¥44,000)</li>
- <li style={{ marginBottom: 6 }}>支付方式:分两期支付
- <ul style={{ paddingLeft: 20 }}>
- <li>第一期:人民币贰万贰仟元整(¥22,000),于2026年1月30日前支付</li>
- <li>第二期:人民币贰万贰仟元整(¥22,000),于2026年2月28日前支付</li>
- </ul>
- </li>
- <li style={{ marginBottom: 6 }}>支付账户:甲方指定的银行账户(账户信息另行提供)</li>
- </ul>
-
- <h3 style={{ color: 'var(--secondary-color)', margin: '15px 0 8px', fontSize: '1rem' }}>三、双方权利义务</h3>
- <ul style={{ paddingLeft: 30, marginBottom: 12 }}>
- <li style={{ marginBottom: 6 }}>乙方应按约定时间足额支付上述款项</li>
- <li style={{ marginBottom: 6 }}>甲方收到全部款项后,双方劳动关系正式解除</li>
- <li style={{ marginBottom: 6 }}>双方互不追究其他法律责任,本协议履行完毕后,争议事项一次性了结</li>
- </ul>
-
- {/* 签名区域 */}
- <div style={{ display: 'flex', justifyContent: 'space-between', marginTop: 20, paddingTop: 15, borderTop: '1px solid var(--border-color)' }}>
- <div style={{ textAlign: 'center', width: '45%' }}>
- <div style={{ borderBottom: '1px solid var(--dark-color)', padding: '15px 0 5px', marginBottom: 5 }}></div>
- <div>甲方(申请人):李晓明</div>
- <div style={{ fontSize: '0.8rem', color: 'var(--gray-color)' }}>签字/盖章</div>
- <div style={{ fontSize: '0.8rem', color: 'var(--gray-color)', marginTop: 5 }}>日期:2026年1月15日</div>
+ {agreementContent ? (
+ renderAgreementContent(agreementContent)
+ ) : (
+ <div style={{ textAlign: 'center', color: 'var(--gray-color)', padding: 40 }}>
+ 暂无协议内容
</div>
- <div style={{ textAlign: 'center', width: '45%' }}>
- <div style={{ borderBottom: '1px solid var(--dark-color)', padding: '15px 0 5px', marginBottom: 5 }}></div>
- <div>乙方(被申请人):广东好又多贸易有限公司</div>
- <div style={{ fontSize: '0.8rem', color: 'var(--gray-color)' }}>签字/盖章</div>
- <div style={{ fontSize: '0.8rem', color: 'var(--gray-color)', marginTop: 5 }}>日期:2026年1月15日</div>
- </div>
- </div>
+ )}
</div>
{/* 操作按钮 */}
@@ -451,71 +1546,176 @@
justifyContent: 'center',
gap: 12,
}}>
- <button style={{
- padding: '10px 20px',
- border: '2px solid #c3e6cb',
- borderRadius: 'var(--border-radius)',
- fontWeight: 600,
- fontSize: '0.9rem',
- cursor: 'pointer',
- display: 'flex',
- alignItems: 'center',
- gap: 6,
- background: '#d4edda',
- color: '#155724',
- }}>
- <i className="fas fa-check-circle"></i>
+ <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 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',
- }}>
+ <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 style={{
- padding: '10px 20px',
- border: '2px solid #bbdefb',
- borderRadius: 'var(--border-radius)',
- fontWeight: 600,
- fontSize: '0.9rem',
- cursor: 'pointer',
- display: 'flex',
- alignItems: 'center',
- gap: 6,
- background: '#e3f2fd',
- color: '#1565c0',
- }}>
- <i className="fas fa-download"></i>
+ <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 style={{
- padding: '10px 20px',
- border: '2px solid #bee5eb',
- borderRadius: 'var(--border-radius)',
- fontWeight: 600,
- fontSize: '0.9rem',
- cursor: 'pointer',
- display: 'flex',
- alignItems: 'center',
- gap: 6,
- background: '#d1ecf1',
- color: '#0c5460',
- }}>
- <i className="fas fa-redo"></i>
+ <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>
);
};
--
Gitblit v1.8.0