From 6a2adec4956d5f5eef09c2e6e16d70460dc27772 Mon Sep 17 00:00:00 2001
From: chengmw <chengmingwei_1984122@126.com>
Date: Wed, 01 Apr 2026 10:17:57 +0800
Subject: [PATCH] feat: 新增调解看板 UI 组件和服务 - 包含头部、警告提示、AI 建议卡片、协商进度、当事人信息卡片及时间轴 API
---
web-app/src/components/common/AppHeader.jsx | 124 ++++
web-app/src/components/dashboard/PartyInfoCard.jsx | 136 +++++
web-app/src/components/common/WarningAlert.css | 119 ++++
web-app/src/services/MediationTimelineAPIService.js | 31 +
web-app/src/components/dashboard/AISuggestionCard.css | 57 ++
web-app/src/components/dashboard/NegotiationProgress.jsx | 88 +++
web-app/src/components/dashboard/AISuggestionCard.jsx | 50 +
web-app/src/components/dashboard/NegotiationProgress.css | 75 ++
openspec/changes/upgrade-homepage-layout/tasks.md | 79 +++
openspec/changes/upgrade-homepage-layout/specs/homepage-layout/spec.md | 166 ++++++
web-app/src/components/common/WarningAlert.jsx | 116 ++++
openspec/changes/upgrade-homepage-layout/design.md | 163 ++++++
web-app/src/components/dashboard/PartyInfoCard.css | 82 +++
web-app/src/components/common/AppHeader.css | 131 +++++
openspec/changes/upgrade-homepage-layout/proposal.md | 88 +++
15 files changed, 1,505 insertions(+), 0 deletions(-)
diff --git a/openspec/changes/upgrade-homepage-layout/design.md b/openspec/changes/upgrade-homepage-layout/design.md
new file mode 100644
index 0000000..aefb0ce
--- /dev/null
+++ b/openspec/changes/upgrade-homepage-layout/design.md
@@ -0,0 +1,163 @@
+# Design: 首页布局升级优化
+
+## Context
+
+当前首页(index.html)需要按照最新原型进行全面升级,涉及多个新组件的添加和现有组件的修改。本次升级主要目标是提升调解员工作效率,通过可视化展示关键信息。
+
+**原型参考**: `document/原型/index.html` 和用户提供的原型图片
+
+## Goals / Non-Goals
+
+### Goals
+- 1:1还原原型图视觉效果
+- 新增蓝色顶部Header,统一系统导航风格
+- 提供预警消息实时提示,提升调解员响应速度
+- 可视化展示申请双方信息和协商沟通进度
+- 增强调解成功率数据展示,提供同比分析
+
+### Non-Goals
+- 不修改现有API调用机制,复用CaseDataContext
+- 不修改底部悬浮控制面板
+- "查看详细策略建议"功能暂不实现跳转
+
+## Decisions
+
+### 1. 组件架构
+
+**Decision**: 新增独立组件,不侵入现有组件逻辑
+
+```
+components/
+├── common/
+│ ├── AppHeader.jsx # 顶部Header(新增)
+│ └── WarningAlert.jsx # 预警提示(新增)
+└── dashboard/
+ ├── PartyInfoCard.jsx # 申请双方信息(新增)
+ ├── NegotiationProgress.jsx # 协商沟通(新增)
+ ├── AISuggestionCard.jsx # AI调解建议(新增)
+ └── TabContainer.jsx # 修改MediationDataBoard
+```
+
+**Rationale**: 保持单一职责,便于维护和测试
+
+### 2. 数据流设计
+
+**Decision**: 复用现有CaseDataContext,新增两个API调用
+
+```
+CaseDataContext
+├── caseData (现有)
+│ ├── mediation.success_rate
+│ ├── mediation.yoy_success_rate
+│ ├── mediation.yoy_before_hours
+│ └── mediation.mediation_count
+├── processNodes (现有)
+└── 新增API调用:
+ ├── getWarningNotifyList(mediationId)
+ └── getPersonList(caseId)
+```
+
+**Rationale**:
+- 预警消息和当事人列表数据独立于timeline,需单独API获取
+- 成功率同比数据从现有timeline字段获取,无需新API
+
+### 3. Header数据来源
+
+**Decision**: 调解员信息从URL参数获取,使用getMergedParams工具
+
+```javascript
+// 从URL参数获取
+const params = getMergedParams();
+const mediatorInfo = {
+ trueName: params.trueName || '调解员',
+ unit: params.unit || '',
+ roleName: params.roleName || '',
+ avatar: params.avatar || DEFAULT_AVATAR
+};
+```
+
+**Rationale**: 调解员信息由外部系统传入,保持与现有参数获取机制一致
+
+### 4. 预警消息展示策略
+
+**Decision**:
+- 单条消息: 直接在消息条展示完整内容
+- 多条消息: 展示第一条 + "还有N条预警" + "查看更多"按钮
+- 点击"查看更多": 弹出Modal展示全部预警列表
+
+### 5. 协商沟通进度计算
+
+**Decision**:
+```javascript
+const currentRound = timeline.mediation?.mediation_count || 0;
+const nodeCount = processNodes.length;
+
+// 总次数计算
+let totalRounds = nodeCount;
+if (currentRound > nodeCount && !isLastNode) {
+ totalRounds = currentRound + 1;
+}
+
+// 渲染点线块
+const dots = Array(totalRounds).fill(false).map((_, i) => i < currentRound);
+```
+
+## Layout Structure
+
+```
+┌────────────────────────────────────────────────────────┐
+│ AppHeader (蓝色背景) │
+│ [矛盾纠纷应用] [🔔2] [👤李X萌 XX区..]│
+├────────────────────────────────────────────────────────┤
+│ TopSection (现有案件信息头部) │
+├────────────────────────────────────────────────────────┤
+│ MediationProgress (调解进度条) │
+├────────────────────────────────────────────────────────┤
+│ WarningAlert (浅黄色预警提示) │
+│ ⚠️ 预警:申请人张三情绪激动... [还有2条] [查看更多]│
+├────────────────────────────────────────────────────────┤
+│ TabContainer │
+│ ┌─────────────────────────────────────────────────────┤
+│ │ MediationDataBoard (调解分析Tab) │
+│ │ ┌──────────────────────┬─────────────────────────┐ │
+│ │ │ 诉求差距分析 │ 申请双方 │ │
+│ │ │ ┌────────────────┐ │ [👤申请人] VS [🏢被申请人]│ │
+│ │ │ │ 主要分歧点... │ │ │ │
+│ │ │ └────────────────┘ │ 预计调解成功率 │ │
+│ │ │ │ 68% ↑+8% 较3小时前 │ │
+│ │ │ AI调解建议 │ │ │
+│ │ │ ┌────────────────┐ │ 协商沟通 │ │
+│ │ │ │ 建议调解员... │ │ 第6轮 │ │
+│ │ │ │ [查看详细策略] │ │ ●━●━●━●━●━○ │ │
+│ │ │ └────────────────┘ │ │ │
+│ │ └──────────────────────┴─────────────────────────┘ │
+│ └─────────────────────────────────────────────────────┤
+└────────────────────────────────────────────────────────┘
+```
+
+## Risks / Trade-offs
+
+### Risk 1: API响应延迟
+- **风险**: 预警消息和当事人列表API可能响应慢
+- **缓解**: 组件内部loading状态,不阻塞整体页面渲染
+
+### Risk 2: URL参数缺失
+- **风险**: 调解员信息URL参数可能为空
+- **缓解**: 提供默认值和默认头像
+
+### Risk 3: 布局兼容性
+- **风险**: 新增Header可能影响现有页面滚动
+- **缓解**: Header使用fixed定位,调整body padding-top
+
+## Migration Plan
+
+1. 先创建API服务和新组件(不影响现有功能)
+2. 在App.js中添加AppHeader(影响最小)
+3. 在MediationProgress下方添加WarningAlert
+4. 修改MediationDataBoard布局,集成新组件
+5. 调整CSS样式,验证视觉效果
+
+## Open Questions
+
+- [已解答] AI调解建议内容来源? → 先使用Mock数据
+- [已解答] "查看详细策略建议"跳转? → 暂不跳转,提示"功能升级中"
diff --git a/openspec/changes/upgrade-homepage-layout/proposal.md b/openspec/changes/upgrade-homepage-layout/proposal.md
new file mode 100644
index 0000000..e562710
--- /dev/null
+++ b/openspec/changes/upgrade-homepage-layout/proposal.md
@@ -0,0 +1,88 @@
+# Change: 首页布局升级优化
+
+## Why
+当前首页缺少顶部导航Header、预警提示、申请双方信息展示、协商沟通可视化和AI调解建议等关键功能模块,需要按照最新原型图进行布局升级,提升调解员工作效率和用户体验。
+
+## What Changes
+- **新增**: 蓝色顶部Header组件,包含系统名称、通知图标(带消息数量气泡)、调解员信息
+- **新增**: 预警提示消息条组件(浅黄色背景),支持多条消息查看弹窗
+- **新增**: 申请双方信息组件,展示申请人/被申请人信息及情绪标签
+- **修改**: 预计调解成功率组件,增加同比数据展示(如 +8% 较3小时前)
+- **新增**: 协商沟通面板组件,点线式可视化展示沟通轮次
+- **新增**: AI调解建议面板,展示调解建议内容和详情按钮
+- **新增**: 预警消息API服务 (`/api/v1/mediation-timeline/warning-notify-list/{mediation_id}`)
+- **新增**: 当事人列表API服务 (`/api/v1/mediation-timeline/person-list/{case_id}`)
+
+## Impact
+- **Affected specs**: homepage-layout
+- **Affected code**:
+ - `web-app/src/App.js` - 添加Header组件
+ - `web-app/src/App.css` - 新增Header和相关组件样式
+ - `web-app/src/components/dashboard/TopSection.jsx` - 添加预警提示
+ - `web-app/src/components/dashboard/TabContainer.jsx` - 修改MediationDataBoard组件
+ - 新增组件:
+ - `web-app/src/components/common/AppHeader.jsx` - 顶部Header
+ - `web-app/src/components/common/WarningAlert.jsx` - 预警提示
+ - `web-app/src/components/dashboard/PartyInfoCard.jsx` - 申请双方信息
+ - `web-app/src/components/dashboard/NegotiationProgress.jsx` - 协商沟通
+ - `web-app/src/components/dashboard/AISuggestionCard.jsx` - AI调解建议
+ - `web-app/src/services/MediationTimelineAPIService.js` - 新增API服务
+
+## Data Sources
+
+### 1. Header调解员信息
+从URL请求参数获取:
+- `trueName` - 姓名
+- `unit` - 地址
+- `roleName` - 角色
+- `avatar` - 头像(空则使用默认头像)
+
+### 2. 预警消息API
+```
+GET /api/v1/mediation-timeline/warning-notify-list/{mediation_id}
+
+Response:
+{
+ "code": 200,
+ "data": [
+ {
+ "id": "1",
+ "content": "申请人张三情绪激动...",
+ "create_time": "2026-12-20 12:30:15",
+ "level_type": "3"
+ }
+ ]
+}
+```
+
+### 3. 当事人列表API
+```
+GET /api/v1/mediation-timeline/person-list/{case_id}
+
+Response:
+{
+ "code": 200,
+ "data": [
+ {
+ "id": "202602261114261001",
+ "per_type": "15_020008-1",
+ "per_type_name": "申请方当事人",
+ "true_name": "刘树杰",
+ "record_id": "1001",
+ "tag_name": "情绪激动",
+ "tag_style": "red"
+ }
+ ]
+}
+```
+
+### 4. 成功率同比数据
+从 `CaseDataContext` 或 `localStorage.case_data_timeline` 获取:
+- `mediation.yoy_success_rate` - 同比增长率
+- 若为空: `(mediation.success_rate - mediation.last_success_rate) * 100`
+- `mediation.yoy_before_hours` - 对比时间(小时),若为空则为0
+
+### 5. 协商沟通轮次
+- 当前轮次: `timeline.mediation?.mediation_count`
+- 总次数: 默认为流程节点总数
+- 当沟通次数超过总次数且未到最后节点时: 总次数 = 沟通次数 + 1
diff --git a/openspec/changes/upgrade-homepage-layout/specs/homepage-layout/spec.md b/openspec/changes/upgrade-homepage-layout/specs/homepage-layout/spec.md
new file mode 100644
index 0000000..ecfb403
--- /dev/null
+++ b/openspec/changes/upgrade-homepage-layout/specs/homepage-layout/spec.md
@@ -0,0 +1,166 @@
+# Homepage Layout Specification
+
+## ADDED Requirements
+
+### Requirement: App Header Component
+系统 SHALL 在页面顶部显示蓝色背景的Header组件,包含系统名称、通知图标和调解员信息。
+
+#### Scenario: Header displays system name
+- **WHEN** 页面加载完成
+- **THEN** Header左侧显示"矛盾纠纷应用"系统名称
+
+#### Scenario: Header displays notification icon with badge
+- **WHEN** 存在未读通知消息
+- **THEN** Header右侧显示通知图标,图标右上角显示红色气泡,气泡内显示未读消息数量
+
+#### Scenario: Header displays mediator info from URL params
+- **WHEN** URL参数包含trueName、unit、roleName、avatar
+- **THEN** Header右侧显示调解员头像、姓名、"地址|角色"格式的信息
+
+#### Scenario: Header uses default avatar when param empty
+- **WHEN** URL参数avatar为空
+- **THEN** 使用系统默认头像图片
+
+#### Scenario: Notification popup on icon click
+- **WHEN** 用户点击通知图标
+- **THEN** 显示通知消息列表弹窗
+
+---
+
+### Requirement: Warning Alert Component
+系统 SHALL 在调解进度条下方显示预警提示消息条,支持多条消息查看。
+
+#### Scenario: Single warning message display
+- **WHEN** 预警消息API返回1条消息
+- **THEN** 预警条直接显示完整消息内容,背景为浅黄色
+
+#### Scenario: Multiple warning messages display
+- **WHEN** 预警消息API返回多条消息
+- **THEN** 预警条显示第一条消息 + "还有N条预警" + "查看更多"按钮
+
+#### Scenario: Warning modal on view more click
+- **WHEN** 用户点击"查看更多"按钮
+- **THEN** 弹出Modal显示全部预警消息列表
+
+#### Scenario: Warning API call
+- **WHEN** 页面加载且mediationId存在
+- **THEN** 调用 `/api/v1/mediation-timeline/warning-notify-list/{mediation_id}` 获取预警数据
+
+---
+
+### Requirement: Party Info Card Component
+系统 SHALL 在调解分析面板右侧显示申请双方信息卡片。
+
+#### Scenario: Applicant info display
+- **WHEN** 当事人列表API返回申请方当事人数据
+- **THEN** 左侧显示申请人头像、姓名,上方显示情绪标签(如有)
+
+#### Scenario: Respondent info display
+- **WHEN** 当事人列表API返回被申请方当事人数据
+- **THEN** 右侧显示被申请人头像、公司名称,上方显示标签(如有)
+
+#### Scenario: VS separator display
+- **WHEN** 同时存在申请人和被申请人
+- **THEN** 中间显示VS分隔符图标
+
+#### Scenario: Emotion tag style
+- **WHEN** 当事人数据包含tag_name和tag_style
+- **THEN** 按tag_style渲染标签颜色(red=红色等)
+
+#### Scenario: Person list API call
+- **WHEN** 页面加载且caseId存在
+- **THEN** 调用 `/api/v1/mediation-timeline/person-list/{case_id}` 获取当事人数据
+
+---
+
+### Requirement: Success Rate YoY Display
+系统 SHALL 在预计调解成功率组件中显示同比数据。
+
+#### Scenario: YoY rate from API field
+- **WHEN** timeline.mediation.yoy_success_rate存在
+- **THEN** 显示该值作为同比增长率(如+8%)
+
+#### Scenario: YoY rate calculation fallback
+- **WHEN** yoy_success_rate为空
+- **THEN** 计算 (success_rate - last_success_rate) * 100 作为同比增长率
+
+#### Scenario: YoY time display
+- **WHEN** timeline.mediation.yoy_before_hours存在
+- **THEN** 显示"较{yoy_before_hours}小时前"
+
+#### Scenario: YoY time default
+- **WHEN** yoy_before_hours为空
+- **THEN** 显示"较0小时前"
+
+---
+
+### Requirement: Negotiation Progress Component
+系统 SHALL 在调解分析面板右侧显示协商沟通进度组件。
+
+#### Scenario: Round count display
+- **WHEN** timeline.mediation.mediation_count存在
+- **THEN** 显示"第{mediation_count}轮"
+
+#### Scenario: Progress dots default count
+- **WHEN** 沟通次数 <= 流程节点总数
+- **THEN** 总次数 = 流程节点总数,已完成轮次显示蓝色,未完成显示灰色
+
+#### Scenario: Progress dots dynamic count
+- **WHEN** 沟通次数 > 流程节点总数 且 未到最后节点
+- **THEN** 总次数 = 沟通次数 + 1
+
+#### Scenario: Progress dots all complete
+- **WHEN** 已到达最后节点
+- **THEN** 全部点显示蓝色
+
+---
+
+### Requirement: AI Suggestion Card Component
+系统 SHALL 在诉求差距分析下方显示AI调解建议面板。
+
+#### Scenario: AI suggestion content display
+- **WHEN** 面板加载
+- **THEN** 显示AI调解建议内容(Mock数据),背景为浅蓝色
+
+#### Scenario: View detail button display
+- **WHEN** 面板加载
+- **THEN** 底部显示"查看详细策略建议"按钮
+
+#### Scenario: View detail button click
+- **WHEN** 用户点击"查看详细策略建议"按钮
+- **THEN** 显示提示消息"该功能正在升级中,敬请期待!"
+
+---
+
+### Requirement: Warning Notify API Service
+系统 SHALL 提供预警消息列表API服务方法。
+
+#### Scenario: Get warning notify list
+- **WHEN** 调用 getWarningNotifyList(mediationId)
+- **THEN** 发送GET请求到 `/api/v1/mediation-timeline/warning-notify-list/{mediationId}`
+- **AND** 返回预警消息数组
+
+---
+
+### Requirement: Person List API Service
+系统 SHALL 提供当事人列表API服务方法。
+
+#### Scenario: Get person list
+- **WHEN** 调用 getPersonList(caseId)
+- **THEN** 发送GET请求到 `/api/v1/mediation-timeline/person-list/{caseId}`
+- **AND** 返回当事人数组
+
+---
+
+## MODIFIED Requirements
+
+### Requirement: Mediation Data Board Layout
+系统 SHALL 将调解数据看板调整为左右两栏布局。
+
+#### Scenario: Left column content
+- **WHEN** 调解分析Tab激活
+- **THEN** 左列显示:诉求差距分析 + AI调解建议面板
+
+#### Scenario: Right column content
+- **WHEN** 调解分析Tab激活
+- **THEN** 右列显示:申请双方信息 + 预计调解成功率(含同比) + 协商沟通进度
diff --git a/openspec/changes/upgrade-homepage-layout/tasks.md b/openspec/changes/upgrade-homepage-layout/tasks.md
new file mode 100644
index 0000000..8496b29
--- /dev/null
+++ b/openspec/changes/upgrade-homepage-layout/tasks.md
@@ -0,0 +1,79 @@
+# Tasks: 首页布局升级优化
+
+## 1. API服务层
+
+- [ ] 1.1 创建 `MediationTimelineAPIService.js` 服务文件
+- [ ] 1.2 实现 `getWarningNotifyList(mediationId)` 方法 - 获取预警消息列表
+- [ ] 1.3 实现 `getPersonList(caseId)` 方法 - 获取当事人列表
+
+## 2. 蓝色顶部Header组件
+
+- [ ] 2.1 创建 `AppHeader.jsx` 组件
+- [ ] 2.2 实现左侧系统名称"矛盾纠纷应用"展示
+- [ ] 2.3 实现右侧通知图标 + 红色气泡消息数量
+- [ ] 2.4 实现右侧调解员信息展示(从URL参数获取trueName/unit/roleName/avatar)
+- [ ] 2.5 实现通知列表弹窗(点击通知图标触发)
+- [ ] 2.6 添加Header相关CSS样式(蓝色背景渐变)
+- [ ] 2.7 在 `App.js` 中集成 AppHeader 组件
+
+## 3. 预警提示消息组件
+
+- [ ] 3.1 创建 `WarningAlert.jsx` 组件
+- [ ] 3.2 实现浅黄色预警消息条展示(单条时直接显示)
+- [ ] 3.3 实现多条消息时右侧显示数量 + "查看更多"按钮
+- [ ] 3.4 实现预警消息详情弹窗(Modal)
+- [ ] 3.5 添加预警提示相关CSS样式
+- [ ] 3.6 在 `MediationProgress` 组件下方集成预警提示
+
+## 4. 申请双方信息组件
+
+- [ ] 4.1 创建 `PartyInfoCard.jsx` 组件
+- [ ] 4.2 实现申请人信息展示(头像 + 姓名 + 情绪标签)
+- [ ] 4.3 实现中间VS分隔符展示
+- [ ] 4.4 实现被申请人信息展示(头像 + 公司名称 + 标签)
+- [ ] 4.5 实现情绪标签样式(根据tag_style显示不同颜色)
+- [ ] 4.6 添加申请双方信息相关CSS样式
+- [ ] 4.7 在 `MediationDataBoard` 右侧区域集成组件
+
+## 5. 预计调解成功率组件改进
+
+- [ ] 5.1 修改 `MediationDataBoard` 中的成功率展示
+- [ ] 5.2 实现同比数据获取逻辑(yoy_success_rate或计算差值)
+- [ ] 5.3 实现"较X小时前"时间展示(yoy_before_hours)
+- [ ] 5.4 添加成功率同比展示样式(绿色上升箭头 + 百分比)
+
+## 6. 协商沟通组件
+
+- [ ] 6.1 创建 `NegotiationProgress.jsx` 组件
+- [ ] 6.2 实现"协商沟通"标题展示
+- [ ] 6.3 实现"第N轮"文字展示
+- [ ] 6.4 实现点线式沟通进度(默认6个点线块)
+- [ ] 6.5 实现进度着色逻辑(已完成蓝色/未完成灰色)
+- [ ] 6.6 实现总次数动态计算(基于流程节点和沟通次数)
+- [ ] 6.7 添加协商沟通相关CSS样式
+- [ ] 6.8 在 `MediationDataBoard` 右侧区域集成组件
+
+## 7. AI调解建议面板
+
+- [ ] 7.1 创建 `AISuggestionCard.jsx` 组件
+- [ ] 7.2 实现AI建议内容展示(Mock数据)
+- [ ] 7.3 实现"查看详细策略建议"按钮
+- [ ] 7.4 实现按钮点击提示("该功能正在升级中,敬请期待!")
+- [ ] 7.5 添加AI调解建议相关CSS样式(浅蓝色背景)
+- [ ] 7.6 在诉求差距分析下方集成组件
+
+## 8. 布局调整与整合
+
+- [ ] 8.1 调整 `App.css` 整体布局适配新Header
+- [ ] 8.2 调整 `MediationDataBoard` 布局为左右两栏
+- [ ] 8.3 确保各组件响应式适配
+- [ ] 8.4 验证1:1还原原型图效果
+
+## 9. 验证与测试
+
+- [ ] 9.1 验证Header通知功能正常
+- [ ] 9.2 验证预警消息API集成正常
+- [ ] 9.3 验证当事人列表API集成正常
+- [ ] 9.4 验证成功率同比数据计算正确
+- [ ] 9.5 验证协商沟通进度显示正确
+- [ ] 9.6 整体视觉与原型图对比验收
diff --git a/web-app/src/components/common/AppHeader.css b/web-app/src/components/common/AppHeader.css
new file mode 100644
index 0000000..ad94494
--- /dev/null
+++ b/web-app/src/components/common/AppHeader.css
@@ -0,0 +1,131 @@
+/**
+ * AppHeader 组件样式
+ * 蓝色顶部导航栏
+ */
+
+.app-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ background: linear-gradient(135deg, #1a6fb8, #0d4a8a);
+ color: white;
+ padding: 10px 24px;
+ height: 50px;
+ flex-shrink: 0;
+}
+
+/* 左侧区域 */
+.header-left {
+ display: flex;
+ align-items: center;
+}
+
+.header-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ letter-spacing: 1px;
+}
+
+/* 右侧区域 */
+.header-right {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+}
+
+/* 通知图标区域 */
+.header-notification {
+ cursor: pointer;
+ padding: 6px;
+ border-radius: 50%;
+ transition: background 0.2s;
+}
+
+.header-notification:hover {
+ background: rgba(255, 255, 255, 0.15);
+}
+
+.header-bell-icon {
+ font-size: 1.2rem;
+ color: white;
+}
+
+/* 调解员信息区域 */
+.header-mediator {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+
+.mediator-avatar {
+ width: 32px;
+ height: 32px;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+}
+
+.mediator-details {
+ display: flex;
+ flex-direction: column;
+ line-height: 1.3;
+}
+
+.mediator-name {
+ font-size: 0.9rem;
+ font-weight: 600;
+}
+
+.mediator-role {
+ font-size: 0.75rem;
+ opacity: 0.85;
+}
+
+.mediator-status-dot {
+ width: 8px;
+ height: 8px;
+ background: #52c41a;
+ border-radius: 50%;
+ margin-left: 4px;
+}
+
+/* 通知列表弹窗 */
+.notification-popover .ant-popover-inner {
+ max-width: 320px;
+}
+
+.notification-list {
+ max-height: 300px;
+ overflow-y: auto;
+}
+
+.notification-item {
+ padding: 8px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.notification-item:last-child {
+ border-bottom: none;
+}
+
+.notification-content {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.notification-text {
+ font-size: 0.85rem;
+ color: #333;
+ line-height: 1.4;
+}
+
+.notification-time {
+ font-size: 0.75rem;
+ color: #999;
+}
+
+.notification-empty {
+ padding: 20px;
+ text-align: center;
+ color: #999;
+ font-size: 0.85rem;
+}
diff --git a/web-app/src/components/common/AppHeader.jsx b/web-app/src/components/common/AppHeader.jsx
new file mode 100644
index 0000000..abd1f39
--- /dev/null
+++ b/web-app/src/components/common/AppHeader.jsx
@@ -0,0 +1,124 @@
+/**
+ * AppHeader 组件 - 蓝色顶部导航栏
+ * 包含系统名称、通知图标、调解员信息
+ */
+
+import React, { useState, useCallback } from 'react';
+import { Badge, Popover, List, Avatar } from 'antd';
+import { BellOutlined, UserOutlined } from '@ant-design/icons';
+import { getMergedParams } from '../../utils/urlParams';
+import './AppHeader.css';
+
+// 默认头像
+const DEFAULT_AVATAR = 'http://gz.hugeinfo.com.cn/dyh/wx414ae04ac3f10b4e/images/pngAI_logo.png';
+
+/**
+ * 从URL参数获取调解员信息
+ */
+const getMediatorInfo = () => {
+ const params = getMergedParams();
+ return {
+ trueName: params.trueName || '调解员',
+ unit: params.unit || '',
+ roleName: params.roleName || '管理员',
+ avatar: params.avatar || ''
+ };
+};
+
+/**
+ * 通知列表组件
+ */
+const NotificationList = ({ notifications, onClose }) => {
+ if (!notifications?.length) {
+ return (
+ <div className="notification-empty">
+ <span>暂无新通知</span>
+ </div>
+ );
+ }
+
+ return (
+ <List
+ className="notification-list"
+ dataSource={notifications}
+ renderItem={(item) => (
+ <List.Item className="notification-item">
+ <div className="notification-content">
+ <span className="notification-text">{item.content || item.title}</span>
+ <span className="notification-time">{item.create_time || item.time}</span>
+ </div>
+ </List.Item>
+ )}
+ />
+ );
+};
+
+/**
+ * AppHeader 主组件
+ */
+const AppHeader = ({ notifications = [] }) => {
+ const [popoverVisible, setPopoverVisible] = useState(false);
+ const mediatorInfo = getMediatorInfo();
+
+ // 处理Popover显示状态
+ const handlePopoverChange = useCallback((visible) => {
+ setPopoverVisible(visible);
+ }, []);
+
+ // 渲染通知图标和气泡
+ const renderNotificationIcon = () => (
+ <Badge count={notifications.length} offset={[-2, 2]} size="small">
+ <BellOutlined className="header-bell-icon" />
+ </Badge>
+ );
+
+ // 渲染调解员信息
+ const renderMediatorInfo = () => (
+ <div className="header-mediator">
+ <Avatar
+ src={mediatorInfo.avatar || DEFAULT_AVATAR}
+ icon={!mediatorInfo.avatar && <UserOutlined />}
+ className="mediator-avatar"
+ />
+ <div className="mediator-details">
+ <span className="mediator-name">{mediatorInfo.trueName}</span>
+ <span className="mediator-role">
+ {mediatorInfo.unit && `${mediatorInfo.unit} | `}{mediatorInfo.roleName}
+ </span>
+ </div>
+ <span className="mediator-status-dot" />
+ </div>
+ );
+
+ return (
+ <header className="app-header">
+ {/* 左侧:系统名称 */}
+ <div className="header-left">
+ <span className="header-title">矛盾纠纷应用</span>
+ </div>
+
+ {/* 右侧:通知+调解员信息 */}
+ <div className="header-right">
+ {/* 通知图标 */}
+ <Popover
+ content={<NotificationList notifications={notifications} />}
+ title="系统通知"
+ trigger="click"
+ open={popoverVisible}
+ onOpenChange={handlePopoverChange}
+ placement="bottomRight"
+ overlayClassName="notification-popover"
+ >
+ <div className="header-notification">
+ {renderNotificationIcon()}
+ </div>
+ </Popover>
+
+ {/* 调解员信息 */}
+ {renderMediatorInfo()}
+ </div>
+ </header>
+ );
+};
+
+export default AppHeader;
diff --git a/web-app/src/components/common/WarningAlert.css b/web-app/src/components/common/WarningAlert.css
new file mode 100644
index 0000000..2308984
--- /dev/null
+++ b/web-app/src/components/common/WarningAlert.css
@@ -0,0 +1,119 @@
+/**
+ * WarningAlert 组件样式
+ * 预警提示消息条
+ */
+
+.warning-alert {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ background: linear-gradient(135deg, #fffbe6, #fff7e0);
+ border: 1px solid #ffe58f;
+ border-left: 4px solid #faad14;
+ border-radius: 6px;
+ padding: 10px 16px;
+ margin: 0 24px 12px;
+}
+
+.warning-alert-content {
+ display: flex;
+ align-items: center;
+ flex: 1;
+ gap: 6px;
+ min-width: 0;
+}
+
+.warning-icon {
+ color: #faad14;
+ font-size: 1rem;
+ flex-shrink: 0;
+}
+
+.warning-label {
+ color: #d46b08;
+ font-weight: 600;
+ font-size: 0.9rem;
+ flex-shrink: 0;
+}
+
+.warning-text {
+ color: #ad6800;
+ font-size: 0.9rem;
+ line-height: 1.4;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.warning-alert-actions {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-shrink: 0;
+ margin-left: 16px;
+}
+
+.warning-count {
+ color: #d46b08;
+ font-size: 0.85rem;
+ font-weight: 500;
+}
+
+.warning-more-btn {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ background: transparent;
+ border: 1px solid #faad14;
+ color: #d46b08;
+ padding: 4px 10px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.warning-more-btn:hover {
+ background: #faad14;
+ color: white;
+}
+
+/* 弹窗样式 */
+.warning-modal .ant-modal-body {
+ max-height: 400px;
+ overflow-y: auto;
+}
+
+.warning-modal-item {
+ display: flex;
+ flex-direction: column;
+ padding: 12px 0;
+ border-bottom: 1px solid #f0f0f0;
+}
+
+.warning-modal-item:last-child {
+ border-bottom: none;
+}
+
+.warning-modal-content {
+ display: flex;
+ gap: 8px;
+ line-height: 1.5;
+}
+
+.warning-modal-index {
+ color: #faad14;
+ font-weight: 600;
+ flex-shrink: 0;
+}
+
+.warning-modal-text {
+ color: #333;
+ font-size: 0.9rem;
+}
+
+.warning-modal-time {
+ color: #999;
+ font-size: 0.8rem;
+ margin-top: 6px;
+}
diff --git a/web-app/src/components/common/WarningAlert.jsx b/web-app/src/components/common/WarningAlert.jsx
new file mode 100644
index 0000000..42acb4b
--- /dev/null
+++ b/web-app/src/components/common/WarningAlert.jsx
@@ -0,0 +1,116 @@
+/**
+ * WarningAlert 组件 - 预警提示消息条
+ * 在调解进度条下方显示预警消息
+ */
+
+import React, { useState, useEffect, useCallback } from 'react';
+import { Modal, List } from 'antd';
+import { WarningOutlined, RightOutlined } from '@ant-design/icons';
+import { useCaseData } from '../../contexts/CaseDataContext';
+import MediationTimelineAPIService from '../../services/MediationTimelineAPIService';
+import './WarningAlert.css';
+
+/**
+ * 预警消息列表弹窗
+ */
+const WarningModal = ({ visible, warnings, onClose }) => (
+ <Modal
+ title={<span><WarningOutlined style={{ color: '#faad14', marginRight: 8 }} />预警消息列表</span>}
+ open={visible}
+ onCancel={onClose}
+ footer={null}
+ width={520}
+ className="warning-modal"
+ >
+ <List
+ dataSource={warnings}
+ renderItem={(item, index) => (
+ <List.Item className="warning-modal-item">
+ <div className="warning-modal-content">
+ <span className="warning-modal-index">{index + 1}.</span>
+ <span className="warning-modal-text">{item.content}</span>
+ </div>
+ <span className="warning-modal-time">{item.create_time}</span>
+ </List.Item>
+ )}
+ />
+ </Modal>
+);
+
+/**
+ * WarningAlert 主组件
+ */
+const WarningAlert = () => {
+ const { caseData } = useCaseData();
+ const [warnings, setWarnings] = useState([]);
+ const [modalVisible, setModalVisible] = useState(false);
+ const [loading, setLoading] = useState(false);
+
+ const mediationId = caseData?.mediation?.id;
+
+ // 加载预警消息
+ const loadWarnings = useCallback(async () => {
+ if (!mediationId) return;
+
+ setLoading(true);
+ try {
+ const response = await MediationTimelineAPIService.getWarningNotifyList(mediationId);
+ setWarnings(response.data || []);
+ } catch (err) {
+ console.error('加载预警消息失败:', err);
+ setWarnings([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [mediationId]);
+
+ // 组件挂载时加载数据
+ useEffect(() => {
+ loadWarnings();
+ }, [loadWarnings]);
+
+ // 打开弹窗
+ const handleOpenModal = useCallback(() => {
+ setModalVisible(true);
+ }, []);
+
+ // 关闭弹窗
+ const handleCloseModal = useCallback(() => {
+ setModalVisible(false);
+ }, []);
+
+ // 无预警消息时不渲染
+ if (!warnings.length || loading) {
+ return null;
+ }
+
+ const firstWarning = warnings[0];
+ const hasMore = warnings.length > 1;
+
+ return (
+ <div className="warning-alert">
+ <div className="warning-alert-content">
+ <WarningOutlined className="warning-icon" />
+ <span className="warning-label">预警:</span>
+ <span className="warning-text">{firstWarning.content}</span>
+ </div>
+
+ {hasMore && (
+ <div className="warning-alert-actions">
+ <span className="warning-count">还有{warnings.length - 1}条预警</span>
+ <button className="warning-more-btn" onClick={handleOpenModal}>
+ 查看更多 <RightOutlined />
+ </button>
+ </div>
+ )}
+
+ <WarningModal
+ visible={modalVisible}
+ warnings={warnings}
+ onClose={handleCloseModal}
+ />
+ </div>
+ );
+};
+
+export default WarningAlert;
diff --git a/web-app/src/components/dashboard/AISuggestionCard.css b/web-app/src/components/dashboard/AISuggestionCard.css
new file mode 100644
index 0000000..01b6a3b
--- /dev/null
+++ b/web-app/src/components/dashboard/AISuggestionCard.css
@@ -0,0 +1,57 @@
+/**
+ * AISuggestionCard 组件样式
+ * AI调解建议面板
+ */
+
+.ai-suggestion-card {
+ background: linear-gradient(135deg, #e6f4ff, #f0f7ff);
+ border-radius: 8px;
+ padding: 14px 16px;
+ margin-top: 12px;
+ border-left: 4px solid #1a6fb8;
+}
+
+.ai-suggestion-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 10px;
+}
+
+.ai-suggestion-icon {
+ font-size: 1rem;
+ color: #1a6fb8;
+}
+
+.ai-suggestion-title {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #0d4a8a;
+}
+
+.ai-suggestion-content {
+ font-size: 0.85rem;
+ line-height: 1.6;
+ color: #333;
+ max-height: 80px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+}
+
+.ai-suggestion-footer {
+ margin-top: 10px;
+ text-align: center;
+}
+
+.ai-suggestion-btn {
+ padding: 0;
+ font-size: 0.85rem;
+ color: #1a6fb8;
+}
+
+.ai-suggestion-btn:hover {
+ color: #0d4a8a;
+}
diff --git a/web-app/src/components/dashboard/AISuggestionCard.jsx b/web-app/src/components/dashboard/AISuggestionCard.jsx
new file mode 100644
index 0000000..4663f36
--- /dev/null
+++ b/web-app/src/components/dashboard/AISuggestionCard.jsx
@@ -0,0 +1,50 @@
+/**
+ * AISuggestionCard 组件 - AI调解建议
+ * 展示AI生成的调解建议内容
+ */
+
+import React, { useCallback } from 'react';
+import { Button, message } from 'antd';
+import { BulbOutlined, RightOutlined } from '@ant-design/icons';
+import './AISuggestionCard.css';
+
+// Mock数据 - AI调解建议内容
+const MOCK_SUGGESTION = `建议调解员优先安抚申请人张三的情绪。目前双方在补偿金额上的差距已缩小至15%,可尝试引入"互谅互让"原则进行最后博弈。建议调解员优先安抚申请人张三的情绪。目前双方在补偿金额上的差距已缩小至15%,可尝试引入"互谅互让"原则进行最后博弈。建议调解员优先安抚申请人张三的情绪。`;
+
+/**
+ * AISuggestionCard 主组件
+ */
+const AISuggestionCard = () => {
+ // 点击查看详细策略建议
+ const handleViewDetail = useCallback(() => {
+ message.info('该功能正在升级中,敬请期待!');
+ }, []);
+
+ return (
+ <div className="ai-suggestion-card">
+ {/* 标题 */}
+ <div className="ai-suggestion-header">
+ <BulbOutlined className="ai-suggestion-icon" />
+ <span className="ai-suggestion-title">AI 调解建议</span>
+ </div>
+
+ {/* 建议内容 */}
+ <div className="ai-suggestion-content">
+ {MOCK_SUGGESTION}
+ </div>
+
+ {/* 查看详情按钮 */}
+ <div className="ai-suggestion-footer">
+ <Button
+ type="link"
+ className="ai-suggestion-btn"
+ onClick={handleViewDetail}
+ >
+ 查看详细策略建议 <RightOutlined />
+ </Button>
+ </div>
+ </div>
+ );
+};
+
+export default AISuggestionCard;
diff --git a/web-app/src/components/dashboard/NegotiationProgress.css b/web-app/src/components/dashboard/NegotiationProgress.css
new file mode 100644
index 0000000..d2d6c83
--- /dev/null
+++ b/web-app/src/components/dashboard/NegotiationProgress.css
@@ -0,0 +1,75 @@
+/**
+ * NegotiationProgress 组件样式
+ * 协商沟通进度点线式展示
+ */
+
+.negotiation-progress {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 12px 16px;
+ border-top: 3px solid #1a6fb8;
+}
+
+.negotiation-header {
+ margin-bottom: 8px;
+}
+
+.negotiation-title {
+ font-size: 0.85rem;
+ color: #6c757d;
+ font-weight: 500;
+}
+
+.negotiation-round {
+ margin-bottom: 12px;
+}
+
+.round-text {
+ font-size: 1.3rem;
+ font-weight: 700;
+ color: #212529;
+}
+
+/* 进度点容器 */
+.negotiation-dots {
+ display: flex;
+ align-items: center;
+}
+
+/* 单个进度点+线 */
+.progress-dot-wrapper {
+ display: flex;
+ align-items: center;
+ flex: 1;
+}
+
+.progress-dot-wrapper.last {
+ flex: 0;
+}
+
+/* 进度点 */
+.progress-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ background: #d9d9d9;
+ flex-shrink: 0;
+ transition: background 0.3s;
+}
+
+.progress-dot.active {
+ background: #1a6fb8;
+}
+
+/* 连接线 */
+.progress-line {
+ flex: 1;
+ height: 3px;
+ background: #d9d9d9;
+ margin: 0 2px;
+ transition: background 0.3s;
+}
+
+.progress-line.active {
+ background: #1a6fb8;
+}
diff --git a/web-app/src/components/dashboard/NegotiationProgress.jsx b/web-app/src/components/dashboard/NegotiationProgress.jsx
new file mode 100644
index 0000000..31050ff
--- /dev/null
+++ b/web-app/src/components/dashboard/NegotiationProgress.jsx
@@ -0,0 +1,88 @@
+/**
+ * NegotiationProgress 组件 - 协商沟通进度
+ * 点线式可视化展示沟通轮次
+ */
+
+import React, { useMemo } from 'react';
+import { useCaseData } from '../../contexts/CaseDataContext';
+import './NegotiationProgress.css';
+
+/**
+ * 计算总轮次和已完成轮次
+ */
+const calculateRounds = (mediationCount, nodeCount, currentNodeIndex) => {
+ const currentRound = mediationCount || 0;
+ let totalRounds = nodeCount || 6;
+ const isLastNode = currentNodeIndex >= nodeCount - 1;
+
+ // 当沟通次数超过总次数但未到最后节点时,总次数 = 沟通次数 + 1
+ if (currentRound > totalRounds && !isLastNode) {
+ totalRounds = currentRound + 1;
+ }
+
+ return { currentRound, totalRounds, isLastNode };
+};
+
+/**
+ * 进度点组件
+ */
+const ProgressDot = ({ isActive, isLast }) => (
+ <div className={`progress-dot-wrapper ${isLast ? 'last' : ''}`}>
+ <div className={`progress-dot ${isActive ? 'active' : ''}`} />
+ {!isLast && <div className={`progress-line ${isActive ? 'active' : ''}`} />}
+ </div>
+);
+
+/**
+ * NegotiationProgress 主组件
+ */
+const NegotiationProgress = () => {
+ const { caseData, processNodes } = useCaseData();
+
+ // 从数据中获取值
+ const mediationCount = caseData?.mediation?.mediation_count || 0;
+ const nodeCount = processNodes?.length || 6;
+
+ // 计算当前节点索引
+ const currentNodeIndex = useMemo(() => {
+ if (!processNodes?.length) return 0;
+ const activeNode = processNodes.findIndex(n => n.nodeState === 1);
+ return activeNode >= 0 ? activeNode : processNodes.length - 1;
+ }, [processNodes]);
+
+ // 计算轮次数据
+ const { currentRound, totalRounds, isLastNode } = useMemo(() => {
+ return calculateRounds(mediationCount, nodeCount, currentNodeIndex);
+ }, [mediationCount, nodeCount, currentNodeIndex]);
+
+ // 生成进度点数据
+ const dots = useMemo(() => {
+ return Array(totalRounds).fill(false).map((_, index) => {
+ // 如果是最后节点,全部显示蓝色
+ if (isLastNode) return true;
+ return index < currentRound;
+ });
+ }, [totalRounds, currentRound, isLastNode]);
+
+ return (
+ <div className="negotiation-progress">
+ <div className="negotiation-header">
+ <span className="negotiation-title">协商沟通</span>
+ </div>
+ <div className="negotiation-round">
+ <span className="round-text">第{currentRound}轮</span>
+ </div>
+ <div className="negotiation-dots">
+ {dots.map((isActive, index) => (
+ <ProgressDot
+ key={index}
+ isActive={isActive}
+ isLast={index === dots.length - 1}
+ />
+ ))}
+ </div>
+ </div>
+ );
+};
+
+export default NegotiationProgress;
diff --git a/web-app/src/components/dashboard/PartyInfoCard.css b/web-app/src/components/dashboard/PartyInfoCard.css
new file mode 100644
index 0000000..0a34ebe
--- /dev/null
+++ b/web-app/src/components/dashboard/PartyInfoCard.css
@@ -0,0 +1,82 @@
+/**
+ * PartyInfoCard 组件样式
+ * 申请双方信息展示
+ */
+
+.party-info-card {
+ background: #f8f9fa;
+ border-radius: 8px;
+ padding: 12px 16px;
+ margin-bottom: 16px;
+}
+
+.party-info-title {
+ font-size: 0.9rem;
+ color: #6c757d;
+ margin-bottom: 12px;
+ font-weight: 500;
+}
+
+.party-info-content {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+/* 当事人卡片 */
+.party-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ position: relative;
+ flex: 1;
+ padding: 8px;
+}
+
+/* 情绪标签 */
+.party-tag {
+ position: absolute;
+ top: -4px;
+ left: 50%;
+ transform: translateX(-50%);
+ font-size: 0.7rem;
+ padding: 0 6px;
+ line-height: 18px;
+ border-radius: 4px;
+}
+
+.party-avatar {
+ margin-top: 12px;
+ margin-bottom: 8px;
+}
+
+.party-role {
+ font-size: 0.75rem;
+ color: #999;
+ margin-bottom: 4px;
+}
+
+.party-name {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: #333;
+ text-align: center;
+ max-width: 100px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* VS分隔符 */
+.vs-separator {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 0 8px;
+}
+
+.vs-icon {
+ font-size: 1.5rem;
+ color: #d9d9d9;
+}
diff --git a/web-app/src/components/dashboard/PartyInfoCard.jsx b/web-app/src/components/dashboard/PartyInfoCard.jsx
new file mode 100644
index 0000000..495a5a7
--- /dev/null
+++ b/web-app/src/components/dashboard/PartyInfoCard.jsx
@@ -0,0 +1,136 @@
+/**
+ * PartyInfoCard 组件 - 申请双方信息
+ * 展示申请人和被申请人信息,包含头像、姓名和情绪标签
+ */
+
+import React, { useState, useEffect, useCallback } from 'react';
+import { Avatar, Tag } from 'antd';
+import { UserOutlined, TeamOutlined } from '@ant-design/icons';
+import { useCaseData } from '../../contexts/CaseDataContext';
+import MediationTimelineAPIService from '../../services/MediationTimelineAPIService';
+import './PartyInfoCard.css';
+
+// 默认头像
+const DEFAULT_AVATAR_PERSON = 'http://gz.hugeinfo.com.cn/dyh/wx414ae04ac3f10b4e/images/pngAI_logo.png';
+
+/**
+ * 根据per_type判断是申请方还是被申请方
+ */
+const isApplicant = (perType) => {
+ return perType?.includes('15_020008-1') || perType?.toLowerCase().includes('applicant');
+};
+
+/**
+ * 获取标签颜色样式
+ */
+const getTagColor = (tagStyle) => {
+ const colorMap = {
+ red: 'red',
+ orange: 'orange',
+ yellow: 'gold',
+ green: 'green',
+ blue: 'blue',
+ warning: 'orange',
+ danger: 'red',
+ success: 'green',
+ primary: 'blue'
+ };
+ return colorMap[tagStyle] || 'default';
+};
+
+/**
+ * 当事人信息卡片
+ */
+const PartyCard = ({ person, isApplicantSide }) => {
+ const avatarIcon = isApplicantSide ? <UserOutlined /> : <TeamOutlined />;
+ const avatarBg = isApplicantSide ? '#1a6fb8' : '#faad14';
+
+ return (
+ <div className={`party-card ${isApplicantSide ? 'applicant' : 'respondent'}`}>
+ {/* 标签 */}
+ {person.tag_name && (
+ <Tag color={getTagColor(person.tag_style)} className="party-tag">
+ {person.tag_name}
+ </Tag>
+ )}
+
+ {/* 头像 */}
+ <Avatar
+ size={48}
+ icon={avatarIcon}
+ style={{ backgroundColor: avatarBg }}
+ className="party-avatar"
+ />
+
+ {/* 角色标签 */}
+ <span className="party-role">
+ {isApplicantSide ? '申请人' : '被申请人'}
+ </span>
+
+ {/* 姓名/公司名 */}
+ <span className="party-name">{person.true_name}</span>
+ </div>
+ );
+};
+
+/**
+ * VS分隔符
+ */
+const VSSeparator = () => (
+ <div className="vs-separator">
+ <span className="vs-icon">⚖</span>
+ </div>
+);
+
+/**
+ * PartyInfoCard 主组件
+ */
+const PartyInfoCard = () => {
+ const { caseData } = useCaseData();
+ const [persons, setPersons] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const caseId = caseData?.caseId || caseData?.case_id;
+
+ // 加载当事人数据
+ const loadPersons = useCallback(async () => {
+ if (!caseId) return;
+
+ setLoading(true);
+ try {
+ const response = await MediationTimelineAPIService.getPersonList(caseId);
+ setPersons(response.data || []);
+ } catch (err) {
+ console.error('加载当事人列表失败:', err);
+ setPersons([]);
+ } finally {
+ setLoading(false);
+ }
+ }, [caseId]);
+
+ // 组件挂载时加载数据
+ useEffect(() => {
+ loadPersons();
+ }, [loadPersons]);
+
+ // 分离申请方和被申请方
+ const applicant = persons.find(p => isApplicant(p.per_type));
+ const respondent = persons.find(p => !isApplicant(p.per_type));
+
+ if (loading || persons.length === 0) {
+ return null;
+ }
+
+ return (
+ <div className="party-info-card">
+ <div className="party-info-title">申请双方</div>
+ <div className="party-info-content">
+ {applicant && <PartyCard person={applicant} isApplicantSide={true} />}
+ <VSSeparator />
+ {respondent && <PartyCard person={respondent} isApplicantSide={false} />}
+ </div>
+ </div>
+ );
+};
+
+export default PartyInfoCard;
diff --git a/web-app/src/services/MediationTimelineAPIService.js b/web-app/src/services/MediationTimelineAPIService.js
new file mode 100644
index 0000000..40c73a9
--- /dev/null
+++ b/web-app/src/services/MediationTimelineAPIService.js
@@ -0,0 +1,31 @@
+/**
+ * 调解时间线扩展API Service
+ * 处理预警消息、当事人列表等相关接口
+ * 接口前缀: /api/v1/mediation-timeline/*
+ */
+
+import { request } from './request';
+
+class MediationTimelineAPIService {
+ /**
+ * 获取预警消息列表
+ * GET /api/v1/mediation-timeline/warning-notify-list/{mediation_id}
+ * @param {string} mediationId - 调解ID
+ * @returns {Promise<Array>} 预警消息列表
+ */
+ static getWarningNotifyList(mediationId) {
+ return request.get(`/api/v1/mediation-timeline/warning-notify-list/${mediationId}`);
+ }
+
+ /**
+ * 获取当事人列表
+ * GET /api/v1/mediation-timeline/person-list/{case_id}
+ * @param {string} caseId - 案件ID
+ * @returns {Promise<Array>} 当事人列表
+ */
+ static getPersonList(caseId) {
+ return request.get(`/api/v1/mediation-timeline/person-list/${caseId}`);
+ }
+}
+
+export default MediationTimelineAPIService;
--
Gitblit v1.8.0