From 88a31d5a960bd10f3799bc00f8aa24461567d06e Mon Sep 17 00:00:00 2001
From: shimai <shimai@example.com>
Date: Tue, 07 Apr 2026 15:23:43 +0800
Subject: [PATCH] Merge branch 'test/tony.cheng/260312' of http://120.79.193.119:9090/r/~chengmw/cloud-melody-front into test/shimai.huang/260309

---
 web-app/src/components/dashboard/NegotiationProgress.jsx |   88 ++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 88 insertions(+), 0 deletions(-)

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;

--
Gitblit v1.8.0