|
|
@@ -18,6 +18,8 @@ import ReactFlow, {
|
|
|
EdgeLabelRenderer,
|
|
|
BaseEdge,
|
|
|
getStraightPath,
|
|
|
+ getBezierPath,
|
|
|
+ getSmoothStepPath,
|
|
|
EdgeTypes,
|
|
|
} from 'reactflow';
|
|
|
import 'reactflow/dist/style.css';
|
|
|
@@ -140,6 +142,7 @@ import { UserVO } from '../types';
|
|
|
import { segregationPointApi, SegregationPointVO } from '../api/spm';
|
|
|
import { getFormPage, getForm, FormVO } from '../api/bpm/form';
|
|
|
import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
|
|
|
+import { dictDataApi, DictDataVO } from '../api/DictData';
|
|
|
|
|
|
// 节点配置
|
|
|
const nodeConfigs = [
|
|
|
@@ -587,6 +590,8 @@ const nodeTypes = {
|
|
|
|
|
|
// 全局变量存储删除函数(用于边组件)
|
|
|
let globalDeleteEdgeFn: ((id: string) => void) | null = null;
|
|
|
+// 全局变量存储 edges(用于边组件获取 type)
|
|
|
+let globalEdges: Edge[] = [];
|
|
|
|
|
|
// 自定义边组件 - 定义在组件外部以确保稳定引用
|
|
|
function CustomEdgeWithDelete({
|
|
|
@@ -598,15 +603,38 @@ function CustomEdgeWithDelete({
|
|
|
selected,
|
|
|
markerEnd,
|
|
|
style,
|
|
|
+ type: propType,
|
|
|
}: any) {
|
|
|
- const [edgePath, labelX, labelY] = getStraightPath({
|
|
|
- sourceX,
|
|
|
- sourceY,
|
|
|
- targetX,
|
|
|
- targetY,
|
|
|
- });
|
|
|
+ // 从全局 edges 中查找对应的边来获取 type
|
|
|
+ const edge = globalEdges.find(e => e.id === id);
|
|
|
+ const edgeType = edge?.type || propType || 'straight';
|
|
|
+
|
|
|
+ // 根据边的类型选择路径生成函数
|
|
|
+ let edgePath: string;
|
|
|
+ let labelX: number;
|
|
|
+ let labelY: number;
|
|
|
+
|
|
|
+ if (edgeType === 'smoothstep') {
|
|
|
+ // 使用 smoothstep 路径(平滑步进曲线,可以拐弯)
|
|
|
+ // 调整 borderRadius 参数使曲线更平滑
|
|
|
+ [edgePath, labelX, labelY] = getSmoothStepPath({
|
|
|
+ sourceX,
|
|
|
+ sourceY,
|
|
|
+ targetX,
|
|
|
+ targetY,
|
|
|
+ borderRadius: 15, // 圆角半径,使曲线更平滑
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 使用直线路径
|
|
|
+ [edgePath, labelX, labelY] = getStraightPath({
|
|
|
+ sourceX,
|
|
|
+ sourceY,
|
|
|
+ targetX,
|
|
|
+ targetY,
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- console.log('边组件渲染,ID:', id, 'selected:', selected, 'labelX:', labelX, 'labelY:', labelY);
|
|
|
+ console.log('边组件渲染,ID:', id, 'propType:', propType, 'edgeType:', edgeType, 'edge对象:', edge, 'sourceX:', sourceX, 'sourceY:', sourceY, 'targetX:', targetX, 'targetY:', targetY, 'selected:', selected);
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
@@ -663,6 +691,7 @@ function CustomEdgeWithDelete({
|
|
|
const edgeTypes: EdgeTypes = {
|
|
|
straight: CustomEdgeWithDelete,
|
|
|
default: CustomEdgeWithDelete,
|
|
|
+ smoothstep: CustomEdgeWithDelete,
|
|
|
};
|
|
|
|
|
|
|
|
|
@@ -726,6 +755,8 @@ export default function ProcessDesigner() {
|
|
|
const [isolationPoints, setIsolationPoints] = useState<SegregationPointVO[]>([]);
|
|
|
// 表单列表
|
|
|
const [formList, setFormList] = useState<FormVO[]>([]);
|
|
|
+ // 隔离方式字典数据
|
|
|
+ const [isolationMethodDictList, setIsolationMethodDictList] = useState<DictDataVO[]>([]);
|
|
|
|
|
|
const loadNodeCache = useCallback((nodeId: string) => {
|
|
|
try {
|
|
|
@@ -750,6 +781,23 @@ export default function ProcessDesigner() {
|
|
|
console.log('Edges 状态更新:', edges.length, edges);
|
|
|
}, [edges]);
|
|
|
|
|
|
+ // 获取隔离方式字典数据
|
|
|
+ const getIsolationMethodDictList = async () => {
|
|
|
+ try {
|
|
|
+ const response = await dictDataApi.getDictDataPage({
|
|
|
+ pageNo: 1,
|
|
|
+ pageSize: -1,
|
|
|
+ dictType: 'isolation_method',
|
|
|
+ });
|
|
|
+ const data = (response as any)?.data || response;
|
|
|
+ const list = data?.list || [];
|
|
|
+ setIsolationMethodDictList(list);
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('获取隔离方式字典失败:', error);
|
|
|
+ setIsolationMethodDictList([]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
// 加载角色用户列表和隔离点列表
|
|
|
useEffect(() => {
|
|
|
const loadRoleUsers = async () => {
|
|
|
@@ -789,6 +837,7 @@ export default function ProcessDesigner() {
|
|
|
loadRoleUsers();
|
|
|
loadIsolationPoints();
|
|
|
loadFormList();
|
|
|
+ getIsolationMethodDictList();
|
|
|
}, []);
|
|
|
|
|
|
// 节点配置状态
|
|
|
@@ -1072,6 +1121,7 @@ export default function ProcessDesigner() {
|
|
|
...updatedEdges[existingEdgeIndex],
|
|
|
sourceHandle,
|
|
|
targetHandle,
|
|
|
+ type: 'straight', // 统一使用直线
|
|
|
};
|
|
|
// 保存历史
|
|
|
const newHistory = history.slice(0, historyIndex + 1);
|
|
|
@@ -1081,6 +1131,7 @@ export default function ProcessDesigner() {
|
|
|
return updatedEdges;
|
|
|
}
|
|
|
|
|
|
+ // 统一使用直线连接
|
|
|
const edgeId = `edge-${params.source}-${sourceHandle || 'default'}-${params.target}-${targetHandle || 'default'}-${Date.now()}`;
|
|
|
const newEdge: Edge = {
|
|
|
id: edgeId,
|
|
|
@@ -1090,11 +1141,14 @@ export default function ProcessDesigner() {
|
|
|
targetHandle: targetHandle || undefined,
|
|
|
animated: false,
|
|
|
style: { strokeWidth: 2, stroke: '#94a3b8' },
|
|
|
- type: 'straight',
|
|
|
+ type: 'straight', // 统一使用直线
|
|
|
};
|
|
|
+
|
|
|
+ console.log('创建新边,类型:', edgeType, '边对象:', newEdge);
|
|
|
// 直接添加到数组,不使用 addEdge(因为 addEdge 可能会过滤掉某些连接)
|
|
|
const newEdges = [...eds, newEdge];
|
|
|
console.log('新连接已添加:', newEdge);
|
|
|
+ console.log('新边的类型:', newEdge.type);
|
|
|
console.log('当前所有连接数量:', newEdges.length);
|
|
|
// 保存历史
|
|
|
const newHistory = history.slice(0, historyIndex + 1);
|
|
|
@@ -1232,6 +1286,11 @@ export default function ProcessDesigner() {
|
|
|
notificationPerson: source.notificationPerson || '',
|
|
|
notificationTime: source.notificationTime || '',
|
|
|
});
|
|
|
+
|
|
|
+ // 如果是创建作业节点,且当前tab是"提交表单",自动切换到"节点信息"tab
|
|
|
+ if (node.data?.type === 'createJob' && activeTabKey === 'form') {
|
|
|
+ setActiveTabKey('info');
|
|
|
+ }
|
|
|
}, [loadNodeCache, nodes, setEdges]);
|
|
|
|
|
|
// 画布点击处理(取消选择)
|
|
|
@@ -1257,7 +1316,7 @@ export default function ProcessDesigner() {
|
|
|
setEdges((eds) => {
|
|
|
const updatedEdges = eds.map((e) => ({
|
|
|
...e,
|
|
|
- type: 'straight', // 强制设置为 straight 类型
|
|
|
+ type: edge.type || 'straight', // 保持原有类型
|
|
|
selected: e.id === edge.id,
|
|
|
}));
|
|
|
const clickedEdge = updatedEdges.find(e => e.id === edge.id);
|
|
|
@@ -1282,28 +1341,36 @@ export default function ProcessDesigner() {
|
|
|
setSelectedEdge(null);
|
|
|
}, [history, historyIndex, nodes]);
|
|
|
|
|
|
- // 更新全局删除函数
|
|
|
+ // 更新全局删除函数和 edges
|
|
|
useEffect(() => {
|
|
|
globalDeleteEdgeFn = handleDeleteEdge;
|
|
|
+ globalEdges = edges;
|
|
|
return () => {
|
|
|
globalDeleteEdgeFn = null;
|
|
|
+ globalEdges = [];
|
|
|
};
|
|
|
- }, [handleDeleteEdge]);
|
|
|
+ }, [handleDeleteEdge, edges]);
|
|
|
|
|
|
- // 确保所有边都使用 'straight' 类型(用于自定义边组件)
|
|
|
+ // 确保所有边都使用直线类型
|
|
|
useEffect(() => {
|
|
|
setEdges((eds) => {
|
|
|
- const needsUpdate = eds.some(e => e.type !== 'straight');
|
|
|
- if (needsUpdate) {
|
|
|
- console.log('统一设置所有边的类型为 straight');
|
|
|
- return eds.map(e => ({
|
|
|
- ...e,
|
|
|
- type: 'straight',
|
|
|
- }));
|
|
|
+ const updatedEdges = eds.map(edge => {
|
|
|
+ // 如果类型不是 straight,统一改为 straight
|
|
|
+ if (edge.type !== 'straight') {
|
|
|
+ return { ...edge, type: 'straight' };
|
|
|
+ }
|
|
|
+ return edge;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查是否有更新
|
|
|
+ const hasUpdate = updatedEdges.some((e, index) => e.type !== eds[index]?.type);
|
|
|
+ if (hasUpdate) {
|
|
|
+ return updatedEdges;
|
|
|
}
|
|
|
+
|
|
|
return eds;
|
|
|
});
|
|
|
- }, []); // 只在组件挂载时执行一次
|
|
|
+ }, [setEdges]);
|
|
|
|
|
|
// 删除节点
|
|
|
const handleDeleteNode = useCallback((nodeId?: string) => {
|
|
|
@@ -2023,14 +2090,14 @@ export default function ProcessDesigner() {
|
|
|
connectionMode={ConnectionMode.Loose}
|
|
|
defaultEdgeOptions={{
|
|
|
style: { strokeWidth: 2, stroke: '#94a3b8' },
|
|
|
- type: 'straight',
|
|
|
+ // 不设置默认type,让onConnect中的逻辑决定
|
|
|
}}
|
|
|
edgesUpdatable={true}
|
|
|
edgesFocusable={true}
|
|
|
edgeUpdaterRadius={10}
|
|
|
>
|
|
|
<Controls className="!bg-white !border !border-gray-200 !rounded-lg !shadow-md" />
|
|
|
- <Background variant={BackgroundVariant.Dots} gap={16} size={1} color="#e5e7eb" />
|
|
|
+ <Background variant={BackgroundVariant.Lines} gap={16} size={1} color="#e5e7eb" />
|
|
|
<MiniMap
|
|
|
nodeColor={(node) => {
|
|
|
const config = nodeConfigs.find(c => c.type === node.type);
|
|
|
@@ -2080,7 +2147,14 @@ export default function ProcessDesigner() {
|
|
|
<div className="flex-1 p-4 overflow-y-auto">
|
|
|
<Tabs
|
|
|
activeKey={activeTabKey}
|
|
|
- onChange={setActiveTabKey}
|
|
|
+ onChange={(key) => {
|
|
|
+ // 如果是创建作业节点,不允许切换到"提交表单"tab
|
|
|
+ if (key === 'form' && selectedNode.data?.type === 'createJob') {
|
|
|
+ setActiveTabKey('info');
|
|
|
+ } else {
|
|
|
+ setActiveTabKey(key);
|
|
|
+ }
|
|
|
+ }}
|
|
|
className="[&_.ant-tabs-tab]:!px-4 [&_.ant-tabs-tab]:!py-2.5 [&_.ant-tabs-tab-active]:!text-blue-600 [&_.ant-tabs-tab-active]:!font-semibold [&_.ant-tabs-ink-bar]:!bg-blue-600 [&_.ant-tabs-tab]:!text-gray-600 [&_.ant-tabs-tab]:!border-b-2 [&_.ant-tabs-tab]:!border-transparent [&_.ant-tabs-tab:hover]:!text-blue-500"
|
|
|
items={[
|
|
|
{
|
|
|
@@ -2408,13 +2482,44 @@ export default function ProcessDesigner() {
|
|
|
className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
|
|
|
allowClear
|
|
|
>
|
|
|
- {nodes
|
|
|
- .filter(n => n.data?.type === 'isolation')
|
|
|
- .map(node => (
|
|
|
+ {(() => {
|
|
|
+ // 找到所有与当前"解除隔离"节点连线的"隔离/方案"节点
|
|
|
+ // 连线方向:隔离节点(source) -> 解除隔离节点(target)
|
|
|
+ const currentNodeId = selectedNode?.id;
|
|
|
+
|
|
|
+ // 找到所有以当前节点为 target 的 edge(隔离节点指向解除隔离节点)
|
|
|
+ const incomingEdges = edges.filter(edge =>
|
|
|
+ String(edge.target) === String(currentNodeId)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 也检查反向连线(解除隔离节点指向隔离节点,虽然不常见但兼容)
|
|
|
+ const outgoingEdges = edges.filter(edge =>
|
|
|
+ String(edge.source) === String(currentNodeId)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 收集所有已连线的隔离节点ID
|
|
|
+ const connectedIsolationNodeIds = new Set<string>();
|
|
|
+ incomingEdges.forEach(edge => {
|
|
|
+ connectedIsolationNodeIds.add(String(edge.source));
|
|
|
+ });
|
|
|
+ outgoingEdges.forEach(edge => {
|
|
|
+ connectedIsolationNodeIds.add(String(edge.target));
|
|
|
+ });
|
|
|
+
|
|
|
+ // 获取所有隔离节点
|
|
|
+ const isolationNodes = nodes.filter(n => n.data?.type === 'isolation');
|
|
|
+
|
|
|
+ // 如果有连线,只显示已连线的隔离节点;否则显示所有隔离节点(兼容旧数据)
|
|
|
+ const nodesToShow = connectedIsolationNodeIds.size > 0
|
|
|
+ ? isolationNodes.filter(n => connectedIsolationNodeIds.has(String(n.id)))
|
|
|
+ : isolationNodes;
|
|
|
+
|
|
|
+ return nodesToShow.map(node => (
|
|
|
<Select.Option key={node.id} value={node.id}>
|
|
|
{node.data?.label || nodeConfigs.find(c => c.type === 'isolation')?.label || '隔离/方案'}
|
|
|
</Select.Option>
|
|
|
- ))}
|
|
|
+ ));
|
|
|
+ })()}
|
|
|
</Select>
|
|
|
</div>
|
|
|
)}
|
|
|
@@ -2434,9 +2539,11 @@ export default function ProcessDesigner() {
|
|
|
allowClear
|
|
|
disabled={selectedNode.data?.type === 'releaseIsolation'}
|
|
|
>
|
|
|
- <Select.Option value="blindPlate">盲板</Select.Option>
|
|
|
- <Select.Option value="lockTag">上锁挂牌</Select.Option>
|
|
|
- <Select.Option value="remove">拆除</Select.Option>
|
|
|
+ {isolationMethodDictList.map((item) => (
|
|
|
+ <Select.Option key={item.id} value={item.value}>
|
|
|
+ {item.label}
|
|
|
+ </Select.Option>
|
|
|
+ ))}
|
|
|
</Select>
|
|
|
</div>
|
|
|
|
|
|
@@ -2465,7 +2572,8 @@ export default function ProcessDesigner() {
|
|
|
)}
|
|
|
|
|
|
{/* 盲板和拆除:显示负责人 */}
|
|
|
- {(nodeConfig.isolationMethod === 'blindPlate' || nodeConfig.isolationMethod === 'remove') && (
|
|
|
+ {/* 字典值:0=盲板,1=上锁挂牌,2=拆除 */}
|
|
|
+ {(nodeConfig.isolationMethod === '0' || nodeConfig.isolationMethod === '2') && (
|
|
|
<div>
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
负责人
|
|
|
@@ -2491,7 +2599,7 @@ export default function ProcessDesigner() {
|
|
|
)}
|
|
|
|
|
|
{/* 上锁挂牌:显示上锁人和共锁人 */}
|
|
|
- {nodeConfig.isolationMethod === 'lockTag' && (
|
|
|
+ {nodeConfig.isolationMethod === '1' && (
|
|
|
<>
|
|
|
<div>
|
|
|
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
|
@@ -2820,7 +2928,17 @@ export default function ProcessDesigner() {
|
|
|
</div>
|
|
|
),
|
|
|
},
|
|
|
- ].filter(item => item.key !== 'notification')}
|
|
|
+ ].filter(item => {
|
|
|
+ // 过滤掉通知消息tab
|
|
|
+ if (item.key === 'notification') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ // 如果是创建作业节点,过滤掉提交表单tab
|
|
|
+ if (item.key === 'form' && selectedNode.data?.type === 'createJob') {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ })}
|
|
|
/>
|
|
|
</div>
|
|
|
</div>
|