From 4eb71775167ae903aea17bb410c6201e872daf66 Mon Sep 17 00:00:00 2001
From: tony.cheng <chengmingwei_1984122@126.com>
Date: Thu, 05 Feb 2026 16:35:50 +0800
Subject: [PATCH] feat: 优化类案推荐功能 - 相似度分级、分页加载、详情字段扩展及法条显示优化
---
web-app/src/components/dashboard/TabContainer.jsx | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 158 insertions(+), 12 deletions(-)
diff --git a/web-app/src/components/dashboard/TabContainer.jsx b/web-app/src/components/dashboard/TabContainer.jsx
index c753a92..2b42c5f 100644
--- a/web-app/src/components/dashboard/TabContainer.jsx
+++ b/web-app/src/components/dashboard/TabContainer.jsx
@@ -1,6 +1,8 @@
-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个选项卡
@@ -42,7 +44,7 @@
{/* AI调解实时看板 */}
<div className={`tab-pane ${activeTab === 'mediation-board' ? 'active' : ''}`}>
<div className="tab-content-area">
- <MediationBoard />
+ <MediationBoard activeTab={activeTab} />
</div>
</div>
@@ -126,11 +128,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 +281,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 +289,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 +319,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 +341,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 +370,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>
--
Gitblit v1.8.0