import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import ReactFlow, {
  Node,
  Edge,
  addEdge,
  Connection,
  useNodesState,
  useEdgesState,
  Controls,
  Background,
  MiniMap,
  NodeTypes,
  BackgroundVariant,
  Handle,
  Position,
  ConnectionMode,
  EdgeLabelRenderer,
  BaseEdge,
  getStraightPath,
  getBezierPath,
  getSmoothStepPath,
  EdgeTypes,
  type NodeChange,
} from 'reactflow';
import 'reactflow/dist/style.css';
import {
  SaveOutlined,
  ArrowLeftOutlined,
  UndoOutlined,
  RedoOutlined,
  ToolOutlined,
  CheckCircleOutlined,
  FileTextOutlined,
  EditOutlined,
  SafetyOutlined,
  UnlockOutlined,
  LockOutlined,
  KeyOutlined,
  CheckSquareOutlined,
  CloseOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  ZoomInOutlined,
  ZoomOutOutlined,
  // 更多图标用于选择器
  HomeOutlined,
  UserOutlined,
  TeamOutlined,
  SettingOutlined,
  FolderOutlined,
  FileOutlined,
  FolderOpenOutlined,
  DatabaseOutlined,
  CloudOutlined,
  ThunderboltOutlined,
  FireOutlined,
  RocketOutlined,
  StarOutlined,
  HeartOutlined,
  BellOutlined,
  MessageOutlined,
  PhoneOutlined,
  MailOutlined,
  CalendarOutlined,
  ClockCircleOutlined,
  SearchOutlined,
  PlusOutlined,
  MinusOutlined,
  DeleteOutlined,
  EditOutlined as EditIconOutlined,
  EyeOutlined,
  EyeInvisibleOutlined,
  DownloadOutlined,
  UploadOutlined,
  ReloadOutlined,
  PlayCircleOutlined,
  PauseCircleOutlined,
  StopOutlined,
  CheckOutlined,
  CloseCircleOutlined,
  WarningOutlined,
  InfoCircleOutlined,
  QuestionCircleOutlined,
  LinkOutlined,
  ShareAltOutlined,
  CopyOutlined,
  ScissorOutlined,
  PrinterOutlined,
  ShoppingCartOutlined,
  ShoppingOutlined,
  GiftOutlined,
  TrophyOutlined,
  CrownOutlined,
  BulbOutlined,
  ExperimentOutlined,
  BugOutlined,
  CodeOutlined,
  ApiOutlined,
  AppstoreOutlined,
  BarsOutlined,
  MenuOutlined,
  LayoutOutlined,
  TableOutlined,
  UnorderedListOutlined,
  OrderedListOutlined,
  PictureOutlined,
  VideoCameraOutlined,
  SoundOutlined,
  CustomerServiceOutlined,
  GlobalOutlined,
  EnvironmentOutlined,
  CompassOutlined,
  CarOutlined,
  BankOutlined,
  ShopOutlined,
  MedicineBoxOutlined,
  SafetyCertificateOutlined,
  InsuranceOutlined,
  FileProtectOutlined,
  FileSyncOutlined,
  FileSearchOutlined,
  FileAddOutlined,
  FileExcelOutlined,
  FilePdfOutlined,
  FileWordOutlined,
  FileImageOutlined,
  FileZipOutlined,
  FolderAddOutlined,
  FolderViewOutlined,
  ProjectOutlined,
  BuildOutlined,
  ToolOutlined as ToolIconOutlined,
  RobotOutlined,
  BugOutlined as BugIconOutlined,
  ExperimentOutlined as ExperimentIconOutlined,
  FireOutlined as FireIconOutlined,
  ThunderboltOutlined as ThunderboltIconOutlined,
} from '@ant-design/icons';
import { Button, Input, Select, TreeSelect, Checkbox, Tabs, Modal, Dropdown, Popover, message, Card, Alert, InputNumber, Radio, DatePicker, TimePicker, Form as AntdForm, Cascader, Upload, Switch, Tooltip } from 'antd';
import type { MenuProps } from 'antd';
import { toast } from 'sonner';
import { workflowDesignApi, WorkflowDesignVO } from '../api/WorkflowDesign';
import { userApi } from '../api/user';
import { deptApi, type DeptVO } from '../api/dept';
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';
import FormUploadField from './FormUploadField';
import {
  isWorkflowCoLockType,
  isWorkflowIsolationSchemePanelType,
  isWorkflowLockSchemeType,
  isWorkflowUnlockCoLockType,
  isWorkflowUnlockParentMatch,
  isWorkflowUnlockSchemeType,
  resolveWorkflowPaletteType,
} from '../utils/workflowNodeTypes';

/** 抄送部门/人员 ID：支持数组、逗号分隔字符串、JSON 数组字符串，兼容旧 cc* 单选 */
function parseWorkflowCopyIds(multi: unknown, legacySingle: unknown): string[] {
  const pushValid = (out: string[], x: unknown) => {
    if (x !== undefined && x !== null && x !== '') out.push(String(x));
  };
  if (Array.isArray(multi)) {
    const out: string[] = [];
    multi.forEach((x) => pushValid(out, x));
    return out;
  }
  if (typeof multi === 'string' && multi.trim()) {
    try {
      const parsed = JSON.parse(multi);
      if (Array.isArray(parsed)) return parseWorkflowCopyIds(parsed, undefined);
      if (parsed !== null && parsed !== undefined && parsed !== '') {
        return [String(parsed)];
      }
    } catch {
      return multi
        .split(',')
        .map((s) => s.trim())
        .filter((s) => s !== '');
    }
  }
  if (legacySingle !== undefined && legacySingle !== null && legacySingle !== '') {
    return [String(legacySingle)];
  }
  return [];
}

/** 流程保存到后端时，抄送部门/人员 ID 必须为逗号分隔字符串 */
function toWorkflowCopyIdsCommaString(value: unknown): string {
  if (Array.isArray(value)) {
    return value
      .map((item) => String(item).trim())
      .filter((item) => item !== '')
      .join(',');
  }
  if (value === undefined || value === null || value === '') {
    return '';
  }
  return String(value).trim();
}

/**
 * 共锁 / 解除共锁：设计器提交表单不维护隔离方式、隔离点、上锁人等（由关联任务在运行时解析）。
 * 保存/导出流程 JSON 时清空同步进 data 的上述字段，避免与表单语义不一致。
 * - coLock：保留 formId、isolationNodeUuid（上锁任务）、coLockPersons
 * - unlockCoLock：保留 formId、isolationNodeUuid（共锁任务）、coLockPersons（与所选共锁任务同步，供下游使用）
 */
function sanitizeCoLockFamilyNodeDataForExport(
  processedData: Record<string, any>,
  reactFlowNodeType: string | undefined
): Record<string, any> {
  const dataType = processedData?.type as string | undefined;
  const isCoLock =
    isWorkflowCoLockType(reactFlowNodeType) || isWorkflowCoLockType(dataType);
  const isUnlockCoLock =
    isWorkflowUnlockCoLockType(reactFlowNodeType) || isWorkflowUnlockCoLockType(dataType);
  if (!isCoLock && !isUnlockCoLock) {
    return processedData;
  }
  return {
    ...processedData,
    isolationType: '',
    isolationPoints: [],
    isolationNode: [],
    lockPerson: '',
    workerUserId: '',
  };
}

// 节点配置
const nodeConfigs = [
  {
    type: 'createJob',
    label: '创建作业',
    icon: ToolOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-blue-600',
    iconColorCustom: '#165dff',
    borderColor: 'border-blue-100',
  },
  {
    type: 'confirm',
    label: '确认',
    icon: CheckCircleOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-green-600',
    iconColorCustom: '#36d399',
    borderColor: 'border-green-100',
  },
  {
    type: 'review',
    label: '审核',
    icon: FileTextOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-orange-600',
    iconColorCustom: '#fb923c',
    borderColor: 'border-orange-100',
  },
  {
    type: 'inputInfo',
    label: '录入信息',
    icon: EditOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-purple-600',
    iconColorCustom: '#9665ff',
    borderColor: 'border-purple-100',
  },
  {
    type: 'lock',
    label: '上锁',
    icon: LockOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-red-600',
    iconColorCustom: '#f87272',
    borderColor: 'border-red-100',
  },
  {
    type: 'unlock',
    label: '解锁',
    icon: UnlockOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-yellow-600',
    iconColorCustom: '#38bdf8',
    borderColor: 'border-yellow-100',
  },
  {
    type: 'coLock',
    label: '共锁',
    icon: TeamOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-red-600',
    iconColorCustom: '#f87272',
    borderColor: 'border-red-100',
  },
  {
    type: 'unlockCoLock',
    label: '解除共锁',
    icon: KeyOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-yellow-600',
    iconColorCustom: '#38bdf8',
    borderColor: 'border-yellow-100',
  },
  {
    type: 'returnLock',
    label: '还锁',
    icon: LockOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-indigo-600',
    iconColorCustom: '#6b7280',
    borderColor: 'border-indigo-100',
  },
  {
    type: 'complete',
    label: '完成/结束',
    icon: CheckSquareOutlined,
    bgColor: 'bg-white',
    bgColorCustom: '#ffffff',
    iconColor: 'text-gray-600',
    iconColorCustom: '#10b981',
    borderColor: 'border-gray-100',
  },
];

// 可选的图标列表（用于图标选择器）
const availableIcons = [
  { name: 'ToolOutlined', component: ToolOutlined, label: '工具' },
  { name: 'CheckCircleOutlined', component: CheckCircleOutlined, label: '确认' },
  { name: 'FileTextOutlined', component: FileTextOutlined, label: '文件' },
  { name: 'EditOutlined', component: EditOutlined, label: '编辑' },
  { name: 'SafetyOutlined', component: SafetyOutlined, label: '安全' },
  { name: 'UnlockOutlined', component: UnlockOutlined, label: '解锁' },
  { name: 'LockOutlined', component: LockOutlined, label: '锁定' },
  { name: 'KeyOutlined', component: KeyOutlined, label: '钥匙' },
  { name: 'CheckSquareOutlined', component: CheckSquareOutlined, label: '完成' },
  { name: 'HomeOutlined', component: HomeOutlined, label: '首页' },
  { name: 'UserOutlined', component: UserOutlined, label: '用户' },
  { name: 'TeamOutlined', component: TeamOutlined, label: '团队' },
  { name: 'SettingOutlined', component: SettingOutlined, label: '设置' },
  { name: 'FolderOutlined', component: FolderOutlined, label: '文件夹' },
  { name: 'FileOutlined', component: FileOutlined, label: '文件' },
  { name: 'FolderOpenOutlined', component: FolderOpenOutlined, label: '打开文件夹' },
  { name: 'DatabaseOutlined', component: DatabaseOutlined, label: '数据库' },
  { name: 'CloudOutlined', component: CloudOutlined, label: '云' },
  { name: 'ThunderboltOutlined', component: ThunderboltOutlined, label: '闪电' },
  { name: 'FireOutlined', component: FireOutlined, label: '火焰' },
  { name: 'RocketOutlined', component: RocketOutlined, label: '火箭' },
  { name: 'StarOutlined', component: StarOutlined, label: '星星' },
  { name: 'HeartOutlined', component: HeartOutlined, label: '心形' },
  { name: 'BellOutlined', component: BellOutlined, label: '铃铛' },
  { name: 'MessageOutlined', component: MessageOutlined, label: '消息' },
  { name: 'PhoneOutlined', component: PhoneOutlined, label: '电话' },
  { name: 'MailOutlined', component: MailOutlined, label: '邮件' },
  { name: 'CalendarOutlined', component: CalendarOutlined, label: '日历' },
  { name: 'ClockCircleOutlined', component: ClockCircleOutlined, label: '时钟' },
  { name: 'SearchOutlined', component: SearchOutlined, label: '搜索' },
  { name: 'PlusOutlined', component: PlusOutlined, label: '添加' },
  { name: 'MinusOutlined', component: MinusOutlined, label: '减少' },
  { name: 'DeleteOutlined', component: DeleteOutlined, label: '删除' },
  { name: 'EyeOutlined', component: EyeOutlined, label: '查看' },
  { name: 'EyeInvisibleOutlined', component: EyeInvisibleOutlined, label: '隐藏' },
  { name: 'DownloadOutlined', component: DownloadOutlined, label: '下载' },
  { name: 'UploadOutlined', component: UploadOutlined, label: '上传' },
  { name: 'ReloadOutlined', component: ReloadOutlined, label: '刷新' },
  { name: 'PlayCircleOutlined', component: PlayCircleOutlined, label: '播放' },
  { name: 'PauseCircleOutlined', component: PauseCircleOutlined, label: '暂停' },
  { name: 'StopOutlined', component: StopOutlined, label: '停止' },
  { name: 'CheckOutlined', component: CheckOutlined, label: '勾选' },
  { name: 'CloseCircleOutlined', component: CloseCircleOutlined, label: '关闭' },
  { name: 'WarningOutlined', component: WarningOutlined, label: '警告' },
  { name: 'InfoCircleOutlined', component: InfoCircleOutlined, label: '信息' },
  { name: 'QuestionCircleOutlined', component: QuestionCircleOutlined, label: '问号' },
  { name: 'LinkOutlined', component: LinkOutlined, label: '链接' },
  { name: 'ShareAltOutlined', component: ShareAltOutlined, label: '分享' },
  { name: 'CopyOutlined', component: CopyOutlined, label: '复制' },
  { name: 'ScissorOutlined', component: ScissorOutlined, label: '剪切' },
  { name: 'PrinterOutlined', component: PrinterOutlined, label: '打印' },
  { name: 'ShoppingCartOutlined', component: ShoppingCartOutlined, label: '购物车' },
  { name: 'ShoppingOutlined', component: ShoppingOutlined, label: '商店' },
  { name: 'GiftOutlined', component: GiftOutlined, label: '礼物' },
  { name: 'TrophyOutlined', component: TrophyOutlined, label: '奖杯' },
  { name: 'CrownOutlined', component: CrownOutlined, label: '皇冠' },
  { name: 'BulbOutlined', component: BulbOutlined, label: '灯泡' },
  { name: 'ExperimentOutlined', component: ExperimentOutlined, label: '实验' },
  { name: 'BugOutlined', component: BugOutlined, label: '错误' },
  { name: 'CodeOutlined', component: CodeOutlined, label: '代码' },
  { name: 'ApiOutlined', component: ApiOutlined, label: 'API' },
  { name: 'AppstoreOutlined', component: AppstoreOutlined, label: '应用' },
  { name: 'BarsOutlined', component: BarsOutlined, label: '菜单' },
  { name: 'MenuOutlined', component: MenuOutlined, label: '菜单' },
  { name: 'LayoutOutlined', component: LayoutOutlined, label: '布局' },
  { name: 'TableOutlined', component: TableOutlined, label: '表格' },
  { name: 'UnorderedListOutlined', component: UnorderedListOutlined, label: '列表' },
  { name: 'OrderedListOutlined', component: OrderedListOutlined, label: '有序列表' },
  { name: 'PictureOutlined', component: PictureOutlined, label: '图片' },
  { name: 'VideoCameraOutlined', component: VideoCameraOutlined, label: '视频' },
  { name: 'SoundOutlined', component: SoundOutlined, label: '声音' },
  { name: 'CustomerServiceOutlined', component: CustomerServiceOutlined, label: '客服' },
  { name: 'GlobalOutlined', component: GlobalOutlined, label: '全球' },
  { name: 'EnvironmentOutlined', component: EnvironmentOutlined, label: '位置' },
  { name: 'CompassOutlined', component: CompassOutlined, label: '指南针' },
  { name: 'CarOutlined', component: CarOutlined, label: '汽车' },
  { name: 'BankOutlined', component: BankOutlined, label: '银行' },
  { name: 'ShopOutlined', component: ShopOutlined, label: '商店' },
  { name: 'MedicineBoxOutlined', component: MedicineBoxOutlined, label: '医药' },
  { name: 'SafetyCertificateOutlined', component: SafetyCertificateOutlined, label: '证书' },
  { name: 'InsuranceOutlined', component: InsuranceOutlined, label: '保险' },
  { name: 'FileProtectOutlined', component: FileProtectOutlined, label: '保护' },
  { name: 'FileSyncOutlined', component: FileSyncOutlined, label: '同步' },
  { name: 'FileSearchOutlined', component: FileSearchOutlined, label: '搜索文件' },
  { name: 'FileAddOutlined', component: FileAddOutlined, label: '添加文件' },
  { name: 'FileExcelOutlined', component: FileExcelOutlined, label: 'Excel' },
  { name: 'FilePdfOutlined', component: FilePdfOutlined, label: 'PDF' },
  { name: 'FileWordOutlined', component: FileWordOutlined, label: 'Word' },
  { name: 'FileImageOutlined', component: FileImageOutlined, label: '图片文件' },
  { name: 'FileZipOutlined', component: FileZipOutlined, label: '压缩包' },
  { name: 'FolderAddOutlined', component: FolderAddOutlined, label: '添加文件夹' },
  { name: 'FolderViewOutlined', component: FolderViewOutlined, label: '查看文件夹' },
  { name: 'ProjectOutlined', component: ProjectOutlined, label: '项目' },
  { name: 'BuildOutlined', component: BuildOutlined, label: '构建' },
  { name: 'RobotOutlined', component: RobotOutlined, label: '机器人' },
];

// 图标名称到组件的映射
const iconNameMap: Record<string, React.ComponentType<any>> = {
  ToolOutlined,
  CheckCircleOutlined,
  FileTextOutlined,
  EditOutlined,
  SafetyOutlined,
  UnlockOutlined,
  LockOutlined,
  KeyOutlined,
  CheckSquareOutlined,
  HomeOutlined,
  UserOutlined,
  TeamOutlined,
  SettingOutlined,
  FolderOutlined,
  FileOutlined,
  FolderOpenOutlined,
  DatabaseOutlined,
  CloudOutlined,
  ThunderboltOutlined,
  FireOutlined,
  RocketOutlined,
  StarOutlined,
  HeartOutlined,
  BellOutlined,
  MessageOutlined,
  PhoneOutlined,
  MailOutlined,
  CalendarOutlined,
  ClockCircleOutlined,
  SearchOutlined,
  PlusOutlined,
  MinusOutlined,
  DeleteOutlined,
  EyeOutlined,
  EyeInvisibleOutlined,
  DownloadOutlined,
  UploadOutlined,
  ReloadOutlined,
  PlayCircleOutlined,
  PauseCircleOutlined,
  StopOutlined,
  CheckOutlined,
  CloseCircleOutlined,
  WarningOutlined,
  InfoCircleOutlined,
  QuestionCircleOutlined,
  LinkOutlined,
  ShareAltOutlined,
  CopyOutlined,
  ScissorOutlined,
  PrinterOutlined,
  ShoppingCartOutlined,
  ShoppingOutlined,
  GiftOutlined,
  TrophyOutlined,
  CrownOutlined,
  BulbOutlined,
  ExperimentOutlined,
  BugOutlined,
  CodeOutlined,
  ApiOutlined,
  AppstoreOutlined,
  BarsOutlined,
  MenuOutlined,
  LayoutOutlined,
  TableOutlined,
  UnorderedListOutlined,
  OrderedListOutlined,
  PictureOutlined,
  VideoCameraOutlined,
  SoundOutlined,
  CustomerServiceOutlined,
  GlobalOutlined,
  EnvironmentOutlined,
  CompassOutlined,
  CarOutlined,
  BankOutlined,
  ShopOutlined,
  MedicineBoxOutlined,
  SafetyCertificateOutlined,
  InsuranceOutlined,
  FileProtectOutlined,
  FileSyncOutlined,
  FileSearchOutlined,
  FileAddOutlined,
  FileExcelOutlined,
  FilePdfOutlined,
  FileWordOutlined,
  FileImageOutlined,
  FileZipOutlined,
  FolderAddOutlined,
  FolderViewOutlined,
  ProjectOutlined,
  BuildOutlined,
  RobotOutlined,
};

// 自定义节点组件
function CustomNode({ data, selected, id }: any) {
  // 优先使用自定义图标（图片文件名），否则使用节点类型对应的图标
  let Icon = FileTextOutlined;
  let config = null;
  let iconImagePath: string | null = null;
  const paletteType = resolveWorkflowPaletteType(data.type);
  
  // 检查是否是图片文件名（如 "1000.png"）
  if (data.icon && /^\d+\.png$/.test(data.icon)) {
    // 是图片文件名，获取对应的图片路径
    iconImagePath = getIconPathByFileName(data.icon);
    // 尝试找到对应的配置（保持颜色等样式）
    config = nodeConfigs.find(c => c.type === paletteType) || nodeConfigs[0];
  } else if (data.icon && iconNameMap[data.icon]) {
    // 使用自定义图标组件
    Icon = iconNameMap[data.icon];
    // 尝试找到对应的配置（保持颜色等样式）
    config = nodeConfigs.find(c => c.icon === Icon);
    if (!config) {
      // 如果找不到配置，使用默认配置
      config = nodeConfigs.find(c => c.type === paletteType) || nodeConfigs[0];
    }
  } else {
    // 使用节点类型对应的图标
    config = nodeConfigs.find(c => c.type === paletteType);
    Icon = config?.icon || FileTextOutlined;
  }
  // 从节点ID中提取序号，或使用data中的nodeId
  const nodeId = data.nodeId || (id ? String(parseInt(id.split('-').pop() || '0') % 1000).padStart(3, '0') : '001');
  
  // 处理节点名称：如果超过6个字符则换行
  const formatNodeLabel = (text: string): string[] => {
    if (!text) return [];
    if (text.length <= 6) return [text];
    
    // 如果超过6个字符，按6个字符分割
    const lines: string[] = [];
    for (let i = 0; i < text.length; i += 6) {
      lines.push(text.slice(i, i + 6));
    }
    return lines;
  };
  
  const nodeLabel = data.label || config?.label || '';
  const labelLines = formatNodeLabel(nodeLabel);
  
  return (
    <div
      className={`relative px-4 py-4 rounded-lg shadow-sm border-2 w-[200px] min-w-[200px] max-w-[200px] h-auto min-h-[140px] bg-white ${
        selected
          ? 'border-blue-500 shadow-md ring-1 ring-blue-200'
          : 'border-gray-200 hover:border-gray-300'
      } transition-all`}
    >
      {/* 连接点 - 四个方向，每个方向都有唯一的 source 和 target Handle */}
      <Handle
        id="top-source"
        type="source"
        position={Position.Top}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ top: -6, left: '50%', transform: 'translateX(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="top-target"
        type="target"
        position={Position.Top}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ top: -6, left: '50%', transform: 'translateX(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="bottom-source"
        type="source"
        position={Position.Bottom}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ bottom: -6, left: '50%', transform: 'translateX(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="bottom-target"
        type="target"
        position={Position.Bottom}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ bottom: -6, left: '50%', transform: 'translateX(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="left-source"
        type="source"
        position={Position.Left}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ left: -6, top: '50%', transform: 'translateY(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="left-target"
        type="target"
        position={Position.Left}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ left: -6, top: '50%', transform: 'translateY(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="right-source"
        type="source"
        position={Position.Right}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ right: -6, top: '50%', transform: 'translateY(-50%)' }}
        isConnectable={true}
      />
      <Handle
        id="right-target"
        type="target"
        position={Position.Right}
        className="!w-3 !h-3 !bg-green-500 !border-2 !border-white !rounded-full !shadow-sm"
        style={{ right: -6, top: '50%', transform: 'translateY(-50%)' }}
        isConnectable={true}
      />
      
      {/* 垂直布局：顶部图标、中间名称、底部ID */}
      <div className="flex flex-col items-center justify-between gap-3 h-full">
        {/* 顶部：图标 */}
        <div className={`${config?.bgColor || 'bg-gray-50'} ${config?.borderColor || 'border-gray-100'} border p-3 rounded-xl shadow-sm flex items-center justify-center flex-shrink-0`} style={{ borderRadius: '12px' }}>
          {iconImagePath ? (
            <img 
              src={iconImagePath} 
              alt={data.icon || '节点图标'}
              className="w-6 h-6 object-contain"
              onError={(e) => {
                // 如果图片加载失败，回退到默认图标
                console.error('节点图标加载失败:', iconImagePath);
                (e.target as HTMLImageElement).style.display = 'none';
              }}
            />
          ) : (
          <Icon 
              className={`${config?.iconColor || 'text-gray-600'} text-2xl`} 
            style={{ color: config?.iconColorCustom || undefined }}
          />
          )}
        </div>
        {/* 中间：节点名称 */}
        <div className="font-semibold text-sm text-gray-900 leading-tight text-center break-words w-full flex-1 flex flex-col items-center justify-center px-1">
            {labelLines.length > 1 ? (
              labelLines.map((line, index) => (
                <span key={index} className="block">
                  {line}
                </span>
              ))
            ) : (
              <span>{nodeLabel}</span>
            )}
          </div>
        {/* 底部：ID */}
        <div className="text-xs text-gray-500 text-center flex-shrink-0">
            ID: {nodeId}
        </div>
      </div>
    </div>
  );
}

// 各个节点类型
function CreateJobNode(props: any) {
  return <CustomNode {...props} />;
}

function ConfirmNode(props: any) {
  return <CustomNode {...props} />;
}

function ReviewNode(props: any) {
  return <CustomNode {...props} />;
}

function InputInfoNode(props: any) {
  return <CustomNode {...props} />;
}

function IsolationNode(props: any) {
  return <CustomNode {...props} />;
}

function ReleaseIsolationNode(props: any) {
  return <CustomNode {...props} />;
}

function CoLockNode(props: any) {
  return <CustomNode {...props} />;
}

function UnlockCoLockNode(props: any) {
  return <CustomNode {...props} />;
}

function ReturnLockNode(props: any) {
  return <CustomNode {...props} />;
}

function CompleteNode(props: any) {
  return <CustomNode {...props} />;
}

// 自定义节点类型映射
const nodeTypes = {
  createJob: CreateJobNode,
  confirm: ConfirmNode,
  review: ReviewNode,
  inputInfo: InputInfoNode,
  lock: IsolationNode,
  isolation: IsolationNode,
  unlock: ReleaseIsolationNode,
  releaseIsolation: ReleaseIsolationNode,
  coLock: CoLockNode,
  unlockCoLock: UnlockCoLockNode,
  returnLock: ReturnLockNode,
  complete: CompleteNode,
};

// 全局变量存储删除函数（用于边组件）
let globalDeleteEdgeFn: ((id: string) => void) | null = null;
// 全局变量存储 edges（用于边组件获取 type）
let globalEdges: Edge[] = [];

// 自定义边组件 - 定义在组件外部以确保稳定引用
function CustomEdgeWithDelete({
  id,
  sourceX,
  sourceY,
  targetX,
  targetY,
  selected,
  markerStart,
  style,
  type: propType,
}: any) {
  // 从全局 edges 中查找对应的边来获取 type 和 fixedLength
  const edge = globalEdges.find(e => e.id === id);
  const edgeType = edge?.type || propType || 'straight';
  const fixedLength = (edge as any)?.data?.fixedLength || (edge as any)?.fixedLength;
  
  // 计算实际距离
  const dx = targetX - sourceX;
  const dy = targetY - sourceY;
  const actualDistance = Math.sqrt(dx * dx + dy * dy);
  
  // 如果设置了 fixedLength 且实际距离大于 fixedLength，则调整起点和终点
  let adjustedSourceX = sourceX;
  let adjustedSourceY = sourceY;
  let adjustedTargetX = targetX;
  let adjustedTargetY = targetY;
  
  if (fixedLength && actualDistance > fixedLength && actualDistance > 0.001) {
    // 计算单位方向向量
    const unitX = dx / actualDistance;
    const unitY = dy / actualDistance;
    
    // 计算需要缩短的距离（从两端各缩短一半）
    const shortenDistance = (actualDistance - fixedLength) / 2;
    
    // 调整起点（向目标方向移动）
    adjustedSourceX = sourceX + unitX * shortenDistance;
    adjustedSourceY = sourceY + unitY * shortenDistance;
    
    // 调整终点（向源点方向移动）
    adjustedTargetX = targetX - unitX * shortenDistance;
    adjustedTargetY = targetY - unitY * shortenDistance;
  }
  
  // 根据边的类型选择路径生成函数
  let edgePath: string;
  let labelX: number;
  let labelY: number;
  
  if (edgeType === 'smoothstep') {
    // 使用 smoothstep 路径（平滑步进曲线，可以拐弯）
    // 调整 borderRadius 参数使曲线更平滑
    [edgePath, labelX, labelY] = getSmoothStepPath({
      sourceX: adjustedSourceX,
      sourceY: adjustedSourceY,
      targetX: adjustedTargetX,
      targetY: adjustedTargetY,
      borderRadius: 15, // 圆角半径，使曲线更平滑
    });
  } else {
    // 使用直线路径
    [edgePath, labelX, labelY] = getStraightPath({
      sourceX: adjustedSourceX,
      sourceY: adjustedSourceY,
      targetX: adjustedTargetX,
      targetY: adjustedTargetY,
    });
  }

  console.log('边组件渲染，ID:', id, 'propType:', propType, 'edgeType:', edgeType, 'edge对象:', edge, 'sourceX:', sourceX, 'sourceY:', sourceY, 'targetX:', targetX, 'targetY:', targetY, 'selected:', selected);

  return (
    <>
      <BaseEdge
        id={id}
        path={edgePath}
        markerStart={markerStart}
        style={{
          ...style,
          strokeWidth: selected ? 3 : 2,
          stroke: selected ? '#3b82f6' : '#000000',
        }}
      />
      {selected && (
        <EdgeLabelRenderer>
          <div
            style={{
              position: 'absolute',
              left: labelX,
              top: labelY,
              transform: 'translate(-50%, -50%)',
              pointerEvents: 'all',
              zIndex: 1000,
            }}
            className="nodrag nopan"
          >
            <button
              onClick={(e) => {
                e.stopPropagation();
                e.preventDefault();
                console.log('删除按钮被点击，连线ID:', id);
                if (globalDeleteEdgeFn) {
                  globalDeleteEdgeFn(id);
                }
              }}
              onMouseDown={(e) => {
                e.stopPropagation();
              }}
              className="bg-red-500 hover:bg-red-600 rounded-full p-1.5 shadow-lg transition-colors flex items-center justify-center w-8 h-8"
              title="删除连线"
              type="button"
              style={{ cursor: 'pointer' }}
            >
              <DeleteOutlined className="text-sm text-white" style={{ color: 'white' }} />
            </button>
          </div>
        </EdgeLabelRenderer>
      )}
    </>
  );
}

// 自定义边类型映射 - 定义在组件外部
const edgeTypes: EdgeTypes = {
  straight: CustomEdgeWithDelete,
  default: CustomEdgeWithDelete,
  smoothstep: CustomEdgeWithDelete,
};


// 节点类型到图标分类的映射
const getIconCategoryByNodeType = (nodeType: string): string => {
  const typeMap: Record<string, string> = {
    'createJob': '开始',
    'confirm': '确认',
    'review': '审核',
    'inputInfo': '录入',
    'lock': '能量隔离',
    'unlock': '解除隔离',
    'coLock': '能量隔离',
    'unlockCoLock': '解除隔离',
    'isolation': '能量隔离',
    'releaseIsolation': '解除隔离',
    'returnLock': '结束',
    'complete': '结束',
  };
  return typeMap[nodeType] || '开始';
};

// 图标分类配置（分类名称 -> 图标文件列表）
const iconCategories: Record<string, { start: number; end: number }> = {
  '审核': { start: 1000, end: 1011 },
  '开始': { start: 2000, end: 2016 },
  '录入': { start: 3000, end: 3028 },
  '确认': { start: 4000, end: 4024 },
  '结束': { start: 5000, end: 5018 },
  '能量隔离': { start: 6000, end: 6027 },
  '解除隔离': { start: 7000, end: 7021 },
};

// 使用 import.meta.glob 动态导入所有图标
const iconModules = import.meta.glob('../assets/节点图标/**/*.png', { eager: true, as: 'url' });

// 调试：打印加载的图标模块信息
const allIconKeys = Object.keys(iconModules);
console.log('ProcessDesigner: 图标模块加载情况');
console.log('总数量:', allIconKeys.length);
if (allIconKeys.length > 0) {
  console.log('前5个路径示例:', allIconKeys.slice(0, 5));
  // 打印一个完整的路径示例
  const firstKey = allIconKeys[0];
  console.log('第一个路径完整信息:', {
    key: firstKey,
    value: iconModules[firstKey],
    normalized: firstKey.replace(/\\/g, '/')
  });
} else {
  console.error('ProcessDesigner: 未加载到任何图标模块！');
  console.error('请检查路径: ../assets/节点图标/**/*.png');
  console.error('提示: 如果路径正确，可能需要重启开发服务器');
}

// 生成图标路径列表（返回文件名和路径的映射）
const generateIconPaths = (category: string): Array<{ id: string; fileName: string; path: string }> => {
  const config = iconCategories[category];
  if (!config) {
    console.warn(`ProcessDesigner: 分类 ${category} 没有配置`);
    return [];
  }
  
  const paths: Array<{ id: string; fileName: string; path: string }> = [];
  
  // 如果 import.meta.glob 加载成功，使用它
  const allKeys = Object.keys(iconModules);
  
  if (allKeys.length > 0) {
    // 使用 import.meta.glob 的结果
    for (let i = config.start; i <= config.end; i++) {
      const fileName = `${i}.png`;
      const matchingKey = allKeys.find(k => {
        const normalizedKey = k.replace(/\\/g, '/').toLowerCase();
        const normalizedCategory = category.toLowerCase();
        const normalizedFileName = fileName.toLowerCase();
        return normalizedKey.includes(normalizedCategory) && normalizedKey.endsWith(normalizedFileName);
      });
      
      if (matchingKey) {
        const iconPath = iconModules[matchingKey] as string;
        paths.push({ id: `${category}_${i}`, fileName: fileName, path: iconPath });
      }
    }
  } else {
    // Fallback: 使用 new URL 动态构建路径（适用于开发环境）
    console.warn('ProcessDesigner: import.meta.glob 未加载成功，使用 fallback 方式');
    for (let i = config.start; i <= config.end; i++) {
      try {
        // 使用 new URL 构建路径
        const fileName = `${i}.png`;
        const iconPath = new URL(`../assets/节点图标/${category}/${fileName}`, import.meta.url).href;
        paths.push({ id: `${category}_${i}`, fileName: fileName, path: iconPath });
      } catch (e) {
        console.warn(`ProcessDesigner: 无法构建图标路径 ${category}/${i}.png:`, e);
      }
    }
  }
  
  return paths;
};

// 根据文件名获取图标路径（用于节点显示）
const getIconPathByFileName = (fileName: string | undefined): string | null => {
  if (!fileName) return null;
  
  // 如果已经是完整路径，尝试提取文件名
  let actualFileName = fileName;
  if (fileName.includes('/') || fileName.includes('\\')) {
    // 从路径中提取文件名
    const pathParts = fileName.replace(/\\/g, '/').split('/');
    actualFileName = pathParts[pathParts.length - 1];
  }
  
  // 从文件名中提取数字和分类
  const match = actualFileName.match(/^(\d+)\.png$/);
  if (!match) return null;
  
  const iconNumber = parseInt(match[1], 10);
  
  // 根据数字范围判断分类
  let category = '';
  if (iconNumber >= 1000 && iconNumber <= 1011) category = '审核';
  else if (iconNumber >= 2000 && iconNumber <= 2016) category = '开始';
  else if (iconNumber >= 3000 && iconNumber <= 3028) category = '录入';
  else if (iconNumber >= 4000 && iconNumber <= 4024) category = '确认';
  else if (iconNumber >= 5000 && iconNumber <= 5018) category = '结束';
  else if (iconNumber >= 6000 && iconNumber <= 6027) category = '能量隔离';
  else if (iconNumber >= 7000 && iconNumber <= 7021) category = '解除隔离';
  
  if (!category) return null;
  
  // 查找对应的路径
  const allKeys = Object.keys(iconModules);
  const matchingKey = allKeys.find(k => {
    const normalizedKey = k.replace(/\\/g, '/').toLowerCase();
    return normalizedKey.includes(category.toLowerCase()) && normalizedKey.endsWith(actualFileName.toLowerCase());
  });
  
  if (matchingKey) {
    return iconModules[matchingKey] as string;
  }
  
  return null;
};

// 从图标路径或文件名中提取文件名（用于保存JSON）
const extractIconFileName = (icon: string | undefined): string | undefined => {
  if (!icon) return undefined;
  
  // 如果已经是文件名格式（如 "1000.png"），直接返回
  if (/^\d+\.png$/.test(icon)) {
    return icon;
  }
  
  // 如果是路径，提取文件名
  if (icon.includes('/') || icon.includes('\\')) {
    const pathParts = icon.replace(/\\/g, '/').split('/');
    const fileName = pathParts[pathParts.length - 1];
    // 如果提取的文件名符合格式，返回它
    if (/^\d+\.png$/.test(fileName)) {
      return fileName;
    }
  }
  
  // 否则返回原值（可能是其他格式的图标）
  return icon;
};

export default function ProcessDesigner() {
  const navigate = useNavigate();
  const location = useLocation();
  
  // 返回流程模板菜单的辅助函数
  const backToProcessTemplateMenu = () => {
    // 默认返回：隔离作业 -> 流程模板
    let menuInfo = { menu: 'isolationWork', subMenu: 'processTemplate' };
    try {
      const source = sessionStorage.getItem('processDesignerSource');
      if (source) {
        const parsed = JSON.parse(source);
        if (parsed?.menu && parsed?.subMenu) {
          menuInfo = { menu: parsed.menu, subMenu: parsed.subMenu };
        }
        sessionStorage.removeItem('processDesignerSource');
      }
    } catch (e) {
      console.error('解析 processDesignerSource 失败:', e);
    }

    sessionStorage.setItem('navigateToMenu', JSON.stringify(menuInfo));
    sessionStorage.setItem('lastActiveMenu', JSON.stringify(menuInfo));
    navigate('/dashboard');
  };
  const [nodes, setNodes, onNodesChangeBase] = useNodesState([]);
  const [edges, setEdges, onEdgesChange] = useEdgesState([]);
  
  // 保存初始状态，用于检测是否有未保存的更改
  const initialNodesRef = useRef<Node[]>([]);
  const initialEdgesRef = useRef<Edge[]>([]);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  // 是否正处于「从服务器/缓存加载或恢复」阶段，此期间不把 onNodesChange/onEdgesChange 视为用户修改
  const isLoadingFromServerOrRestoreRef = useRef(false);
  
  // 从URL参数获取流程ID
  const workflowId = React.useMemo(() => {
    const params = new URLSearchParams(location.search);
    const id = params.get('id');
    return id ? parseInt(id, 10) : null;
  }, [location.search]);
  const [selectedNode, setSelectedNode] = useState<Node | null>(null);
  const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
  const [activeTabKey, setActiveTabKey] = useState<string>('info');
  const [rightPanelCollapsed, setRightPanelCollapsed] = useState(false);
  const reactFlowWrapper = useRef<HTMLDivElement>(null);
  const [reactFlowInstance, setReactFlowInstance] = useState<any>(null);
  const [zoom, setZoom] = useState(1);
  const [iconPickerOpen, setIconPickerOpen] = useState(false);
  // 简单缓存：将每个节点的配置持久化到 sessionStorage，key 按节点ID
  const cachePrefix = 'process_designer_node_';
  const [exportVisible, setExportVisible] = useState(false);
  const [exportContent, setExportContent] = useState('');
  const [importVisible, setImportVisible] = useState(false);
  const [importJson, setImportJson] = useState('');
  // 存储工作流详情（name、description等）
  const [workflowDetail, setWorkflowDetail] = useState<WorkflowDesignVO | null>(null);
  const [loadingDetail, setLoadingDetail] = useState(false);
  
  // 表单预览相关状态
  const [formPreviewVisible, setFormPreviewVisible] = useState(false);
  const [formPreviewData, setFormPreviewData] = useState<FormVO | null>(null);
  const [formPreviewLoading, setFormPreviewLoading] = useState(false);
  const [formPreviewDetailData, setFormPreviewDetailData] = useState<FormCreateData>({
    rule: [],
    option: {}
  });
  const formPreviewForm = AntdForm.useForm()[0];
  
  const defaultFormConfig = {
    name: '',
    labelPosition: 'right',
    formSize: 'middle',
    labelSuffix: '',
    labelWidth: 100,
    hideRequiredMark: false,
    showValidationError: true,
    inlineValidation: false,
    showSubmitButton: false,
    showResetButton: false,
  };
  
  // 角色用户列表
  const [drawerUsers, setDrawerUsers] = useState<UserVO[]>([]); // 负责人（jtdrawer）
  const [lockerUsers, setLockerUsers] = useState<UserVO[]>([]); // 上锁人（jtlocker）
  const [colockerUsers, setColockerUsers] = useState<UserVO[]>([]); // 共锁人（jtcolocker）
  const [ccDeptList, setCcDeptList] = useState<DeptVO[]>([]);
  const [ccUserList, setCcUserList] = useState<UserVO[]>([]);
  const ccDeptTreeData = useMemo(() => {
    const nodeMap = new Map<string, any>();
    const roots: any[] = [];
    ccDeptList.forEach((dept) => {
      const id = String(dept.id);
      nodeMap.set(id, {
        key: `dept-${id}`,
        value: id,
        title: dept.name,
        children: [] as any[],
      });
    });
    ccDeptList.forEach((dept) => {
      const id = String(dept.id);
      const parentId = String(dept.parentId ?? '');
      const node = nodeMap.get(id);
      const parent = nodeMap.get(parentId);
      if (parent && parentId !== id) {
        parent.children.push(node);
      } else {
        roots.push(node);
      }
    });
    return roots;
  }, [ccDeptList]);
  const ccUserTreeData = useMemo(() => {
    const buildDeptNode = (node: any): any => ({
      key: node.key,
      value: node.value,
      title: node.title,
      selectable: false,
      children: (node.children || []).map((c: any) => buildDeptNode(c)),
    });
    const roots = ccDeptTreeData.map((n: any) => buildDeptNode(n));
    const deptNodeMap = new Map<string, any>();
    const walk = (nodes: any[]) => {
      nodes.forEach((n) => {
        deptNodeMap.set(String(n.value), n);
        if (Array.isArray(n.children) && n.children.length > 0) walk(n.children);
      });
    };
    walk(roots);
    const unknownDeptNode = {
      key: 'dept-unknown',
      value: 'unknown',
      title: '未分配部门',
      selectable: false,
      children: [] as any[],
    };
    ccUserList.forEach((user) => {
      const userNode = {
        key: `user-${user.id}`,
        value: String(user.id),
        title: user.nickname || user.username,
        isLeaf: true,
      };
      const deptKey = user.deptId !== undefined && user.deptId !== null ? String(user.deptId) : '';
      const deptNode = deptNodeMap.get(deptKey);
      if (deptNode) {
        deptNode.children = [...(deptNode.children || []), userNode];
      } else {
        unknownDeptNode.children.push(userNode);
      }
    });
    if (unknownDeptNode.children.length > 0) {
      roots.push(unknownDeptNode);
    }
    return roots;
  }, [ccDeptTreeData, ccUserList]);
  // 隔离点列表
  const [isolationPoints, setIsolationPoints] = useState<SegregationPointVO[]>([]);
  // 表单列表
  const [formList, setFormList] = useState<FormVO[]>([]);
  // 隔离方式字典数据
  const [isolationTypeDictList, setIsolationTypeDictList] = useState<DictDataVO[]>([]);

  const loadNodeCache = useCallback((nodeId: string) => {
    try {
      const raw = sessionStorage.getItem(`${cachePrefix}${nodeId}`);
      if (!raw) return null;
      return JSON.parse(raw);
    } catch {
      return null;
    }
  }, []);

  const saveNodeCache = useCallback((nodeId: string, data: any) => {
    try {
      sessionStorage.setItem(`${cachePrefix}${nodeId}`, JSON.stringify(data));
    } catch {
      // ignore
    }
  }, []);

  // 流程设计JSON缓存相关函数
  const getCacheKey = useCallback((id: number | null) => {
    return id ? `process_designer_cache_${id}` : null;
  }, []);

  // 保存流程设计JSON到缓存
  const saveWorkflowCache = useCallback((id: number | null, nodes: Node[], edges: Edge[]) => {
    if (!id) return;
    
    try {
      // 构建导出数据（与handleSave中的逻辑相同）
      const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
      const nodeIdMap = new Map<string, string>();
      nodes.forEach(n => {
        nodeIdMap.set(n.id, n.id);
      });

      nodes.forEach(n => {
        const uuid = nodeIdMap.get(n.id) || n.id;
        adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
      });
      edges.forEach(e => {
        const sourceUuid = nodeIdMap.get(e.source) || e.source;
        const targetUuid = nodeIdMap.get(e.target) || e.target;
        if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
        if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
        adjacency[sourceUuid].parentUuid.push(targetUuid);
        adjacency[targetUuid].childrenUuid.push(sourceUuid);
      });

      const exportData = {
        nodeCount: nodes.length,
        edgeCount: edges.length,
        adjacency,
        nodes: nodes.map(n => {
          const cachedData = loadNodeCache(n.id);
          const mergedData = cachedData 
            ? { ...n.data, ...cachedData }
            : n.data;
          
          // 将 responsible 转换为 workerUserId，并确保值是字符串
          // 同时提取四个模板代码字段到顶层
          const { responsible, smsTemplateCode, messageTemplateCode, emailTemplateCode, appTemplateCode, ...restData } = mergedData;
          const processedData = sanitizeCoLockFamilyNodeDataForExport(
            {
              ...restData,
              workerUserId:
                responsible !== undefined && responsible !== null && responsible !== ''
                  ? String(responsible)
                  : '',
              copyDeptIds: toWorkflowCopyIdsCommaString((restData as any).copyDeptIds),
              copyUserIds: toWorkflowCopyIdsCommaString((restData as any).copyUserIds),
            },
            n.type
          );

          const nodeObj: any = {
            uuid: n.id,
            type: n.type,
            position: n.position,
            nodeName: processedData.label || '',
            nodeIcon: processedData.icon || processedData.type || n.type || '',
            smsTemplateCode: smsTemplateCode || 'false',
            messageTemplateCode: messageTemplateCode || 'false',
            emailTemplateCode: emailTemplateCode || 'false',
            appTemplateCode: appTemplateCode || 'false',
            data: processedData,
          };
          return nodeObj;
        }),
        edges: edges.map(e => {
          return {
            id: e.id,
            source: nodeIdMap.get(e.source) || e.source,
            target: nodeIdMap.get(e.target) || e.target,
            sourceHandle: e.sourceHandle,
            targetHandle: e.targetHandle,
            type: e.type || 'straight',
          };
        }),
      };

      const content = JSON.stringify(exportData, null, 2);
      const cacheKey = getCacheKey(id);
      if (cacheKey) {
        localStorage.setItem(cacheKey, content);
        console.log('流程设计已保存到缓存:', cacheKey);
      }
    } catch (error) {
      console.error('保存流程设计缓存失败:', error);
    }
  }, [loadNodeCache, getCacheKey]);

  // 读取流程设计JSON缓存
  const loadWorkflowCache = useCallback((id: number | null): string | null => {
    if (!id) return null;
    const cacheKey = getCacheKey(id);
    if (!cacheKey) return null;
    try {
      return localStorage.getItem(cacheKey);
    } catch {
      return null;
    }
  }, [getCacheKey]);

  // 清除流程设计JSON缓存
  const clearWorkflowCache = useCallback((id: number | null) => {
    if (!id) return;
    const cacheKey = getCacheKey(id);
    if (cacheKey) {
      localStorage.removeItem(cacheKey);
      console.log('流程设计缓存已清除:', cacheKey);
    }
  }, [getCacheKey]);

  // 清除当前流程的所有节点缓存
  const clearCurrentWorkflowNodeCache = useCallback(() => {
    if (!nodes || nodes.length === 0) return;
    try {
      nodes.forEach((node) => {
        const nodeCacheKey = `${cachePrefix}${node.id}`;
        sessionStorage.removeItem(nodeCacheKey);
      });
      console.log('当前流程的节点缓存已清除');
    } catch (error) {
      console.error('清除节点缓存失败:', error);
    }
  }, [nodes]);

  // 比较两个 JSON 字符串内容是否相同（忽略格式差异）
  const compareJsonContent = useCallback((json1: string | null, json2: string | null): boolean => {
    if (!json1 || !json2) return false;
    try {
      const obj1 = JSON.parse(json1);
      const obj2 = JSON.parse(json2);
      // 使用 JSON.stringify 比较，确保顺序一致
      return JSON.stringify(obj1) === JSON.stringify(obj2);
    } catch {
      // 如果解析失败，直接比较字符串
      return json1.trim() === json2.trim();
    }
  }, []);

  // 比较当前状态和初始状态，检测是否有未保存的更改
  const checkUnsavedChanges = useCallback(() => {
    // 比较节点数量
    if (nodes.length !== initialNodesRef.current.length) {
      return true;
    }
    // 比较边数量
    if (edges.length !== initialEdgesRef.current.length) {
      return true;
    }
    
    // 创建初始节点的映射，方便通过ID查找
    const initialNodesMap = new Map(initialNodesRef.current.map(n => [n.id, n]));
    const initialEdgesMap = new Map(initialEdgesRef.current.map(e => [e.id, e]));
    
    // 深度比较节点（包括位置、数据等）
    const nodesChanged = nodes.some((node) => {
      const initialNode = initialNodesMap.get(node.id);
      if (!initialNode) return true; // 新节点
      
      // 比较基本属性
      if (node.type !== initialNode.type ||
          node.position.x !== initialNode.position.x ||
          node.position.y !== initialNode.position.y) {
        return true;
      }
      
      // 比较节点数据（包括缓存数据）
      const cachedData = loadNodeCache(node.id);
      const mergedData = cachedData ? { ...node.data, ...cachedData } : node.data;
      // 对于初始节点，我们只比较初始数据，不考虑初始时的缓存
      const initialMergedData = initialNode.data;
      
      return JSON.stringify(mergedData) !== JSON.stringify(initialMergedData);
    });
    
    if (nodesChanged) {
      return true;
    }
    
    // 检查是否有节点被删除
    const hasDeletedNode = initialNodesRef.current.some(initialNode => 
      !nodes.find(n => n.id === initialNode.id)
    );
    if (hasDeletedNode) {
      return true;
    }
    
    // 深度比较边
    const edgesChanged = edges.some((edge) => {
      const initialEdge = initialEdgesMap.get(edge.id);
      if (!initialEdge) return true; // 新边
      
      return edge.source !== initialEdge.source ||
             edge.target !== initialEdge.target ||
             edge.sourceHandle !== initialEdge.sourceHandle ||
             edge.targetHandle !== initialEdge.targetHandle ||
             edge.type !== initialEdge.type;
    });
    
    if (edgesChanged) {
      return true;
    }
    
    // 检查是否有边被删除
    const hasDeletedEdge = initialEdgesRef.current.some(initialEdge => 
      !edges.find(e => e.id === initialEdge.id)
    );
    
    return hasDeletedEdge;
  }, [nodes, edges, loadNodeCache]);

  // 调试：监听 edges 变化
  useEffect(() => {
    console.log('Edges 状态更新:', edges.length, edges);
  }, [edges]);

  // 仅在用户真正修改过（hasUnsavedChanges）时写入缓存，供异常退出后恢复使用
  useEffect(() => {
    if (workflowId && (nodes.length > 0 || edges.length > 0) && hasUnsavedChanges) {
      saveWorkflowCache(workflowId, nodes, edges);
    }
  }, [nodes, edges, workflowId, hasUnsavedChanges, saveWorkflowCache]);

  // 获取隔离方式字典数据
  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 || [];
      setIsolationTypeDictList(list);
    } catch (error: any) {
      console.error('获取隔离方式字典失败:', error);
      setIsolationTypeDictList([]);
    }
  };

  // 加载角色用户列表和隔离点列表
  useEffect(() => {
    const loadRoleUsers = async () => {
      try {
        // 并行加载三种角色的用户
        const [drawerRes, lockerRes, colockerRes] = await Promise.all([
          userApi.getRoleUser('jtdrawer'),
          userApi.getRoleUser('jtlocker'),
          userApi.getRoleUser('jtcolocker'),
        ]);
        setDrawerUsers(drawerRes || []);
        setLockerUsers(lockerRes || []);
        setColockerUsers(colockerRes || []);
      } catch (error) {
        console.error('加载角色用户失败:', error);
      }
    };
    
    const loadIsolationPoints = async () => {
      try {
        const res = await segregationPointApi.getIsIsolationPointPage({ pageNo: 1, pageSize: -1 });
        setIsolationPoints(res.list || []);
      } catch (error) {
        console.error('加载隔离点列表失败:', error);
      }
    };
    
    const loadFormList = async () => {
      try {
        const res = await getFormPage({ pageNo: 1, pageSize: -1, status: 0 }); // 只获取开启状态（status=0）的表单
        setFormList(res.list || []);
      } catch (error) {
        console.error('加载表单列表失败:', error);
      }
    };

    const loadCcOptions = async () => {
      try {
        const [depts, users] = await Promise.all([
          deptApi.getSimpleDeptList(),
          userApi.getSimpleUserList(),
        ]);
        setCcDeptList(depts || []);
        setCcUserList(users || []);
      } catch (error) {
        console.error('加载抄送下拉数据失败:', error);
      }
    };
    
    loadRoleUsers();
    loadIsolationPoints();
    loadFormList();
    loadCcOptions();
    getIsolationMethodDictList();
  }, []);

  // 节点配置状态
  const [nodeConfig, setNodeConfig] = useState({
    nodeName: '',
    nodeIcon: '',
    responsible: '',
    remark: '',
    formId: '',
    isolationType: '', // 隔离方式
    isolationPoints: [] as string[], // 隔离点选择（多选）
    isolationNode: [] as string[], // 隔离节点（多选）
    isolationNodeUuid: '', // 选择的隔离/方案节点ID（用于解除隔离节点）
    lockPerson: '', // 上锁人
    coLockPersons: [] as string[], // 共锁人（多选）
    notificationMethods: {
      sms: false,
      message: false,
      email: false,
      app: false,
    },
    notificationPerson: '',
    notificationTime: '',
    smsTemplateCode: 'false',
    messageTemplateCode: 'false',
    emailTemplateCode: 'false',
    appTemplateCode: 'false',
    copyDeptIds: [] as string[],
    copyUserIds: [] as string[],
  });

  // 实时缓存并更新节点配置，避免切换节点后丢失未保存的输入
  // 若当前为隔离/方案节点，将修改内容自动同步到所有已关联的解除隔离节点，避免设计人员忘记手动维护
  useEffect(() => {
    if (selectedNode) {
      const { isolationMethod, ...restData } = selectedNode.data || {};
      // 将 responsible 同时保存为 workerUserId，确保数据一致性
      const responsibleValue = nodeConfig.responsible || '';
      const updatedData = {
        ...restData,
        label: nodeConfig.nodeName,
        icon: nodeConfig.nodeIcon,
        responsible: responsibleValue,
        workerUserId: responsibleValue, // 同时保存 workerUserId 字段
        remark: nodeConfig.remark,
        formId: nodeConfig.formId,
        isolationType: nodeConfig.isolationType,
        isolationPoints: nodeConfig.isolationPoints,
        isolationNode: nodeConfig.isolationNode,
        isolationNodeUuid: nodeConfig.isolationNodeUuid,
        lockPerson: nodeConfig.lockPerson,
        coLockPersons: nodeConfig.coLockPersons,
        notificationMethods: nodeConfig.notificationMethods,
        notificationPerson: nodeConfig.notificationPerson,
        notificationTime: nodeConfig.notificationTime,
        smsTemplateCode: nodeConfig.smsTemplateCode,
        messageTemplateCode: nodeConfig.messageTemplateCode,
        emailTemplateCode: nodeConfig.emailTemplateCode,
        appTemplateCode: nodeConfig.appTemplateCode,
        copyDeptIds: nodeConfig.copyDeptIds,
        copyUserIds: nodeConfig.copyUserIds,
        nodeName: nodeConfig.nodeName,
        nodeIcon: nodeConfig.nodeIcon,
      };
      // 缓存配置
      saveNodeCache(selectedNode.id, updatedData);
      const isLockSchemeSource = isWorkflowLockSchemeType(selectedNode.data?.type);
      const isCoLockSchemeSource = isWorkflowCoLockType(selectedNode.data?.type);
      const isolationSyncPayload =
        isLockSchemeSource || isCoLockSchemeSource
        ? {
            isolationType: nodeConfig.isolationType,
            isolationPoints: nodeConfig.isolationPoints,
            lockPerson: nodeConfig.lockPerson,
            coLockPersons: nodeConfig.coLockPersons,
            responsible: responsibleValue,
            workerUserId: responsibleValue,
          }
        : null;
      /** 上锁节点变更时同步到「共锁」节点：不覆盖共锁节点自身的共锁人 */
      const lockSchemeToCoLockPayload = isLockSchemeSource
        ? {
            isolationType: nodeConfig.isolationType,
            isolationPoints: nodeConfig.isolationPoints,
            lockPerson: nodeConfig.lockPerson,
            responsible: responsibleValue,
            workerUserId: responsibleValue,
          }
        : null;
      // 实时更新节点显示；若当前为上锁/共锁节点，则把修改内容同步到已关联的解锁/解除共锁节点
      setNodes((nds) =>
        nds.map((node) => {
          if (node.id === selectedNode.id) {
            return { ...node, data: updatedData };
          }
          if (
            isolationSyncPayload &&
            isWorkflowUnlockSchemeType(node.data?.type) &&
            isLockSchemeSource &&
            String(node.data?.isolationNodeUuid) === String(selectedNode.id)
          ) {
            const releaseData = {
              ...node.data,
              ...isolationSyncPayload,
            };
            saveNodeCache(node.id, releaseData);
            return { ...node, data: releaseData };
          }
          if (
            isolationSyncPayload &&
            isWorkflowUnlockCoLockType(node.data?.type) &&
            isCoLockSchemeSource &&
            String(node.data?.isolationNodeUuid) === String(selectedNode.id)
          ) {
            const releaseData = {
              ...node.data,
              ...isolationSyncPayload,
            };
            saveNodeCache(node.id, releaseData);
            return { ...node, data: releaseData };
          }
          if (
            lockSchemeToCoLockPayload &&
            isWorkflowCoLockType(node.data?.type) &&
            isLockSchemeSource &&
            String(node.data?.isolationNodeUuid) === String(selectedNode.id)
          ) {
            const prevCo = Array.isArray(node.data?.coLockPersons) ? node.data.coLockPersons : [];
            const releaseData = {
              ...node.data,
              ...lockSchemeToCoLockPayload,
              coLockPersons: prevCo,
            };
            saveNodeCache(node.id, releaseData);
            return { ...node, data: releaseData };
          }
          return node;
        })
      );
    }
  }, [nodeConfig, selectedNode, saveNodeCache, setNodes]);

  // 历史记录（用于撤销/重做）
  const [history, setHistory] = useState<{ nodes: Node[]; edges: Edge[] }[]>([]);
  const [historyIndex, setHistoryIndex] = useState(-1);

  // 加载 JSON 数据到画布（可复用的函数）
  const loadJsonToCanvas = useCallback((jsonString: string, updateHistory: boolean = false) => {
    if (updateHistory) {
      isLoadingFromServerOrRestoreRef.current = true;
    }
    try {
      const data = JSON.parse(jsonString);
      
      if (!data.nodes || !Array.isArray(data.nodes)) {
        message.error('JSON 格式错误：缺少 nodes 数组');
        return false;
      }
      
      if (!data.edges || !Array.isArray(data.edges)) {
        message.error('JSON 格式错误：缺少 edges 数组');
        return false;
      }

      // 创建uuid到id的映射（导入时uuid改回id）
      const uuidToIdMap = new Map<string, string>();
      data.nodes.forEach((node: any) => {
        // 兼容处理：支持uuid字段或id字段
        const nodeId = node.uuid || node.id;
        uuidToIdMap.set(nodeId, nodeId); // 值保持不变
      });

      // 还原节点，确保 data 包含所有配置字段
      const importedNodes = data.nodes.map((node: any) => {
        const nodeData = node.data || {};
        // 兼容处理：支持uuid字段或id字段
        const nodeId = node.uuid || node.id;
        
        // 如果顶层有 nodeName 和 nodeIcon，将它们还原到 data 中
        const topLevelLabel = node.nodeName || '';
        const topLevelIcon = node.nodeIcon || '';
        
        // 从顶层读取四个模板代码字段（与 uuid 同级）
        const topLevelSmsTemplateCode = node.smsTemplateCode;
        const topLevelMessageTemplateCode = node.messageTemplateCode;
        const topLevelEmailTemplateCode = node.emailTemplateCode;
        const topLevelAppTemplateCode = node.appTemplateCode;
        
        // 兼容处理：优先使用 workerUserId，如果没有则使用 responsible（向后兼容），并确保值是字符串
        const workerUserIdValue = nodeData.workerUserId !== undefined && nodeData.workerUserId !== null && nodeData.workerUserId !== ''
          ? String(nodeData.workerUserId)
          : (nodeData.responsible !== undefined && nodeData.responsible !== null && nodeData.responsible !== ''
              ? String(nodeData.responsible)
              : '');
        
        // 确保所有字段都存在，使用默认值填充缺失的字段
        const completeData = {
          label: topLevelLabel || nodeData.label || '', // 优先使用顶层的 nodeName
          type: nodeData.type || node.type,
          nodeId: nodeData.nodeId || '',
          icon: topLevelIcon || nodeData.icon || nodeData.type || node.type, // 优先使用顶层的 nodeIcon
          responsible: workerUserIdValue, // 内部仍使用 responsible 字段名，但值来自 workerUserId 或 responsible
          workerUserId: workerUserIdValue, // 同时保存 workerUserId 字段，确保数据一致性
          remark: nodeData.remark || '',
          formId: nodeData.formId || nodeData.submitForm ? String(nodeData.formId || nodeData.submitForm) : '', // 兼容旧的 submitForm 字段，确保是字符串类型
          isolationType: nodeData.isolationType || '',
          isolationPoints: Array.isArray(nodeData.isolationPoints) ? nodeData.isolationPoints : [],
          isolationNode: Array.isArray(nodeData.isolationNode) ? nodeData.isolationNode : [],
          isolationNodeUuid: nodeData.isolationNodeUuid || '',
          lockPerson: nodeData.lockPerson || '',
          coLockPersons: Array.isArray(nodeData.coLockPersons) ? nodeData.coLockPersons : [],
          notificationMethods: nodeData.notificationMethods || {
            sms: false,
            message: false,
            email: false,
            app: false,
          },
          notificationPerson: nodeData.notificationPerson || '',
          notificationTime: nodeData.notificationTime || '',
          smsTemplateCode: topLevelSmsTemplateCode !== undefined ? topLevelSmsTemplateCode : (nodeData.smsTemplateCode || 'false'),
          messageTemplateCode: topLevelMessageTemplateCode !== undefined ? topLevelMessageTemplateCode : (nodeData.messageTemplateCode || 'false'),
          emailTemplateCode: topLevelEmailTemplateCode !== undefined ? topLevelEmailTemplateCode : (nodeData.emailTemplateCode || 'false'),
          appTemplateCode: topLevelAppTemplateCode !== undefined ? topLevelAppTemplateCode : (nodeData.appTemplateCode || 'false'),
          copyDeptIds: parseWorkflowCopyIds(nodeData.copyDeptIds, nodeData.ccDeptId),
          copyUserIds: parseWorkflowCopyIds(nodeData.copyUserIds, nodeData.ccUserId),
          ...nodeData, // 保留其他可能的字段
        };
        
        return {
          id: nodeId, // 使用uuid或id作为id
          type: node.type,
          position: node.position || { x: 0, y: 0 },
          data: completeData,
        };
      });

      // 还原连接线（source和target需要从uuid映射回id）
      const importedEdges = data.edges.map((edge: any) => {
        // 兼容处理：如果source/target是uuid，映射回id；如果是id，直接使用
        const sourceId = uuidToIdMap.get(edge.source) || edge.source;
        const targetId = uuidToIdMap.get(edge.target) || edge.target;
        
        return {
          id: edge.id,
          source: sourceId,
          target: targetId,
          sourceHandle: edge.sourceHandle,
          targetHandle: edge.targetHandle,
          type: edge.type || 'straight',
          animated: false,
          style: { strokeWidth: 2, stroke: '#000000' },
          markerStart: {
            type: 'arrowclosed',
            color: '#000000',
          },
          data: {
            fixedLength: edge.data?.fixedLength || 150, // 保留导入的 fixedLength 或设置为默认值 150
          },
        };
      });

      // 更新节点和边
      setNodes(importedNodes);
      setEdges(importedEdges);

      // 缓存每个节点的配置（确保缓存包含完整数据）
      importedNodes.forEach((node: Node) => {
        if (node.data) {
          saveNodeCache(node.id, node.data);
        }
      });

      // 如果需要更新历史记录（初始化加载时）
      if (updateHistory) {
        setHistory([{ nodes: importedNodes, edges: importedEdges }]);
        setHistoryIndex(0);
        // 更新初始状态，用于检测未保存的更改
        initialNodesRef.current = JSON.parse(JSON.stringify(importedNodes));
        initialEdgesRef.current = JSON.parse(JSON.stringify(importedEdges));
        setHasUnsavedChanges(false);
        // 短时内不把后续的 onNodesChange/onEdgesChange 视为用户修改，避免保存后再次进入仍弹「未保存」框
        setTimeout(() => {
          isLoadingFromServerOrRestoreRef.current = false;
        }, 200);
      }

      return true;
    } catch (error: any) {
      if (updateHistory) {
        isLoadingFromServerOrRestoreRef.current = false;
      }
      console.error('加载 JSON 失败:', error);
      message.error('JSON 格式错误：' + (error?.message || '解析失败'));
      return false;
    }
  }, [saveNodeCache, setNodes, setEdges, setHistory, setHistoryIndex]);

  // 进入页面时获取工作流详情
  useEffect(() => {
    const fetchWorkflowDetail = async () => {
      if (!workflowId) {
        return;
      }

      setLoadingDetail(true);
      try {
        const detail = await workflowDesignApi.selectWorkflowDesignById(workflowId);
        setWorkflowDetail(detail);
        
        // 检查是否有缓存
        const cachedContent = loadWorkflowCache(workflowId);
        const hasApiContent = detail.content && detail.content.trim().length > 0;
        const hasCacheContent = cachedContent && cachedContent.trim().length > 0;
        
        // 如果接口有内容且缓存也有内容，需要比较内容是否相同
        if (hasApiContent && hasCacheContent) {
          // 比较缓存内容和接口内容是否相同
          const isContentSame = compareJsonContent(cachedContent, detail.content);
          
          if (isContentSame) {
            // 内容相同，说明用户没有修改，直接使用接口内容，不提示
            loadJsonToCanvas(detail.content, true);
          } else {
            // 内容不同，说明用户有修改，弹出确认框
            Modal.confirm({
              title: '检测到未保存的设计',
              content: '检测到您有未保存的设计稿（可能为异常退出），需要恢复继续设计吗？',
              okText: '恢复',
              cancelText: '不恢复',
              onOk: () => {
                // 使用缓存内容恢复；恢复后视为「未保存」以便点击返回时提示保存或放弃
                loadJsonToCanvas(cachedContent, true);
                setHasUnsavedChanges(true);
              },
              onCancel: () => {
                // 使用接口内容，并清除缓存避免下次再提示
                clearWorkflowCache(workflowId);
                loadJsonToCanvas(detail.content, true);
              },
            });
          }
        } else if (hasCacheContent) {
          // 只有缓存，直接使用缓存
          loadJsonToCanvas(cachedContent, true);
        } else if (hasApiContent) {
          // 只有接口内容，使用接口内容
          loadJsonToCanvas(detail.content, true);
        }
      } catch (error: any) {
        console.error('获取工作流详情失败:', error);
        message.error(error?.message || '获取工作流详情失败');
      } finally {
        setLoadingDetail(false);
      }
    };

    fetchWorkflowDetail();
  }, [workflowId, loadJsonToCanvas, loadWorkflowCache, compareJsonContent, clearWorkflowCache]);

  // 拖拽处理
  const onDragStart = (event: React.DragEvent, nodeType: string) => {
    event.dataTransfer.setData('application/reactflow', nodeType);
    event.dataTransfer.effectAllowed = 'move';
  };

  // 验证连接是否有效（允许所有连接）
  const isValidConnection = useCallback((connection: Connection) => {
    // 允许从任意方向连接到任意方向
    if (!connection.source || !connection.target) {
      return false;
    }
    // 不允许节点连接到自身
    if (connection.source === connection.target) {
      return false;
    }
    return true;
  }, []);

  // 修正 Handle 类型的辅助函数
  const fixHandleType = (handleId: string | null | undefined, isSource: boolean): string | undefined => {
    if (!handleId) return undefined;
    
    // 如果 sourceHandle 是 target 类型，转换为对应的 source Handle
    if (isSource && handleId.endsWith('-target')) {
      const position = handleId.replace('-target', '');
      return `${position}-source`;
    }
    
    // 如果 targetHandle 是 source 类型，转换为对应的 target Handle
    if (!isSource && handleId.endsWith('-source')) {
      const position = handleId.replace('-source', '');
      return `${position}-target`;
    }
    
    return handleId;
  };

  // 连接处理
  const onConnect = useCallback(
    (params: Connection) => {
      // 确保连接参数包含 sourceHandle 和 targetHandle
      console.log('连接参数:', params);
      if (!params.source || !params.target) {
        console.warn('连接参数无效:', params);
        return;
      }
      if (params.source === params.target) {
        console.warn('不能连接节点到自身');
        return;
      }
      
      // 修正 Handle 类型
      let sourceHandle = fixHandleType(params.sourceHandle, true);
      let targetHandle = fixHandleType(params.targetHandle, false);
      
      if (sourceHandle !== params.sourceHandle || targetHandle !== params.targetHandle) {
        console.log('修正 Handle 类型:', {
          sourceHandle: `${params.sourceHandle} -> ${sourceHandle}`,
          targetHandle: `${params.targetHandle} -> ${targetHandle}`
        });
      }
      
      setEdges((eds) => {
        // 检查是否已存在相同的连接（只检查 source 和 target）
        const existingEdgeIndex = eds.findIndex(
          (edge) =>
            edge.source === params.source &&
            edge.target === params.target
        );
        
        if (existingEdgeIndex !== -1) {
          console.log('连接已存在，更新连接点:', { 
            old: { sourceHandle: eds[existingEdgeIndex].sourceHandle, targetHandle: eds[existingEdgeIndex].targetHandle },
            new: { sourceHandle, targetHandle }
          });
          // 更新现有连接的 Handle
          const updatedEdges = [...eds];
          updatedEdges[existingEdgeIndex] = {
            ...updatedEdges[existingEdgeIndex],
            sourceHandle,
            targetHandle,
            type: 'straight', // 统一使用直线
            markerStart: {
              type: 'arrowclosed',
              color: '#000000',
            },
            data: {
              fixedLength: 150, // 设置连接线固定长度为150
            },
          };
          // 保存历史
          const newHistory = history.slice(0, historyIndex + 1);
          newHistory.push({ nodes: [...nodes], edges: [...updatedEdges] });
          setHistory(newHistory);
          setHistoryIndex(newHistory.length - 1);
          return updatedEdges;
        }
        
        // 统一使用直线连接
        const edgeId = `edge-${params.source}-${sourceHandle || 'default'}-${params.target}-${targetHandle || 'default'}-${Date.now()}`;
        const newEdge: Edge = {
          id: edgeId,
          source: params.source!,
          target: params.target!,
          sourceHandle: sourceHandle || undefined,
          targetHandle: targetHandle || undefined,
          animated: false,
          style: { strokeWidth: 2, stroke: '#000000' },
          type: 'straight', // 统一使用直线
          markerStart: {
            type: 'arrowclosed',
            color: '#000000',
          },
          data: {
            fixedLength: 150, // 设置连接线固定长度为150
          },
        };
        
        console.log('创建新边，类型:', newEdge.type, '边对象:', 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);
        newHistory.push({ nodes: [...nodes], edges: [...newEdges] });
        setHistory(newHistory);
        setHistoryIndex(newHistory.length - 1);
        return newEdges;
      });
      setHasUnsavedChanges(true);
    },
    [setEdges, history, historyIndex, nodes]
  );

  // 更新连接线（拖拽边的端点重新连接）
  const onEdgeUpdate = useCallback(
    (oldEdge: Edge, newConnection: Connection) => {
      console.log('更新连接线:', { oldEdge, newConnection });
      
      if (!newConnection.source || !newConnection.target) {
        console.warn('新连接参数无效');
        return;
      }
      
      if (newConnection.source === newConnection.target) {
        console.warn('不能连接节点到自身');
        return;
      }
      
      // 修正 Handle 类型
      let sourceHandle = fixHandleType(newConnection.sourceHandle, true);
      let targetHandle = fixHandleType(newConnection.targetHandle, false);
      
      setEdges((eds) => {
        // 找到要更新的边
        const edgeIndex = eds.findIndex((edge) => edge.id === oldEdge.id);
        
        if (edgeIndex === -1) {
          console.warn('未找到要更新的边');
          return eds;
        }
        
        // 检查新连接是否与现有连接冲突（除了当前要更新的边）
        const conflictingEdge = eds.find(
          (edge, index) =>
            index !== edgeIndex &&
            edge.source === newConnection.source &&
            edge.target === newConnection.target
        );
        
        if (conflictingEdge) {
          console.warn('连接已存在，无法更新');
          return eds;
        }
        
        // 更新边的连接点
        const updatedEdges = [...eds];
        const existingEdge = updatedEdges[edgeIndex];
        updatedEdges[edgeIndex] = {
          ...existingEdge,
          source: newConnection.source!,
          target: newConnection.target!,
          sourceHandle: sourceHandle || undefined,
          targetHandle: targetHandle || undefined,
          data: {
            ...(existingEdge.data || {}),
            fixedLength: (existingEdge.data as any)?.fixedLength || 150, // 保留或设置 fixedLength
          },
        };
        
        console.log('连接线已更新:', updatedEdges[edgeIndex]);
        
        // 保存历史
        const newHistory = history.slice(0, historyIndex + 1);
        newHistory.push({ nodes: [...nodes], edges: [...updatedEdges] });
        setHistory(newHistory);
        setHistoryIndex(newHistory.length - 1);
        
        return updatedEdges;
      });
      setHasUnsavedChanges(true);
    },
    [history, historyIndex, nodes]
  );

  /** 统一的“选中节点”处理（用于点击与框选/程序选中等场景） */
  const selectNodeAndSyncRightPanel = useCallback((node: Node) => {
    setSelectedNode(node);
    setSelectedEdge(null); // 选中节点时取消连线选择
    setActiveTabKey('info'); // 切换节点时重置到第一个tab
    // 清除所有边的选中状态
    setEdges((eds) =>
      eds.map((e) => ({
        ...e,
        selected: false,
      }))
    );
    // 加载节点配置
    const nodeData = node.data || {};
    const cache = loadNodeCache(node.id);
    const source = cache || nodeData;
    const config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(source.type));
    
    // 如果是解锁/解除共锁节点且已选择了关联方案节点，则从关联节点获取配置
    let isolationType = source.isolationType || '';
    let isolationPointsData = source.isolationPoints || [];
    let lockPerson = source.lockPerson || '';
    let coLockPersons = source.coLockPersons || [];
    // 优先使用 workerUserId，如果没有则使用 responsible（向后兼容），并确保值是字符串
    let responsible = (source.workerUserId !== undefined && source.workerUserId !== null && source.workerUserId !== '')
      ? String(source.workerUserId)
      : (source.responsible !== undefined && source.responsible !== null && source.responsible !== '')
        ? String(source.responsible)
        : '';
    
    if (
      (isWorkflowUnlockSchemeType(source.type) || isWorkflowUnlockCoLockType(source.type)) &&
      source.isolationNodeUuid
    ) {
      const targetNode = nodes.find(
        (n) =>
          n.id === source.isolationNodeUuid &&
          isWorkflowUnlockParentMatch(source.type, n.data?.type)
      );
      if (targetNode) {
        // 优先从缓存中读取，缓存中没有则从 node.data 读取
        const targetCache = loadNodeCache(targetNode.id);
        const targetSource = targetCache || targetNode.data || {};
        isolationType = targetSource.isolationType || '';
        isolationPointsData = targetSource.isolationPoints || [];
        lockPerson = targetSource.lockPerson || '';
        coLockPersons = targetSource.coLockPersons || [];
        // 优先使用 workerUserId，如果没有则使用 responsible（向后兼容），并确保值是字符串
        responsible = (targetSource.workerUserId !== undefined && targetSource.workerUserId !== null && targetSource.workerUserId !== '')
          ? String(targetSource.workerUserId)
          : (targetSource.responsible !== undefined && targetSource.responsible !== null && targetSource.responsible !== '')
            ? String(targetSource.responsible)
            : '';
      }
    } else if (isWorkflowCoLockType(source.type) && source.isolationNodeUuid) {
      const targetNode = nodes.find(
        (n) =>
          n.id === source.isolationNodeUuid && isWorkflowLockSchemeType(n.data?.type)
      );
      if (targetNode) {
        const targetCache = loadNodeCache(targetNode.id);
        const targetSource = targetCache || targetNode.data || {};
        isolationType = targetSource.isolationType || '';
        isolationPointsData = targetSource.isolationPoints || [];
        lockPerson = targetSource.lockPerson || '';
        responsible = (targetSource.workerUserId !== undefined && targetSource.workerUserId !== null && targetSource.workerUserId !== '')
          ? String(targetSource.workerUserId)
          : (targetSource.responsible !== undefined && targetSource.responsible !== null && targetSource.responsible !== '')
            ? String(targetSource.responsible)
            : '';
        coLockPersons = Array.isArray(source.coLockPersons) ? source.coLockPersons : [];
      }
    }
    
    setNodeConfig({
      nodeName: source.label || config?.label || '',
      nodeIcon: source.icon || source.type || '',
      responsible: responsible,
      remark: source.remark || '',
      formId: source.formId || source.submitForm ? String(source.formId || source.submitForm) : '', // 兼容旧的 submitForm 字段，确保是字符串类型
      isolationType: isolationType,
      isolationPoints: isolationPointsData,
      isolationNode: Array.isArray(source.isolationNode) ? source.isolationNode : (source.isolationNode ? [source.isolationNode] : []),
      isolationNodeUuid: source.isolationNodeUuid || '',
      lockPerson: lockPerson,
      coLockPersons: coLockPersons,
      notificationMethods: source.notificationMethods || {
        sms: false,
        message: false,
        email: false,
        app: false,
      },
      notificationPerson: source.notificationPerson || '',
      notificationTime: source.notificationTime || '',
      smsTemplateCode: source.smsTemplateCode || 'false',
      messageTemplateCode: source.messageTemplateCode || 'false',
      emailTemplateCode: source.emailTemplateCode || 'false',
      appTemplateCode: source.appTemplateCode || 'false',
      copyDeptIds: parseWorkflowCopyIds(source.copyDeptIds, source.ccDeptId),
      copyUserIds: parseWorkflowCopyIds(source.copyUserIds, source.ccUserId),
    });
    
    // 如果是创建作业节点，且当前tab是"提交表单"，自动切换到"节点信息"tab
    if (node.data?.type === 'createJob' && activeTabKey === 'form') {
      setActiveTabKey('info');
    }
  }, [activeTabKey, loadNodeCache, nodes, setEdges]);

  // 节点点击处理
  const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
    selectNodeAndSyncRightPanel(node);
  }, [selectNodeAndSyncRightPanel]);

  // 兜底：当 ReactFlow 选中态变化（例如多次切换、框选、或某些情况下 click 事件未触发）时，同步右侧面板
  const onSelectionChange = useCallback((params: { nodes: Node[]; edges: Edge[] }) => {
    const node = params.nodes?.[0];
    if (node && node.id !== selectedNode?.id) {
      selectNodeAndSyncRightPanel(node);
    }
  }, [selectNodeAndSyncRightPanel, selectedNode?.id]);

  // 兜底：当选中节点变化时，强制把右侧面板切回“节点信息”
  // 解决在“提交表单”Tab 下切换节点偶发不刷新/不切换的问题
  useEffect(() => {
    if (!selectedNode) return;
    setActiveTabKey('info');
  }, [selectedNode?.id]);

  // 画布点击处理（取消选择）
  const onPaneClick = useCallback(() => {
    setSelectedNode(null);
    setSelectedEdge(null);
    // 清除所有边的选中状态
    setEdges((eds) =>
      eds.map((e) => ({
        ...e,
        selected: false,
      }))
    );
  }, [setEdges]);

  // 连线点击处理
  const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
    event.stopPropagation();
    console.log('连线被点击:', edge.id, '当前type:', edge.type);
    setSelectedEdge(edge);
    setSelectedNode(null); // 点击连线时取消节点选择
    // 更新边的选中状态，并确保类型是 straight
    setEdges((eds) => {
      const updatedEdges = eds.map((e) => ({
        ...e,
        type: edge.type || 'straight', // 保持原有类型
        selected: e.id === edge.id,
      }));
      const clickedEdge = updatedEdges.find(e => e.id === edge.id);
      console.log('更新后的边状态:', clickedEdge?.selected, 'type:', clickedEdge?.type);
      return updatedEdges;
    });
  }, [setEdges]);

  // 删除连线
  const handleDeleteEdge = useCallback((edgeId: string) => {
    console.log('删除连线:', edgeId);
    setEdges((eds) => {
      const newEdges = eds.filter((edge) => edge.id !== edgeId);
      console.log('删除后的连线数量:', newEdges.length);
      // 保存历史
      const newHistory = history.slice(0, historyIndex + 1);
      newHistory.push({ nodes: [...nodes], edges: [...newEdges] });
      setHistory(newHistory);
      setHistoryIndex(newHistory.length - 1);
      return newEdges;
    });
    setSelectedEdge(null);
    setHasUnsavedChanges(true);
  }, [history, historyIndex, nodes]);

  // 更新全局删除函数和 edges
  useEffect(() => {
    globalDeleteEdgeFn = handleDeleteEdge;
    globalEdges = edges;
    return () => {
      globalDeleteEdgeFn = null;
      globalEdges = [];
    };
  }, [handleDeleteEdge, edges]);

  // 确保所有边都使用直线类型
  useEffect(() => {
    setEdges((eds) => {
      const updatedEdges = eds.map(edge => {
        // 如果类型不是 straight，统一改为 straight
        if (edge.type !== 'straight') {
          return { 
            ...edge, 
            type: 'straight',
            markerStart: {
              type: 'arrowclosed',
              color: '#000000',
            },
          };
        }
        // 确保所有边都有箭头标记
        if (!edge.markerStart) {
          return {
            ...edge,
            markerStart: {
              type: 'arrowclosed',
              color: '#000000',
            },
          };
        }
        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) => {
    const targetNodeId = nodeId || selectedNode?.id;
    if (!targetNodeId) return;

    Modal.confirm({
      title: '确认删除',
      content: '确定要删除这个节点吗？删除后无法恢复。',
      okText: '确定删除',
      okType: 'danger',
      cancelText: '取消',
      onOk: () => {
        setNodes((nds) => {
          const newNodes = nds.filter((node) => node.id !== targetNodeId);
          // 同时删除相关的边
          setEdges((eds) => {
            const newEdges = eds.filter(
              (edge) => edge.source !== targetNodeId && edge.target !== targetNodeId
            );
            // 保存历史
            const newHistory = history.slice(0, historyIndex + 1);
            newHistory.push({ nodes: [...newNodes], edges: [...newEdges] });
            setHistory(newHistory);
            setHistoryIndex(newHistory.length - 1);
            return newEdges;
          });
          // 保存历史
          const newHistory = history.slice(0, historyIndex + 1);
          newHistory.push({ nodes: [...newNodes], edges: [...edges] });
          setHistory(newHistory);
          setHistoryIndex(newHistory.length - 1);
          return newNodes;
        });
        if (selectedNode?.id === targetNodeId) {
          setSelectedNode(null);
        }
        setHasUnsavedChanges(true);
        toast.success('节点已删除');
      },
    });
  }, [selectedNode, setNodes, setEdges, history, historyIndex, edges]);

  // 键盘事件处理（Delete 键删除节点）
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      // 如果焦点在输入框、文本域或可编辑元素中，不触发删除节点
      const target = event.target as HTMLElement;
      const isInputElement = 
        target.tagName === 'INPUT' || 
        target.tagName === 'TEXTAREA' || 
        target.isContentEditable ||
        target.closest('input') ||
        target.closest('textarea') ||
        target.closest('[contenteditable="true"]');
      
      if (isInputElement) {
        return; // 在输入框中，不处理删除节点
      }
      
      if ((event.key === 'Delete' || event.key === 'Backspace') && selectedNode) {
        event.preventDefault();
        handleDeleteNode();
      }
    };

    window.addEventListener('keydown', handleKeyDown);
    return () => {
      window.removeEventListener('keydown', handleKeyDown);
    };
  }, [selectedNode, handleDeleteNode]);

  // 右键菜单处理
  const onNodeContextMenu = useCallback(
    (event: React.MouseEvent, node: Node) => {
      event.preventDefault();
      setSelectedNode(node);
    },
    []
  );

  // 撤销
  const handleUndo = useCallback(() => {
    if (historyIndex > 0) {
      const prevState = history[historyIndex - 1];
      setNodes(prevState.nodes);
      setEdges(prevState.edges);
      setHistoryIndex(historyIndex - 1);
      setHasUnsavedChanges(true);
    }
  }, [history, historyIndex, setNodes, setEdges]);

  // 重做
  const handleRedo = useCallback(() => {
    if (historyIndex < history.length - 1) {
      const nextState = history[historyIndex + 1];
      setNodes(nextState.nodes);
      setEdges(nextState.edges);
      setHistoryIndex(historyIndex + 1);
      setHasUnsavedChanges(true);
    }
  }, [history, historyIndex, setNodes, setEdges]);

  // 网格步长，与画布 snapGrid 一致
  const GRID_SIZE = 16;
  // 水平线磁吸阈值：放置时与已有节点 Y 相差在此范围内则对齐到该节点水平线
  const HORIZONTAL_SNAP_THRESHOLD = 40;

  // 拖放处理：支持网格对齐 + 与前一个节点同一水平线磁吸
  const onDrop = useCallback(
    (event: React.DragEvent) => {
      event.preventDefault();

      const type = event.dataTransfer.getData('application/reactflow');
      if (!type || !reactFlowInstance) return;

      let position = reactFlowInstance.screenToFlowPosition({
        x: event.clientX,
        y: event.clientY,
      });

      const config = nodeConfigs.find(c => c.type === type);
      const timestamp = Date.now();
      const nodeId = `${type}-${timestamp}`;

      setNodes((nds) => {
        // 网格对齐
        position = {
          x: Math.round(position.x / GRID_SIZE) * GRID_SIZE,
          y: Math.round(position.y / GRID_SIZE) * GRID_SIZE,
        };
        // 水平线磁吸：若有已有节点，且当前 Y 与最后一个节点 Y 接近，则对齐到同一水平线
        if (nds.length > 0) {
          const lastNode = nds[nds.length - 1];
          const refY = lastNode.position.y;
          if (Math.abs(position.y - refY) <= HORIZONTAL_SNAP_THRESHOLD) {
            position.y = refY;
          }
        }

        const nodeNumber = nds.length + 1;
        const newNode: Node = {
          id: nodeId,
          type,
          position: { ...position },
          data: {
            label: config?.label || type,
            type,
            nodeId: String(nodeNumber).padStart(3, '0'),
            smsTemplateCode: 'false',
            messageTemplateCode: 'false',
            emailTemplateCode: 'false',
            appTemplateCode: 'false',
          },
        };

        const newNodes = nds.concat(newNode);
        // 保存历史
        const newHistory = history.slice(0, historyIndex + 1);
        newHistory.push({ nodes: [...newNodes], edges: [...edges] });
        setHistory(newHistory);
        setHistoryIndex(newHistory.length - 1);
        return newNodes;
      });
      setHasUnsavedChanges(true);
    },
    [reactFlowInstance, setNodes, history, historyIndex, edges]
  );

  const onDragOver = useCallback((event: React.DragEvent) => {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'move';
  }, []);

  /** 是否为真正会影响设计内容的变更（排除纯选中态/尺寸测量等 UI 变更） */
  const isMeaningfulRfChange = useCallback((changes: Array<{ type?: string; dragging?: boolean }>) => {
    return changes.some((ch) => {
      // ReactFlow 的 select/dimensions 多为 UI 变化，不应标记“未保存”
      if (ch.type === 'select' || ch.type === 'dimensions') return false;
      // position 只有在拖拽时才视为修改（避免点击选中触发的非拖拽 position 事件）
      if (ch.type === 'position') return Boolean(ch.dragging);
      return true;
    });
  }, []);

  // 拖拽节点时的水平线磁吸：与画布上其他节点 Y 接近时对齐到同一水平线；节点变更时标记为已修改
  const onNodesChange = useCallback(
    (changes: NodeChange[]) => {
      const modified = changes.map((ch) => {
        if (ch.type === 'position' && ch.position != null && ch.dragging) {
          const otherNodes = nodes.filter((n) => n.id !== ch.id);
          let newY = ch.position!.y;
          for (const n of otherNodes) {
            if (Math.abs(ch.position!.y - n.position.y) <= HORIZONTAL_SNAP_THRESHOLD) {
              newY = n.position.y;
              break;
            }
          }
          return { ...ch, position: { ...ch.position!, y: newY } };
        }
        return ch;
      });
      if (!isLoadingFromServerOrRestoreRef.current && isMeaningfulRfChange(modified as any)) {
        setHasUnsavedChanges(true);
      }
      onNodesChangeBase(modified);
    },
    [nodes, onNodesChangeBase, isMeaningfulRfChange]
  );

  // 边变更时标记为已修改（仅用户操作会触发 onEdgesChange）；加载/恢复阶段不标记
  const onEdgesChangeWithDirty = useCallback(
    (changes: Parameters<typeof onEdgesChange>[0]) => {
      if (!isLoadingFromServerOrRestoreRef.current && isMeaningfulRfChange(changes as any)) {
        setHasUnsavedChanges(true);
      }
      onEdgesChange(changes);
    },
    [onEdgesChange, isMeaningfulRfChange]
  );

  // 更新节点配置
  const updateNodeConfig = useCallback(() => {
    if (!selectedNode) return;

    setNodes((nds) => {
      const updatedNodes = nds.map((node) => {
        if (node.id === selectedNode.id) {
          const { isolationMethod, ...restNodeData } = node.data || {};
          const updatedNode = {
            ...node,
            data: {
              ...restNodeData,
              label: nodeConfig.nodeName,
              type: nodeConfig.nodeIcon || node.data?.type, // 更新节点类型（图标）
              icon: nodeConfig.nodeIcon, // 保存图标名称
              responsible: nodeConfig.responsible,
              remark: nodeConfig.remark,
              formId: nodeConfig.formId ? String(nodeConfig.formId) : '',
              isolationType: nodeConfig.isolationType,
              isolationPoints: nodeConfig.isolationPoints,
              isolationNode: nodeConfig.isolationNode,
              isolationNodeUuid: nodeConfig.isolationNodeUuid,
              lockPerson: nodeConfig.lockPerson,
              coLockPersons: nodeConfig.coLockPersons,
              notificationMethods: nodeConfig.notificationMethods,
              notificationPerson: nodeConfig.notificationPerson,
              notificationTime: nodeConfig.notificationTime,
              smsTemplateCode: nodeConfig.smsTemplateCode,
              messageTemplateCode: nodeConfig.messageTemplateCode,
              emailTemplateCode: nodeConfig.emailTemplateCode,
              appTemplateCode: nodeConfig.appTemplateCode,
              copyDeptIds: nodeConfig.copyDeptIds,
              copyUserIds: nodeConfig.copyUserIds,
            },
          };
          // 缓存当前节点配置
          saveNodeCache(node.id, updatedNode.data);
          // 更新 selectedNode 的引用，以便顶部标题能显示最新值
          setSelectedNode(updatedNode);
          return updatedNode;
        }
        return node;
      });
      // 保存历史
      const newHistory = history.slice(0, historyIndex + 1);
      newHistory.push({ nodes: [...updatedNodes], edges: [...edges] });
      setHistory(newHistory);
      setHistoryIndex(newHistory.length - 1);
      return updatedNodes;
    });
    setHasUnsavedChanges(true);
    toast.success('节点配置已保存');
  }, [selectedNode, nodeConfig, setNodes, setSelectedNode, history, historyIndex, edges]);

  // 保存流程（不跳转页面）
  const handleSaveWithoutNavigate = async () => {
    try {
      // 构建导出数据（与导出JSON逻辑相同）
      const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
      const nodeIdMap = new Map<string, string>();
      nodes.forEach(n => {
        nodeIdMap.set(n.id, n.id);
      });

      nodes.forEach(n => {
        const uuid = nodeIdMap.get(n.id) || n.id;
        adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
      });
      edges.forEach(e => {
        const sourceUuid = nodeIdMap.get(e.source) || e.source;
        const targetUuid = nodeIdMap.get(e.target) || e.target;
        if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
        if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
        // 调换字段：原来childrenUuid的地方现在用parentUuid，原来parentUuid的地方现在用childrenUuid
        adjacency[sourceUuid].parentUuid.push(targetUuid);
        adjacency[targetUuid].childrenUuid.push(sourceUuid);
      });

      const exportData = {
        nodeCount: nodes.length,
        edgeCount: edges.length,
        adjacency,
        nodes: nodes.map(n => {
          const cachedData = loadNodeCache(n.id);
          const mergedData = cachedData 
            ? { ...n.data, ...cachedData }
            : n.data;
          
          // 将 responsible 转换为 workerUserId，并确保值是字符串
          // 同时提取四个模板代码字段到顶层
          const { responsible, smsTemplateCode, messageTemplateCode, emailTemplateCode, appTemplateCode, ...restData } = mergedData;
          const processedData = sanitizeCoLockFamilyNodeDataForExport(
            {
              ...restData,
              workerUserId:
                responsible !== undefined && responsible !== null && responsible !== ''
                  ? String(responsible)
                  : '',
              copyDeptIds: toWorkflowCopyIdsCommaString((restData as any).copyDeptIds),
              copyUserIds: toWorkflowCopyIdsCommaString((restData as any).copyUserIds),
            },
            n.type
          );

          const nodeObj: any = {
            uuid: n.id,
            type: n.type,
            position: n.position,
            nodeName: processedData.label || '',
            nodeIcon: processedData.icon || processedData.type || n.type || '',
            smsTemplateCode: smsTemplateCode || 'false',
            messageTemplateCode: messageTemplateCode || 'false',
            emailTemplateCode: emailTemplateCode || 'false',
            appTemplateCode: appTemplateCode || 'false',
            data: processedData,
          };
          return nodeObj;
        }),
        edges: edges.map(e => {
          return {
            id: e.id,
            source: nodeIdMap.get(e.source) || e.source,
            target: nodeIdMap.get(e.target) || e.target,
            sourceHandle: e.sourceHandle,
            targetHandle: e.targetHandle,
            type: e.type,
          };
        }),
      };

      // 将整个 JSON 作为 content 字段传递
      const content = JSON.stringify(exportData, null, 2);
      
      // 必须有流程ID才能保存（因为新建时只传了name，设计完成后必须调用更新接口）
      if (!workflowId) {
        message.warning('无法保存：缺少流程ID，请先创建流程');
        return;
      }

      // 从暂存的详情中获取 name 和 description
      const name = workflowDetail?.name || '';
      const description = workflowDetail?.description || '';

      // 确保所有节点都包含模板代码字段，检查并修复缺失的字段
      exportData.nodes.forEach((node: any) => {
        if (node.smsTemplateCode === undefined) node.smsTemplateCode = 'false';
        if (node.messageTemplateCode === undefined) node.messageTemplateCode = 'false';
        if (node.emailTemplateCode === undefined) node.emailTemplateCode = 'false';
        if (node.appTemplateCode === undefined) node.appTemplateCode = 'false';
      });
      
      // 重新序列化 content，确保所有节点都包含模板代码字段
      const finalContent = JSON.stringify(exportData, null, 2);

      // 调用更新接口，传递 id、content、name、description
      await workflowDesignApi.updateWorkflowDesign({
        id: workflowId,
        content: finalContent,
        name: name,
        description: description,
      });

      message.success('流程保存成功');
      
      // 保存成功后清除缓存
      clearWorkflowCache(workflowId);
      
      // 更新初始状态
      initialNodesRef.current = JSON.parse(JSON.stringify(nodes));
      initialEdgesRef.current = JSON.parse(JSON.stringify(edges));
      setHasUnsavedChanges(false);
    } catch (error: any) {
      console.error('保存流程失败:', error);
      message.error(error?.message || '流程保存失败');
    }
  };

  // 保存流程
  const handleSave = async () => {
    try {
      // 构建导出数据（与导出JSON逻辑相同）
      const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
      const nodeIdMap = new Map<string, string>();
      nodes.forEach(n => {
        nodeIdMap.set(n.id, n.id);
      });

      nodes.forEach(n => {
        const uuid = nodeIdMap.get(n.id) || n.id;
        adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
      });
      edges.forEach(e => {
        const sourceUuid = nodeIdMap.get(e.source) || e.source;
        const targetUuid = nodeIdMap.get(e.target) || e.target;
        if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
        if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
        // 调换字段：原来childrenUuid的地方现在用parentUuid，原来parentUuid的地方现在用childrenUuid
        adjacency[sourceUuid].parentUuid.push(targetUuid);
        adjacency[targetUuid].childrenUuid.push(sourceUuid);
      });

      const exportData = {
        nodeCount: nodes.length,
        edgeCount: edges.length,
        adjacency,
        nodes: nodes.map(n => {
          const cachedData = loadNodeCache(n.id);
          const mergedData = cachedData 
            ? { ...n.data, ...cachedData }
            : n.data;
          
          // 将 responsible 转换为 workerUserId，并确保值是字符串
          // 同时提取四个模板代码字段到顶层
          const { responsible, smsTemplateCode, messageTemplateCode, emailTemplateCode, appTemplateCode, ...restData } = mergedData;
          const processedData = sanitizeCoLockFamilyNodeDataForExport(
            {
              ...restData,
              workerUserId:
                responsible !== undefined && responsible !== null && responsible !== ''
                  ? String(responsible)
                  : '',
            },
            n.type
          );

          const nodeObj: any = {
            uuid: n.id,
            type: n.type,
            position: n.position,
            nodeName: processedData.label || '',
            nodeIcon: processedData.icon || processedData.type || n.type || '',
            smsTemplateCode: smsTemplateCode || 'false',
            messageTemplateCode: messageTemplateCode || 'false',
            emailTemplateCode: emailTemplateCode || 'false',
            appTemplateCode: appTemplateCode || 'false',
            data: processedData,
          };
          return nodeObj;
        }),
        edges: edges.map(e => {
          return {
            id: e.id,
            source: nodeIdMap.get(e.source) || e.source,
            target: nodeIdMap.get(e.target) || e.target,
            sourceHandle: e.sourceHandle,
            targetHandle: e.targetHandle,
            type: e.type,
          };
        }),
      };

      // 将整个 JSON 作为 content 字段传递
      const content = JSON.stringify(exportData, null, 2);
      
      // 必须有流程ID才能保存（因为新建时只传了name，设计完成后必须调用更新接口）
      if (!workflowId) {
        message.warning('无法保存：缺少流程ID，请先创建流程');
        return;
      }

      // 从暂存的详情中获取 name 和 description
      const name = workflowDetail?.name || '';
      const description = workflowDetail?.description || '';

      // 确保所有节点都包含模板代码字段，检查并修复缺失的字段
      exportData.nodes.forEach((node: any) => {
        if (node.smsTemplateCode === undefined) node.smsTemplateCode = 'false';
        if (node.messageTemplateCode === undefined) node.messageTemplateCode = 'false';
        if (node.emailTemplateCode === undefined) node.emailTemplateCode = 'false';
        if (node.appTemplateCode === undefined) node.appTemplateCode = 'false';
      });
      
      // 重新序列化 content，确保所有节点都包含模板代码字段
      const finalContent = JSON.stringify(exportData, null, 2);

      // 调用更新接口，传递 id、content、name、description
      await workflowDesignApi.updateWorkflowDesign({
        id: workflowId,
        content: finalContent,
        name: name,
        description: description,
      });

      // 保存成功后清除缓存
      clearWorkflowCache(workflowId);
      
      // 更新初始状态
      initialNodesRef.current = JSON.parse(JSON.stringify(nodes));
      initialEdgesRef.current = JSON.parse(JSON.stringify(edges));
      setHasUnsavedChanges(false);
      
      // 显示成功提示
      message.success('流程保存成功');
      
      // 等待提示显示后再跳转（给用户看到成功提示的时间）
      setTimeout(() => {
        // 保存成功后返回流程设计列表页面（与返回按钮逻辑相同）
        backToProcessTemplateMenu();
      }, 500);
    } catch (error: any) {
      console.error('保存流程失败:', error);
      message.error(error?.message || '流程保存失败');
    }
  };

  // 返回（仅在有未保存修改时弹框：保存 或 放弃并返回）
  // 同时用 checkUnsavedChanges() 实时检测，避免「导入 JSON 后只改了节点配置/未点保存」等路径未设 hasUnsavedChanges 导致不提示
  const handleBack = () => {
    const hasChange = hasUnsavedChanges || checkUnsavedChanges();
    if (hasChange) {
      Modal.confirm({
        title: '未保存的更改',
        content: '检测到您有未保存的更改，请先保存后再返回，或放弃更改并返回。',
        okText: '保存',
        cancelText: '放弃并返回',
        maskClosable: false,
        closable: false,
        onOk: async () => {
          // 调用保存函数
          try {
            // 构建导出数据（与handleSave中的逻辑相同）
            const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
            const nodeIdMap = new Map<string, string>();
            nodes.forEach(n => {
              nodeIdMap.set(n.id, n.id);
            });

            nodes.forEach(n => {
              const uuid = nodeIdMap.get(n.id) || n.id;
              adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
            });
            edges.forEach(e => {
              const sourceUuid = nodeIdMap.get(e.source) || e.source;
              const targetUuid = nodeIdMap.get(e.target) || e.target;
              if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
              if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
              adjacency[sourceUuid].parentUuid.push(targetUuid);
              adjacency[targetUuid].childrenUuid.push(sourceUuid);
            });

            const exportData = {
              nodeCount: nodes.length,
              edgeCount: edges.length,
              adjacency,
              nodes: nodes.map(n => {
                const cachedData = loadNodeCache(n.id);
                const mergedData = cachedData 
                  ? { ...n.data, ...cachedData }
                  : n.data;
                
                // 将 responsible 转换为 workerUserId，并确保值是字符串
                // 同时提取四个模板代码字段到顶层
                const { responsible, smsTemplateCode, messageTemplateCode, emailTemplateCode, appTemplateCode, ...restData } = mergedData;
                const processedData = sanitizeCoLockFamilyNodeDataForExport(
                  {
                    ...restData,
                    workerUserId:
                      responsible !== undefined && responsible !== null && responsible !== ''
                        ? String(responsible)
                        : '',
                    copyDeptIds: toWorkflowCopyIdsCommaString((restData as any).copyDeptIds),
                    copyUserIds: toWorkflowCopyIdsCommaString((restData as any).copyUserIds),
                  },
                  n.type
                );

                const nodeObj: any = {
                  uuid: n.id,
                  type: n.type,
                  position: n.position,
                  nodeName: processedData.label || '',
                  nodeIcon: processedData.icon || processedData.type || n.type || '',
                  smsTemplateCode: smsTemplateCode || 'false',
                  messageTemplateCode: messageTemplateCode || 'false',
                  emailTemplateCode: emailTemplateCode || 'false',
                  appTemplateCode: appTemplateCode || 'false',
                  data: processedData,
                };
                return nodeObj;
              }),
              edges: edges.map(e => {
                return {
                  id: e.id,
                  source: nodeIdMap.get(e.source) || e.source,
                  target: nodeIdMap.get(e.target) || e.target,
                  sourceHandle: e.sourceHandle,
                  targetHandle: e.targetHandle,
                  type: e.type,
                };
              }),
            };

            // 确保所有节点都包含模板代码字段，检查并修复缺失的字段
            exportData.nodes.forEach((node: any) => {
              if (node.smsTemplateCode === undefined) node.smsTemplateCode = 'false';
              if (node.messageTemplateCode === undefined) node.messageTemplateCode = 'false';
              if (node.emailTemplateCode === undefined) node.emailTemplateCode = 'false';
              if (node.appTemplateCode === undefined) node.appTemplateCode = 'false';
            });
            
            const content = JSON.stringify(exportData, null, 2);
            
            if (!workflowId) {
              message.warning('无法保存：缺少流程ID，请先创建流程');
              return;
            }

            const name = workflowDetail?.name || '';
            const description = workflowDetail?.description || '';

            await workflowDesignApi.updateWorkflowDesign({
              id: workflowId,
              content: content,
              name: name,
              description: description,
            });

            clearWorkflowCache(workflowId);
            message.success('流程保存成功');
            
            // 更新初始状态
            initialNodesRef.current = JSON.parse(JSON.stringify(nodes));
            initialEdgesRef.current = JSON.parse(JSON.stringify(edges));
            setHasUnsavedChanges(false);
            
            // 保存成功后返回
            setTimeout(() => {
              backToProcessTemplateMenu();
            }, 500);
          } catch (error: any) {
            console.error('保存流程失败:', error);
            message.error(error?.message || '流程保存失败');
          }
        },
        onCancel: () => {
          // 放弃并返回：清除缓存后跳转到流程模板列表
          clearWorkflowCache(workflowId);
          backToProcessTemplateMenu();
        },
      });
    } else {
      // 没有未保存的更改，直接返回
      backToProcessTemplateMenu();
    }
  };

  // 缩放控制
  const handleZoomIn = () => {
    if (reactFlowInstance) {
      reactFlowInstance.zoomIn();
    }
  };

  const handleZoomOut = () => {
    if (reactFlowInstance) {
      reactFlowInstance.zoomOut();
    }
  };

  // 导出流程 JSON
  const handleExportJson = useCallback(() => {
    // 创建节点ID到UUID的映射（导出时id改为uuid，但值保持不变）
    const nodeIdMap = new Map<string, string>();
    nodes.forEach(n => {
      nodeIdMap.set(n.id, n.id); // 值保持不变，只是字段名改为uuid
    });

    // adjacency使用uuid作为key
    const adjacency: Record<string, { parentUuid: string[]; childrenUuid: string[] }> = {};
    nodes.forEach(n => {
      const uuid = nodeIdMap.get(n.id) || n.id;
      adjacency[uuid] = adjacency[uuid] || { parentUuid: [], childrenUuid: [] };
    });
    edges.forEach(e => {
      const sourceUuid = nodeIdMap.get(e.source) || e.source;
      const targetUuid = nodeIdMap.get(e.target) || e.target;
      if (!adjacency[sourceUuid]) adjacency[sourceUuid] = { parentUuid: [], childrenUuid: [] };
      if (!adjacency[targetUuid]) adjacency[targetUuid] = { parentUuid: [], childrenUuid: [] };
      // 调换字段：原来childrenUuid的地方现在用parentUuid，原来parentUuid的地方现在用childrenUuid
      adjacency[sourceUuid].parentUuid.push(targetUuid);
      adjacency[targetUuid].childrenUuid.push(sourceUuid);
    });

    const exportData = {
      nodeCount: nodes.length,
      edgeCount: edges.length,
      adjacency,
      nodes: nodes.map(n => {
        // 从缓存中读取最新配置
        const cachedData = loadNodeCache(n.id);
        // 合并缓存数据和节点数据，缓存数据优先级更高
        const mergedData = cachedData 
          ? { ...n.data, ...cachedData }
          : n.data;
        
        // 将 responsible 转换为 workerUserId，并确保值是字符串
        // 同时提取四个模板代码字段到顶层
        const { responsible, smsTemplateCode, messageTemplateCode, emailTemplateCode, appTemplateCode, ...restData } = mergedData;
        const processedData = sanitizeCoLockFamilyNodeDataForExport(
          {
            ...restData,
            workerUserId:
              responsible !== undefined && responsible !== null && responsible !== ''
                ? String(responsible)
                : '',
            copyDeptIds: toWorkflowCopyIdsCommaString((restData as any).copyDeptIds),
            copyUserIds: toWorkflowCopyIdsCommaString((restData as any).copyUserIds),
          },
          n.type
        );

        // 导出时将id字段改为uuid，但值保持不变
        // 将 nodeName 和 nodeIcon 从 data 中提取到顶层，方便后端识别，但保留 data 中的原始字段
        const nodeObj: any = {
          uuid: n.id, // 字段名改为uuid，值保持不变
          type: n.type,
          position: n.position,
          nodeName: processedData.label || '', // 从 data.label 提取到顶层作为 nodeName
          nodeIcon: processedData.icon || processedData.type || n.type || '', // 从 data.icon 或 data.type 提取到顶层作为 nodeIcon
          smsTemplateCode: smsTemplateCode || 'false',
          messageTemplateCode: messageTemplateCode || 'false',
          emailTemplateCode: emailTemplateCode || 'false',
          appTemplateCode: appTemplateCode || 'false',
          data: processedData, // 保留完整的 data，包括 label 和 icon，但 responsible 已转换为 workerUserId
        };
        return nodeObj;
      }),
      edges: edges.map(e => {
        // edges的source和target也需要改为对应的uuid（值相同）
        return {
          id: e.id,
          source: nodeIdMap.get(e.source) || e.source, // 使用映射后的值
          target: nodeIdMap.get(e.target) || e.target, // 使用映射后的值
          sourceHandle: e.sourceHandle,
          targetHandle: e.targetHandle,
          type: e.type,
        };
      }),
    };
    setExportContent(JSON.stringify(exportData, null, 2));
    setExportVisible(true);
  }, [nodes, edges, loadNodeCache]);

  // 复制JSON到剪贴板
  const handleCopyJson = useCallback(async () => {
    console.log('复制按钮被点击，exportContent长度:', exportContent?.length);
    
    if (!exportContent || exportContent.trim() === '') {
      console.warn('exportContent为空');
      message.warning('没有可复制的内容');
      return;
    }

    try {
      // 优先使用现代 Clipboard API
      if (navigator.clipboard && navigator.clipboard.writeText) {
        console.log('使用 Clipboard API');
        await navigator.clipboard.writeText(exportContent);
        console.log('复制成功');
        message.success('复制成功！JSON已复制到剪贴板');
        return;
      }
      throw new Error('Clipboard API not available');
    } catch (error) {
      console.log('Clipboard API失败，使用降级方案:', error);
      // 降级方案：使用传统方法
      try {
        const textArea = document.createElement('textarea');
        textArea.value = exportContent;
        textArea.style.position = 'fixed';
        textArea.style.top = '0';
        textArea.style.left = '0';
        textArea.style.width = '2em';
        textArea.style.height = '2em';
        textArea.style.padding = '0';
        textArea.style.border = 'none';
        textArea.style.outline = 'none';
        textArea.style.boxShadow = 'none';
        textArea.style.background = 'transparent';
        textArea.style.opacity = '0';
        textArea.readOnly = true;
        textArea.setAttribute('contenteditable', 'true');
        
        document.body.appendChild(textArea);
        textArea.focus();
        textArea.select();
        textArea.setSelectionRange(0, exportContent.length);
        
        const successful = document.execCommand('copy');
        document.body.removeChild(textArea);
        
        if (successful) {
          console.log('传统方法复制成功');
          message.success('复制成功！JSON已复制到剪贴板');
        } else {
          console.warn('传统方法复制失败');
          message.error('复制失败，请手动复制');
        }
      } catch (err) {
        console.error('复制失败:', err);
        message.error('复制失败，请手动复制');
      }
    }
  }, [exportContent]);

  // 导入流程 JSON
  const handleImportJson = useCallback(() => {
    try {
      const data = JSON.parse(importJson);
      
      if (!data.nodes || !Array.isArray(data.nodes)) {
        toast.error('JSON 格式错误：缺少 nodes 数组');
        return;
      }
      
      if (!data.edges || !Array.isArray(data.edges)) {
        toast.error('JSON 格式错误：缺少 edges 数组');
        return;
      }

      // 创建uuid到id的映射（导入时uuid改回id）
      const uuidToIdMap = new Map<string, string>();
      data.nodes.forEach((node: any) => {
        // 兼容处理：支持uuid字段或id字段
        const nodeId = node.uuid || node.id;
        uuidToIdMap.set(nodeId, nodeId); // 值保持不变
      });

      // 还原节点，确保 data 包含所有配置字段
      const importedNodes = data.nodes.map((node: any) => {
        const nodeData = node.data || {};
        // 兼容处理：支持uuid字段或id字段
        const nodeId = node.uuid || node.id;
        
        // 如果顶层有 nodeName 和 nodeIcon，将它们还原到 data 中
        const topLevelLabel = node.nodeName || '';
        const topLevelIcon = node.nodeIcon || '';
        
        // 从顶层读取四个模板代码字段（与 uuid 同级）
        const topLevelSmsTemplateCode = node.smsTemplateCode;
        const topLevelMessageTemplateCode = node.messageTemplateCode;
        const topLevelEmailTemplateCode = node.emailTemplateCode;
        const topLevelAppTemplateCode = node.appTemplateCode;
        
        // 兼容处理：优先使用 workerUserId，如果没有则使用 responsible（向后兼容），并确保值是字符串
        const workerUserIdValue = nodeData.workerUserId !== undefined && nodeData.workerUserId !== null && nodeData.workerUserId !== ''
          ? String(nodeData.workerUserId)
          : (nodeData.responsible !== undefined && nodeData.responsible !== null && nodeData.responsible !== ''
              ? String(nodeData.responsible)
              : '');
        
        // 确保所有字段都存在，使用默认值填充缺失的字段
        const completeData = {
          label: topLevelLabel || nodeData.label || '', // 优先使用顶层的 nodeName
          type: nodeData.type || node.type,
          nodeId: nodeData.nodeId || '',
          icon: topLevelIcon || nodeData.icon || nodeData.type || node.type, // 优先使用顶层的 nodeIcon
          responsible: workerUserIdValue, // 内部仍使用 responsible 字段名，但值来自 workerUserId 或 responsible
          workerUserId: workerUserIdValue, // 同时保存 workerUserId 字段，确保数据一致性
          remark: nodeData.remark || '',
          formId: nodeData.formId || nodeData.submitForm ? String(nodeData.formId || nodeData.submitForm) : '', // 兼容旧的 submitForm 字段，确保是字符串类型
          isolationType: nodeData.isolationType || '',
          isolationPoints: Array.isArray(nodeData.isolationPoints) ? nodeData.isolationPoints : [],
          isolationNode: Array.isArray(nodeData.isolationNode) ? nodeData.isolationNode : [],
          isolationNodeUuid: nodeData.isolationNodeUuid || '',
          lockPerson: nodeData.lockPerson || '',
          coLockPersons: Array.isArray(nodeData.coLockPersons) ? nodeData.coLockPersons : [],
          notificationMethods: nodeData.notificationMethods || {
            sms: false,
            message: false,
            email: false,
            app: false,
          },
          notificationPerson: nodeData.notificationPerson || '',
          notificationTime: nodeData.notificationTime || '',
          smsTemplateCode: topLevelSmsTemplateCode !== undefined ? topLevelSmsTemplateCode : (nodeData.smsTemplateCode || 'false'),
          messageTemplateCode: topLevelMessageTemplateCode !== undefined ? topLevelMessageTemplateCode : (nodeData.messageTemplateCode || 'false'),
          emailTemplateCode: topLevelEmailTemplateCode !== undefined ? topLevelEmailTemplateCode : (nodeData.emailTemplateCode || 'false'),
          appTemplateCode: topLevelAppTemplateCode !== undefined ? topLevelAppTemplateCode : (nodeData.appTemplateCode || 'false'),
          ...nodeData, // 保留其他可能的字段
        };
        
        return {
          id: nodeId, // 使用uuid或id作为id
          type: node.type,
          position: node.position || { x: 0, y: 0 },
          data: completeData,
        };
      });

      // 还原连接线（source和target需要从uuid映射回id）
      const importedEdges = data.edges.map((edge: any) => {
        // 兼容处理：如果source/target是uuid，映射回id；如果是id，直接使用
        const sourceId = uuidToIdMap.get(edge.source) || edge.source;
        const targetId = uuidToIdMap.get(edge.target) || edge.target;
        
        return {
          id: edge.id,
          source: sourceId,
          target: targetId,
          sourceHandle: edge.sourceHandle,
          targetHandle: edge.targetHandle,
          type: edge.type || 'straight',
          animated: false,
          style: { strokeWidth: 2, stroke: '#000000' },
          markerStart: {
            type: 'arrowclosed',
            color: '#000000',
          },
          data: {
            fixedLength: edge.data?.fixedLength || 150, // 保留导入的 fixedLength 或设置为默认值 150
          },
        };
      });

      // 更新节点和边
      setNodes(importedNodes);
      setEdges(importedEdges);

      // 缓存每个节点的配置（确保缓存包含完整数据）
      importedNodes.forEach((node: Node) => {
        if (node.data) {
          saveNodeCache(node.id, node.data);
        }
      });

      // 如果当前有选中的节点，重新加载其配置
      if (selectedNode) {
        const updatedNode = importedNodes.find(n => n.id === selectedNode.id);
        if (updatedNode) {
          const cache = loadNodeCache(updatedNode.id);
          const source = cache || updatedNode.data || {};
          const config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(source.type));
          setNodeConfig({
            nodeName: source.label || config?.label || '',
            nodeIcon: source.icon || source.type || '',
            responsible: source.responsible || '',
            remark: source.remark || '',
            formId: source.formId || source.submitForm ? String(source.formId || source.submitForm) : '', // 兼容旧的 submitForm 字段，确保是字符串类型
            isolationType: source.isolationType || '',
            isolationPoints: source.isolationPoints || [],
            isolationNode: Array.isArray(source.isolationNode) ? source.isolationNode : (source.isolationNode ? [source.isolationNode] : []),
            isolationNodeUuid: source.isolationNodeUuid || '',
            lockPerson: source.lockPerson || '',
            coLockPersons: source.coLockPersons || [],
            notificationMethods: source.notificationMethods || {
              sms: false,
              message: false,
              email: false,
              app: false,
            },
            notificationPerson: source.notificationPerson || '',
            notificationTime: source.notificationTime || '',
            smsTemplateCode: source.smsTemplateCode || 'false',
            messageTemplateCode: source.messageTemplateCode || 'false',
            emailTemplateCode: source.emailTemplateCode || 'false',
            appTemplateCode: source.appTemplateCode || 'false',
          });
          setSelectedNode(updatedNode);
        } else {
          // 如果选中的节点不在导入的数据中，清除选择
          setSelectedNode(null);
        }
      }

      // 保存历史
      const newHistory = [{ nodes: importedNodes, edges: importedEdges }];
      setHistory(newHistory);
      setHistoryIndex(0);

      // 更新初始状态，用于检测未保存的更改
      initialNodesRef.current = JSON.parse(JSON.stringify(importedNodes));
      initialEdgesRef.current = JSON.parse(JSON.stringify(importedEdges));
      setHasUnsavedChanges(false);

      toast.success('流程导入成功');
      setImportVisible(false);
      setImportJson('');
    } catch (error: any) {
      toast.error(`导入失败：${error.message || 'JSON 格式错误'}`);
    }
  }, [importJson, setNodes, setEdges, saveNodeCache, loadNodeCache, selectedNode, nodeConfigs, setNodeConfig, setSelectedNode, history, setHistory, setHistoryIndex]);

  // 获取节点描述
  const getNodeDescription = (type: string) => {
    const descriptions: { [key: string]: string } = {
      createJob: '该节点为作业创建人员创建作业录入信息开始节点。',
      confirm: '该节点为作业确认，为"上一节点"操作分配确认模式及确认人员权限',
      review: '该节点为作业审核，为"上一节点"操作分配审核模式及审核人员权限',
      inputInfo: '该节点为作业录入提交，可提交信息或图片，主要为"信息确认"',
      lock: '该节点为作业隔离类型选择，主要包括盲板，上锁挂牌，拆除等。',
      unlock: '该节点在解锁时与所选上锁节点保持隔离方式等信息一致。',
      coLock: '该节点需选择流程中的上锁任务并配置共锁人；隔离信息随所选上锁任务一致。',
      unlockCoLock: '该节点在解除共锁时与所选共锁节点保持隔离方式等信息一致。',
      isolation: '该节点为作业隔离类型选择，主要包括盲板，上锁挂牌，拆除等。',
      releaseIsolation: '该节点为作业隔离类型选择，主要包括盲板，上锁挂牌，拆除等。',
      returnLock: '该节点为还锁操作，归还钥匙，确认隔离操作完成',
      complete: '该节点为流程结束点',
    };
    return descriptions[type] || '该节点的功能描述。';
  };

  const selectedDataType = selectedNode?.data?.type || '';
  const isUnlockDesignerReadOnly =
    isWorkflowUnlockSchemeType(selectedDataType) || isWorkflowUnlockCoLockType(selectedDataType);
  const isSchemeDesignerPanel = isWorkflowIsolationSchemePanelType(selectedDataType);

  return (
    <div className="h-screen w-screen flex flex-col bg-gray-50">
      {/* 顶部工具栏 */}
      <div className="h-14 bg-white border-b border-gray-200 flex items-center justify-between px-4 shadow-sm z-10">
        <div className="flex items-center gap-2">
          <span className="text-lg font-bold text-blue-600 mr-2">流程设计器</span>
          <div className="h-5 w-px bg-gray-300 mx-1" />
          {workflowDetail?.name && (
            <>
              <span className="text-gray-700 font-medium mr-4" style={{marginRight:'10px',marginLeft:'50px',fontSize:'17px',fontWeight:'bold'}}>{workflowDetail.name}</span>
              <div className="h-5 w-px bg-gray-300 mx-2" />
            </>
          )}
        <Button
          type="primary"
          className="flex items-center gap-1.5"
          onClick={handleSave}
        >
          <SaveOutlined />
          <span>保存</span>
        </Button>
          <div className="h-5 w-px bg-gray-300 mx-1" />
          <Button 
            size="small" 
            className="flex items-center gap-1.5"
            onClick={handleBack}
          >
            <ArrowLeftOutlined />
            <span>返回</span>
          </Button>
          <div className="h-5 w-px bg-gray-300 mx-1" />
          <Button
            size="small"
            className="flex items-center gap-1.5"
            onClick={handleUndo}
            disabled={historyIndex <= 0}
          >
            <UndoOutlined />
            <span>撤销</span>
          </Button>
          <Button
            size="small"
            className="flex items-center gap-1.5"
            onClick={handleRedo}
            disabled={historyIndex >= history.length - 1}
          >
            <RedoOutlined />
            <span>重做</span>
          </Button>
          <Button
            size="small"
            className="flex items-center gap-1.5"
            onClick={handleExportJson}
          >
            <UploadOutlined />
            <span>导出JSON</span>
          </Button>
          <Button
            size="small"
            className="flex items-center gap-1.5"
            onClick={() => setImportVisible(true)}
          >
            <DownloadOutlined />
            <span>导入JSON</span>
          </Button>
          <div className="h-5 w-px bg-gray-300 mx-1" />
          <Button
            size="small"
            icon={<ZoomOutOutlined />}
            onClick={handleZoomOut}
          />
          <span className="text-sm text-gray-600 px-2 min-w-[50px] text-center">
            {Math.round(zoom * 100)}%
          </span>
          <Button
            size="small"
            icon={<ZoomInOutlined />}
            onClick={handleZoomIn}
          />
        </div>
      </div>

      {/* 主内容区 */}
      <div className="flex-1 flex overflow-hidden">
        {/* 左侧节点面板 */}
        <div className="w-56 bg-gray-50 border-r border-gray-200 overflow-y-auto">
          <div className="p-2 space-y-3">
            {nodeConfigs.filter(config => config.type !== 'returnLock').map((config) => {
              const Icon = config.icon;
              return (
                <div
                  key={config.type}
                  draggable
                  onDragStart={(e) => onDragStart(e, config.type)}
                  className={`${config.bgColor} ${config.borderColor} border p-3 rounded-2xl shadow-md flex flex-col items-center justify-center gap-2 cursor-move transition-all hover:shadow-lg`}
                  style={{ 
                    borderRadius: '16px', 
                    minWidth: '80px', 
                    minHeight: '80px',
                    backgroundColor: config.bgColorCustom || undefined
                  }}
                >
                  <Icon 
                    className={`${config.iconColor} text-2xl`} 
                    style={{ color: config.iconColorCustom || undefined }}
                  />
                  <span className="text-xs font-medium text-center leading-tight" style={{ color: '#6b7280' }}>
                    {config.label}
                  </span>
                </div>
              );
            })}
          </div>
        </div>

        {/* 中间画布 */}
        <div className="flex-1 relative bg-white" ref={reactFlowWrapper}>
          <ReactFlow
            nodes={nodes}
            edges={edges}
            onNodesChange={onNodesChange}
            onEdgesChange={onEdgesChangeWithDirty}
            onConnect={onConnect}
            onEdgeUpdate={onEdgeUpdate}
            isValidConnection={isValidConnection}
            onNodeClick={onNodeClick}
            onSelectionChange={onSelectionChange}
            onNodeContextMenu={onNodeContextMenu}
            onEdgeClick={onEdgeClick}
            onPaneClick={onPaneClick}
            onDrop={onDrop}
            onDragOver={onDragOver}
            deleteKeyCode={['Delete', 'Backspace']}
            nodeTypes={nodeTypes as NodeTypes}
            edgeTypes={edgeTypes}
            fitView
            onInit={setReactFlowInstance}
            onMove={(_, viewport) => setZoom(viewport.zoom)}
            connectionMode={ConnectionMode.Loose}
            connectionRadius={40}
            snapToGrid={true}
            snapGrid={[16, 16]}
            defaultEdgeOptions={{
              style: { strokeWidth: 2, stroke: '#000000' },
              markerStart: {
                type: 'arrowclosed',
                color: '#000000',
              },
              // 不设置默认type，让onConnect中的逻辑决定
            }}
            edgesUpdatable={true}
            edgesFocusable={true}
            edgeUpdaterRadius={10}
          >
            <Controls 
              className="!bg-white !border !border-gray-200 !rounded-lg !shadow-md" 
              style={{ position: 'absolute', top: '10px', right: '10px', bottom: 'auto', left: 'auto' }}
            />
            <Background variant={BackgroundVariant.Lines} gap={16} size={1} color="#e5e7eb" />
            <MiniMap
              nodeColor={(node) => {
                const config = nodeConfigs.find(
                  (c) => c.type === resolveWorkflowPaletteType(node.type as string)
                );
                if (config?.color === 'bg-blue-500') return '#3b82f6';
                if (config?.color === 'bg-green-500') return '#10b981';
                if (config?.color === 'bg-orange-500') return '#f97316';
                if (config?.color === 'bg-red-500') return '#ef4444';
                if (config?.color === 'bg-yellow-500') return '#eab308';
                if (config?.color === 'bg-indigo-500') return '#6366f1';
                if (config?.color === 'bg-purple-500') return '#a855f7';
                return '#6b7280';
              }}
              maskColor="rgba(0, 0, 0, 0.05)"
              className="!bg-white !border !border-gray-200 !rounded-lg"
            />
          </ReactFlow>
        </div>

        {/* 右侧属性面板 */}
        {!rightPanelCollapsed && (
          <div className="w-80 bg-gray-50 border-l border-gray-200 overflow-y-auto">
          {selectedNode ? (
            <div className="h-full flex flex-col">
              {/* 头部 */}
              <div className="p-4 bg-white border-b border-gray-200 flex items-center justify-between sticky top-0 z-10 shadow-sm">
                <h3 className="text-base font-semibold text-gray-900">
                  {nodeConfig.nodeName || selectedNode.data?.label || '节点'} 设置
                </h3>
                <div className="flex items-center gap-2">
                  <Tooltip title="删除当前节点">
                    <Button
                      type="text"
                      danger
                      size="small"
                      onClick={() => handleDeleteNode()}
                      className="text-red-500 hover:text-red-600"
                      icon={<DeleteOutlined />}
                    />
                  </Tooltip>
                  <Tooltip title="收缩面板">
                    <button
                      onClick={() => setRightPanelCollapsed(!rightPanelCollapsed)}
                      className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"
                    >
                      <MenuFoldOutlined className="text-gray-500" />
                    </button>
                  </Tooltip>
                </div>
              </div>

              {/* 内容 */}
              <div className="flex-1 p-4 overflow-y-auto">
                <Tabs
                  activeKey={activeTabKey}
                  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={[
                    {
                      key: 'info',
                      label: '节点信息',
                      children: (
                        <div className="space-y-5">
                          {/* 节点名称 */}
                          <div>
                            {/* 描述 - 创建作业、确认、审核、录入信息、上锁/解锁/共锁类节点、还锁与结束节点显示在节点名称顶部 */}
                            {(selectedNode.data?.type === 'createJob' || selectedNode.data?.type === 'confirm' || selectedNode.data?.type === 'review' || selectedNode.data?.type === 'inputInfo' || isSchemeDesignerPanel || selectedNode.data?.type === 'returnLock' || selectedNode.data?.type === 'complete') && (
                              <div className="text-xs text-gray-500 leading-relaxed mb-2">
                                {getNodeDescription(selectedNode.data?.type || '')}
                              </div>
                            )}
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                              节点名称 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                              {selectedNode.data?.type && (
                                <span className="text-gray-500 font-normal ml-2">({selectedNode.data.type})</span>
                              )}
                            </label>
                            {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && (
                              <p className="text-xs text-gray-500 mb-2.5 leading-relaxed">
                                (默认名称: {nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(selectedNode.data?.type))?.label || '节点名称'}，可根据需求调整)
                              </p>
                            )}
                            {selectedNode.data?.type === 'createJob' && (
                              <p className="text-xs text-gray-500 mb-2.5 leading-relaxed">
                                (默认名称: {nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(selectedNode.data?.type))?.label || '节点名称'}，可根据需求调整)
                              </p>
                            )}
                            <Input
                              value={nodeConfig.nodeName || ''}
                              onChange={(e) =>
                                setNodeConfig({ ...nodeConfig, nodeName: e.target.value })
                              }
                              placeholder="请输入节点名称"
                              className="rounded-lg border-gray-200 h-10"
                            />
                          </div>

{/* 底部描述已移至顶部，不再在此显示 */}

                          {/* 显示图标 */}
                          <div>
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                              显示图标
                            </label>
                            <Popover
                              content={
                                <div style={{ width: '400px', maxHeight: '600px', overflowY: 'auto', padding: '12px' }}>
                                  {['开始', '确认', '审核', '录入', '能量隔离', '解除隔离', '结束'].map((category) => {
                                    const iconPaths = generateIconPaths(category);
                                    return (
                                      <div key={category} className="mb-6">
                                        {/* 分类标题带蓝色装饰条 */}
                                        <div className="flex items-center mb-3">
                                          <div className="w-1 h-5 bg-blue-500 rounded-full" style={{ minWidth: '4px', marginRight: '8px' }}></div>
                                          <span className="text-sm font-bold text-gray-800">{category}</span>
                                        </div>
                                        {/* 图标网格 */}
                                        {iconPaths.length > 0 ? (
                                          <div className="grid grid-cols-5 gap-3" style={{ gridTemplateColumns: 'repeat(5, 1fr)' }}>
                                          {iconPaths.map((iconItem) => {
                                            const isSelected = nodeConfig.nodeIcon === iconItem.fileName;
                                      return (
                                        <div
                                                key={iconItem.id}
                                          onClick={() => {
                                                  // 保存文件名而不是路径
                                                  setNodeConfig({ ...nodeConfig, nodeIcon: iconItem.fileName });
                                            setIconPickerOpen(false);
                                            // 立即更新节点显示
                                            setNodes((nds) => {
                                              return nds.map((node) => {
                                                if (node.id === selectedNode.id) {
                                                  return {
                                                    ...node,
                                                    data: {
                                                      ...node.data,
                                                            icon: iconItem.fileName, // 保存文件名
                                                    },
                                                  };
                                                }
                                                return node;
                                              });
                                            });
                                          }}
                                          className={`
                                                  rounded-lg border-2 cursor-pointer transition-all
                                                  flex items-center justify-center overflow-hidden
                                            hover:border-blue-400 hover:bg-blue-50
                                            ${isSelected ? 'border-blue-500 bg-blue-50' : 'border-gray-200 bg-white'}
                                          `}
                                                style={{ width: '25px', height: '25px' }}
                                                title={category}
                                        >
                                                <img 
                                                  src={iconItem.path} 
                                                  alt={category}
                                                  className="w-6 h-6 object-contain"
                                                    onError={(e) => {
                                                      // 如果图片加载失败，显示占位符
                                                      console.error('ProcessDesigner: 图标加载失败', {
                                                        path: iconItem.path,
                                                        id: iconItem.id,
                                                        category
                                                      });
                                                      const target = e.target as HTMLImageElement;
                                                      target.style.display = 'none';
                                                      // 显示错误占位符
                                                      const parent = target.parentElement;
                                                      if (parent && !parent.querySelector('.error-placeholder')) {
                                                        const placeholder = document.createElement('div');
                                                        placeholder.className = 'error-placeholder text-xs text-gray-400';
                                                        placeholder.textContent = '?';
                                                        parent.appendChild(placeholder);
                                                      }
                                                    }}
                                                    onLoad={() => {
                                                      // console.log('ProcessDesigner: 图标加载成功', iconItem.path);
                                                    }}
                                                  />
                                        </div>
                                      );
                                    })}
                                  </div>
                                        ) : (
                                          <div className="text-sm text-gray-500 py-4 text-center">
                                            该分类暂无图标（请检查控制台日志）
                                          </div>
                                        )}
                                      </div>
                                    );
                                  })}
                                </div>
                              }
                              trigger="click"
                              open={iconPickerOpen}
                              onOpenChange={setIconPickerOpen}
                              placement="right"
                            >
                              <div className="border border-gray-200 rounded-lg p-3 bg-white flex items-center gap-3 cursor-pointer hover:border-blue-400 transition-colors shadow-sm">
                                {(() => {
                                  // 如果设置了自定义图标（图片文件名），显示图片
                                  if (nodeConfig.nodeIcon && /^\d+\.png$/.test(nodeConfig.nodeIcon)) {
                                    const iconPath = getIconPathByFileName(nodeConfig.nodeIcon);
                                    if (iconPath) {
                                      return (
                                        <>
                                          <div className="w-12 h-12 rounded-lg border border-gray-200 bg-white flex items-center justify-center overflow-hidden" style={{ borderRadius: '12px' }}>
                                            <img 
                                              src={iconPath} 
                                              alt="节点图标"
                                              className="w-full h-full object-contain"
                                              onError={(e) => {
                                                // 如果图片加载失败，显示默认图标
                                                console.error('图标加载失败:', nodeConfig.nodeIcon);
                                              }}
                                            />
                                          </div>
                                          <span className="text-sm text-gray-600">选择图标</span>
                                        </>
                                      );
                                    }
                                  }
                                  
                                  // 否则使用默认图标
                                  let Icon = FileTextOutlined;
                                  let config = null;
                                    const iconType = selectedNode.data?.type;
                                    config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(iconType));
                                    Icon = config?.icon || FileTextOutlined;
                                  
                                  return (
                                    <>
                                      <div className={`${config?.bgColor || 'bg-gray-50'} ${config?.borderColor || 'border-gray-100'} border p-3 rounded-xl shadow-sm flex items-center justify-center`} style={{ borderRadius: '12px' }}>
                                        <Icon 
                                          className={`${config?.iconColor || 'text-gray-600'} text-lg`} 
                                          style={{ color: config?.iconColorCustom || undefined }}
                                        />
                                      </div>
                                      <span className="text-sm text-gray-600">选择图标</span>
                                    </>
                                  );
                                })()}
                              </div>
                            </Popover>
                          </div>

                          {/* 负责人 - 确认节点显示在图标下方 */}
                          {selectedNode.data?.type === 'confirm' && (
                            <div>
                              <label className="block text-sm font-medium text-gray-700 mb-2">
                                负责人
                              </label>
                              <Select
                                value={nodeConfig.responsible || undefined}
                                onChange={(value) =>
                                  setNodeConfig({ ...nodeConfig, responsible: value || '' })
                                }
                                placeholder="请选择负责人"
                                className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                allowClear
                              >
                                {drawerUsers.map(user => (
                                  <Select.Option key={user.id} value={String(user.id)}>{user.nickname || user.username}</Select.Option>
                                ))}
                              </Select>
                              <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
                                对该任务或步骤节点进行处理的人员，若不选则需要在创建作业时进行选择。
                              </p>
                            </div>
                          )}

                          {/* 负责人 - 审核节点显示在图标下方 */}
                          {selectedNode.data?.type === 'review' && (
                            <div>
                              <label className="block text-sm font-medium text-gray-700 mb-2">
                                负责人
                              </label>
                              <Select
                                value={nodeConfig.responsible || undefined}
                                onChange={(value) =>
                                  setNodeConfig({ ...nodeConfig, responsible: value || '' })
                                }
                                placeholder="请选择负责人"
                                className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                allowClear
                              >
                                {drawerUsers.map(user => (
                                  <Select.Option key={user.id} value={String(user.id)}>{user.nickname || user.username}</Select.Option>
                                ))}
                              </Select>
                              <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
                                对该任务或步骤节点进行处理的人员，若不选则需要在创建作业时进行选择。
                              </p>
                            </div>
                          )}

                          {/* 负责人 - 创建作业与上锁/解锁/共锁类方案节点不显示 */}
                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && !isSchemeDesignerPanel && (
                            <div>
                              <label className="block text-sm font-medium text-gray-700 mb-2">
                                负责人
                              </label>
                              <Select
                                value={nodeConfig.responsible || undefined}
                                onChange={(value) =>
                                  setNodeConfig({ ...nodeConfig, responsible: value || '' })
                                }
                                placeholder="请选择负责人"
                                className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                allowClear
                              >
                                {drawerUsers.map(user => (
                                  <Select.Option key={user.id} value={String(user.id)}>{user.nickname || user.username}</Select.Option>
                                ))}
                              </Select>
                              <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
                                对该任务或步骤节点进行处理的人员，若不选则需要在创建作业时进行选择
                              </p>
                            </div>
                          )}

                          {/* 备注 - 创建作业、确认、审核、录入信息、上锁/解锁/共锁类、还锁和完成节点不显示 */}
                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && selectedNode.data?.type !== 'inputInfo' && !isSchemeDesignerPanel && selectedNode.data?.type !== 'returnLock' && selectedNode.data?.type !== 'complete' && (
                            <div>
                              <label className="block text-sm font-medium text-gray-700 mb-2">
                                备注
                              </label>
                              <Input.TextArea
                                value={nodeConfig.remark || ''}
                                onChange={(e) =>
                                  setNodeConfig({ ...nodeConfig, remark: e.target.value })
                                }
                                placeholder="请输入备注"
                                rows={3}
                                className="rounded-lg border-gray-200"
                              />
                            </div>
                          )}

                          <div className="flex gap-2">
                            <Button
                              type="primary"
                              onClick={handleSaveWithoutNavigate}
                              className="flex-1 rounded-lg"
                              size="large"
                            >
                              <SaveOutlined />
                              <span className="ml-1">保存</span>
                            </Button>
                          </div>
                        </div>
                      ),
                    },
                    {
                      key: 'form',
                      label: '提交表单',
                      children: (
                        <div className="space-y-5">
                          <div>
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                              提交表单 <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                            </label>
                            <div className="flex gap-2">
                            <Select
                              value={nodeConfig.formId || undefined}
                              onChange={(value) =>
                                setNodeConfig({ ...nodeConfig, formId: value ? String(value) : '' })
                              }
                              placeholder="请选择提交表单"
                                className="flex-1 [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                              allowClear
                            >
                              {formList.map(form => (
                                <Select.Option key={form.id} value={String(form.id)}>{form.name}</Select.Option>
                              ))}
                            </Select>
                              <Button
                                icon={<EyeOutlined />}
                                onClick={async () => {
                                  if (!nodeConfig.formId) {
                                    message.warning('请先选择表单');
                                    return;
                                  }
                                  setFormPreviewLoading(true);
                                  try {
                                    const formData = await getForm(Number(nodeConfig.formId));
                                    setFormPreviewData(formData);
                                    // 解析表单配置和字段
                                    setConfAndFields2(setFormPreviewDetailData, formData.conf, formData.fields);
                                    setFormPreviewVisible(true);
                                  } catch (error: any) {
                                    message.error(error?.message || '获取表单详情失败');
                                  } finally {
                                    setFormPreviewLoading(false);
                                  }
                                }}
                                disabled={!nodeConfig.formId}
                                loading={formPreviewLoading}
                                className="flex-shrink-0"
                              >
                                预览
                              </Button>
                            </div>
                          </div>

                          {/* 上锁/解锁/共锁/解除共锁 节点特有的字段 */}
                          {isSchemeDesignerPanel && (
                            <>
                              {(isWorkflowUnlockSchemeType(selectedDataType) ||
                                isWorkflowUnlockCoLockType(selectedDataType)) && (
                                <div>
                                  <label className="block text-sm font-medium text-gray-700 mb-2">
                                    {isWorkflowUnlockCoLockType(selectedDataType)
                                      ? '选择共锁任务'
                                      : '选择上锁任务'}
                                  </label>
                                  <Select
                                    value={nodeConfig.isolationNodeUuid || undefined}
                                    onChange={(value) => {
                                      setNodeConfig((prev) => {
                                        const target = nodes.find(
                                          (n) =>
                                            n.id === value &&
                                            isWorkflowUnlockParentMatch(selectedDataType, n.data?.type)
                                        );
                                        if (target) {
                                          const cache = loadNodeCache(target.id);
                                          const source = cache || target.data || {};
                                          return {
                                            ...prev,
                                            isolationNodeUuid: value || '',
                                            isolationType: source.isolationType || '',
                                            isolationPoints: source.isolationPoints || [],
                                            isolationNode: Array.isArray(source.isolationNode)
                                              ? source.isolationNode
                                              : (source.isolationNode ? [source.isolationNode] : []),
                                            responsible: source.responsible || '',
                                            lockPerson: source.lockPerson || '',
                                            coLockPersons: source.coLockPersons || [],
                                          };
                                        }
                                        return { ...prev, isolationNodeUuid: value || '' };
                                      });
                                    }}
                                    placeholder={
                                      isWorkflowUnlockCoLockType(selectedDataType)
                                        ? '请选择共锁任务'
                                        : '请选择上锁任务'
                                    }
                                    className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                    allowClear
                                  >
                                    {(() => {
                                      const wantCoLockParent = isWorkflowUnlockCoLockType(selectedDataType);
                                      const currentNodeId = selectedNode?.id;
                                      if (!currentNodeId) return [];

                                      let queue = [String(currentNodeId)];
                                      const upstreamIds = new Set<string>();

                                      while (queue.length > 0) {
                                        const nextQueue: string[] = [];
                                        for (const id of queue) {
                                          edges.forEach((edge) => {
                                            if (String(edge.target) !== id) return;
                                            const src = String(edge.source);
                                            if (upstreamIds.has(src)) return;
                                            upstreamIds.add(src);
                                            nextQueue.push(src);
                                          });
                                        }
                                        queue = nextQueue;
                                      }

                                      const schemeNodes = nodes.filter((n) =>
                                        wantCoLockParent
                                          ? isWorkflowCoLockType(n.data?.type)
                                          : isWorkflowLockSchemeType(n.data?.type)
                                      );
                                      const nodesToShow = schemeNodes.filter((n) =>
                                        upstreamIds.has(String(n.id))
                                      );
                                      const nodesToShowFinal =
                                        nodesToShow.length > 0 ? nodesToShow : schemeNodes;
                                      const defaultLabel = wantCoLockParent
                                        ? nodeConfigs.find((c) => c.type === 'coLock')?.label || '共锁'
                                        : nodeConfigs.find((c) => c.type === 'lock')?.label || '上锁';

                                      return nodesToShowFinal.map((node) => (
                                        <Select.Option key={node.id} value={node.id}>
                                          {node.data?.label || defaultLabel}
                                        </Select.Option>
                                      ));
                                    })()}
                                  </Select>
                                </div>
                              )}

                              {isWorkflowUnlockCoLockType(selectedDataType) &&
                                !!nodeConfig.isolationNodeUuid && (
                                <div>
                                  <label className="block text-sm font-medium text-gray-700 mb-2">
                                    共锁人（可多选）
                                  </label>
                                  <Select
                                    mode="multiple"
                                    value={nodeConfig.coLockPersons}
                                    onChange={(value) =>
                                      setNodeConfig({ ...nodeConfig, coLockPersons: value })
                                    }
                                    placeholder="请选择共锁人"
                                    className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                    allowClear
                                    disabled={isUnlockDesignerReadOnly}
                                  >
                                    {colockerUsers.map((user) => (
                                      <Select.Option key={user.id} value={user.id}>
                                        {user.nickname || user.username}
                                      </Select.Option>
                                    ))}
                                  </Select>
                                </div>
                              )}

                              {isWorkflowCoLockType(selectedDataType) && (
                                <>
                                  <div>
                                    <label className="block text-sm font-medium text-gray-700 mb-2">
                                      选择上锁任务{' '}
                                      <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                                    </label>
                                    <Select
                                      value={nodeConfig.isolationNodeUuid || undefined}
                                      onChange={(value) => {
                                        setNodeConfig((prev) => {
                                          const target = nodes.find(
                                            (n) =>
                                              n.id === value && isWorkflowLockSchemeType(n.data?.type)
                                          );
                                          if (target) {
                                            const cache = loadNodeCache(target.id);
                                            const source = cache || target.data || {};
                                            const resp =
                                              source.workerUserId !== undefined &&
                                              source.workerUserId !== null &&
                                              source.workerUserId !== ''
                                                ? String(source.workerUserId)
                                                : source.responsible || '';
                                            return {
                                              ...prev,
                                              isolationNodeUuid: value || '',
                                              isolationType: source.isolationType || '',
                                              isolationPoints: source.isolationPoints || [],
                                              isolationNode: Array.isArray(source.isolationNode)
                                                ? source.isolationNode
                                                : source.isolationNode
                                                  ? [source.isolationNode]
                                                  : [],
                                              responsible: resp,
                                              lockPerson: source.lockPerson || '',
                                              coLockPersons: prev.coLockPersons,
                                            };
                                          }
                                          return { ...prev, isolationNodeUuid: value || '' };
                                        });
                                      }}
                                      placeholder="请选择上锁任务"
                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                      allowClear
                                    >
                                      {(() => {
                                        const currentNodeId = selectedNode?.id;
                                        if (!currentNodeId) return [];

                                        let queue = [String(currentNodeId)];
                                        const upstreamIds = new Set<string>();

                                        while (queue.length > 0) {
                                          const nextQueue: string[] = [];
                                          for (const id of queue) {
                                            edges.forEach((edge) => {
                                              if (String(edge.target) !== id) return;
                                              const src = String(edge.source);
                                              if (upstreamIds.has(src)) return;
                                              upstreamIds.add(src);
                                              nextQueue.push(src);
                                            });
                                          }
                                          queue = nextQueue;
                                        }

                                        const schemeNodes = nodes.filter((n) =>
                                          isWorkflowLockSchemeType(n.data?.type)
                                        );
                                        const nodesToShow = schemeNodes.filter((n) =>
                                          upstreamIds.has(String(n.id))
                                        );
                                        const nodesToShowFinal =
                                          nodesToShow.length > 0 ? nodesToShow : schemeNodes;
                                        const defaultLabel =
                                          nodeConfigs.find((c) => c.type === 'lock')?.label || '上锁';

                                        return nodesToShowFinal.map((node) => (
                                          <Select.Option key={node.id} value={node.id}>
                                            {node.data?.label || defaultLabel}
                                          </Select.Option>
                                        ));
                                      })()}
                                    </Select>
                                  </div>
                                  <div>
                                    <label className="block text-sm font-medium text-gray-700 mb-2">
                                      共锁人（可多选）
                                    </label>
                                    <Select
                                      mode="multiple"
                                      value={nodeConfig.coLockPersons}
                                      onChange={(value) =>
                                        setNodeConfig({ ...nodeConfig, coLockPersons: value })
                                      }
                                      placeholder="请选择共锁人"
                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                      allowClear
                                    >
                                      {colockerUsers.map((user) => (
                                        <Select.Option key={user.id} value={user.id}>
                                          {user.nickname || user.username}
                                        </Select.Option>
                                      ))}
                                    </Select>
                                  </div>
                                </>
                              )}

                              {!isWorkflowCoLockType(selectedDataType) &&
                                !isWorkflowUnlockCoLockType(selectedDataType) && (
                                <>
                                  <div>
                                    <label className="block text-sm font-medium text-gray-700 mb-2">
                                      隔离方式
                                    </label>
                                    <Select
                                      value={nodeConfig.isolationType || undefined}
                                      onChange={(value) =>
                                        setNodeConfig({ ...nodeConfig, isolationType: value || '' })
                                      }
                                      placeholder="请选择隔离方式"
                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                      allowClear
                                      disabled={isUnlockDesignerReadOnly}
                                    >
                                      {isolationTypeDictList.map((item) => (
                                        <Select.Option key={item.id} value={item.value}>
                                          {item.label}
                                        </Select.Option>
                                      ))}
                                    </Select>
                                  </div>

                                  <div>
                                    <label className="block text-sm font-medium text-gray-700 mb-2">
                                      隔离点选择（可多选）
                                    </label>
                                    <Select
                                      mode="multiple"
                                      value={nodeConfig.isolationPoints}
                                      onChange={(value) =>
                                        setNodeConfig({ ...nodeConfig, isolationPoints: value })
                                      }
                                      placeholder="请选择隔离点"
                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                      allowClear
                                      disabled={isUnlockDesignerReadOnly}
                                    >
                                      {isolationPoints.map((point: any, index) => (
                                        <Select.Option
                                          key={point.pointId || point.id || `point-${index}`}
                                          value={point.pointId || point.id}
                                        >
                                          {point.pointName}
                                        </Select.Option>
                                      ))}
                                    </Select>
                                  </div>

                                  {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
                                    <div>
                                      <label className="block text-sm font-medium text-gray-700 mb-2">
                                        负责人
                                      </label>
                                      <Select
                                        value={nodeConfig.responsible || undefined}
                                        onChange={(value) =>
                                          setNodeConfig({ ...nodeConfig, responsible: value || '' })
                                        }
                                        placeholder="请选择负责人"
                                        className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                        allowClear
                                        disabled={isUnlockDesignerReadOnly}
                                      >
                                        {drawerUsers.map((user) => (
                                          <Select.Option key={user.id} value={user.id}>
                                            {user.nickname || user.username}
                                          </Select.Option>
                                        ))}
                                      </Select>
                                      <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
                                        对该任务或步骤节点进行处理的人员，若不选则需要在创建作业时进行选择。
                                      </p>
                                    </div>
                                  )}

                                  {nodeConfig.isolationType === '1' && (
                                    <>
                                      <div>
                                        <label className="block text-sm font-medium text-gray-700 mb-2">
                                          上锁人
                                        </label>
                                        <Select
                                          value={nodeConfig.lockPerson || undefined}
                                          onChange={(value) =>
                                            setNodeConfig({ ...nodeConfig, lockPerson: value || '' })
                                          }
                                          placeholder="请选择上锁人"
                                          className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                          allowClear
                                          disabled={isUnlockDesignerReadOnly}
                                        >
                                          {lockerUsers.map((user) => (
                                            <Select.Option key={user.id} value={user.id}>
                                              {user.nickname || user.username}
                                            </Select.Option>
                                          ))}
                                        </Select>
                                      </div>
                                      {!isWorkflowLockSchemeType(selectedDataType) &&
                                        !isWorkflowUnlockSchemeType(selectedDataType) && (
                                        <div>
                                          <label className="block text-sm font-medium text-gray-700 mb-2">
                                            共锁人（可多选）
                                          </label>
                                          <Select
                                            mode="multiple"
                                            value={nodeConfig.coLockPersons}
                                            onChange={(value) =>
                                              setNodeConfig({ ...nodeConfig, coLockPersons: value })
                                            }
                                            placeholder="请选择共锁人"
                                            className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                            allowClear
                                            disabled={isUnlockDesignerReadOnly}
                                          >
                                            {colockerUsers.map((user) => (
                                              <Select.Option key={user.id} value={user.id}>
                                                {user.nickname || user.username}
                                              </Select.Option>
                                            ))}
                                          </Select>
                                        </div>
                                      )}
                                    </>
                                  )}
                                </>
                              )}
                            </>
                          )}
                          <div className="flex gap-2">
                            <Button
                              type="primary"
                              onClick={handleSaveWithoutNavigate}
                              className="flex-1 rounded-lg"
                              size="large"
                            >
                              <SaveOutlined />
                              <span className="ml-1">保存</span>
                            </Button>
                          </div>
                        </div>
                      ),
                    },
                    {
                      key: 'notification',
                      label: '通知消息',
                      children: (
                        <div className="space-y-5">
                          <div>
                            <label className="block text-sm font-medium text-gray-700 mb-3">
                              通知方式
                            </label>
                            <div className="bg-gray-50 p-3 rounded-lg">
                              <div className="mb-3">
                                <Checkbox
                                  checked={nodeConfig.notificationMethods.sms}
                                  onChange={(e) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      notificationMethods: {
                                        ...nodeConfig.notificationMethods,
                                        sms: e.target.checked,
                                      },
                                      smsTemplateCode: e.target.checked ? 'true' : 'false',
                                    })
                                  }
                                >
                                  短信
                                </Checkbox>
                              </div>
                              <div className="mb-3">
                                <Checkbox
                                  checked={nodeConfig.notificationMethods.message}
                                  onChange={(e) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      notificationMethods: {
                                        ...nodeConfig.notificationMethods,
                                        message: e.target.checked,
                                      },
                                      messageTemplateCode: e.target.checked ? 'true' : 'false',
                                    })
                                  }
                                >
                                  站内信
                                </Checkbox>
                              </div>
                              <div className="mb-3">
                                <Checkbox
                                  checked={nodeConfig.notificationMethods.email}
                                  onChange={(e) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      notificationMethods: {
                                        ...nodeConfig.notificationMethods,
                                        email: e.target.checked,
                                      },
                                      emailTemplateCode: e.target.checked ? 'true' : 'false',
                                    })
                                  }
                                >
                                  邮件
                                </Checkbox>
                              </div>
                              <div>
                                <Checkbox
                                  checked={nodeConfig.notificationMethods.app}
                                  onChange={(e) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      notificationMethods: {
                                        ...nodeConfig.notificationMethods,
                                        app: e.target.checked,
                                      },
                                      appTemplateCode: e.target.checked ? 'true' : 'false',
                                    })
                                  }
                                >
                                  APP通知
                                </Checkbox>
                              </div>
                            </div>
                          </div>
                          <div>
                            <label className="block text-sm font-medium text-gray-700 mb-3">
                              通知对象
                            </label>
                            <div className="mb-3 space-y-1 text-xs text-gray-500">
                              <div>1. 系统将默认发送消息给任务负责人（或上锁人\共锁人)</div>
                              <div>2. 请选择消息抄送对象：</div>
                            </div>
                            <div className="space-y-3 bg-gray-50 p-3 rounded-lg">
                              <div>
                                <label className="block text-sm font-medium text-gray-700 mb-2">
                                  抄送给部门（部门下的所有人）：
                                </label>
                                <TreeSelect
                                  multiple
                                  value={nodeConfig.copyDeptIds.length ? nodeConfig.copyDeptIds : undefined}
                                  onChange={(value) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      copyDeptIds: Array.isArray(value) ? value.map(String) : value != null && value !== '' ? [String(value)] : [],
                                    })
                                  }
                                  placeholder="请选择部门（可多选）"
                                  treeData={ccDeptTreeData}
                                  className="w-full"
                                  allowClear
                                  showSearch
                                  treeNodeFilterProp="title"
                                  treeDefaultExpandAll
                                />
                              </div>
                              <div>
                                <label className="block text-sm font-medium text-gray-700 mb-2">
                                  抄送给人员：
                                </label>
                                <TreeSelect
                                  multiple
                                  value={nodeConfig.copyUserIds.length ? nodeConfig.copyUserIds : undefined}
                                  onChange={(value) =>
                                    setNodeConfig({
                                      ...nodeConfig,
                                      copyUserIds: Array.isArray(value) ? value.map(String) : value != null && value !== '' ? [String(value)] : [],
                                    })
                                  }
                                  placeholder="请选择人员（可多选）"
                                  treeData={ccUserTreeData}
                                  className="w-full"
                                  allowClear
                                  showSearch
                                  treeNodeFilterProp="title"
                                  treeDefaultExpandAll
                                />
                              </div>
                            </div>
                          </div>
                          {/* <div>
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                              通知人
                            </label>
                            <Select
                              value={nodeConfig.notificationPerson || undefined}
                              onChange={(value) =>
                                setNodeConfig({ ...nodeConfig, notificationPerson: value || '' })
                              }
                              placeholder="请选择通知人"
                              className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                              allowClear
                            >
                              <Select.Option value="taskResponsible">任务负责人</Select.Option>
                              <Select.Option value="taskParticipant">任务参与人</Select.Option>
                              <Select.Option value="responsibleAndParticipant">负责人和参与人</Select.Option>
                              <Select.Option value="specifiedPerson">指定人</Select.Option>
                            </Select>
                          </div> */}
                          {/* <div>
                            <label className="block text-sm font-medium text-gray-700 mb-2">
                              通知时间
                            </label>
                            <Select
                              value={nodeConfig.notificationTime || undefined}
                              onChange={(value) =>
                                setNodeConfig({ ...nodeConfig, notificationTime: value || '' })
                              }
                              placeholder="请选择通知时间"
                              className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                              allowClear
                            >
                              <Select.Option value="before">执行前（上一个节点结束后）</Select.Option>
                              <Select.Option value="after">执行后（该节点结束后）</Select.Option>
                              <Select.Option value="time">选择时间</Select.Option>
                              <Select.Option value="30min">任务开始前30分钟</Select.Option>
                              <Select.Option value="1h">任务开始前1小时</Select.Option>
                              <Select.Option value="2h">任务开始前2小时</Select.Option>
                              <Select.Option value="4h">任务开始前4小时</Select.Option>
                              <Select.Option value="5h">任务开始前5小时</Select.Option>
                              <Select.Option value="8h">任务开始前8小时</Select.Option>
                              <Select.Option value="12h">任务开始前12小时</Select.Option>
                              <Select.Option value="24h">任务开始前24小时</Select.Option>
                              <Select.Option value="48h">任务开始前48小时</Select.Option>
                            </Select>
                          </div> */}
                          <div className="flex gap-2">
                            <Button
                              type="primary"
                              onClick={handleSaveWithoutNavigate}
                              className="flex-1 rounded-lg"
                              size="large"
                            >
                              <SaveOutlined />
                              <span className="ml-1">保存</span>
                            </Button>
                          </div>
                        </div>
                      ),
                    },
                  ].filter(item => {
                    // 如果是创建作业节点，过滤掉提交表单tab
                    if (item.key === 'form' && selectedNode.data?.type === 'createJob') {
                      return false;
                    }
                    return true;
                  })}
                />
              </div>
            </div>
          ) : (
            <div className="h-full flex flex-col">
              {/* 头部 */}
              <div className="p-4 bg-white border-b border-gray-200 flex items-center justify-end sticky top-0 z-10 shadow-sm">
                <Tooltip title="收缩面板">
                  <button
                    onClick={() => setRightPanelCollapsed(!rightPanelCollapsed)}
                    className="p-1.5 hover:bg-gray-100 rounded-lg transition-colors"
                  >
                    <MenuFoldOutlined className="text-gray-500" />
                  </button>
                </Tooltip>
              </div>
              {/* 内容 */}
              <div className="p-4 text-center text-gray-500 mt-20">
                <p className="text-sm">请点击画布中的节点查看和编辑属性</p>
              </div>
            </div>
          )}
        </div>
        )}
        {/* 收缩状态下的展开按钮 */}
        {rightPanelCollapsed && (
          <div className="w-12 bg-gray-50 border-l border-gray-200 flex items-center justify-center">
            <Tooltip title="展开面板" placement="left">
              <button
                onClick={() => setRightPanelCollapsed(false)}
                className="p-2 hover:bg-gray-100 rounded-lg transition-colors"
              >
                <MenuUnfoldOutlined className="text-gray-500 text-lg" />
              </button>
            </Tooltip>
          </div>
        )}
      </div>
      <Modal
        open={exportVisible}
        title="流程 JSON 导出"
        onCancel={() => setExportVisible(false)}
        onOk={() => setExportVisible(false)}
        width={800}
        okText="关闭"
        cancelText="取消"
        style={{ top: 20 }}
        styles={{ body: { maxHeight: 'calc(90vh - 120px)', overflowY: 'auto', overflowX: 'hidden', padding: 0 } }}
      >
        <div className="relative">
          {/* 固定在顶部的复制按钮 */}
          <div className="sticky top-0 bg-white z-10 px-6 py-3 flex justify-end">
            <Button
              type="default"
              icon={<CopyOutlined />}
              onClick={handleCopyJson}
            >
              复制JSON
            </Button>
          </div>
          {/* JSON内容区域 */}
          <div className="p-6">
            <div className="bg-gray-50 border border-gray-200 rounded-lg p-3 overflow-x-hidden">
              <pre className="text-xs text-gray-800 whitespace-pre-wrap break-words font-mono overflow-x-hidden">
                {exportContent}
              </pre>
            </div>
          </div>
        </div>
      </Modal>

      <Modal
        open={importVisible}
        title="导入流程 JSON"
        onCancel={() => {
          setImportVisible(false);
          setImportJson('');
        }}
        onOk={handleImportJson}
        width={800}
        okText="导入"
        cancelText="取消"
      >
        <div className="space-y-3">
          <p className="text-sm text-gray-600">
            请粘贴流程 JSON 数据，导入后将替换当前画布内容
          </p>
          <Input.TextArea
            value={importJson}
            onChange={(e) => setImportJson(e.target.value)}
            placeholder="请粘贴 JSON 数据..."
            rows={15}
            className="font-mono text-xs"
          />
        </div>
        </Modal>

      {/* 表单预览Modal */}
      <Modal
        open={formPreviewVisible}
        title={`预览表单 - ${formPreviewData?.name || ''}`}
        onCancel={() => {
          setFormPreviewVisible(false);
          setFormPreviewData(null);
          setFormPreviewDetailData({ rule: [], option: {} });
        }}
        footer={[
          <Button key="close" onClick={() => {
            setFormPreviewVisible(false);
            setFormPreviewData(null);
            setFormPreviewDetailData({ rule: [], option: {} });
          }}>
            关闭
          </Button>
        ]}
        width={960}
        style={{ top: 20 }}
      >
        <div className="p-2">
          {(() => {
            // 与表单管理一致：兼容 option.formConfig 或 option 即 formConfig 两种结构
            const formConfig = {
              ...defaultFormConfig,
              ...(formPreviewDetailData.option?.formConfig || formPreviewDetailData.option || {}),
            };
            const layoutColumns = formConfig.layoutColumns || 1;
            const gridStyle = layoutColumns > 1 ? {
              display: 'grid',
              gridTemplateColumns: `repeat(${layoutColumns}, minmax(0, 1fr))`,
              gap: '12px',
              rowGap: '16px',
            } : undefined;

            // 与表单设计器一致：每个字段使用 field.labelWidth ?? formConfig.labelWidth ?? 100
            const getPreviewItemLayout = (f: any) => {
              const raw = f.labelWidth ?? formConfig.labelWidth ?? 100;
              const w = (typeof raw === 'number' && raw > 0) ? raw : 100;
              const isTop = formConfig.labelPosition === 'top';
              return {
                labelCol: isTop ? undefined : { flex: `${w}px`, style: { minWidth: `${w}px`, textAlign: formConfig.labelPosition === 'right' ? 'right' : 'left' } },
                wrapperCol: isTop ? undefined : { flex: 'auto', style: { minWidth: 0 } },
              };
            };

            // 渲染字段预览（支持嵌套结构）
            const renderFieldPreview = (field: any, parentSpanStyle?: React.CSSProperties): React.ReactNode => {
              const spanStyle = parentSpanStyle || (layoutColumns > 1 ? { gridColumn: `span ${Math.min(layoutColumns, field.span || 1)}` } : undefined);
              const itemLayout = getPreviewItemLayout(field);

              // 处理容器类型（card 和 grid）
              if (field.type === 'card') {
                const children = field.children || [];
                // 优先使用 label（字段名称），如果没有则使用 cardTitle，最后使用默认值
                const cardTitle = field.label || field.cardTitle || '卡片容器';
                return (
                  <div key={field.id} style={spanStyle} className="mb-4">
                    <Card title={cardTitle} className="w-full">
                      <div className="space-y-4">
                        {children.map((child: any) => renderFieldPreview(child))}
                      </div>
                    </Card>
                  </div>
                );
              }

              if (field.type === 'grid') {
                const gridColumns = field.gridColumns || 2;
                const children = field.children || [];
                return (
                  <div key={field.id} style={spanStyle} className="mb-4">
                    <div
                      style={{
                        display: 'grid',
                        gridTemplateColumns: `repeat(${gridColumns}, minmax(0, 1fr))`,
                        gap: '16px',
                      }}
                    >
                      {children.map((child: any) => {
                        const childSpanStyle = gridColumns > 1 
                          ? { gridColumn: `span ${Math.min(gridColumns, child.span || 1)}` } 
                          : undefined;
                        return (
                          <div key={child.id} style={childSpanStyle}>
                            {renderFieldPreview(child)}
                          </div>
                        );
                      })}
                    </div>
                  </div>
                );
              }

              // 处理 alert 类型
              if (field.type === 'alert') {
                return (
                  <div key={field.id} className="mb-4" style={spanStyle}>
                    <Alert
                      message={field.alertTitle || field.label}
                      description={field.alertDescription}
                      type={field.alertType || 'info'}
                      showIcon={field.alertShowIcon !== false}
                      closable={field.alertClosable}
                    />
                  </div>
                );
              }

              // 处理普通字段
              switch (field.type) {
                case 'textarea':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Input.TextArea 
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'} 
                          rows={4}
                          allowClear={field.showClear}
                          maxLength={field.maxLength}
                          readOnly={field.readOnly}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'number':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <InputNumber
                          style={{ width: '100%' }}
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
                          max={field.maxLength}
                          readOnly={field.readOnly}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'select':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Select
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择'}
                          allowClear={field.showClear}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        >
                          {(field.options || []).map((opt: any, idx: number) => (
                            <Select.Option key={idx} value={opt.value}>{opt.label}</Select.Option>
                          ))}
                        </Select>
                      </AntdForm.Item>
                    </div>
                  );
                case 'date':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <DatePicker
                          style={{ width: '100%' }}
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期'}
                          allowClear={field.showClear}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'daterange':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <DatePicker.RangePicker
                          style={{ width: '100%' }}
                          placeholder={Array.isArray(field.placeholder) ? field.placeholder : ['开始日期', '结束日期']}
                          allowClear={field.showClear}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'datetime':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <DatePicker
                          style={{ width: '100%' }}
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择日期时间'}
                          showTime={{ format: 'HH:mm:ss' }}
                          format="YYYY-MM-DD HH:mm:ss"
                          allowClear={field.showClear}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'timepicker':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <TimePicker
                          style={{ width: '100%' }}
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请选择时间'}
                          format="HH:mm:ss"
                          allowClear={field.showClear}
                          disabled={field.disabled}
                          size={field.size || 'middle'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                case 'switch':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        valuePropName="checked"
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Switch disabled={field.disabled} />
                      </AntdForm.Item>
                    </div>
                  );
                case 'radio':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Radio.Group>
                          {(field.options || []).map((opt: any, idx: number) => (
                            <Radio key={idx} value={opt.value}>{opt.label}</Radio>
                          ))}
                        </Radio.Group>
                      </AntdForm.Item>
                    </div>
                  );
                case 'checkbox':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Checkbox.Group>
                          {(field.options || []).map((opt: any, idx: number) => (
                            <Checkbox key={idx} value={opt.value}>{opt.label}</Checkbox>
                          ))}
                        </Checkbox.Group>
                      </AntdForm.Item>
                    </div>
                  );
                case 'upload':
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <FormUploadField
                          uploadType={field.uploadType}
                          maxCount={field.maxCount}
                          accept={field.accept}
                          disabled={field.disabled}
                        />
                      </AntdForm.Item>
                    </div>
                  );
                default:
                  return (
                    <div key={field.id} style={spanStyle}>
                      <AntdForm.Item
                        label={(field.label || field.title || '') + (formConfig.labelSuffix || '')}
                        name={field.name || field.field}
                        required={field.required && !formConfig.hideRequiredMark}
                        help={field.hint}
                        labelCol={itemLayout.labelCol}
                        wrapperCol={itemLayout.wrapperCol}
                      >
                        <Input 
                          type={field.inputType || 'text'}
                          placeholder={typeof field.placeholder === 'string' ? field.placeholder : '请输入'}
                        />
                      </AntdForm.Item>
                    </div>
                  );
              }
            };

            return (
              <>
                <style>{`
                  .form-preview-grid .ant-form-item {
                    margin-bottom: 12px;
                  }
                  .form-preview-grid .ant-form-item-control {
                    min-width: 0;
                  }
                `}</style>
                <AntdForm
                  form={formPreviewForm}
                  layout={formConfig.labelPosition === 'top' ? 'vertical' : 'horizontal'}
                  size={formConfig.formSize === 'default' ? 'middle' : formConfig.formSize}
                  requiredMark={formConfig.hideRequiredMark ? false : undefined}
                  wrapperCol={formConfig.labelPosition === 'top' ? undefined : { flex: 'auto', style: { minWidth: 0 } }}
                >
                  <div
                    style={gridStyle}
                    className={layoutColumns === 1 ? 'space-y-4' : 'form-preview-grid'}
                  >
                    {(formPreviewDetailData.rule || []).map((field: any) => renderFieldPreview(field))}
                  </div>
                </AntdForm>
              </>
            );
          })()}
        </div>
      </Modal>
    </div>
  );
}
