| | |
| | | import React, { useState } from 'react'; |
| | | import React, { useState, useEffect } from 'react'; |
| | | import { useCaseData } from '../../contexts/CaseDataContext'; |
| | | import { formatDuration, formatSuccessRate, formatRoundCount } from '../../utils/stateTranslator'; |
| | | import ProcessAPIService from '../../services/ProcessAPIService'; |
| | | import { message, Spin } from 'antd'; |
| | | |
| | | /** |
| | | * 选项卡容器组件 - 4个选项卡 |
| | |
| | | {/* AI调解实时看板 */} |
| | | <div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}> |
| | | <div className="tab-content-area"> |
| | | <MediationBoard /> |
| | | <MediationBoard activeTab={activeTab} /> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | /** |
| | | * 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' }], |
| | |
| | | { |
| | | 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 = { |
| | |
| | | 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 ( |
| | |
| | | 沟通情况总结 |
| | | </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)', |
| | |
| | | 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> |