import { bitable, FieldType, ToastType } from '@lark-base-open/js-sdk';
import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber, Input, Progress, Switch, Tag } from '@douyinfe/semi-ui';
import { useState, useEffect, useRef } from 'react';
import { addDays, format, differenceInCalendarDays } from 'date-fns';
import { zhCN } from 'date-fns/locale';
import { executePricingQuery, executeSecondaryProcessQuery, executePricingDetailsQuery } from './services/apiService';

const { Title, Text } = Typography;

// 统一的日期格式常量
const DATE_FORMATS = {
  DISPLAY_WITH_TIME: 'yyyy-MM-dd HH:mm',           // 显示格式：2026-02-20 16:54
  DISPLAY_DATE_ONLY: 'yyyy-MM-dd',                 // 日期格式：2026-02-20
  STORAGE_FORMAT: 'yyyy-MM-dd HH:mm:ss',           // 存储格式：2026-02-20 16:54:00
  CHINESE_DATE: 'yyyy年MM月dd日',                   // 中文格式：2026年02月20日
} as const;

// 统一的星期显示
const WEEKDAYS = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] as const;

// 统一的字段提取函数
const extractText = (val: any) => {
  if (Array.isArray(val) && val.length > 0) {
    const item = val[0];
    return typeof item === 'string' ? item : (item?.text || item?.name || '');
  } else if (typeof val === 'string') {
    return val;
  } else if (typeof val === 'number') {
    return String(val);
  } else if (val && typeof val === 'object') {
    return val.text || val.name || val.value?.toString?.() || '';
  }
  return '';
};

export default function App() {
  type ProcessGroupInstance = {
    id: string;
    groupName: string;
    displayName: string;
  };

  const nextProcessGroupInstanceIdRef = useRef(1);
  const createProcessGroupInstance = (groupName: string, displayName?: string): ProcessGroupInstance => {
    const name = (groupName || '').trim();
    const id = `${Date.now()}_${nextProcessGroupInstanceIdRef.current++}`;
    return { id, groupName: name, displayName: (displayName || name).trim() || name };
  };

  const deriveGroupOrderDraftFromTimelineResults = (results: any[]): ProcessGroupInstance[] => {
    const arr = Array.isArray(results) ? results : [];
    const seen = new Set<string>();
    const out: ProcessGroupInstance[] = [];
    for (const r of arr) {
      const g = (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim();
      if (!g || seen.has(g)) continue;
      seen.add(g);
      out.push(createProcessGroupInstance(g));
    }
    return out;
  };

  const deriveGroupOrderDraftByProcessOrder = (nodes: any[]): ProcessGroupInstance[] => {
    const arr = Array.isArray(nodes) ? nodes : [];
    const groupToMinOrder = new Map<string, number>();
    const groupToFirstIndex = new Map<string, number>();
    for (let i = 0; i < arr.length; i++) {
      const r: any = arr[i];
      const g = (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim();
      if (!g) continue;
      if (!groupToFirstIndex.has(g)) groupToFirstIndex.set(g, i);
      const n = Number(r?.processOrder);
      if (Number.isFinite(n)) {
        const prev = groupToMinOrder.get(g);
        if (typeof prev === 'undefined' || n < prev) groupToMinOrder.set(g, n);
      }
    }
    const groups = Array.from(groupToFirstIndex.keys());
    groups.sort((a, b) => {
      const ao = groupToMinOrder.get(a);
      const bo = groupToMinOrder.get(b);
      const aHas = typeof ao !== 'undefined';
      const bHas = typeof bo !== 'undefined';
      if (aHas && bHas) {
        if (ao !== bo) return ao - bo;
      } else if (aHas !== bHas) {
        return aHas ? -1 : 1;
      }
      const ai = groupToFirstIndex.get(a) || 0;
      const bi = groupToFirstIndex.get(b) || 0;
      if (ai !== bi) return ai - bi;
      return a.localeCompare(b, 'zh-Hans-CN');
    });
    return groups.map(g => createProcessGroupInstance(g));
  };

  const applyGroupOrderConfigToTimelineResults = (results: any[], config: ProcessGroupInstance[]) => {
    const base = Array.isArray(results) ? results : [];
    const order = Array.isArray(config) ? config : [];
    if (base.length === 0 || order.length === 0) return base;

    const configuredBaseNames = new Set<string>();
    for (const inst of order) {
      const bn = (inst?.groupName || '').trim();
      if (bn) configuredBaseNames.add(bn);
    }

    const baseNameToNodes = new Map<string, any[]>();
    for (const r of base) {
      const bn = (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim();
      if (!bn) continue;
      if (!baseNameToNodes.has(bn)) baseNameToNodes.set(bn, []);
      baseNameToNodes.get(bn)!.push(r);
    }

    const expanded: any[] = [];
    for (const inst of order) {
      const bn = (inst?.groupName || '').trim();
      if (!bn) continue;
      const nodes = baseNameToNodes.get(bn) || [];
      for (const n of nodes) {
        expanded.push({
          ...n,
          processGroupBase: bn,
          processGroupInstanceId: inst.id,
          processGroupInstanceName: inst.displayName,
        });
      }
    }

    const leftovers = base.filter(r => {
      const bn = (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim();
      return bn ? !configuredBaseNames.has(bn) : true;
    });

    return expanded.concat(leftovers);
  };

  const [selectedRecords, setSelectedRecords] = useState<string[]>([]);
  const [recordDetails, setRecordDetails] = useState<any[]>([]);
  const [loading, setLoading] = useState(false);
  const [queryResults, setQueryResults] = useState<any[]>([]);
  const [queryLoading, setQueryLoading] = useState(false);
  const [secondaryProcessResults, setSecondaryProcessResults] = useState<any[]>([]);
  const [secondaryProcessLoading, setSecondaryProcessLoading] = useState(false);
  const [pricingDetailsResults, setPricingDetailsResults] = useState<any[]>([]);
  const [pricingDetailsLoading, setPricingDetailsLoading] = useState(false);
  
  // 标签相关状态
  const [labelOptions, setLabelOptions] = useState<{[key: string]: any[]}>({});
  const [labelAdjustmentFlow, setLabelAdjustmentFlow] = useState(false);
  const [selectedLabels, setSelectedLabels] = useState<{[key: string]: string | string[]}>({});
  const [labelLoading, setLabelLoading] = useState(false);
  const [excludedDatesOverride, setExcludedDatesOverride] = useState<string[]>([]);
  const [excludedDatesOverrideText, setExcludedDatesOverrideText] = useState<string>('');
  const [excludedDatesByNodeOverride, setExcludedDatesByNodeOverride] = useState<Record<string, string[]>>({});
  const excludedDatesByNodeOverrideRef = useRef<Record<string, string[]>>({});
  const pendingRecalculateAfterExcludedDatesRef = useRef(false);
  const pendingRecalculateAfterCalculateRef = useRef(false);
  const pendingRecalculateAfterCalculateAdjustmentsRef = useRef<Record<string, number> | null>(null);
  const [excludedDatesAdjustVisible, setExcludedDatesAdjustVisible] = useState(false);
  const [excludedDatesByNodeDraft, setExcludedDatesByNodeDraft] = useState<Record<string, string[]>>({});
  const [excludedDatesAddDraft, setExcludedDatesAddDraft] = useState<Record<string, Date | null>>({});
  const pendingGroupConfigCalcRef = useRef<{
    overrideData?: {
      selectedRecords?: string[],
      recordDetails?: any[],
      selectedLabels?: { [key: string]: string | string[] },
      expectedDate?: Date | null,
      startTime?: Date | null,
      excludedDates?: string[]
    },
    showUI: boolean
  } | null>(null);
  
  // 客户期望日期状态
  const [expectedDate, setExpectedDate] = useState<Date | null>(null);
  // 起始时间状态（从货期记录表获取，新记录则使用当前时间）
  const [startTime, setStartTime] = useState<Date | null>(null);
  const [timelineDirection, setTimelineDirection] = useState<'forward' | 'backward'>('forward');
  const [calculatedRequiredStartTime, setCalculatedRequiredStartTime] = useState<Date | null>(null);
  const [allocationVisible, setAllocationVisible] = useState(false);
  const [allocationMode, setAllocationMode] = useState<'auto' | 'manual'>('auto');
  const [allocationExtraDays, setAllocationExtraDays] = useState<number>(0);
  const [allocationDraft, setAllocationDraft] = useState<{[key: number]: number}>({});
  const [allocationNodesSnapshot, setAllocationNodesSnapshot] = useState<any[]>([]);
  const [allocationExcluded, setAllocationExcluded] = useState<{[key: number]: boolean}>({});
  const [groupOrderConfig, setGroupOrderConfig] = useState<ProcessGroupInstance[]>([]);
  const [groupConfigVisible, setGroupConfigVisible] = useState(false);
  const [groupOrderDraft, setGroupOrderDraft] = useState<ProcessGroupInstance[]>([]);
  const [draggingGroupIndex, setDraggingGroupIndex] = useState<number | null>(null);
  
  // 预览相关状态（已移除未使用的 previewLoading 状态）
  
  // 时效计算相关状态
  const [timelineVisible, setTimelineVisible] = useState(false);
  const [timelineLoading, setTimelineLoading] = useState(false);
  const [timelineResults, setTimelineResults] = useState<any[]>([]);
  const [timelineAdjustments, setTimelineAdjustments] = useState<Record<string, number>>({});
  // 交期余量扣减状态：记录从交期余量中扣减的天数
  const [deliveryMarginDeductions, setDeliveryMarginDeductions] = useState<number>(0);
  // 最后流程完成日期调整状态：记录最后流程完成日期增加的天数
  const [completionDateAdjustment, setCompletionDateAdjustment] = useState<number>(0);
  // 实际完成日期状态：记录每个节点的实际完成日期
  const [actualCompletionDates, setActualCompletionDates] = useState<{[key: number]: Date | null}>({});
  // 基础缓冲期天数（可配置），用于计算动态缓冲期，默认14天
  const [baseBufferDays, setBaseBufferDays] = useState<number>(14);
  const [lockedExpectedDeliveryDateTs, setLockedExpectedDeliveryDateTs] = useState<number | null>(null);
  const [isExpectedDeliveryDateLocked, setIsExpectedDeliveryDateLocked] = useState<boolean>(false);
  // 快照回填来源（foreign_id、款式、颜色、文本2）
  const [currentForeignId, setCurrentForeignId] = useState<string | null>(null);
  const [currentStyleText, setCurrentStyleText] = useState<string>('');
  const [currentColorText, setCurrentColorText] = useState<string>('');
  // 标题中的款号/颜色编辑开关：默认锁定，点击笔按钮开放编辑
  const [styleColorEditable, setStyleColorEditable] = useState<boolean>(false);
  const [currentText2, setCurrentText2] = useState<string>('');
  const [currentVersionNumber, setCurrentVersionNumber] = useState<number | null>(null);
  const [currentDeliveryRecordId, setCurrentDeliveryRecordId] = useState<string | null>(null);
  // 功能入口模式与调整相关状态
  const [mode, setMode] = useState<'generate' | 'adjust' | null>(null);
  const [modeSelectionVisible, setModeSelectionVisible] = useState(true);
  const [adjustLoading, setAdjustLoading] = useState(false);
  const [batchModalVisible, setBatchModalVisible] = useState(false);
  const [batchStartRow, setBatchStartRow] = useState<number>(1);
  const [batchEndRow, setBatchEndRow] = useState<number>(1);
  const [batchTotalRows, setBatchTotalRows] = useState<number>(0);
  const [batchLoading, setBatchLoading] = useState(false);
  const [batchProcessedCount, setBatchProcessedCount] = useState<number>(0);
  const [batchProcessingTotal, setBatchProcessingTotal] = useState<number>(0);
  const [batchSuccessCount, setBatchSuccessCount] = useState<number>(0);
  const [batchFailureCount, setBatchFailureCount] = useState<number>(0);
  const [batchProgressList, setBatchProgressList] = useState<{ index: number; foreignId: string; status: 'success' | 'failed'; message?: string }[]>([]);
  const [batchCurrentRowInfo, setBatchCurrentRowInfo] = useState<{ index: number; foreignId: string; style: string; color: string } | null>(null);
  const batchAbortRef = useRef<boolean>(false);
  const lastBufferDeficitRef = useRef<number>(0);
  // 删除未使用的 deliveryRecords 状态
  const [selectedDeliveryRecordId, setSelectedDeliveryRecordId] = useState<string>('');
  // 从货期记录读取到的record_ids（用于保存回写）
  const [restoredRecordIds, setRestoredRecordIds] = useState<string[]>([]);
  // 原始文本格式的record_ids（不做JSON化写回）
  const [restoredRecordIdsText, setRestoredRecordIdsText] = useState<string>('');
  
  // 已移除：批量处理与表/视图选择相关状态
  
  // 全局变量重置：在切换功能或切换版单/批量数据时，清空页面与计算相关状态
  const resetGlobalState = (opts?: { resetMode?: boolean }) => {
    // 运行时加载状态
    setLoading(false);
    setQueryLoading(false);
    setSecondaryProcessLoading(false);
    setPricingDetailsLoading(false);
    setLabelLoading(false);
    setAdjustLoading(false);
    setTimelineLoading(false);

    // 页面与计算数据
    setSelectedRecords([]);
    setRecordDetails([]);
    setSelectedLabels({});
    setExcludedDatesOverride([]);
    setExcludedDatesOverrideText('');
    setExcludedDatesByNodeOverride({});
    excludedDatesByNodeOverrideRef.current = {};
    setExcludedDatesAdjustVisible(false);
    setExcludedDatesByNodeDraft({});
    setExcludedDatesAddDraft({});
    setExpectedDate(null);
    setStartTime(null);
    setCalculatedRequiredStartTime(null);
    setAllocationVisible(false);
    setAllocationExtraDays(0);
    setAllocationDraft({});
    setAllocationExcluded({});
    setAllocationNodesSnapshot([]);
    setTimelineVisible(false);
    setTimelineResults([]);
    setTimelineAdjustments({});
    // 新增：重置固定缓冲期、实际完成日期以及一次性建议缓冲期应用标志
    setBaseBufferDays(14);
    setActualCompletionDates({});
    setHasAppliedSuggestedBuffer(false);
    setIsRestoringSnapshot(false);
    setRestoredRecordIds([]);
    setRestoredRecordIdsText('');
    setLockedExpectedDeliveryDateTs(null);
    setIsExpectedDeliveryDateLocked(false);

    // 重置初始快照捕获状态
    try {
      hasCapturedInitialSnapshotRef.current = false;
      initialSnapshotRef.current = null;
    } catch {}
    try {
      lastBufferDeficitRef.current = 0;
    } catch {}

    // 移除：批量模式当前记录信息

    // 当前回填状态
    setCurrentForeignId(null);
    setCurrentStyleText('');
    setCurrentColorText('');
    setCurrentText2('');
    setCurrentVersionNumber(null);
    setCurrentDeliveryRecordId(null);
    setLabelAdjustmentFlow(false);

    setGroupOrderConfig([]);
    setGroupConfigVisible(false);
    setGroupOrderDraft([]);
    setDraggingGroupIndex(null);
    pendingGroupConfigCalcRef.current = null;

    // 可选：重置模式
    if (opts?.resetMode) {
      setMode(null);
      setModeSelectionVisible(true);
    }
  };

  // 指定的数据表ID和视图ID
  const TABLE_ID = 'tblPIJ7unndydSMu';
  
  // 当弹窗打开时，默认从记录/选择数据预填款号与颜色（不覆盖已有值）
  const ensureStyleColorDefaults = async () => {
    try {
      const needStyle = !currentStyleText || currentStyleText.trim() === '';
      const needColor = !currentColorText || currentColorText.trim() === '';
      if (!needStyle && !needColor) return;

      // 优先使用已读取的记录详情
      const currentRecordDetails = recordDetails;
      if (currentRecordDetails && currentRecordDetails.length > 0) {
        const first = currentRecordDetails[0];
        const styleVal = needStyle ? extractText(first?.fields?.['fld6Uw95kt']) || '' : currentStyleText;
        const colorVal = needColor ? extractText(first?.fields?.['flde85ni4O']) || '' : currentColorText;
        if (needStyle && styleVal) setCurrentStyleText(styleVal);
        if (needColor && colorVal) setCurrentColorText(colorVal);
        return;
      }

      // 其次使用当前选择记录，做一次读取
      if (selectedRecords && selectedRecords.length > 0) {
        try {
          const table = await bitable.base.getTable(TABLE_ID);
          const firstRecord = await table.getRecordById(selectedRecords[0]);
          const styleVal2 = needStyle ? extractText(firstRecord?.fields?.['fld6Uw95kt']) || '' : currentStyleText;
          const colorVal2 = needColor ? extractText(firstRecord?.fields?.['flde85ni4O']) || '' : currentColorText;
          if (needStyle && styleVal2) setCurrentStyleText(styleVal2);
          if (needColor && colorVal2) setCurrentColorText(colorVal2);
        } catch (e) {
          console.warn('读取所选记录的款号/颜色失败，保持现有值', e);
        }
      }
    } catch (e) {
      console.warn('预填款号/颜色失败', e);
    }
  };

  useEffect(() => {
    if (timelineVisible) {
      // 打开弹窗时进行一次预填，避免手动输入
      ensureStyleColorDefaults();
    }
  }, [timelineVisible]);

  useEffect(() => {
    excludedDatesByNodeOverrideRef.current = excludedDatesByNodeOverride || {};
  }, [excludedDatesByNodeOverride]);

  useEffect(() => {
    if (!groupConfigVisible && groupOrderConfig.length > 0 && pendingGroupConfigCalcRef.current) {
      const payload = pendingGroupConfigCalcRef.current;
      pendingGroupConfigCalcRef.current = null;
      setTimelineLoading(true);
      handleCalculateTimeline(true, payload?.overrideData, payload?.showUI ?? true);
    }
  }, [groupConfigVisible, groupOrderConfig]);

  const VIEW_ID = 'vewb28sjuX';

  // 标签表ID
  const LABEL_TABLE_ID = 'tblPnQscqwqopJ8V';

  const PROCESS_CONFIG_TABLE_ID = 'tblMygOc6T9o4sYU';
  const NODE_NAME_FIELD_ID = 'fld0g9L9Fw';
  const PROCESS_LABEL_FIELD_ID = 'fldrVTa23X';
  const PROCESS_ORDER_FIELD_ID = 'fldbfJQ4Zs';
  const WEEKEND_DAYS_FIELD_ID = 'fld2BvjbIN';
  const START_DATE_RULE_FIELD_ID = 'fld0KsQ2j3';
  const DATE_ADJUSTMENT_RULE_FIELD_ID = 'fld0KsQ2j3';
  const EXCLUDED_DATES_FIELD_ID = 'fldGxzC5uG';
  const PROCESS_GROUP_FIELD_ID = 'fldtjxS4oO';
  
  const TIMELINE_TABLE_ID = 'tblPnQscqwqopJ8V'; // 时效数据表ID
  const TIMELINE_FIELD_ID = 'fldniJmZe3'; // 时效值字段ID
  const TIMELINE_NODE_FIELD_ID = 'fldeIZzokl'; // 时效表中的节点名称字段ID
  const CALCULATION_METHOD_FIELD_ID = 'fldxfLZNUu'; // 时效计算方式字段ID
  const TIMELINE_LABEL12_FIELD_ID = 'fldZZmb2LV'; // 标签12（时效数据表）

  // 新表ID（批量生成表）
  const BATCH_TABLE_ID = 'tblXO7iSxBYxrqtY';
  const BATCH_ROW_NUMBER_FIELD_ID = 'fldiqlTVsU';

  const activateTableForPaging = async (table: any) => {
    try {
      if (typeof (table as any)?.getRecordIdListByPage === 'function') {
        await (table as any).getRecordIdListByPage({ pageSize: 1 });
        return;
      }
    } catch {}
    try {
      if (typeof (table as any)?.getRecordIdList === 'function') {
        await (table as any).getRecordIdList();
      }
    } catch {}
  };

  const safeGetRecordsByPage = async (table: any, req: any) => {
    const cleaned: any = { ...(req || {}) };
    if (!cleaned.viewId) delete cleaned.viewId;
    if (!cleaned.pageToken) delete cleaned.pageToken;
    if (!cleaned.pageSize || Number(cleaned.pageSize) <= 0) cleaned.pageSize = 200;
    if (Number(cleaned.pageSize) > 200) cleaned.pageSize = 200;

    try {
      return await table.getRecordsByPage(cleaned);
    } catch (e: any) {
      if (e?.code === 12) {
        await activateTableForPaging(table);
        return await table.getRecordsByPage(cleaned);
      }
      throw e;
    }
  };

  const fetchAllRecordsByPage = async (table: any, params?: any) => {
    let token: any = undefined;
    let all: any[] = [];
    for (let i = 0; i < 10000; i++) {
      const requestedPageSize = Math.min(200, (params && params.pageSize) ? params.pageSize : 200);
      const req: any = { pageSize: requestedPageSize, ...(params || {}) };
      if (token) req.pageToken = token;
      const res: any = await safeGetRecordsByPage(table, req);
      const recs: any[] = Array.isArray(res?.records) ? res.records : [];
      all = all.concat(recs);
      const nextToken = res?.pageToken || res?.nextPageToken;
      const hm = !!res?.hasMore;
      token = nextToken || undefined;
      if (!hm && !nextToken) break;
    }
    return all;
  };

  const getRecordTotalByPage = async (table: any, params?: any) => {
    const res: any = await table.getRecordIdListByPage({ pageSize: 1, ...(params || {}) });
    return res?.total || 0;
  };

  const parseBatchRowNumber = (raw: any): number | null => {
    const text = extractText(raw);
    const n = typeof raw === 'number'
      ? raw
      : (text && text.trim() !== '' ? Number(text) : NaN);
    return Number.isFinite(n) ? n : null;
  };

  // 已移除：调整模式不再加载货期记录列表

  // 入口选择处理
  const chooseMode = (m: 'generate' | 'adjust') => {
    // 切换功能时重置全局变量，但保留新的mode
    resetGlobalState({ resetMode: false });
    setMode(m);
    setIsExpectedDeliveryDateLocked(false);
    setLockedExpectedDeliveryDateTs(null);
    setModeSelectionVisible(false);
  };

  const openBatchModal = async () => {
    try {
      const batchTable = await bitable.base.getTable(BATCH_TABLE_ID);
      const total = await getRecordTotalByPage(batchTable);
      let totalByRowNumber = 0;
      try {
        const rows = await fetchAllRecordsByPage(batchTable, undefined);
        for (const r of rows) {
          const no = parseBatchRowNumber((r?.fields || {})[BATCH_ROW_NUMBER_FIELD_ID]);
          if (no !== null) totalByRowNumber = Math.max(totalByRowNumber, no);
        }
      } catch {}
      const totalRows = totalByRowNumber > 0 ? totalByRowNumber : total;
      setBatchTotalRows(totalRows);
      setBatchStartRow(1);
      setBatchEndRow(totalRows > 0 ? totalRows : 1);
      setBatchProcessingTotal(totalRows);
    } catch {
      setBatchTotalRows(0);
      setBatchStartRow(1);
      setBatchEndRow(1);
      setBatchProcessingTotal(0);
    }
    setBatchModalVisible(true);
  };

  const restoreBaseBufferDaysFromSnapshot = (snapshot: any) => {
    const snapshotDirection = snapshot?.timelineDirection;
    if (snapshotDirection === 'backward') return;
    const candidates: any[] = [
      snapshot?.bufferManagement?.baseDays,
      snapshot?.baseBufferDays,
      snapshot?.baseBuferDays,
      snapshot?.baseDays,
      snapshot?.bufferManagement?.baseBuferDays,
    ];
    for (const c of candidates) {
      const n = typeof c === 'number'
        ? c
        : (typeof c === 'string' ? parseFloat(c) : NaN);
      if (Number.isFinite(n)) {
        setBaseBufferDays(Math.max(0, Math.ceil(n)));
        return;
      }
    }
  };

  const restoreTimelineDirectionFromSnapshot = (snapshot: any) => {
    const candidates: any[] = [
      snapshot?.timelineDirection,
      snapshot?.timelineCalculationState?.timelineDirection,
      snapshot?.timelineCalculationState?.calculationDirection,
      snapshot?.calculationDirection,
    ];
    for (const c of candidates) {
      if (c === 'forward' || c === 'backward') {
        setTimelineDirection(c);
        return;
      }
    }
  };

  const normalizeExcludedDatesOverride = (raw: any): string[] => {
    const parts: string[] = [];
    if (Array.isArray(raw)) {
      for (const el of raw) {
        const s = typeof el === 'string' ? el : extractText(el);
        if (s) parts.push(s);
      }
    } else if (typeof raw === 'string') {
      parts.push(...raw.split(/[\,\n\r\s]+/));
    } else if (raw && typeof raw === 'object') {
      const s = extractText(raw);
      if (s) parts.push(...s.split(/[\,\n\r\s]+/));
    }

    const out: string[] = [];
    const seen = new Set<string>();
    for (const p of parts) {
      const t = (p || '').trim();
      if (!t) continue;
      const d = parseDate(t);
      if (!d || isNaN(d.getTime())) continue;
      const normalized = formatDate(d, 'DISPLAY_DATE_ONLY');
      if (!normalized) continue;
      if (!seen.has(normalized)) {
        seen.add(normalized);
        out.push(normalized);
      }
    }
    out.sort();
    return out;
  };

  const restoreExcludedDatesOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
    const candidates: any[] = [
      snapshot?.excludedDatesOverride,
      snapshot?.timelineCalculationState?.excludedDatesOverride,
      snapshot?.timelineCalculationState?.excludedDates,
    ];
    for (const c of candidates) {
      const normalized = normalizeExcludedDatesOverride(c);
      if (normalized.length > 0) {
        setExcludedDatesOverride(normalized);
        setExcludedDatesOverrideText(normalized.join('\n'));
        return;
      }
    }

    const results = Array.isArray(timelineResultsCandidate)
      ? timelineResultsCandidate
      : (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);

    if (results.length > 0) {
      const union: string[] = [];
      for (const r of results) {
        const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
        union.push(...arr);
      }
      const normalized = normalizeExcludedDatesOverride(union);
      if (normalized.length > 0) {
        setExcludedDatesOverride(normalized);
        setExcludedDatesOverrideText(normalized.join('\n'));
        return;
      }
    }

    setExcludedDatesOverride([]);
    setExcludedDatesOverrideText('');
  };

  const handleExcludedDatesOverrideTextChange = (next: any) => {
    const text = typeof next === 'string'
      ? next
      : (next?.target?.value ?? '');
    setExcludedDatesOverrideText(text);
    setExcludedDatesOverride(normalizeExcludedDatesOverride(text));
  };

  const buildExcludedDatesNodeKey = (nodeName: any, processOrder: any, indexFallback?: number, instanceKey?: any) => {
    const name = (typeof nodeName === 'string' ? nodeName : extractText(nodeName)).trim();
    const order = (typeof processOrder === 'number' || typeof processOrder === 'string')
      ? String(processOrder)
      : '';
    const inst = (typeof instanceKey === 'string' ? instanceKey : extractText(instanceKey)).trim();
    if (name && order) return inst ? `${order}::${name}::${inst}` : `${order}::${name}`;
    if (name) return inst ? `${name}::${inst}` : name;
    if (indexFallback !== undefined) return `#${indexFallback + 1}`;
    return '#unknown';
  };

  const buildTimelineAdjustmentKey = (node: any, indexFallback?: number) => {
    return buildExcludedDatesNodeKey(
      node?.nodeName,
      node?.processOrder,
      indexFallback,
      node?.processGroupInstanceId || node?.processGroupInstanceName
    );
  };

  const normalizeTimelineAdjustmentsFromSnapshot = (raw: any, resultsFromSnapshot?: any[]): Record<string, number> => {
    if (!raw || typeof raw !== 'object') return {};

    const entries = Object.entries(raw)
      .map(([k, v]) => [String(k).trim(), Number(v)] as const)
      .filter(([k, v]) => k && Number.isFinite(v) && v !== 0);

    if (entries.length === 0) return {};

    const snapshotResults = Array.isArray(resultsFromSnapshot) ? resultsFromSnapshot : [];
    const numericKeys: number[] = [];
    for (const [k] of entries) {
      const n = Number(k);
      if (Number.isInteger(n)) numericKeys.push(n);
    }

    if (snapshotResults.length > 0 && numericKeys.length > 0) {
      const byProcessOrder = new Map<number, { node: any; idx: number }>();
      for (let i = 0; i < snapshotResults.length; i++) {
        const r = snapshotResults[i];
        const orderRaw = r?.processOrder;
        const order = typeof orderRaw === 'number'
          ? orderRaw
          : (typeof orderRaw === 'string' ? Number(orderRaw) : NaN);
        if (!Number.isFinite(order)) continue;
        if (!byProcessOrder.has(order)) byProcessOrder.set(order, { node: r, idx: i });
      }

      const eps = 1e-6;
      const indexMatchHits = (() => {
        let hits = 0;
        for (const [k, v] of entries) {
          const n = Number(k);
          if (!Number.isInteger(n)) continue;
          if (n < 0 || n >= snapshotResults.length) continue;
          const r = snapshotResults[n];
          const adj = Number(r?.adjustment);
          if (Number.isFinite(adj) && Math.abs(adj - v) < eps) {
            hits++;
            continue;
          }
          const base = Number(r?.timelineValue);
          const adjusted = Number(r?.adjustedTimelineValue);
          if (Number.isFinite(base) && Number.isFinite(adjusted) && Math.abs((adjusted - base) - v) < eps) {
            hits++;
            continue;
          }
        }
        return hits;
      })();

      const processOrderHits = numericKeys.filter(k => k !== 0 && byProcessOrder.has(k)).length;
      const shouldTreatAsIndex = indexMatchHits >= Math.ceil(numericKeys.length / 2);
      const shouldTreatAsProcessOrder = !shouldTreatAsIndex && processOrderHits >= Math.ceil(numericKeys.length / 2);

      const out: Record<string, number> = {};
      for (const [k, v] of entries) {
        const n = Number(k);
        if (!Number.isInteger(n)) {
          out[k] = Math.round(v * 100) / 100;
          continue;
        }

        if (shouldTreatAsIndex && n >= 0 && n < snapshotResults.length) {
          out[buildTimelineAdjustmentKey(snapshotResults[n], n)] = Math.round(v * 100) / 100;
          continue;
        }

        if (shouldTreatAsProcessOrder) {
          const hit = byProcessOrder.get(n);
          if (hit) {
            out[buildTimelineAdjustmentKey(hit.node, hit.idx)] = Math.round(v * 100) / 100;
            continue;
          }
        }

        if (n >= 0 && n < snapshotResults.length) {
          out[buildTimelineAdjustmentKey(snapshotResults[n], n)] = Math.round(v * 100) / 100;
          continue;
        }

        out[k] = Math.round(v * 100) / 100;
      }

      return out;
    }

    const out: Record<string, number> = {};
    for (const [k, v] of entries) {
      out[k] = Math.round((v as number) * 100) / 100;
    }
    return out;
  };

  const deriveTimelineAdjustmentsFromResults = (resultsFromSnapshot: any[]): Record<string, number> => {
    const results = Array.isArray(resultsFromSnapshot) ? resultsFromSnapshot : [];
    const out: Record<string, number> = {};
    for (let i = 0; i < results.length; i++) {
      const r = results[i];
      const v = Number(r?.adjustment);
      if (!Number.isFinite(v) || v === 0) continue;
      const key = buildTimelineAdjustmentKey(r, i);
      out[key] = Math.round(v * 100) / 100;
    }
    return out;
  };

  const tryParseProcessOrderFromKey = (nodeKey: string): number | null => {
    const key = String(nodeKey || '').trim();
    const idx = key.indexOf('::');
    if (idx <= 0) return null;
    const left = key.slice(0, idx).trim();
    const order = Number(left);
    if (!Number.isFinite(order)) return null;
    return order;
  };

  const extractNodeNameFromKey = (nodeKey: string): string => {
    const key = String(nodeKey || '').trim();
    const idx = key.indexOf('::');
    if (idx < 0) return key;
    return key.slice(idx + 2).trim();
  };

  const normalizeNodeNameForMatch = (name: any): string => {
    const raw = (typeof name === 'string' ? name : extractText(name)).trim();
    if (!raw) return '';
    return raw
      .replace(/[\s:：\-_.()（）【】\[\]<>《》]/g, '')
      .toLowerCase();
  };

  const remapTimelineAdjustmentsToNewResults = (
    prevResults: any[],
    nextResults: any[]
  ): Record<string, number> => {
    const out: Record<string, number> = {};
    const prevArr = Array.isArray(prevResults) ? prevResults : [];
    const prevAdjustmentByName = new Map<string, number>();
    for (let i = 0; i < prevArr.length; i++) {
      const r = prevArr[i];
      const name = (typeof r?.nodeName === 'string' ? r.nodeName : extractText(r?.nodeName)).trim();
      if (!name) continue;
      const rowAdj = Number(r?.adjustment);
      if (Number.isFinite(rowAdj) && rowAdj !== 0) {
        const existed = prevAdjustmentByName.get(name);
        if (existed === undefined || existed === 0) prevAdjustmentByName.set(name, Math.round(rowAdj * 100) / 100);
        continue;
      }
      const base = Number(r?.timelineValue);
      const adjusted = Number(r?.adjustedTimelineValue);
      if (Number.isFinite(base) && Number.isFinite(adjusted)) {
        const d = adjusted - base;
        if (Number.isFinite(d) && d !== 0) {
          const existed = prevAdjustmentByName.get(name);
          if (existed === undefined || existed === 0) prevAdjustmentByName.set(name, Math.round(d * 100) / 100);
        }
      }
    }

    const nextArr = Array.isArray(nextResults) ? nextResults : [];
    for (let i = 0; i < nextArr.length; i++) {
      const node = nextArr[i];
      const name = (typeof node?.nodeName === 'string' ? node.nodeName : extractText(node?.nodeName)).trim();
      if (!name) continue;
      const adj = prevAdjustmentByName.get(name);
      if (adj === undefined || adj === 0) continue;
      out[buildTimelineAdjustmentKey(node, i)] = Math.round(adj * 100) / 100;
    }

    return out;
  };

  const buildExcludedDatesByNodeFromTimeline = (results: any[]): Record<string, string[]> => {
    const map: Record<string, string[]> = {};
    for (let i = 0; i < results.length; i++) {
      const r = results[i];
      const key = buildExcludedDatesNodeKey(
        r?.nodeName,
        r?.processOrder,
        i,
        r?.processGroupInstanceId || r?.processGroupInstanceName
      );
      const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
      map[key] = normalizeExcludedDatesOverride(arr);
    }
    return map;
  };

  const normalizeExcludedDatesByNodeMap = (raw: any): Record<string, string[]> => {
    const out: Record<string, string[]> = {};
    if (!raw) return out;
    if (typeof raw !== 'object') return out;

    for (const [k, v] of Object.entries(raw)) {
      const key = (k || '').trim();
      if (!key) continue;
      const normalized = normalizeExcludedDatesOverride(v);
      out[key] = normalized;
    }
    return out;
  };

  const restoreExcludedDatesByNodeOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
    const candidates: any[] = [
      snapshot?.excludedDatesByNodeOverride,
      snapshot?.excludedDatesByNode,
      snapshot?.timelineCalculationState?.excludedDatesByNodeOverride,
      snapshot?.timelineCalculationState?.excludedDatesByNode,
    ];

    for (const c of candidates) {
      const normalized = normalizeExcludedDatesByNodeMap(c);
      if (Object.keys(normalized).length > 0) {
        excludedDatesByNodeOverrideRef.current = normalized;
        setExcludedDatesByNodeOverride(normalized);
        return;
      }
    }

    const results = Array.isArray(timelineResultsCandidate)
      ? timelineResultsCandidate
      : (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);
    if (results.length > 0) {
      const derived = buildExcludedDatesByNodeFromTimeline(results);
      excludedDatesByNodeOverrideRef.current = derived;
      setExcludedDatesByNodeOverride(derived);
      return;
    }

    excludedDatesByNodeOverrideRef.current = {};
    setExcludedDatesByNodeOverride({});
  };

  const groupDatesByMonth = (dates: string[]) => {
    const groups = new Map<string, string[]>();
    for (const d of dates) {
      const ds = (d || '').trim();
      if (!ds) continue;
      const month = ds.slice(0, 7);
      if (!groups.has(month)) groups.set(month, []);
      groups.get(month)!.push(ds);
    }
    const out = Array.from(groups.entries())
      .map(([month, ds]) => ({
        month,
        dates: Array.from(new Set(ds)).sort(),
      }))
      .sort((a, b) => a.month.localeCompare(b.month));
    return out;
  };

  const openExcludedDatesAdjustModal = () => {
    const baseResults = Array.isArray(timelineResults) ? timelineResults : [];
    const draft: Record<string, string[]> = {};
    for (let i = 0; i < baseResults.length; i++) {
      const r = baseResults[i];
      const key = buildExcludedDatesNodeKey(
        r?.nodeName,
        r?.processOrder,
        i,
        r?.processGroupInstanceId || r?.processGroupInstanceName
      );
      const existing = excludedDatesByNodeOverrideRef.current?.[key];
      const fromResult = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
      draft[key] = normalizeExcludedDatesOverride(existing ?? fromResult);
    }
    setExcludedDatesByNodeDraft(draft);
    setExcludedDatesAddDraft({});
    setExcludedDatesAdjustVisible(true);
  };

  const closeExcludedDatesAdjustModal = () => {
    setExcludedDatesAdjustVisible(false);
    setExcludedDatesByNodeDraft({});
    setExcludedDatesAddDraft({});
  };

  const removeExcludedDateFromDraft = (nodeKey: string, date: string) => {
    setExcludedDatesByNodeDraft(prev => {
      const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
      const next = current.filter(d => d !== date);
      return { ...(prev || {}), [nodeKey]: next };
    });
  };

  const addExcludedDateToDraft = (nodeKey: string, date: Date | null) => {
    if (!(date instanceof Date) || isNaN(date.getTime())) return;
    const normalized = formatDate(date, 'DISPLAY_DATE_ONLY');
    if (!normalized) return;
    setExcludedDatesByNodeDraft(prev => {
      const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
      const next = Array.from(new Set([...current, normalized])).sort();
      return { ...(prev || {}), [nodeKey]: next };
    });
  };

  const clearExcludedDatesDraftForNode = (nodeKey: string) => {
    setExcludedDatesByNodeDraft(prev => ({ ...(prev || {}), [nodeKey]: [] }));
  };

  // 根据货期记录ID读取节点详情并还原流程数据
  const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => {
    if (!deliveryRecordId) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.warning, message: '请先选择一条货期记录' });
      }
      return;
    }
    // 新增：在读取新单据前重置关键状态，避免跨单据串值（缓冲期/实际完成日期/起始日期等）
    // 保留当前模式不变
    resetGlobalState({ resetMode: false });
      setTimelineLoading(true);
    try {
      const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
      const deliveryRecord: any = await deliveryTable.getRecordById(deliveryRecordId);
      const deliveryFields: any = deliveryRecord?.fields || {};

      const nodeDetailsVal = deliveryFields?.[DELIVERY_NODE_DETAILS_FIELD_ID];
      let expectedDeliveryTsFromField: number | null = null;
      try {
        const expectedDeliveryVal = deliveryFields?.[DELIVERY_EXPECTED_DATE_FIELD_ID];
        let ts: number | null = null;
        if (typeof expectedDeliveryVal === 'number') {
          ts = expectedDeliveryVal;
        } else if (Array.isArray(expectedDeliveryVal) && expectedDeliveryVal.length > 0 && typeof expectedDeliveryVal[0] === 'number') {
          ts = expectedDeliveryVal[0];
        } else {
          const extracted = extractText(expectedDeliveryVal);
          if (extracted && extracted.trim() !== '') {
            const parsed = new Date(extracted);
            if (!isNaN(parsed.getTime())) ts = parsed.getTime();
          }
        }
        expectedDeliveryTsFromField = ts;
      } catch {}
      // 读取record_ids文本字段并保留原始文本（用于原样写回）
      try {
        const recordIdsTextVal = deliveryFields?.[DELIVERY_RECORD_IDS_FIELD_ID];
        const raw = extractText(recordIdsTextVal);
        if (raw && raw.trim() !== '') {
          setRestoredRecordIdsText(raw.trim());
          // 若需要解析为数组供内部使用，可保留解析逻辑（不影响写回原始文本）
          try {
            const json = JSON.parse(raw);
            if (Array.isArray(json)) {
              const parsedFromText = json.filter((id: any) => typeof id === 'string' && id.trim() !== '');
              if (parsedFromText.length > 0) setRestoredRecordIds(parsedFromText);
            }
          } catch {
            const parsedFromText = raw.split(/[\,\s]+/).map((s: string) => s.trim()).filter(Boolean);
            if (parsedFromText.length > 0) setRestoredRecordIds(parsedFromText);
          }
        }
      } catch (e) {
        console.warn('解析record_ids文本字段失败，忽略:', e);
      }

      // 优先使用货期记录表中的快照字段进行一键还原（新方案）
      try {
        const deliverySnapVal = deliveryFields?.[DELIVERY_SNAPSHOT_JSON_FIELD_ID];
        let deliverySnapStr: string | null = null;
        if (typeof deliverySnapVal === 'string') {
          deliverySnapStr = deliverySnapVal;
        } else if (Array.isArray(deliverySnapVal)) {
          const texts = deliverySnapVal
            .filter((el: any) => el && el.type === 'text' && typeof el.text === 'string')
            .map((el: any) => el.text);
          deliverySnapStr = texts.length > 0 ? texts.join('') : null;
        } else if (deliverySnapVal && typeof deliverySnapVal === 'object') {
          if ((deliverySnapVal as any).text && typeof (deliverySnapVal as any).text === 'string') {
            deliverySnapStr = (deliverySnapVal as any).text;
          } else if ((deliverySnapVal as any).type === 'text' && typeof (deliverySnapVal as any).text === 'string') {
            deliverySnapStr = (deliverySnapVal as any).text;
          }
        }

        if (deliverySnapStr && deliverySnapStr.trim() !== '') {
          setIsRestoringSnapshot(true);
          const snapshot = JSON.parse(deliverySnapStr);
          restoreTimelineDirectionFromSnapshot(snapshot);
          restoreBaseBufferDaysFromSnapshot(snapshot);
          restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
          restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);

          // 恢复页面与全局状态
          if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
          if (!mode && snapshot.mode) setMode(snapshot.mode);
          if (snapshot.foreignId) setCurrentForeignId(snapshot.foreignId);
          if (snapshot.styleText) setCurrentStyleText(snapshot.styleText);
          if (snapshot.colorText) setCurrentColorText(snapshot.colorText);
          if (snapshot.text2) setCurrentText2(snapshot.text2);

          if (snapshot.generationModeState) {
            const genState = snapshot.generationModeState;
            if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId);
            if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText);
            if (genState.currentColorText) setCurrentColorText(genState.currentColorText);
            if (genState.currentText2) setCurrentText2(genState.currentText2);
            if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber);
            if (genState.recordDetails && Array.isArray(genState.recordDetails)) {
              setRecordDetails(genState.recordDetails);
            }
          }

          if (snapshot.version !== undefined) {
            let vNum: number | null = null;
            if (typeof snapshot.version === 'number') {
              vNum = snapshot.version;
            } else if (typeof snapshot.version === 'string') {
              const match = snapshot.version.match(/\d+/);
              if (match) vNum = parseInt(match[0], 10);
            }
            if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
          }

          {
            const normalized = snapshot.timelineAdjustments
              ? normalizeTimelineAdjustmentsFromSnapshot(snapshot.timelineAdjustments, snapshot?.timelineResults)
              : deriveTimelineAdjustmentsFromResults(snapshot?.timelineResults);
            setTimelineAdjustments(normalized);
          }
          {
            const snapLockedFlag = snapshot?.isExpectedDeliveryDateLocked;
            const snapLockedTs = snapshot?.lockedExpectedDeliveryDateTs;
            const shouldLock = snapLockedFlag === true || (snapLockedFlag === undefined && snapLockedTs !== undefined && snapLockedTs !== null);
            if (shouldLock) {
              const ts = (typeof snapLockedTs === 'number' && Number.isFinite(snapLockedTs))
                ? snapLockedTs
                : expectedDeliveryTsFromField;
              if (ts !== null && ts !== undefined) {
                setLockedExpectedDeliveryDateTs(ts);
                setIsExpectedDeliveryDateLocked(true);
              } else {
                setLockedExpectedDeliveryDateTs(null);
                setIsExpectedDeliveryDateLocked(false);
              }
            } else {
              setLockedExpectedDeliveryDateTs(null);
              setIsExpectedDeliveryDateLocked(false);
            }
          }
          restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
          restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
          if (snapshot.expectedDateTimestamp) {
            setExpectedDate(new Date(snapshot.expectedDateTimestamp));
          } else if (snapshot.expectedDateString) {
            setExpectedDate(new Date(snapshot.expectedDateString));
          }

          // 优先从快照恢复起始时间
          let startTimeRestored = false;
          if (snapshot.startTimestamp) {
            setStartTime(new Date(snapshot.startTimestamp));
            startTimeRestored = true;
          } else if (snapshot.startString) {
            const parsed = new Date(snapshot.startString);
            if (!isNaN(parsed.getTime())) {
              setStartTime(parsed);
              startTimeRestored = true;
            }
          }

          if (!startTimeRestored) {
            const startTimeValue = deliveryFields?.[DELIVERY_START_TIME_FIELD_ID];
            if (startTimeValue) {
              let extractedStartTime: Date | null = null;
              if (typeof startTimeValue === 'number') {
                extractedStartTime = new Date(startTimeValue);
              } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
                const timestamp = startTimeValue[0];
                if (typeof timestamp === 'number') extractedStartTime = new Date(timestamp);
              }
              if (extractedStartTime && !isNaN(extractedStartTime.getTime())) setStartTime(extractedStartTime);
            }
          }

          // 完整快照直接包含timelineResults，优先使用
          if (Array.isArray(snapshot.timelineResults)) {
            setTimelineResults(snapshot.timelineResults);
            Modal.confirm({
              title: '是否调整标签？',
              content: '选择“是”将允许修改标签并重新生成计划（版本按V2/V3/V4递增）',
              okText: '是，调整标签',
              cancelText: '否，直接还原',
              onOk: async () => {
                setLabelAdjustmentFlow(true);
                setTimelineVisible(false);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' });
                }
              },
              onCancel: async () => {
                setLabelAdjustmentFlow(false);
                setTimelineVisible(true);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.success, message: '已按货期记录快照还原流程数据' });
                }
              }
            });
            setTimelineLoading(false);
            setIsRestoringSnapshot(false);
            return;
          }

          // 兼容完整快照标识但没有直接timelineResults的情况
          if (snapshot.isCompleteSnapshot || snapshot.snapshotType === 'complete' || snapshot.isGlobalSnapshot) {
            // 若没有timelineResults，视为旧格式，保持兼容：不在此分支拼装，后续走旧流程
          } else {
            // 非完整快照则进入旧流程（从节点记录扫描快照）
          }
        }
      } catch (e) {
        console.warn('从货期记录快照字段还原失败，回退到旧流程:', e);
      }

      let recordIds: string[] = [];
      if (nodeDetailsVal && typeof nodeDetailsVal === 'object' && (nodeDetailsVal as any).recordIds) {
        recordIds = (nodeDetailsVal as any).recordIds as string[];
      } else if (Array.isArray(nodeDetailsVal)) {
        recordIds = nodeDetailsVal.map((item: any) => item?.recordId || item?.id || item).filter(Boolean);
      }

      // 注意：不将节点详情的recordIds写入restoredRecordIds，避免读取为空时写入非空

      if (!recordIds || recordIds.length === 0) {
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({ toastType: ToastType.warning, message: '该货期记录未包含节点详情或为空' });
        }
        setTimelineLoading(false);
        return;
      }

      const processTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID);
      try {
        const processSnapshotField: any = await processTable.getField(PROCESS_SNAPSHOT_JSON_FIELD_ID);
        let snapStr: string | null = null;
        for (const id of recordIds) {
          const snapVal = await processSnapshotField.getValue(id);
          const candidate = extractText(snapVal);
          if (candidate && candidate.trim() !== '') {
            snapStr = candidate;
            break;
          }
        }
          if (snapStr && snapStr.trim() !== '') {
            const snapshot = JSON.parse(snapStr);
            if (Array.isArray(snapshot.timelineResults)) {
              setIsRestoringSnapshot(true);
              restoreTimelineDirectionFromSnapshot(snapshot);
              restoreBaseBufferDaysFromSnapshot(snapshot);
              restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
              restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);

              if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
              if (!mode && snapshot.mode) setMode(snapshot.mode);
              if (snapshot.foreignId) setCurrentForeignId(snapshot.foreignId);
            if (snapshot.styleText) setCurrentStyleText(snapshot.styleText);
            if (snapshot.colorText) setCurrentColorText(snapshot.colorText);
            if (snapshot.text2) setCurrentText2(snapshot.text2);

            if (snapshot.generationModeState) {
              const genState = snapshot.generationModeState;
              if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId);
              if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText);
              if (genState.currentColorText) setCurrentColorText(genState.currentColorText);
              if (genState.currentText2) setCurrentText2(genState.currentText2);
              if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber);
              if (genState.recordDetails && Array.isArray(genState.recordDetails)) {
                setRecordDetails(genState.recordDetails);
              }
            }

            if (snapshot.version !== undefined) {
              let vNum: number | null = null;
              if (typeof snapshot.version === 'number') {
                vNum = snapshot.version;
              } else if (typeof snapshot.version === 'string') {
                const match = snapshot.version.match(/\d+/);
                if (match) {
                  vNum = parseInt(match[0], 10);
                }
              }
              if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
            }

            {
              const normalized = snapshot.timelineAdjustments
                ? normalizeTimelineAdjustmentsFromSnapshot(snapshot.timelineAdjustments, snapshot?.timelineResults)
                : deriveTimelineAdjustmentsFromResults(snapshot?.timelineResults);
              setTimelineAdjustments(normalized);
            }
            {
              const snapLockedFlag = snapshot?.isExpectedDeliveryDateLocked;
              const snapLockedTs = snapshot?.lockedExpectedDeliveryDateTs;
              const shouldLock = snapLockedFlag === true || (snapLockedFlag === undefined && snapLockedTs !== undefined && snapLockedTs !== null);
              if (shouldLock && typeof snapLockedTs === 'number' && Number.isFinite(snapLockedTs)) {
                setLockedExpectedDeliveryDateTs(snapLockedTs);
                setIsExpectedDeliveryDateLocked(true);
              } else {
                setLockedExpectedDeliveryDateTs(null);
                setIsExpectedDeliveryDateLocked(false);
              }
            }
            if (snapshot.expectedDateTimestamp) {
              setExpectedDate(new Date(snapshot.expectedDateTimestamp));
            } else if (snapshot.expectedDateString) {
              setExpectedDate(new Date(snapshot.expectedDateString));
            }

            let startTimeRestored = false;
            if (snapshot.startTimestamp) {
              setStartTime(new Date(snapshot.startTimestamp));
              startTimeRestored = true;
            } else if (snapshot.startString) {
              const parsed = new Date(snapshot.startString);
              if (!isNaN(parsed.getTime())) {
                setStartTime(parsed);
                startTimeRestored = true;
              }
            }
            if (!startTimeRestored) {
              const startTimeValue = deliveryFields?.[DELIVERY_START_TIME_FIELD_ID];
              if (startTimeValue) {
                let extractedStartTime: Date | null = null;
                if (typeof startTimeValue === 'number') {
                  extractedStartTime = new Date(startTimeValue);
                } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
                  const timestamp = startTimeValue[0];
                  if (typeof timestamp === 'number') {
                    extractedStartTime = new Date(timestamp);
                  }
                }
                if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
                  setStartTime(extractedStartTime);
                }
              }
            }

            setTimelineResults(snapshot.timelineResults);
            Modal.confirm({
              title: '是否调整标签？',
              content: '选择“是”将允许修改标签并重新生成计划（版本按V2/V3/V4递增）',
              okText: '是，调整标签',
              cancelText: '否，直接还原',
              onOk: async () => {
                setLabelAdjustmentFlow(true);
                setTimelineVisible(false);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' });
                }
              },
              onCancel: async () => {
                setLabelAdjustmentFlow(false);
                setTimelineVisible(true);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' });
                }
              }
            });
            setTimelineLoading(false);
            setIsRestoringSnapshot(false);
            return;
          }
        }
      } catch (e) {
        console.warn('从节点快照快速还原失败，继续旧流程:', e);
      }

      const records = await Promise.all(recordIds.map(id => processTable.getRecordById(id)));

      // 优先使用文本2快照一模一样还原
      try {
        // 在所有记录中查找非空快照
        let snapStr: string | null = null;
        for (const rec of records) {
          const snapVal = rec?.fields?.[PROCESS_SNAPSHOT_JSON_FIELD_ID];
          let candidate: string | null = null;
          if (typeof snapVal === 'string') {
            candidate = snapVal;
          } else if (Array.isArray(snapVal)) {
            // 文本结构：拼接所有text片段
            const texts = snapVal
              .filter((el: any) => el && el.type === 'text' && typeof el.text === 'string')
              .map((el: any) => el.text);
            candidate = texts.length > 0 ? texts.join('') : null;
          } else if (snapVal && typeof snapVal === 'object') {
            // 兼容 {text: '...'} 或 {type:'text', text:'...'}
            if ((snapVal as any).text && typeof (snapVal as any).text === 'string') {
              candidate = (snapVal as any).text;
            } else if ((snapVal as any).type === 'text' && typeof (snapVal as any).text === 'string') {
              candidate = (snapVal as any).text;
            }
          }
          if (candidate && candidate.trim() !== '') {
            snapStr = candidate;
            break;
          }
        }
        if (snapStr && snapStr.trim() !== '') {
          setIsRestoringSnapshot(true); // 开始快照还原
          const snapshot = JSON.parse(snapStr);
          restoreTimelineDirectionFromSnapshot(snapshot);
          restoreBaseBufferDaysFromSnapshot(snapshot);
          // 恢复页面状态
          if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
          // 保留当前模式，不覆写为快照的模式（避免调整模式被还原为生成模式）
          if (!mode && snapshot.mode) setMode(snapshot.mode);
          // 快照回填的foreign_id/款式/颜色/版本
          if (snapshot.foreignId) setCurrentForeignId(snapshot.foreignId);
          if (snapshot.styleText) setCurrentStyleText(snapshot.styleText);
          if (snapshot.colorText) setCurrentColorText(snapshot.colorText);
          if (snapshot.text2) setCurrentText2(snapshot.text2);
          
          // 恢复生成模式完整状态（如果存在）
          if (snapshot.generationModeState) {
            const genState = snapshot.generationModeState;
            if (genState.currentForeignId) setCurrentForeignId(genState.currentForeignId);
            if (genState.currentStyleText) setCurrentStyleText(genState.currentStyleText);
            if (genState.currentColorText) setCurrentColorText(genState.currentColorText);
            if (genState.currentText2) setCurrentText2(genState.currentText2);
            if (genState.currentVersionNumber !== undefined) setCurrentVersionNumber(genState.currentVersionNumber);
            if (genState.recordDetails && Array.isArray(genState.recordDetails)) {
              // 恢复记录详情（如果需要的话）
              console.log('恢复生成模式记录详情:', genState.recordDetails.length, '条记录');
            }
            console.log('恢复生成模式状态:', {
              hasSelectedLabels: genState.hasSelectedLabels,
              labelSelectionComplete: genState.labelSelectionComplete,
              recordCount: genState.recordDetails?.length || 0
            });
          }
          
          if (snapshot.version !== undefined) {
            let vNum: number | null = null;
            if (typeof snapshot.version === 'number') {
              vNum = snapshot.version;
            } else if (typeof snapshot.version === 'string') {
              const match = snapshot.version.match(/\d+/);
              if (match) {
                vNum = parseInt(match[0], 10);
              }
            }
            if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
          }
          {
            const normalized = snapshot.timelineAdjustments
              ? normalizeTimelineAdjustmentsFromSnapshot(snapshot.timelineAdjustments, snapshot?.timelineResults)
              : deriveTimelineAdjustmentsFromResults(snapshot?.timelineResults);
            setTimelineAdjustments(normalized);
          }
          restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
          restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
          if (snapshot.expectedDateTimestamp) {
            setExpectedDate(new Date(snapshot.expectedDateTimestamp));
          } else if (snapshot.expectedDateString) {
            setExpectedDate(new Date(snapshot.expectedDateString));
          }
          
          // 优先从快照恢复起始时间，如果快照中没有则从当前货期记录中获取
          let startTimeRestored = false;
          if (snapshot.startTimestamp) {
            setStartTime(new Date(snapshot.startTimestamp));
            startTimeRestored = true;
          } else if (snapshot.startString) {
            const parsed = new Date(snapshot.startString);
            if (!isNaN(parsed.getTime())) {
              setStartTime(parsed);
              startTimeRestored = true;
            }
          }
          
          // 如果快照中没有起始时间信息，则从当前选中的货期记录中获取
          if (!startTimeRestored) {
            const startTimeValue = deliveryFields?.[DELIVERY_START_TIME_FIELD_ID];
            if (startTimeValue) {
              let extractedStartTime: Date | null = null;
              if (typeof startTimeValue === 'number') {
                extractedStartTime = new Date(startTimeValue);
              } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
                const timestamp = startTimeValue[0];
                if (typeof timestamp === 'number') {
                  extractedStartTime = new Date(timestamp);
                }
              }
              
              if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
                setStartTime(extractedStartTime);
              }
            }
          }
          
          if (Array.isArray(snapshot.timelineResults)) {
            setTimelineResults(snapshot.timelineResults);
            Modal.confirm({
              title: '是否调整标签？',
              content: '选择“是”将允许修改标签并重新生成计划（版本按V2/V3/V4递增）',
              okText: '是，调整标签',
              cancelText: '否，直接还原',
              onOk: async () => {
                setLabelAdjustmentFlow(true);
                setTimelineVisible(false);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' });
                }
              },
              onCancel: async () => {
                setLabelAdjustmentFlow(false);
                setTimelineVisible(true);
                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' });
                }
              }
            });
            setTimelineLoading(false);
            setIsRestoringSnapshot(false);
            return;
          } else if (snapshot.isCompleteSnapshot || snapshot.snapshotType === 'complete' || 
                     snapshot.isGlobalSnapshot || snapshot.isCombinedSnapshot) {
            // 处理完整快照格式：每个节点都包含完整数据
            console.log('检测到完整快照格式，直接使用快照数据');
            
            // 如果是完整快照，直接使用其中的timelineResults
            if (snapshot.isCompleteSnapshot && snapshot.timelineResults) {
              setTimelineResults(snapshot.timelineResults);
              Modal.confirm({
                title: '是否调整标签？',
                content: '选择“是”将允许修改标签并重新生成计划（版本按V2/V3/V4递增）',
                okText: '是，调整标签',
                cancelText: '否，直接还原',
                onOk: async () => {
                  setLabelAdjustmentFlow(true);
                  setTimelineVisible(false);
                  if (bitable.ui.showToast) {
                    await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' });
                  }
                },
                onCancel: async () => {
                  setLabelAdjustmentFlow(false);
                  setTimelineVisible(true);
                  if (bitable.ui.showToast) {
                    await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' });
                  }
                }
              });
              if (snapshot.generationModeState) {
                console.log('恢复生成模式状态:', snapshot.generationModeState);
                const genState = snapshot.generationModeState;
                
                // 恢复foreign_id状态
                if (genState.currentForeignId) {
                  setCurrentForeignId(genState.currentForeignId);
                  console.log('恢复foreign_id状态:', genState.currentForeignId);
                }
                
                // 恢复款式和颜色状态
                if (genState.currentStyleText) {
                  setCurrentStyleText(genState.currentStyleText);
                  console.log('恢复款式状态:', genState.currentStyleText);
                }
                if (genState.currentColorText) {
                  setCurrentColorText(genState.currentColorText);
                  console.log('恢复颜色状态:', genState.currentColorText);
                }
                
                // 恢复text2状态
                if (genState.currentText2) {
                  setCurrentText2(genState.currentText2);
                  console.log('恢复text2状态:', genState.currentText2);
                }
                
                // 恢复版本号状态
                if (genState.currentVersionNumber !== undefined) {
                  setCurrentVersionNumber(genState.currentVersionNumber);
                  console.log('恢复版本号状态:', genState.currentVersionNumber);
                }
                
                // 恢复记录详情状态
                if (genState.recordDetails && Array.isArray(genState.recordDetails)) {
                  setRecordDetails(genState.recordDetails);
                  console.log('恢复记录详情状态，记录数量:', genState.recordDetails.length);
                }
              }
              
              // 恢复时间线计算状态
              if (snapshot.timelineCalculationState) {
                console.log('恢复时间线计算状态:', snapshot.timelineCalculationState);
              }
              
              if (bitable.ui.showToast) {
                await bitable.ui.showToast({ 
                  toastType: ToastType.success, 
                  message: '已按完整快照还原流程数据' 
                });
              }
              setTimelineLoading(false);
              setIsRestoringSnapshot(false);
              return;
            }
            
            // 兼容旧版本分散快照格式的处理逻辑
            // 新版本的分散快照格式：需要从所有节点收集数据
            console.log('检测到新版本快照格式，开始收集所有节点的快照数据');
            
            try {
              // 收集所有节点的快照数据
              const nodeSnapshots: any[] = [];
              let globalSnapshotData = snapshot.isCompleteSnapshot ? snapshot : null;
              
              // 遍历所有记录，收集快照数据
              for (const record of records) {
                const fields = record?.fields || {};
                const snapshotField = fields[PROCESS_SNAPSHOT_JSON_FIELD_ID];
                
                if (snapshotField) {
                  let nodeSnapStr = '';
                  
                  // 解析快照字段（支持多种格式）
                  if (typeof snapshotField === 'string') {
                    nodeSnapStr = snapshotField;
                  } else if (Array.isArray(snapshotField)) {
                    const texts = snapshotField
                      .filter((el: any) => el && el.type === 'text' && typeof el.text === 'string')
                      .map((el: any) => el.text);
                    nodeSnapStr = texts.length > 0 ? texts.join('') : '';
                  } else if (snapshotField && typeof snapshotField === 'object') {
                    if ((snapshotField as any).text && typeof (snapshotField as any).text === 'string') {
                      nodeSnapStr = (snapshotField as any).text;
                    }
                  }
                  
                  if (nodeSnapStr && nodeSnapStr.trim() !== '') {
                    try {
                      const nodeSnapshot = JSON.parse(nodeSnapStr);
                      
                      // 批量模式现在使用扁平化结构，直接从快照中提取全局数据
                      if (nodeSnapshot.isCompleteSnapshot && !globalSnapshotData) {
                        globalSnapshotData = {
                          version: nodeSnapshot.version,
                          foreignId: nodeSnapshot.foreignId,
                          styleText: nodeSnapshot.styleText,
                          colorText: nodeSnapshot.colorText,
                          text2: nodeSnapshot.text2,
                          mode: nodeSnapshot.mode,
                          timelineDirection: nodeSnapshot.timelineDirection,
                          lockedExpectedDeliveryDateTs: nodeSnapshot.lockedExpectedDeliveryDateTs,
                          isExpectedDeliveryDateLocked: nodeSnapshot.isExpectedDeliveryDateLocked,
                          selectedLabels: nodeSnapshot.selectedLabels,
                          expectedDateTimestamp: nodeSnapshot.expectedDateTimestamp,
                          expectedDateString: nodeSnapshot.expectedDateString,
                          startTimestamp: nodeSnapshot.startTimestamp,
                          startString: nodeSnapshot.startString,
                          timelineAdjustments: nodeSnapshot.timelineAdjustments,
                          // 恢复智能缓冲期管理状态
                          bufferManagement: nodeSnapshot.bufferManagement,
                          // 恢复连锁调整系统状态
                          chainAdjustmentSystem: nodeSnapshot.chainAdjustmentSystem,
                          // 恢复生成模式状态
                          generationModeState: nodeSnapshot.generationModeState,
                          // 恢复时间线计算状态
                          timelineCalculationState: nodeSnapshot.timelineCalculationState,
                          totalNodes: nodeSnapshot.totalNodes
                        };
                      }
                      
                      // 扁平化结构中，每个快照都包含完整的节点数据
                      if (nodeSnapshot.isCompleteSnapshot) {
                        // 确保adjustedTimelineValue有正确的默认值
                        const adjustedTimelineValue = nodeSnapshot.adjustedTimelineValue !== undefined ? 
                          nodeSnapshot.adjustedTimelineValue : nodeSnapshot.timelineValue;
                        
                        // 处理日期格式，优先使用时间戳格式
                        let estimatedStart = nodeSnapshot.estimatedStart;
                        let estimatedEnd = nodeSnapshot.estimatedEnd;
                        
                        // 如果有时间戳格式的日期，使用时间戳重新格式化
                        if (nodeSnapshot.estimatedStartTimestamp) {
                          try {
                            estimatedStart = formatDate(new Date(nodeSnapshot.estimatedStartTimestamp));
                          } catch (error) {
                            console.warn('时间戳格式开始时间转换失败:', error);
                          }
                        }
                        
                        if (nodeSnapshot.estimatedEndTimestamp) {
                          try {
                            estimatedEnd = formatDate(new Date(nodeSnapshot.estimatedEndTimestamp));
                          } catch (error) {
                            console.warn('时间戳格式结束时间转换失败:', error);
                          }
                        }
                        
                        // 调试日志
                        console.log(`节点 ${nodeSnapshot.nodeName} 快照还原:`, {
                          timelineValue: nodeSnapshot.timelineValue,
                          adjustedTimelineValue: adjustedTimelineValue,
                          originalAdjustedTimelineValue: nodeSnapshot.adjustedTimelineValue,
                          estimatedStart: estimatedStart,
                          estimatedEnd: estimatedEnd,
                          hasTimestamps: {
                            start: Boolean(nodeSnapshot.estimatedStartTimestamp),
                            end: Boolean(nodeSnapshot.estimatedEndTimestamp)
                          },
                          nodeCalculationState: nodeSnapshot.nodeCalculationState
                        });
                        
                        nodeSnapshots.push({
                          processOrder: nodeSnapshot.processOrder,
                          nodeName: nodeSnapshot.nodeName || nodeSnapshot.currentNodeName,
                          matchedLabels: nodeSnapshot.matchedLabels,
                          timelineValue: nodeSnapshot.timelineValue,
                          estimatedStart: estimatedStart,
                          estimatedEnd: estimatedEnd,
                          timelineRecordId: nodeSnapshot.timelineRecordId,
                          allMatchedRecords: nodeSnapshot.allMatchedRecords,
                          isAccumulated: nodeSnapshot.isAccumulated,
                          weekendDaysConfig: nodeSnapshot.weekendDaysConfig,
                          excludedDates: nodeSnapshot.excludedDates,
                          actualExcludedDates: nodeSnapshot.actualExcludedDates,
                          actualExcludedDatesCount: nodeSnapshot.actualExcludedDatesCount,
                          calculationMethod: nodeSnapshot.calculationMethod,
                          ruleDescription: nodeSnapshot.ruleDescription,
                          skippedWeekends: nodeSnapshot.skippedWeekends,
                          actualDays: nodeSnapshot.actualDays,
                          adjustedTimelineValue: adjustedTimelineValue,
                          adjustment: nodeSnapshot.adjustment || 0,
                          adjustmentDescription: nodeSnapshot.adjustmentDescription || '',
                          startDateRule: nodeSnapshot.startDateRule,
                          dateAdjustmentRule: nodeSnapshot.dateAdjustmentRule,
                          // 恢复节点计算状态
                          nodeCalculationState: nodeSnapshot.nodeCalculationState,
                          // 恢复连锁调整节点状态
                          chainAdjustmentNode: nodeSnapshot.chainAdjustmentNode
                        });
                      }
                    } catch (parseError) {
                      console.warn('解析节点快照失败:', parseError);
                    }
                  }
                }
              }
              
              // 按流程顺序排序节点快照
              nodeSnapshots.sort((a, b) => (a.processOrder || 0) - (b.processOrder || 0));
              
              console.log('收集到的节点快照数量:', nodeSnapshots.length);
              console.log('全局快照数据:', globalSnapshotData);
              
              // 验证数据完整性
              if (globalSnapshotData && globalSnapshotData.totalNodes && 
                  nodeSnapshots.length === globalSnapshotData.totalNodes) {
                
                // 重组完整的 timelineResults
                restoreTimelineDirectionFromSnapshot(globalSnapshotData);
                restoreBaseBufferDaysFromSnapshot(globalSnapshotData);
                restoreExcludedDatesOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
                restoreExcludedDatesByNodeOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
                setTimelineAdjustments(deriveTimelineAdjustmentsFromResults(nodeSnapshots));
                setTimelineResults(nodeSnapshots);
                setTimelineVisible(true);

                {
                  const snapLockedFlag = (globalSnapshotData as any)?.isExpectedDeliveryDateLocked;
                  const snapLockedTs = (globalSnapshotData as any)?.lockedExpectedDeliveryDateTs;
                  const shouldLock = snapLockedFlag === true || (snapLockedFlag === undefined && snapLockedTs !== undefined && snapLockedTs !== null);
                  if (shouldLock && typeof snapLockedTs === 'number' && Number.isFinite(snapLockedTs)) {
                    setLockedExpectedDeliveryDateTs(snapLockedTs);
                    setIsExpectedDeliveryDateLocked(true);
                  } else {
                    setLockedExpectedDeliveryDateTs(null);
                    setIsExpectedDeliveryDateLocked(false);
                  }
                }
                
                // 恢复智能缓冲期状态（如果存在）
                if (globalSnapshotData.bufferManagement) {
                  console.log('恢复智能缓冲期状态:', globalSnapshotData.bufferManagement);
                  // 这里可以添加额外的状态恢复逻辑，如果需要的话
                }
                
                // 恢复连锁调整系统状态（如果存在）
                if (globalSnapshotData.chainAdjustmentSystem) {
                  console.log('恢复连锁调整系统状态:', globalSnapshotData.chainAdjustmentSystem);
                  // 这里可以添加额外的状态恢复逻辑，如果需要的话
                }
                
                // 恢复生成模式状态（如果存在）
                if (globalSnapshotData.generationModeState) {
                  console.log('恢复生成模式状态:', globalSnapshotData.generationModeState);
                  const genState = globalSnapshotData.generationModeState;
                  
                  // 确保生成模式的关键状态被正确恢复
                  if (genState.currentForeignId && !currentForeignId) {
                    setCurrentForeignId(genState.currentForeignId);
                  }
                  if (genState.currentStyleText && !currentStyleText) {
                    setCurrentStyleText(genState.currentStyleText);
                  }
                  if (genState.currentColorText && !currentColorText) {
                    setCurrentColorText(genState.currentColorText);
                  }
                  if (genState.currentText2 && !currentText2) {
                    setCurrentText2(genState.currentText2);
                  }
                  if (genState.currentVersionNumber !== undefined && !currentVersionNumber) {
                    setCurrentVersionNumber(genState.currentVersionNumber);
                  }
                }
                
                // 恢复时间线计算状态（如果存在）
                if (globalSnapshotData.timelineCalculationState) {
                  console.log('恢复时间线计算状态:', globalSnapshotData.timelineCalculationState);
                }

                if (bitable.ui.showToast) {
                  await bitable.ui.showToast({ 
                    toastType: ToastType.success, 
                    message: `已从 ${nodeSnapshots.length} 个节点快照还原完整流程数据（包含智能缓冲期和连锁调整状态）` 
                  });
                }
                setTimelineLoading(false);
                setIsRestoringSnapshot(false); // 快照还原完成
                return; // 快照还原完成，退出函数
              } else {
                console.warn('快照数据不完整，降级为基于字段的还原');
                console.log('期望节点数:', globalSnapshotData?.totalNodes, '实际节点数:', nodeSnapshots.length);
              }
              
            } catch (collectError) {
              console.warn('收集节点快照数据失败，降级为基于字段的还原:', collectError);
            }
          }
        }
      } catch (snapError) {
        console.warn('解析快照失败，降级为基于字段的还原:', snapError);
        setIsRestoringSnapshot(false); // 快照还原失败，重置标志
      }

      // 如果到达这里，说明没有成功的快照还原，重置标志
      setIsRestoringSnapshot(false);

      const results = records.map((rec: any) => {
        const fields = rec?.fields || {};
        const processOrder = fields[PROCESS_ORDER_FIELD_ID_DATA];
        const nodeName = fields[PROCESS_NAME_FIELD_ID];
        const startTs = fields[ESTIMATED_START_DATE_FIELD_ID];
        const endTs = fields[ESTIMATED_END_DATE_FIELD_ID];
        const startDate = typeof startTs === 'number' ? new Date(startTs) : (startTs ? new Date(startTs) : null);
        const endDate = typeof endTs === 'number' ? new Date(endTs) : (endTs ? new Date(endTs) : null);
        return {
          processOrder: typeof processOrder === 'number' ? processOrder : parseInt(processOrder) || undefined,
          nodeName: typeof nodeName === 'string' ? nodeName : (nodeName?.text || ''),
          estimatedStart: startDate ? format(startDate, DATE_FORMATS.STORAGE_FORMAT) : '未找到时效数据',
          estimatedEnd: endDate ? format(endDate, DATE_FORMATS.STORAGE_FORMAT) : '未找到时效数据',
          timelineRecordId: rec?.id || rec?.recordId || null,
          timelineValue: undefined,
          calculationMethod: undefined,
          weekendDaysConfig: [],
          matchedLabels: [],
          skippedWeekends: 0,
          actualDays: undefined,
          startDateRule: undefined,
          dateAdjustmentRule: undefined,
          ruleDescription: undefined,
          adjustmentDescription: undefined
        };
      });

      // 如果没有快照恢复起始时间，则从当前货期记录中获取起始时间
      const startTimeValue = deliveryFields?.[DELIVERY_START_TIME_FIELD_ID];
      if (startTimeValue) {
        let extractedStartTime: Date | null = null;
        if (typeof startTimeValue === 'number') {
          extractedStartTime = new Date(startTimeValue);
        } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
          const timestamp = startTimeValue[0];
          if (typeof timestamp === 'number') {
            extractedStartTime = new Date(timestamp);
          }
        }
        
        if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
          setStartTime(extractedStartTime);
        }
      }

      const sorted = results.sort((a, b) => (a.processOrder || 0) - (b.processOrder || 0));
      setTimelineResults(sorted);
      setTimelineVisible(true);

      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.success, message: '已从货期记录还原流程数据' });
      }
    } catch (error) {
      console.error('从货期记录还原流程数据失败:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.error, message: `还原失败: ${(error as Error).message}` });
      }
    } finally {
      setTimelineLoading(false);
    }
  };

  // 流程数据表相关常量
  const PROCESS_DATA_TABLE_ID = 'tblsJzCxXClj5oK5'; // 流程数据表ID
  const FOREIGN_ID_FIELD_ID = 'fld3oxJmpr'; // foreign_id字段
  const PROCESS_NAME_FIELD_ID = 'fldR79qEG3'; // 流程名称字段
  const PROCESS_ORDER_FIELD_ID_DATA = 'fldmND6vjT'; // 流程顺序字段
  const ESTIMATED_START_DATE_FIELD_ID = 'fldlzvHjYP'; // 预计开始日期字段
  const ESTIMATED_END_DATE_FIELD_ID = 'fldaPtY7Jk'; // 预计完成日期字段
  const PROCESS_SNAPSHOT_JSON_FIELD_ID = 'fldSHTxfnC'; // 文本2：用于保存计算页面快照(JSON)
  const PROCESS_VERSION_FIELD_ID = 'fldwk5X7Yw'; // 版本字段
  const PROCESS_TIMELINESS_FIELD_ID = 'fldEYCXnWt'; // 时效字段（天）
  const PROCESS_STYLE_FIELD_ID = 'fld8xVqHJW'; // 款式字段（流程数据表）
  const PROCESS_COLOR_FIELD_ID = 'fld3F1zGYe'; // 颜色字段（流程数据表）
  const PROCESS_GROUP_FIELD_ID_DATA = 'fldq6PTX5F'; // 流程组字段（流程数据表，单选）

  // 货期记录表相关常量
  const DELIVERY_RECORD_TABLE_ID = 'tblwiA49gksQrnfg'; // 货期记录表ID
  const DELIVERY_FOREIGN_ID_FIELD_ID = 'fld0gAIcHS'; // foreign_id字段（需要替换为实际字段ID）
  const DELIVERY_LABELS_FIELD_ID = 'fldp0cDP2T'; // 标签汇总字段（需要替换为实际字段ID）
  const DELIVERY_STYLE_FIELD_ID = 'fldJRFxwB1'; // 款式字段（需要替换为实际字段ID）
  const DELIVERY_COLOR_FIELD_ID = 'fldhA1uBMy'; // 颜色字段（需要替换为实际字段ID）
  const DELIVERY_CREATE_TIME_FIELD_ID = 'fldP4w79LQ'; // 生成时间字段（需要替换为实际字段ID）
  const DELIVERY_EXPECTED_DATE_FIELD_ID = 'fldrjlzsxn'; // 预计交付日期字段（需要替换为实际字段ID）
  const DELIVERY_NODE_DETAILS_FIELD_ID = 'fldu1KL9yC'; // 节点详情字段（需要替换为实际字段ID）
  const DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID = 'fldYNluU8D'; // 客户期望日期字段
  const DELIVERY_ADJUSTMENT_INFO_FIELD_ID = 'fldNc6nNsz'; // 货期调整信息字段（需要替换为实际字段ID）
  const DELIVERY_VERSION_FIELD_ID = 'fld5OmvZrn'; // 版本字段（新增）
  const DELIVERY_SNAPSHOT_JSON_FIELD_ID = 'fldEYIvHeP'; // 货期记录表：完整快照(JSON)
  // 起始时间字段（货期记录表新增）
  const DELIVERY_START_TIME_FIELD_ID = 'fld727qCAv';
  // 文本2字段（货期记录表新增）
  const DELIVERY_TEXT2_FIELD_ID = 'fldG6LZnmU';
  // 记录ID文本字段（货期记录表新增）
  const DELIVERY_RECORD_IDS_FIELD_ID = 'fldq3u7h7H';
  const DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID = 'fldZFdZDKj';

  // OMS看板表相关常量（新增）
  const OMS_BOARD_TABLE_ID = 'tbl7j8bCpUbFmGuk'; // OMS看板表ID
  const OMS_PLAN_TEXT_FIELD_ID = 'fldH0jPZE0'; // OMS看板：货期计划（文本结构）
  const OMS_PLAN_VERSION_FIELD_ID = 'fldwlIUf4z'; // OMS看板：计划版本（公式数字）
  const OMS_DELIVERY_RECORD_ID_FIELD_ID = 'fldjEIP9yC'; // OMS看板：货期记录表record_id

  // 已移除中国法定节假日相关常量和配置

  // 这个变量声明也不需要了
  // const nodeNameToOptionId = new Map();

  // 统一的日期格式化函数
  const formatDate = (date: Date | string, formatType: keyof typeof DATE_FORMATS = 'DISPLAY_WITH_TIME'): string => {
    try {
      if (!date) return '';
      
      const dateObj = typeof date === 'string' ? parseDate(date) : date;
      if (!dateObj || isNaN(dateObj.getTime())) {
        return '';
      }
      
      return format(dateObj, DATE_FORMATS[formatType], { locale: zhCN });
    } catch (error) {
      console.error('日期格式化失败:', error, { date, formatType });
      return '';
    }
  };

  // 统一的日期解析函数
  const parseDate = (dateStr: string): Date | null => {
    try {
      if (!dateStr || dateStr.includes('未找到') || dateStr.includes('时效值为0')) {
        return null;
      }
      
      // 如果是时间戳格式，直接转换
      if (/^\d{13}$/.test(dateStr)) {
        const date = new Date(parseInt(dateStr));
        if (!isNaN(date.getTime())) {
          return date;
        }
      }
      
      // 移除所有星期信息（支持"星期X"和"周X"格式）
      let cleanStr = dateStr
        .replace(/\s*星期[一二三四五六日天]\s*/g, ' ')
        .replace(/\s*周[一二三四五六日天]\s*/g, ' ')
        .replace(/\s+/g, ' ')
        .trim();
      
      // 处理可能的格式问题：如果日期和时间连在一起了，添加空格
      cleanStr = cleanStr.replace(/(\d{4}-\d{2}-\d{2})(\d{2}:\d{2})/, '$1 $2');
      
      // 尝试标准解析
      let date = new Date(cleanStr);
      if (!isNaN(date.getTime())) {
        return date;
      }
      
      // 手动解析 "YYYY-MM-DD HH:mm" 或 "YYYY-MM-DD HH:mm:ss" 格式
      const match = cleanStr.match(/(\d{4})-(\d{2})-(\d{2})(?:\s+(\d{2}):(\d{2})(?::(\d{2}))?)?/);
      if (match) {
        const [, year, month, day, hour = '0', minute = '0', second = '0'] = match;
        return new Date(
          parseInt(year), 
          parseInt(month) - 1, 
          parseInt(day), 
          parseInt(hour), 
          parseInt(minute), 
          parseInt(second)
        );
      }
      
      throw new Error(`无法解析日期格式: ${cleanStr}`);
    } catch (error: any) {
      console.error('日期解析失败:', { dateStr, error: error?.message });
      return null;
    }
  };

  // 获取星期几（统一格式）
  const getDayOfWeek = (dateStr: string | Date): string => {
    try {
      const date = typeof dateStr === 'string' ? parseDate(dateStr) : dateStr;
      if (!date || isNaN(date.getTime())) {
        return '';
      }
      return WEEKDAYS[date.getDay()];
    } catch (error) {
      console.error('获取星期失败:', error, { dateStr });
      return '';
    }
  };

  // 重构计算实际跨度天数函数
  const calculateActualDays = (startDateStr: string | Date, endDateStr: string | Date): number => {
    try {
      const startDate = typeof startDateStr === 'string' ? parseDate(startDateStr) : startDateStr;
      const endDate = typeof endDateStr === 'string' ? parseDate(endDateStr) : endDateStr;
      
      if (!startDate || !endDate || isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
        console.log('日期解析失败:', { startDateStr, endDateStr, startDate, endDate });
        return 0;
      }
      
      // 计算日期差异（只考虑日期部分）
      const startDateOnly = new Date(startDate.getFullYear(), startDate.getMonth(), startDate.getDate());
      const endDateOnly = new Date(endDate.getFullYear(), endDate.getMonth(), endDate.getDate());
      
      const diffTime = endDateOnly.getTime() - startDateOnly.getTime();
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
      
      console.log('自然日计算:', { 
        startDateStr: formatDate(startDate), 
        endDateStr: formatDate(endDate), 
        diffDays 
      });
      
      return Math.max(0, diffDays);
    } catch (error) {
      console.error('计算自然日出错:', error, { startDateStr, endDateStr });
      return 0;
    }
  };

  // 计算跳过的周末天数 - 支持空的休息日配置
  const calculateSkippedWeekends = (startDate: Date | string, endDate: Date | string, weekendDays: number[] = []): number => {
    if (weekendDays.length === 0) return 0; // 如果没有配置休息日，则不跳过任何天数
    
    try {
      const start = typeof startDate === 'string' ? new Date(startDate) : startDate;
      const end = typeof endDate === 'string' ? new Date(endDate) : endDate;
      
      if (isNaN(start.getTime()) || isNaN(end.getTime())) {
        return 0;
      }
      
      let count = 0;
      const current = new Date(start);
      
      while (current <= end) {
        if (weekendDays.includes(current.getDay())) {
          count++;
        }
        current.setDate(current.getDate() + 1);
      }
      
      return count;
    } catch (error) {
      return 0;
    }
  };

  // 计算时间范围内实际跳过的自定义日期
  const calculateExcludedDatesInRange = (startDate: Date | string, endDate: Date | string, excludedDates: string[] = []): { count: number, dates: string[] } => {
    if (!excludedDates || excludedDates.length === 0) {
      return { count: 0, dates: [] };
    }
    
    try {
      const start = typeof startDate === 'string' ? parseDate(startDate) : startDate;
      const end = typeof endDate === 'string' ? parseDate(endDate) : endDate;
      
      if (!start || !end || isNaN(start.getTime()) || isNaN(end.getTime())) {
        return { count: 0, dates: [] };
      }
      
      const actualExcludedDates: string[] = [];
      
      for (const excludedDateStr of excludedDates) {
        const excludedDate = parseDate(excludedDateStr);
        if (excludedDate && excludedDate >= start && excludedDate <= end) {
          actualExcludedDates.push(excludedDateStr);
        }
      }
      
      return { count: actualExcludedDates.length, dates: actualExcludedDates };
    } catch (error) {
      console.error('计算跳过日期失败:', error);
      return { count: 0, dates: [] };
    }
  };

  // 已移除法定节假日跳过统计函数

  // 已移除中国节假日判断函数

  // 判断是否为自定义周末 - 支持空的休息日配置
  const isCustomWeekend = (date: Date, weekendDays: number[] = []): boolean => {
    if (weekendDays.length === 0) return false; // 如果没有配置休息日，则不认为是周末
    return weekendDays.includes(date.getDay());
  };

  // 判断是否为工作日 - 排除表格休息日、以及节点自定义不参与计算日期
  const isBusinessDay = (date: Date, weekendDays: number[] = [], excludedDates: string[] = []): boolean => {
    try {
      const dateStr = format(date, 'yyyy-MM-dd');
      const isExcluded = Array.isArray(excludedDates) && excludedDates.includes(dateStr);
      return !isCustomWeekend(date, weekendDays) && !isExcluded;
    } catch {
      return !isCustomWeekend(date, weekendDays);
    }
  };

  // 日期调整函数
  const adjustStartDateByRule = (date: Date, ruleJson: string): Date => {
    if (!ruleJson || ruleJson.trim() === '') {
      return date;
    }
    
    try {
      const config = JSON.parse(ruleJson);
      const adjustedDate = new Date(date);
      const currentDayOfWeek = adjustedDate.getDay(); // 0=周日, 1=周一, ..., 6=周六
      
      // 转换为1-7格式（1=周一, 7=周日）
      const dayOfWeek = currentDayOfWeek === 0 ? 7 : currentDayOfWeek;
      
      if (config.rules && Array.isArray(config.rules)) {
        for (const rule of config.rules) {
          if (rule.condition === 'dayOfWeek' && rule.value === dayOfWeek) {
            if (rule.action === 'delayToNextWeek') {
              // 计算下周目标日期
              const targetDay = rule.targetDay || 1; // 默认周一
              const daysToAdd = 7 - dayOfWeek + targetDay;
              adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);
              
              console.log(`应用规则: ${rule.description || '未知规则'}, 原日期: ${format(date, 'yyyy-MM-dd')}, 调整后: ${format(adjustedDate, 'yyyy-MM-dd')}`);
              break; // 只应用第一个匹配的规则
            }
          }
        }
      }
      
      return adjustedDate;
    } catch (error) {
      console.error('解析日期调整规则失败:', error);
      return date; // 解析失败时返回原日期
    }
  };

  // JSON格式日期调整函数 - 修改为返回调整结果和描述
  const adjustStartDateByJsonRule = (date: Date, ruleJson: string): { adjustedDate: Date, description?: string } => {
    if (!ruleJson || ruleJson.trim() === '') {
      return { adjustedDate: date };
    }
    
    try {
      const rules = JSON.parse(ruleJson);
      const dayOfWeek = date.getDay(); // 0=周日, 1=周一, ..., 6=周六
      const dayKey = dayOfWeek === 0 ? '7' : dayOfWeek.toString(); // 转换为1-7格式
      
      const rule = rules[dayKey];
      if (!rule) {
        return { adjustedDate: date }; // 没有匹配的规则，返回原日期
      }
      
      const adjustedDate = new Date(date);
      let description = rule.description || '';
      
      switch (rule.action) {
        case 'delayInSameWeek':
          // 在本周内延期到指定星期几
          const targetDay = rule.targetDay;
          const currentDay = dayOfWeek === 0 ? 7 : dayOfWeek; // 转换为1-7格式
          
          if (targetDay > currentDay) {
            // 延期到本周的目标日期
            adjustedDate.setDate(adjustedDate.getDate() + (targetDay - currentDay));
          } else {
            // 如果目标日期已过，延期到下周的目标日期
            adjustedDate.setDate(adjustedDate.getDate() + (7 - currentDay + targetDay));
          }
          break;
          
        case 'delayToNextWeek':
          // 延期到下周的指定星期几
          const nextWeekTargetDay = rule.targetDay;
          const daysToAdd = 7 - (dayOfWeek === 0 ? 7 : dayOfWeek) + nextWeekTargetDay;
          adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);
          break;
          
        case 'delayDays':
          // 直接延期指定天数
          const delayDays = rule.days || 0;
          adjustedDate.setDate(adjustedDate.getDate() + delayDays);
          break;
          
        default:
          console.warn(`未知的调整动作: ${rule.action}`);
          break;
      }
      
      console.log(`应用调整规则: ${dayKey} -> ${JSON.stringify(rule)}, 原日期: ${date.toDateString()}, 调整后: ${adjustedDate.toDateString()}`);
      return { adjustedDate, description };
      
    } catch (error) {
      console.error('解析日期调整规则失败:', error, '规则内容:', ruleJson);
      return { adjustedDate: date }; // 解析失败时返回原日期
    }
  };

  // 调整到下一个工作时间开始点
  const adjustToNextWorkingHour = (date: Date, calculationMethod: string, weekendDays: number[] = [], excludedDates: string[] = []): Date => {
    const result = new Date(date);
    
    if (calculationMethod === '内部') {
      const hour = result.getHours();
      const minute = result.getMinutes();
      
      // 如果是工作时间外（18:00之后或9:00之前），调整到下一个工作日的9:00
      if (hour >= 18 || hour < 9) {
        // 如果是当天18:00之后，调整到下一个工作日
        if (hour >= 18) {
          result.setDate(result.getDate() + 1);
        }
        
        // 找到下一个工作日（考虑休息日和自定义跳过日期）
        while (!isBusinessDay(result, weekendDays, excludedDates)) {
          result.setDate(result.getDate() + 1);
        }
        
        // 设置为9:00:00
        result.setHours(9, 0, 0, 0);
      }
    }
    
    return result;
  };

  const hasNonEmptyRuleValue = (value: any): boolean => {
    if (value === null || value === undefined) return false;
    if (typeof value === 'string') return value.trim().length > 0;
    if (typeof value === 'number') return true;
    if (Array.isArray(value)) return value.some(item => hasNonEmptyRuleValue(item));
    if (typeof value === 'object') {
      const v: any = value;
      if (typeof v.text === 'string') return v.text.trim().length > 0;
      if (typeof v.name === 'string') return v.name.trim().length > 0;
      if (v.value !== undefined) return hasNonEmptyRuleValue(v.value);
      return Object.keys(v).length > 0;
    }
    return Boolean(value);
  };

  const setTimeOfDay = (date: Date, hour: number, minute: number): Date => {
    const result = new Date(date);
    result.setHours(hour, minute, 0, 0);
    return result;
  };

  const shouldForceEndTimeTo18 = (nodeLike: any): boolean => {
    return hasNonEmptyRuleValue(nodeLike?.startDateRule) || hasNonEmptyRuleValue(nodeLike?.dateAdjustmentRule);
  };

  // 内部工作时间计算函数
  const addInternalBusinessTime = (startDate: Date, businessDays: number, weekendDays: number[] = [], excludedDates: string[] = []): Date => {
    const result = new Date(startDate);
    if (!businessDays || businessDays === 0) return result;
    const isNegative = businessDays < 0;
    const absDays = Math.abs(businessDays);
    
    // 处理整数天（仅计入工作日）
    const wholeDays = Math.floor(absDays);
    let processedDays = 0;
    while (processedDays < wholeDays) {
      result.setDate(result.getDate() + (isNegative ? -1 : 1));
      if (isBusinessDay(result, weekendDays, excludedDates)) {
        processedDays++;
      }
    }
    
    // 处理小数部分（按9小时工作制）
    const fractionalDays = absDays - wholeDays;
    if (fractionalDays > 0) {
      const workingHours = fractionalDays * 9; // 内部按9小时工作制
      let currentHour = result.getHours();
      let currentMinute = result.getMinutes();
      
      if (!isNegative) {
        // 正向：确保在工作时间内开始
        if (currentHour < 9) {
          currentHour = 9;
          currentMinute = 0;
        } else if (currentHour >= 18) {
          // 跳到下一个工作日的9:00
          result.setDate(result.getDate() + 1);
          while (!isBusinessDay(result, weekendDays, excludedDates)) {
            result.setDate(result.getDate() + 1);
          }
          currentHour = 9;
          currentMinute = 0;
        }
        
        const totalMinutes = currentHour * 60 + currentMinute + workingHours * 60;
        const finalHour = Math.floor(totalMinutes / 60);
        const finalMinute = totalMinutes % 60;
        
        if (finalHour >= 18) {
          const overflowHours = finalHour - 18;
          const overflowMinutes = finalMinute;
          result.setDate(result.getDate() + 1);
          while (!isBusinessDay(result, weekendDays, excludedDates)) {
            result.setDate(result.getDate() + 1);
          }
          result.setHours(9 + overflowHours, overflowMinutes, 0, 0);
        } else {
          result.setHours(finalHour, finalMinute, 0, 0);
        }
      } else {
        // 负向：从当前时间向前回退工作小时，规范到工作时间窗口
        if (currentHour > 18) {
          // 当天超过18:00，先归位到18:00
          result.setHours(18, 0, 0, 0);
          currentHour = 18;
          currentMinute = 0;
        } else if (currentHour < 9) {
          // 早于9:00，跳到前一个工作日的18:00
          result.setDate(result.getDate() - 1);
          while (!isBusinessDay(result, weekendDays, excludedDates)) {
            result.setDate(result.getDate() - 1);
          }
          result.setHours(18, 0, 0, 0);
          currentHour = 18;
          currentMinute = 0;
        }
        
        const totalMinutes = currentHour * 60 + currentMinute - workingHours * 60;
        if (totalMinutes >= 9 * 60) {
          const finalHour = Math.floor(totalMinutes / 60);
          const finalMinute = totalMinutes % 60;
          result.setHours(finalHour, finalMinute, 0, 0);
        } else {
          // 需要跨到前一个工作日，计算欠缺分钟数
          let deficit = 9 * 60 - totalMinutes; // 需要从前一工作日的18:00再退回的分钟数
          // 跳到前一个工作日
          result.setDate(result.getDate() - 1);
          while (!isBusinessDay(result, weekendDays, excludedDates)) {
            result.setDate(result.getDate() - 1);
          }
          // 从18:00开始退 deficit 分钟
          result.setHours(18, 0, 0, 0);
          result.setMinutes(result.getMinutes() - deficit);
        }
      }
    }
    
    return result;
  };

  // 添加工作日 - 使用表格配置的休息日与节点自定义跳过日期
  const addBusinessDaysWithHolidays = (startDate: Date, businessDays: number, weekendDays: number[] = [], excludedDates: string[] = []): Date => {
    const result = new Date(startDate);
    if (!businessDays || businessDays === 0) return result;
    const isNegative = businessDays < 0;
    const absDays = Math.abs(businessDays);
    let processedDays = 0;
    
    // 先处理整数工作日
    const wholeDays = Math.floor(absDays);
    while (processedDays < wholeDays) {
      result.setDate(result.getDate() + (isNegative ? -1 : 1));
      if (isBusinessDay(result, weekendDays, excludedDates)) {
        processedDays++;
      }
    }
    
    // 再处理小数部分（按24小时制）
    const fractionalDays = absDays - wholeDays;
    if (fractionalDays > 0) {
      const hours = fractionalDays * 24;
      result.setHours(result.getHours() + (isNegative ? -hours : hours));
    }
    
    return result;
  };



  // 获取标签数据
  const fetchLabelOptions = async () => {
    setLabelLoading(true);
    try {
      // 获取标签表
      const labelTable = await bitable.base.getTable(LABEL_TABLE_ID);
      
      // 1. 先获取所有字段的元数据
      const fieldMetaList = await labelTable.getFieldMetaList();
      
      // 2. 筛选出标签1-标签11的字段
      const labelFields: {[key: string]: string} = {}; // 存储字段名到字段ID的映射
      
      for (const fieldMeta of fieldMetaList) {
        if (!fieldMeta || typeof (fieldMeta as any).name !== 'string' || typeof (fieldMeta as any).id !== 'string') {
          continue;
        }
        const match = (fieldMeta as any).name.match(/^标签([1-9]|10|11)$/);
        if (match) {
          const labelKey = `标签${match[1]}`;
          labelFields[labelKey] = (fieldMeta as any).id;
        }
      }
      
      console.log('找到的标签字段:', labelFields);
      
      // 3. 处理标签数据 - 从字段选项获取而不是从记录数据获取
      const options: {[key: string]: any[]} = {};
      
      // 初始化标签的选项数组
      for (let i = 1; i <= 12; i++) {
        options[`标签${i}`] = [];
      }

      const getSelectFieldOptions = async (table: any, fieldId: string): Promise<any[]> => {
        const field = await table.getField(fieldId);
        const fieldMeta = await field.getMeta();
        if (fieldMeta.type === FieldType.SingleSelect || fieldMeta.type === FieldType.MultiSelect) {
          const selectField = field as any;
          const fieldOptions = await selectField.getOptions();
          return (fieldOptions || [])
            .filter((option: any) => option && typeof option.name === 'string')
            .map((option: any) => ({ label: option.name, value: option.name }));
        }
        const records = await fetchAllRecordsByPage(table, undefined);
        const values = new Set<string>();
        for (const r of records) {
          const raw = (r?.fields || {})[fieldId];
          const tokens = Array.isArray(raw) ? raw : [raw];
          for (const t of tokens) {
            const s = extractText(t)?.trim();
            if (s) values.add(s);
          }
        }
        return Array.from(values)
          .sort((a, b) => a.localeCompare(b, 'zh-Hans-CN'))
          .map(v => ({ label: v, value: v }));
      };
      
      // 4. 遍历标签字段，获取每个字段的选项
      for (const [labelKey, fieldId] of Object.entries(labelFields)) {
        try {
          options[labelKey] = await getSelectFieldOptions(labelTable, fieldId);
        } catch (error) {
          console.warn(`获取${labelKey}字段选项失败:`, error);
          // 如果获取选项失败，保持空数组
          options[labelKey] = [];
        }
      }

      try {
        const timelineTable = await bitable.base.getTable(TIMELINE_TABLE_ID);
        const label12Options = await getSelectFieldOptions(timelineTable, TIMELINE_LABEL12_FIELD_ID);
        const unique = new Map<string, any>();
        for (const opt of label12Options) {
          if (opt && typeof opt.value === 'string' && opt.value.trim()) {
            unique.set(opt.value.trim(), { label: opt.value.trim(), value: opt.value.trim() });
          }
        }
        options['标签12'] = Array.from(unique.values()).sort((a, b) => a.value.localeCompare(b.value, 'zh-Hans-CN'));
      } catch (error) {
        console.warn('获取标签12字段选项失败:', error);
        options['标签12'] = [];
      }
      
      console.log('处理后的标签选项:', options);
      
      setLabelOptions(options);
      
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.success,
          message: `标签选项加载成功，共找到 ${Object.keys(labelFields).length} 个标签字段`
        });
      }
    } catch (error) {
      console.error('获取标签选项失败:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '获取标签选项失败，请检查表格配置'
        });
      }
    } finally {
      setLabelLoading(false);
    }
  };

  // 组件加载时获取标签数据和初始化起始时间
  useEffect(() => {
    const initializeData = async () => {
      await fetchLabelOptions();
      await initializeStartTime();
    };
    
    initializeData();
  }, []);

  const getDeliveryRecordIdFromOmsRecord = async (omsRecordId: string): Promise<string | null> => {
    if (!omsRecordId) return null;
    const omsTable = await bitable.base.getTable(OMS_BOARD_TABLE_ID);
    const omsRecord = await omsTable.getRecordById(omsRecordId);
    const raw = omsRecord?.fields?.[OMS_DELIVERY_RECORD_ID_FIELD_ID];
    const text = extractText(raw)?.trim();
    return text ? text : null;
  };

  // 初始化起始时间：从货期记录表获取，新记录则使用当前时间
  const initializeStartTime = async () => {
    try {
      const selection = await bitable.base.getSelection();
      const recordId = selection?.recordId || '';
      const tableId = selection?.tableId || '';
      
      if (recordId && tableId === DELIVERY_RECORD_TABLE_ID) {
        // 如果选中的是货期记录表的记录，从中获取起始时间
        const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
        const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
        const startTimeValue = await startTimeField.getValue(recordId);
        
        if (startTimeValue) {
          let extractedStartTime: Date | null = null;
          if (typeof startTimeValue === 'number') {
            extractedStartTime = new Date(startTimeValue);
          } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
            const timestamp = startTimeValue[0];
            if (typeof timestamp === 'number') {
              extractedStartTime = new Date(timestamp);
            }
          }
          
          if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
            setStartTime(extractedStartTime);
            return;
          }
        }
      } else if (recordId && tableId === OMS_BOARD_TABLE_ID) {
        // 从OMS看板读取货期记录ID后，尝试获取其起始时间
        try {
          const deliveryRecordId = await getDeliveryRecordIdFromOmsRecord(recordId);
          if (deliveryRecordId) {
            const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
            const deliveryRecord: any = await deliveryTable.getRecordById(deliveryRecordId);
            const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID];
            if (startTimeValue) {
              let extractedStartTime: Date | null = null;
              if (typeof startTimeValue === 'number') {
                extractedStartTime = new Date(startTimeValue);
              } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
                const timestamp = startTimeValue[0];
                if (typeof timestamp === 'number') extractedStartTime = new Date(timestamp);
              }
              if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
                setStartTime(extractedStartTime);
                return;
              }
            }
          }

          /*
          // 原始逻辑：从OMS看板用“货期计划 + 计划版本”匹配货期记录（已停用）
          const omsTable = await bitable.base.getTable(OMS_BOARD_TABLE_ID);
          const planTextField: any = await omsTable.getField(OMS_PLAN_TEXT_FIELD_ID);
          const planVersionField: any = await omsTable.getField(OMS_PLAN_VERSION_FIELD_ID);
          const planTextRaw = await planTextField.getValue(recordId);
          const planVersionRaw = await planVersionField.getValue(recordId);
          const planText = extractText(planTextRaw)?.trim();
          let planVersion: number | null = null;
          if (typeof planVersionRaw === 'number') {
            planVersion = planVersionRaw;
          } else if (typeof planVersionRaw === 'string') {
            const m = planVersionRaw.match(/\d+(?:\.\d+)?/);
            if (m) planVersion = parseFloat(m[0]);
          } else if (planVersionRaw && typeof planVersionRaw === 'object') {
            const v = (planVersionRaw as any).value ?? (planVersionRaw as any).text;
            if (typeof v === 'number') planVersion = v;
            else if (typeof v === 'string') {
              const m = v.match(/\d+(?:\.\d+)?/);
              if (m) planVersion = parseFloat(m[0]);
            }
          }

          if (planText && planVersion !== null) {
            const deliveryRecordId = await findDeliveryRecordIdByPlan(planText, planVersion);
            if (deliveryRecordId) {
              const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
              const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
              const startTimeValue = await startTimeField.getValue(deliveryRecordId);
              if (startTimeValue) {
                let extractedStartTime: Date | null = null;
                if (typeof startTimeValue === 'number') {
                  extractedStartTime = new Date(startTimeValue);
                } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) {
                  const timestamp = startTimeValue[0];
                  if (typeof timestamp === 'number') extractedStartTime = new Date(timestamp);
                }
                if (extractedStartTime && !isNaN(extractedStartTime.getTime())) {
                  setStartTime(extractedStartTime);
                  return;
                }
              }
            }
          }
          */
        } catch (e) {
          console.warn('从OMS看板匹配起始时间失败，使用当前时间:', e);
        }
      }
      
      // 如果没有找到有效的起始时间，使用当前时间
      setStartTime(new Date());
    } catch (error) {
      console.error('初始化起始时间失败:', error);
      // 出错时使用当前时间作为默认值
      setStartTime(new Date());
    }
  };

  // 根据OMS看板的“货期计划”和“计划版本”匹配货期记录ID
  const findDeliveryRecordIdByPlan = async (planText: string, planVersion: number): Promise<string | null> => {
    try {
      const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
      const versionField: any = await deliveryTable.getField(DELIVERY_VERSION_FIELD_ID);
      const planFilter: any = {
        conjunction: 'and',
        conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }]
      };
      const sort = DELIVERY_CREATE_TIME_FIELD_ID
        ? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }]
        : undefined;

      const eps = 1e-9;
      const normalizePlanText = (s: string) => s.replace(/\s+/g, '').trim();
      const parseVersionFromRaw = (raw: any): number | null => {
        if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
        const s = typeof raw === 'string' ? raw : extractText(raw);
        const m = (s || '').match(/\d+(?:\.\d+)?/);
        return m ? parseFloat(m[0]) : null;
      };

      {
        try {
          let token: any = undefined;
          for (let i = 0; i < 10000; i++) {
            const req: any = { pageSize: 200, filter: planFilter, sort };
            if (token) req.pageToken = token;
            const res: any = await safeGetRecordsByPage(deliveryTable, req);
            const recs: any[] = Array.isArray(res?.records) ? res.records : [];

            for (const r of recs) {
              const recordId = r?.recordId || r?.id || null;
              if (!recordId) continue;

              const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
              const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
              if (!recordPlanText) continue;

              if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;

              const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
              const recordVersion = parseVersionFromRaw(recordVersionRaw);
              if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
                return recordId;
              }
            }

            const nextToken = res?.pageToken;
            const hasMore = !!res?.hasMore;
            token = nextToken;
            if (!hasMore && !nextToken) break;
          }
        } catch {}
      }

      {
        try {
          let token: any = undefined;
          for (let i = 0; i < 10000; i++) {
            const res: any = await versionField.getFieldValueListByPage({
              pageSize: 200,
              pageToken: token,
              filter: planFilter,
              sort,
              stringValue: true
            });
            const fieldValues: any[] = Array.isArray(res?.fieldValues) ? res.fieldValues : [];

            for (const fv of fieldValues) {
              const recordId = fv?.recordId;
              const v = parseVersionFromRaw(fv?.value);
              if (v !== null && Math.abs(v - planVersion) < eps) {
                return recordId || null;
              }
            }

            const nextToken = res?.pageToken;
            const hasMore = !!res?.hasMore;
            token = nextToken;
            if (!hasMore && !nextToken) break;
          }
        } catch {}
      }

      {
        try {
          let token: any = undefined;
          for (let i = 0; i < 10000; i++) {
            const req: any = { pageSize: 200, sort };
            if (token) req.pageToken = token;
            const res: any = await safeGetRecordsByPage(deliveryTable, req);
            const recs: any[] = Array.isArray(res?.records) ? res.records : [];

            for (const r of recs) {
              const recordId = r?.recordId || r?.id || null;
              if (!recordId) continue;

              const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
              const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
              if (!recordPlanText) continue;

              if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;

              const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
              const recordVersion = parseVersionFromRaw(recordVersionRaw);
              if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
                return recordId;
              }
            }

            const nextToken = res?.pageToken;
            const hasMore = !!res?.hasMore;
            token = nextToken;
            if (!hasMore && !nextToken) break;
          }
        } catch {}
      }
      return null;
    } catch (error) {
      console.error('匹配货期记录失败:', error);
      return null;
    }
  };

  // 已移除：批量表/视图加载与切换逻辑

  // 处理标签选择变化
  const handleLabelChange = (labelKey: string, value: string | string[]) => {
    setSelectedLabels(prev => ({
      ...prev,
      [labelKey]: value
    }));
  };

  // 清空标签选择
  const handleClearLabels = () => {
    setSelectedLabels({});
    setExpectedDate(null); // 清空客户期望日期
  };
  
  // 计算预计开始和完成时间
  const handleCalculateTimeline = async (
    skipValidation: boolean = false,
    overrideData?: {
      selectedRecords?: string[],
      recordDetails?: any[],
      selectedLabels?: {[key: string]: string | string[]},
      expectedDate?: Date | null,
      startTime?: Date | null,
      excludedDates?: string[]
    },
    showUI: boolean = true // 新增参数控制是否显示UI
  ) => {
    // 使用传入的数据或全局状态
    const currentSelectedRecords = overrideData?.selectedRecords || selectedRecords;
    const currentRecordDetails = overrideData?.recordDetails || recordDetails;
    const currentSelectedLabels = overrideData?.selectedLabels || selectedLabels;
    const currentExpectedDate = overrideData?.expectedDate || expectedDate;
    const currentStartTime = overrideData?.startTime || startTime;
    const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : [];
    const globalExcludedDates = normalizeExcludedDatesOverride(currentExcludedDates);
    const excludedByNode = excludedDatesByNodeOverrideRef.current || {};
    const isBackward = timelineDirection === 'backward';
    
    console.log('=== handleCalculateTimeline - 使用的数据 ===');
    console.log('currentSelectedRecords:', currentSelectedRecords);
    console.log('currentSelectedLabels:', currentSelectedLabels);
    console.log('currentExpectedDate:', currentExpectedDate);
    console.log('currentStartTime:', currentStartTime);
    
    {
      const requiredLabelKeys = Array.from({ length: 10 }, (_, i) => `标签${i + 1}`);
      const label7Val = (currentSelectedLabels as any)['标签7'];
      const label7Values = Array.isArray(label7Val)
        ? label7Val.map((s: any) => String(s ?? '').trim()).filter(Boolean)
        : (typeof label7Val === 'string' ? [label7Val.trim()].filter(Boolean) : []);
      const shouldRequireLabel12 = label7Values.length > 0 && !(label7Values.length === 1 && label7Values[0] === '无(二次工艺)');
      if (shouldRequireLabel12) requiredLabelKeys.push('标签12');
      const missing = requiredLabelKeys.filter(key => {
        const val = (currentSelectedLabels as any)[key];
        if (Array.isArray(val)) return val.length === 0;
        return !(typeof val === 'string' && val.trim().length > 0);
      });
      if (missing.length > 0) {
        if (showUI && bitable.ui.showToast) {
          await bitable.ui.showToast({ toastType: ToastType.warning, message: `请填写以下必填标签：${missing.join('、')}` });
        }
        return [];
      }
    }

    if (isBackward && !currentExpectedDate) {
      if (showUI && bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.warning, message: '倒推模式需要先选择客户期望日期' });
      }
      return [];
    }

    if (isBackward) {
      if (!currentStartTime || isNaN(currentStartTime.getTime())) {
        if (showUI && bitable.ui.showToast) {
          await bitable.ui.showToast({ toastType: ToastType.warning, message: '倒推模式需要先选择起始日期' });
        }
        return [];
      }
      if (!currentExpectedDate || isNaN(currentExpectedDate.getTime())) {
        if (showUI && bitable.ui.showToast) {
          await bitable.ui.showToast({ toastType: ToastType.warning, message: '倒推模式需要先选择客户期望日期' });
        }
        return [];
      }
    }

    // 跳过验证（用于批量模式）
    if (!skipValidation) {
      // 检查是否选择了多条记录
      if (currentSelectedRecords.length > 1) {
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.warning,
            message: '计算时效功能仅支持单条记录，请重新选择单条记录后再试'
          });
        }
        return;
      }
      
      // 检查是否选择了记录
      if (currentSelectedRecords.length === 0) {
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.warning,
            message: '请先选择一条记录'
          });
        }
        return;
      }
    
      // 可选：检查是否选择了客户期望日期
      if (!currentExpectedDate) {
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.info,
            message: '建议选择客户期望日期以便更好地进行时效计算'
          });
        }
      }
    }
  
    // 移除冗余日志：客户期望日期输出
    setTimelineLoading(true);
    // 生成模式：输出当前数据结构，便于与批量模式对比
    try {
      console.group('=== 生成模式：计算时效 - 当前数据结构 ===');
      // 模式与核心输入
      console.log('mode:', mode);
      console.log('selectedRecords:', currentSelectedRecords);
      console.log('recordDetails:', currentRecordDetails);
      console.log('selectedLabels:', currentSelectedLabels);
      console.log('expectedDate:', currentExpectedDate);
      console.groupEnd();
    } catch (logErr) {
      console.warn('生成模式结构化日志输出失败:', logErr);
    }
    try {
      const splitLabelTokens = (text: string): string[] => {
        return text
          .split(/[,，;；、\n]+/)
          .map(s => s.trim())
          .filter(Boolean);
      };

      const extractLabelTokens = (value: any): string[] => {
        if (!value) return [];
        if (typeof value === 'string') return splitLabelTokens(value);
        if (typeof value === 'number') return [String(value)];
        if (value && typeof value === 'object') {
          if (typeof value.text === 'string') return splitLabelTokens(value.text);
          if (typeof value.name === 'string') return splitLabelTokens(value.name);
          if (typeof value.value === 'string') return splitLabelTokens(value.value);
          if (typeof value.value === 'number') return [String(value.value)];
        }
        if (Array.isArray(value)) {
          const tokens: string[] = [];
          for (const item of value) {
            tokens.push(...extractLabelTokens(item));
          }
          return tokens.filter(Boolean);
        }
        return [];
      };

      const businessLabelValues = new Set<string>();
      for (const selectedValue of Object.values(currentSelectedLabels)) {
        for (const token of extractLabelTokens(selectedValue)) {
          businessLabelValues.add(token);
        }
      }
      
      // 已移除冗余日志：业务选择标签值
      const timelineTable = await bitable.base.getTable(TIMELINE_TABLE_ID);
      const timelineFieldMetaList = await timelineTable.getFieldMetaList();
      const timelineLabelFields: {[key: string]: string} = {};
      
      // 构建时效表的标签字段映射（只执行一次）
      let relationshipFieldId = ''; // 关系字段ID
      let calculationMethodFieldId = ''; // 时效计算方式字段ID
      
      for (const fieldMeta of timelineFieldMetaList) {
        if (!fieldMeta || typeof (fieldMeta as any).name !== 'string' || typeof (fieldMeta as any).id !== 'string') {
          continue;
        }
        const match = (fieldMeta as any).name.match(/^标签([1-9]|10|12)$/);
        if (match) {
          const labelKey = `标签${match[1]}`;
          timelineLabelFields[labelKey] = (fieldMeta as any).id;
        }
        if ((fieldMeta as any).name === '关系' || (fieldMeta as any).id === 'fldaIDAhab') {
          relationshipFieldId = (fieldMeta as any).id;
        }
        if ((fieldMeta as any).name === '时效计算方式' || (fieldMeta as any).id === 'fldxfLZNUu') {
          calculationMethodFieldId = (fieldMeta as any).id;
        }
      }
      
      console.log('时效表标签字段映射:', timelineLabelFields);
      console.log('关系字段ID:', relationshipFieldId);
      
      // 1. 先获取匹配的流程节点（复用预览功能的逻辑）
      const processTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID);
      const processRecords = await fetchAllRecordsByPage(processTable, undefined);
      const matchedProcessNodes: any[] = [];
      
      // 匹配流程配置节点
      for (const record of processRecords) {
        const fields = record.fields;
        const processLabels = fields[PROCESS_LABEL_FIELD_ID];
        const nodeName = fields[NODE_NAME_FIELD_ID];
        const processOrder = fields[PROCESS_ORDER_FIELD_ID]; // 获取流程顺序
        const processGroup = fields[PROCESS_GROUP_FIELD_ID];
        let processGroupText = '';
        if (typeof processGroup === 'string') {
          processGroupText = processGroup;
        } else {
          processGroupText = extractText(processGroup);
        }
        processGroupText = (processGroupText || '').trim();

        if (!processLabels || !nodeName) continue;
        
        // 处理流程配置表中的标签数据 - 修复多选字段处理
        let processLabelTexts: string[] = [];
        if (typeof processLabels === 'string') {
          processLabelTexts = [processLabels];
        } else if (processLabels && processLabels.text) {
          processLabelTexts = [processLabels.text];
        } else if (Array.isArray(processLabels) && processLabels.length > 0) {
          // 处理多选字段，获取所有选项的值
          processLabelTexts = processLabels.map(item => {
            if (typeof item === 'string') {
              return item;
            } else if (item && item.text) {
              return item.text;
            } else if (item && item.value) {
              return item.value;
            }
            return String(item);
          }).filter(text => text && text.trim());
        }
        
        // 处理节点名称
        let nodeNameText = '';
        if (typeof nodeName === 'string') {
          nodeNameText = nodeName;
        } else if (nodeName.text) {
          nodeNameText = nodeName.text;
        }
        
        // 处理流程顺序
        let orderValue = 0;
        if (typeof processOrder === 'number') {
          orderValue = processOrder;
        } else if (typeof processOrder === 'string') {
          orderValue = parseInt(processOrder) || 0;
        } else if (processOrder && processOrder.value !== undefined) {
          orderValue = processOrder.value;
        }
        
        if (processLabelTexts.length === 0 || !nodeNameText) continue;
        
        // 检查是否匹配当前选择的标签 - 修复匹配逻辑
        let isMatched = false;
        const matchedLabels: string[] = [];
        
        for (const [labelKey, labelValue] of Object.entries(currentSelectedLabels)) {
          if (!labelValue) continue;
          
          const valuesToCheck = Array.isArray(labelValue) ? labelValue : [labelValue];
          
          for (const value of valuesToCheck) {
            // 检查用户选择的值是否在流程配置的任何一个标签选项中
            const isValueMatched = processLabelTexts.some(processLabelText => {
              return String(processLabelText ?? '').trim() === String(value ?? '').trim();
            });
            
            if (isValueMatched) {
              isMatched = true;
              matchedLabels.push(`${labelKey}: ${value}`);
              console.log(`匹配成功: ${labelKey} = ${value}`);
            } else {
              console.log(`匹配失败: ${labelKey} = ${value}, 流程配置标签: [${processLabelTexts.join(', ')}]`);
            }
          }
        }
        
        // 处理休息日配置 - 完全从表格字段获取
        let weekendDays: number[] = [];
        const weekendDaysField = fields[WEEKEND_DAYS_FIELD_ID]; // 获取休息日配置
        if (weekendDaysField) {
          console.log('原始休息日字段数据:', weekendDaysField);
          
          if (Array.isArray(weekendDaysField)) {
            // 多选字段返回数组，每个元素可能是选项对象
            weekendDays = weekendDaysField.map(item => {
              // 处理选项对象 {id: "xxx", text: "0"} 或直接的值
              if (item && typeof item === 'object') {
                // 如果是选项对象，取text或id字段
                const value = item.text || item.id || item.value;
                if (typeof value === 'string') {
                  const parsed = parseInt(value);
                  return !isNaN(parsed) && parsed >= 0 && parsed <= 6 ? parsed : null;
                } else if (typeof value === 'number') {
                  return value >= 0 && value <= 6 ? value : null;
                }
              } else if (typeof item === 'string') {
                const parsed = parseInt(item);
                return !isNaN(parsed) && parsed >= 0 && parsed <= 6 ? parsed : null;
              } else if (typeof item === 'number') {
                return item >= 0 && item <= 6 ? item : null;
              }
              return null;
            }).filter(day => day !== null) as number[];
          } else if (typeof weekendDaysField === 'string') {
            // 如果是字符串，尝试解析
            try {
              const parsed = JSON.parse(weekendDaysField);
              if (Array.isArray(parsed)) {
                weekendDays = parsed.filter(day => typeof day === 'number' && day >= 0 && day <= 6);
              }
            } catch {
              // 如果解析失败，尝试按逗号分割
              const parts = weekendDaysField.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n) && n >= 0 && n <= 6);
              if (parts.length > 0) {
                weekendDays = parts;
              }
            }
          } else if (typeof weekendDaysField === 'number' && weekendDaysField >= 0 && weekendDaysField <= 6) {
            weekendDays = [weekendDaysField];
          }
          
          console.log('解析后的休息日配置:', weekendDays);
        }
        // 如果表格中没有配置休息日或配置为空，则该节点没有固定休息日
        // 这样就完全依赖表格数据，不会有任何硬编码的默认值
        
        // 处理不参与计算日期（自定义跳过日期）
        let excludedDates: string[] = [];
        const excludedDatesField = fields[EXCLUDED_DATES_FIELD_ID];
        if (excludedDatesField) {
          console.log('原始不参与计算日期字段数据:', excludedDatesField);
          if (Array.isArray(excludedDatesField)) {
            excludedDates = excludedDatesField.map(item => {
              if (item && typeof item === 'object') {
                const val = item.text || item.name || item.value || item.id || '';
                return String(val).trim();
              }
              return String(item).trim();
            }).filter(d => !!d);
          } else if (typeof excludedDatesField === 'string') {
            excludedDates = excludedDatesField.split(/[\,\s]+/).map(s => s.trim()).filter(Boolean);
          } else if (excludedDatesField && typeof excludedDatesField === 'object' && excludedDatesField.text) {
            excludedDates = [String(excludedDatesField.text).trim()].filter(Boolean);
          }
          console.log('解析后的不参与计算日期:', excludedDates);
        }
        
        if (isMatched) {
          // 获取起始日期调整规则
          const startDateRule = fields[START_DATE_RULE_FIELD_ID];
          // 获取JSON格式的日期调整规则
          const dateAdjustmentRule = fields[DATE_ADJUSTMENT_RULE_FIELD_ID];
          
          matchedProcessNodes.push({
            id: record.id,
            nodeName: nodeNameText,
            processLabels: processLabelTexts.join(', '),
            matchedLabels: matchedLabels,
            processOrder: orderValue, // 添加流程顺序
            weekendDays: weekendDays, // 添加休息日配置
            excludedDates: excludedDates, // 添加自定义跳过日期
            startDateRule: startDateRule, // 添加起始日期调整规则
            dateAdjustmentRule: dateAdjustmentRule, // 添加JSON格式日期调整规则
            processGroup: processGroupText
          });
        }
      }
      
      const allGroups = Array.from(
        new Set(
          matchedProcessNodes
            .map(n => (n.processGroup || '').trim())
            .filter(g => g && g.length > 0)
        )
      );

      if (allGroups.length > 1 && groupOrderConfig.length === 0 && showUI) {
        const initial = deriveGroupOrderDraftByProcessOrder(matchedProcessNodes);
        setGroupOrderDraft(initial);
        setGroupConfigVisible(true);
        setTimelineLoading(false);
        pendingGroupConfigCalcRef.current = {
          overrideData: {
            selectedRecords: currentSelectedRecords,
            recordDetails: currentRecordDetails,
            selectedLabels: currentSelectedLabels,
            expectedDate: currentExpectedDate,
            startTime: currentStartTime,
            excludedDates: currentExcludedDates,
          },
          showUI,
        };
        return [];
      }

      let orderedProcessNodes: any[] = matchedProcessNodes;

      if (groupOrderConfig.length > 0) {
        const configuredBaseNames = new Set<string>();
        for (const inst of groupOrderConfig) {
          const base = (inst?.groupName || '').trim();
          if (base) configuredBaseNames.add(base);
        }

        const baseNameToNodes = new Map<string, any[]>();
        for (const n of matchedProcessNodes) {
          const base = (n?.processGroup || '').trim();
          if (!base) continue;
          if (!baseNameToNodes.has(base)) baseNameToNodes.set(base, []);
          baseNameToNodes.get(base)!.push(n);
        }
        for (const [base, list] of baseNameToNodes.entries()) {
          list.sort((a, b) => (Number(a?.processOrder) || 0) - (Number(b?.processOrder) || 0));
          baseNameToNodes.set(base, list);
        }

        const expanded: any[] = [];
        for (const inst of groupOrderConfig) {
          const base = (inst?.groupName || '').trim();
          if (!base) continue;
          const nodesInGroup = baseNameToNodes.get(base) || [];
          for (const n of nodesInGroup) {
            expanded.push({
              ...n,
              processGroupBase: base,
              processGroupInstanceId: inst.id,
              processGroupInstanceName: inst.displayName,
            });
          }
        }

        const leftovers = matchedProcessNodes.filter(n => {
          const base = (n?.processGroup || '').trim();
          return base ? !configuredBaseNames.has(base) : true;
        });
        leftovers.sort((a, b) => (Number(a?.processOrder) || 0) - (Number(b?.processOrder) || 0));

        orderedProcessNodes = expanded.concat(leftovers);
      } else {
        orderedProcessNodes = [...matchedProcessNodes].sort((a, b) => (Number(a?.processOrder) || 0) - (Number(b?.processOrder) || 0));
      }

      console.log('按顺序排列的流程节点:', orderedProcessNodes);
      
      // 2. 优化：预先获取所有时效数据并建立索引
      const timelineRecords = await fetchAllRecordsByPage(timelineTable, undefined);
      
      // 优化2：预处理时效数据，建立节点名称到记录的映射
      const timelineIndexByNode = new Map<string, any[]>();
      
      for (const timelineRecord of timelineRecords) {
        const timelineFields = timelineRecord.fields;
        const timelineNodeName = timelineFields[TIMELINE_NODE_FIELD_ID];
        
        // 处理时效表中的节点名称
        let timelineNodeNames: string[] = [];
        
        if (typeof timelineNodeName === 'string') {
          timelineNodeNames = timelineNodeName.split(',').map((name: string) => name.trim());
        } else if (Array.isArray(timelineNodeName)) {
          timelineNodeNames = timelineNodeName.map(item => {
            if (typeof item === 'string') {
              return item.trim();
            } else if (item && item.text) {
              return item.text.trim();
            }
            return '';
          }).filter(name => name);
        } else if (timelineNodeName && timelineNodeName.text) {
          timelineNodeNames = timelineNodeName.text.split(',').map((name: string) => name.trim());
        }
        
        // 为每个节点名称建立索引
        for (const nodeName of timelineNodeNames) {
          const normalizedName = nodeName.toLowerCase();
          if (!timelineIndexByNode.has(normalizedName)) {
            timelineIndexByNode.set(normalizedName, []);
          }
          timelineIndexByNode.get(normalizedName)!.push({
            record: timelineRecord,
            fields: timelineFields
          });
        }
      }
      
      console.log('时效数据索引构建完成，节点数量:', timelineIndexByNode.size);
      
      const results: any[] = [];
      
      // 3. 按顺序为每个匹配的流程节点查找时效数据并计算累积时间
      let cumulativeTime = isBackward
        ? new Date(currentExpectedDate as Date)
        : (currentStartTime ? new Date(currentStartTime) : new Date());
      const nodesToProcess = isBackward ? [...orderedProcessNodes].reverse() : orderedProcessNodes;
      
      for (let i = 0; i < nodesToProcess.length; i++) {
        const processNode = nodesToProcess[i];
        const nodeKey = buildExcludedDatesNodeKey(
          processNode?.nodeName,
          processNode?.processOrder,
          i,
          processNode?.processGroupInstanceId || processNode?.processGroupInstanceName
        );
        const overrideList = Object.prototype.hasOwnProperty.call(excludedByNode, nodeKey)
          ? excludedByNode[nodeKey]
          : undefined;
        const baseList = Array.isArray(processNode?.excludedDates) ? processNode.excludedDates : [];
        const selectedList = Array.isArray(overrideList) ? overrideList : baseList;
        const nodeExcludedDates = Array.from(new Set([
          ...normalizeExcludedDatesOverride(selectedList),
          ...globalExcludedDates,
        ])).filter(Boolean);
        let timelineValue = null;
        let matchedTimelineRecord = null;
        
        // 优化3：使用索引快速查找候选记录
        const normalizedProcessName = processNode.nodeName.trim().toLowerCase();
        const candidateRecords = timelineIndexByNode.get(normalizedProcessName) || [];
        
        console.log(`节点 ${processNode.nodeName} 找到 ${candidateRecords.length} 个候选时效记录`);
        
        // 在候选记录中进行标签匹配
        let matchedCandidates = []; // 收集所有匹配的候选记录
        
        for (const candidate of candidateRecords) {
          const { record: timelineRecord, fields: timelineFields } = candidate;
          
          // 优化4：使用预构建的字段映射进行标签匹配
          let isLabelsMatched = true;
          
          // 检查时效表中每个有值的标签是否都包含在业务标签中
          for (const [labelKey, timelineFieldId] of Object.entries(timelineLabelFields)) {
            const timelineLabelValue = timelineFields[timelineFieldId];
            
            const timelineLabelTexts = extractLabelTokens(timelineLabelValue);
            
            // 如果时效表中该标签有值，则检查该标签的所有值是否都包含在业务标签中
            if (timelineLabelTexts.length > 0) {
              let allValuesMatched = true;
              
              // 优化5：使用Set的has方法进行快速查找
              for (const timelineText of timelineLabelTexts) {
                if (!businessLabelValues.has(timelineText)) {
                  allValuesMatched = false;
                  console.log(`时效表标签 ${labelKey} 的值 "${timelineText}" 不在业务选择的标签中`);
                  break;
                }
              }
              
              if (!allValuesMatched) {
                console.log(`时效表标签 ${labelKey} 的值 [${timelineLabelTexts.join(', ')}] 不完全包含在业务选择的标签中`);
                isLabelsMatched = false;
                break;
              } else {
                console.log(`时效表标签 ${labelKey} 的值 [${timelineLabelTexts.join(', ')}] 完全匹配成功`);
              }
            }
            // 如果时效表中该标签为空，则跳过检查（空标签不影响匹配）
          }
          
          // 只有标签匹配成功才获取时效数据
                if (isLabelsMatched) {
                  // 找到匹配的节点，获取时效值
                  const timelineValueField = timelineFields[TIMELINE_FIELD_ID];
                  // 获取关系字段值
                  const relationshipField = timelineFields[relationshipFieldId];
                  // 获取时效计算方式字段值
                  const calculationMethodField = timelineFields[calculationMethodFieldId];
                  
                  if (timelineValueField !== null && timelineValueField !== undefined) {
                    let candidateTimelineValue = 0;
                    
                    // 解析时效值
                    if (typeof timelineValueField === 'number') {
                      candidateTimelineValue = timelineValueField;
                    } else if (typeof timelineValueField === 'string') {
                      const parsedValue = parseFloat(timelineValueField);
                      candidateTimelineValue = isNaN(parsedValue) ? 0 : parsedValue;
                    } else if (timelineValueField && typeof timelineValueField === 'object' && timelineValueField.value !== undefined) {
                      candidateTimelineValue = timelineValueField.value;
                    }
                    
                    // 获取计算方式
                    let calculationMethod = '外部'; // 默认值
                    if (calculationMethodField) {
                      if (typeof calculationMethodField === 'string') {
                        calculationMethod = calculationMethodField;
                      } else if (calculationMethodField && typeof calculationMethodField === 'object' && calculationMethodField.text) {
                        calculationMethod = calculationMethodField.text;
                      }
                    }
                    
                    // 根据计算方式转换时效值（小时转天）
                    let convertedTimelineValue = 0;
                    if (calculationMethod === '内部') {
                      convertedTimelineValue = Math.round((candidateTimelineValue / 9) * 1000) / 1000; // 精确到3位小数
                      console.log(`内部计算方式: ${candidateTimelineValue}小时 → ${convertedTimelineValue.toFixed(3)}天`);
                    } else {
                      convertedTimelineValue = Math.round((candidateTimelineValue / 24) * 1000) / 1000; // 精确到3位小数
                      console.log(`外部计算方式: ${candidateTimelineValue}小时 → ${convertedTimelineValue.toFixed(3)}天`);
                    }
                    
                    // 获取关系类型
                    let relationshipType = '默认';
                    if (relationshipField) {
                      if (typeof relationshipField === 'string') {
                        relationshipType = relationshipField;
                      } else if (relationshipField && typeof relationshipField === 'object' && relationshipField.text) {
                        relationshipType = relationshipField.text;
                      }
                    }
                    
                    // 调试时效记录对象结构
                    console.log('时效记录对象结构:', timelineRecord);
                    console.log('记录ID字段:', {
                      id: timelineRecord.id,
                      recordId: timelineRecord.recordId,
                      _id: timelineRecord._id,
                      record_id: timelineRecord.record_id
                    });
                    
                    matchedCandidates.push({
                      record: timelineRecord,
                      timelineValue: convertedTimelineValue, // 使用转换后的值
                      relationshipType: relationshipType,
                      calculationMethod: calculationMethod, // 记录计算方式
                      originalHours: candidateTimelineValue // 保留原始小时值用于日志
                    });
                    
                    // 移除单条记录的详细日志输出，避免日志冗余
                    // console.log(`节点 ${processNode.nodeName} 找到匹配记录:`, {
                    //   记录ID: timelineRecord.id || timelineRecord.recordId || timelineRecord._id || timelineRecord.record_id,
                    //   原始时效值小时: candidateTimelineValue,
                    //   转换后天数: convertedTimelineValue,
                    //   关系类型: relationshipType,
                    //   时效计算方式: calculationMethod
                    // });
                  }
                }
        }
        
        // 在 matchedCandidates 处理之前定义 processingRule
        let processingRule = '默认'; // 默认值
        
        // 根据关系字段和时效计算方式决定如何处理多个匹配的记录
        if (matchedCandidates.length > 0) {
          // 检查所有匹配记录的关系类型和计算方式
          const relationshipTypes = [...new Set(matchedCandidates.map(c => c.relationshipType))];
          const calculationMethods = [...new Set(matchedCandidates.map(c => c.calculationMethod))];
          
          if (relationshipTypes.length > 1) {
            console.warn(`节点 ${processNode.nodeName} 存在多种关系类型:`, relationshipTypes);
          }
          if (calculationMethods.length > 1) {
            console.warn(`节点 ${processNode.nodeName} 存在多种时效计算方式:`, calculationMethods);
          }
          
          // 使用第一个匹配记录的关系类型和计算方式作为处理方式
          const primaryRelationshipType = matchedCandidates[0].relationshipType;
          const primaryCalculationMethod = matchedCandidates[0].calculationMethod;
          
          // 添加调试日志
          console.log(`节点 ${processNode.nodeName} 调试信息:`);
          console.log('primaryCalculationMethod:', primaryCalculationMethod);
          console.log('primaryRelationshipType:', primaryRelationshipType);
          console.log('所有关系类型:', relationshipTypes);
          console.log('所有计算方式:', calculationMethods);
          
          let finalTimelineValue = 0;
          
          // 使用关系字段决定处理方式（累加值、最大值、默认）
          processingRule = primaryRelationshipType || '默认';
          
          console.log('processingRule:', processingRule);
          
          if (processingRule === '累加值') {
            finalTimelineValue = matchedCandidates.reduce((sum, candidate) => sum + candidate.timelineValue, 0);
            const totalOriginalHours = matchedCandidates.reduce((sum, candidate) => sum + candidate.originalHours, 0);
            
            console.log(`节点 ${processNode.nodeName} 累加值处理 - 找到 ${matchedCandidates.length} 条匹配记录:`);
            matchedCandidates.forEach((candidate, index) => {
              console.log(`  记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`);
            });
            console.log(`累加结果: 总计${totalOriginalHours}小时 → ${finalTimelineValue}天`);
            
            matchedTimelineRecord = matchedCandidates[0].record;
          } else if (processingRule === '最大值') {
            finalTimelineValue = Math.max(...matchedCandidates.map(c => c.timelineValue));
            const maxCandidate = matchedCandidates.find(c => c.timelineValue === finalTimelineValue);
            
            console.log(`节点 ${processNode.nodeName} 最大值处理 - 找到 ${matchedCandidates.length} 条匹配记录:`);
            matchedCandidates.forEach((candidate, index) => {
              console.log(`  记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`);
            });
            console.log(`最大值结果: ${maxCandidate?.originalHours}小时(${maxCandidate?.calculationMethod}) → ${finalTimelineValue}天`);
            
            matchedTimelineRecord = maxCandidate?.record || matchedCandidates[0].record;
          } else {
            finalTimelineValue = matchedCandidates[0].timelineValue;
            
            console.log(`节点 ${processNode.nodeName} 默认处理 - 找到 ${matchedCandidates.length} 条匹配记录:`);
            matchedCandidates.forEach((candidate, index) => {
              console.log(`  记录${index + 1}: ID=${candidate.record.id || candidate.record.recordId}, 时效=${candidate.originalHours}小时(${candidate.timelineValue}天), 计算方式=${candidate.calculationMethod}`);
            });
            console.log(`默认结果: 使用第一条记录 ${matchedCandidates[0].originalHours}小时(${matchedCandidates[0].calculationMethod}) → ${finalTimelineValue}天`);
            
            matchedTimelineRecord = matchedCandidates[0].record;
          }
          
          timelineValue = finalTimelineValue;
          // matchedTimelineRecord 已在上面的处理逻辑中设置
        }
        

        
        // 计算当前节点的开始和完成时间（使用工作日计算）
        const calculateTimeline = (startDate: Date, timelineValue: number, calculationMethod: string = '外部') => {
          // 根据计算方式调整开始时间
          const adjustedStartDate = adjustToNextWorkingHour(startDate, calculationMethod, processNode.weekendDays, nodeExcludedDates);
          
          let endDate: Date;
          if (calculationMethod === '内部') {
            // 使用内部工作时间计算
            endDate = addInternalBusinessTime(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
          } else {
            // 使用原有的24小时制计算
            endDate = addBusinessDaysWithHolidays(adjustedStartDate, timelineValue, processNode.weekendDays, nodeExcludedDates);
          }
          
          return {
          startDate: formatDate(adjustedStartDate, 'STORAGE_FORMAT'),
          endDate: formatDate(endDate, 'STORAGE_FORMAT')
        };
        };
        
        // 获取当前节点的计算方式
        let nodeCalculationMethod = '外部'; // 默认值
        if (matchedCandidates.length > 0) {
          nodeCalculationMethod = matchedCandidates[0].calculationMethod;
        } else if (matchedTimelineRecord) {
          const calculationMethodField = matchedTimelineRecord.fields[calculationMethodFieldId];
          if (calculationMethodField) {
            if (typeof calculationMethodField === 'string') {
              nodeCalculationMethod = calculationMethodField;
            } else if (calculationMethodField && typeof calculationMethodField === 'object' && calculationMethodField.text) {
              nodeCalculationMethod = calculationMethodField.text;
            }
          }
        }
        
        let nodeStartTime: Date;
        let nodeEndTime: Date;
        let ruleDescription = '';
        let timelineResult: { startDate: string; endDate: string };
        const forceEndTimeTo18 = shouldForceEndTimeTo18(processNode);

        if (!isBackward) {
          nodeStartTime = new Date(cumulativeTime);

          if (processNode.startDateRule) {
            let ruleJson = '';
            if (typeof processNode.startDateRule === 'string') {
              ruleJson = processNode.startDateRule;
            } else if (processNode.startDateRule && processNode.startDateRule.text) {
              ruleJson = processNode.startDateRule.text;
            }

            if (ruleJson.trim()) {
              nodeStartTime = adjustStartDateByRule(nodeStartTime, ruleJson);
              console.log(`节点 ${processNode.nodeName} 应用起始日期调整规则后的开始时间:`, formatDate(nodeStartTime));
            }
          }

          if (processNode.dateAdjustmentRule) {
            console.log('原始dateAdjustmentRule:', processNode.dateAdjustmentRule);
            let ruleText = '';
            if (typeof processNode.dateAdjustmentRule === 'string') {
              ruleText = processNode.dateAdjustmentRule;
            } else if (Array.isArray(processNode.dateAdjustmentRule)) {
              ruleText = processNode.dateAdjustmentRule
                .filter((item: any) => item.type === 'text')
                .map((item: any) => item.text)
                .join('');
            } else if (processNode.dateAdjustmentRule && processNode.dateAdjustmentRule.text) {
              ruleText = processNode.dateAdjustmentRule.text;
            }

            console.log('解析后的ruleText:', ruleText);
            console.log('调整前的nodeStartTime:', formatDate(nodeStartTime));

            if (ruleText && ruleText.trim() !== '') {
              const result = adjustStartDateByJsonRule(nodeStartTime, ruleText);
              nodeStartTime = result.adjustedDate;
              ruleDescription = result.description || '';
              console.log(`节点 ${processNode.nodeName} 应用JSON日期调整规则后的开始时间:`, formatDate(nodeStartTime));
            }
          }

          timelineResult = timelineValue
            ? calculateTimeline(nodeStartTime, timelineValue, nodeCalculationMethod)
            : {
              startDate: formatDate(nodeStartTime, 'STORAGE_FORMAT'),
              endDate: '未找到时效数据'
            };

          if (timelineValue) {
            const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, nodeExcludedDates);
            if (nodeCalculationMethod === '内部') {
              nodeEndTime = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
            } else {
              nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, nodeExcludedDates);
            }
          } else {
            nodeEndTime = new Date(nodeStartTime);
          }

          if (forceEndTimeTo18 && nodeEndTime && !isNaN(nodeEndTime.getTime())) {
            nodeEndTime = setTimeOfDay(nodeEndTime, 18, 0);
            timelineResult = {
              ...timelineResult,
              endDate: timelineResult.endDate.includes('未找到') ? timelineResult.endDate : formatDate(nodeEndTime, 'STORAGE_FORMAT')
            };
          }
        } else {
          nodeEndTime = new Date(cumulativeTime);
          if (forceEndTimeTo18 && nodeEndTime && !isNaN(nodeEndTime.getTime())) {
            nodeEndTime = setTimeOfDay(nodeEndTime, 18, 0);
          }
          if (timelineValue) {
            if (nodeCalculationMethod === '内部') {
              nodeStartTime = addInternalBusinessTime(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
            } else {
              nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -timelineValue, processNode.weekendDays, nodeExcludedDates);
            }
          } else {
            nodeStartTime = new Date(nodeEndTime);
          }

          timelineResult = timelineValue
            ? {
              startDate: formatDate(nodeStartTime, 'STORAGE_FORMAT'),
              endDate: formatDate(nodeEndTime, 'STORAGE_FORMAT')
            }
            : {
              startDate: formatDate(nodeStartTime, 'STORAGE_FORMAT'),
              endDate: '未找到时效数据'
            };
        }
        
        // 计算跳过的天数
        const skippedWeekends = calculateSkippedWeekends(nodeStartTime, nodeEndTime, processNode.weekendDays);
        const actualDays = calculateActualDays(timelineResult.startDate, timelineResult.endDate);
        
        // 计算时间范围内实际跳过的自定义日期
        const excludedDatesInRange = calculateExcludedDatesInRange(nodeStartTime, nodeEndTime, nodeExcludedDates);
        
        (isBackward ? results.unshift.bind(results) : results.push.bind(results))({
          processOrder: processNode.processOrder,
          nodeName: processNode.nodeName,
          processGroup: processNode.processGroup,
          processGroupInstanceId: processNode.processGroupInstanceId,
          processGroupInstanceName: processNode.processGroupInstanceName,
          matchedLabels: processNode.matchedLabels,
          timelineValue: timelineValue,
          estimatedStart: timelineResult.startDate,
          estimatedEnd: timelineResult.endDate,
          timelineRecordId: matchedTimelineRecord ? 
            (matchedTimelineRecord.id || matchedTimelineRecord.recordId || matchedTimelineRecord._id || matchedTimelineRecord.record_id) : null,
          // 新增：保存所有匹配记录的信息（用于累加情况）
          allMatchedRecords: matchedCandidates.length > 1 ? matchedCandidates.map(candidate => ({
            recordId: candidate.record.id || candidate.record.recordId || candidate.record._id || candidate.record.record_id,
            timelineValue: candidate.timelineValue,
            originalHours: candidate.originalHours,
            calculationMethod: candidate.calculationMethod,
            relationshipType: candidate.relationshipType
          })) : null,
          // 新增：标识是否为累加处理
          isAccumulated: processingRule === '累加值' && matchedCandidates.length > 1,
          weekendDaysConfig: processNode.weekendDays, // 新增：保存休息日配置用于显示
          excludedDates: nodeExcludedDates, // 新增：保存不参与计算日期用于显示与快照
          // 新增：保存时间范围内实际跳过的日期
          actualExcludedDates: excludedDatesInRange.dates,
          actualExcludedDatesCount: excludedDatesInRange.count,
          calculationMethod: nodeCalculationMethod, // 新增：保存计算方式
          ruleDescription: ruleDescription, // 新增：保存规则描述
          skippedWeekends: skippedWeekends,
          actualDays: actualDays,
          // 新增：保存调整规则用于重新计算
          startDateRule: processNode.startDateRule,
          dateAdjustmentRule: processNode.dateAdjustmentRule,
          adjustmentDescription: ruleDescription // 新增：保存调整规则描述
        });
        
        // 更新累积时间：当前节点的完成时间成为下一个节点的开始时间
        if (timelineValue) {
          cumulativeTime = isBackward ? new Date(nodeStartTime) : new Date(nodeEndTime);
        }
        
        console.log(`节点 ${processNode.nodeName} (顺序: ${processNode.processOrder}):`, {
          开始时间: formatDate(nodeStartTime),
          完成时间: formatDate(nodeEndTime),
          时效天数: timelineValue,
          计算方式: nodeCalculationMethod
        });
      }

      if (isBackward && results.length > 0) {
        const parsed = typeof results[0]?.estimatedStart === 'string' ? parseDate(results[0].estimatedStart) : null;
        if (parsed && !isNaN(parsed.getTime())) {
          setCalculatedRequiredStartTime(parsed);
        } else {
          setCalculatedRequiredStartTime(null);
        }
      }

      let delayShowTimelineModal = false;
      if (isBackward) {
        const required = typeof results[0]?.estimatedStart === 'string' ? parseDate(results[0].estimatedStart) : null;
        setCalculatedRequiredStartTime(required && !isNaN(required.getTime()) ? required : null);
        if (required && currentStartTime && !isNaN(currentStartTime.getTime())) {
          const diffDays = differenceInCalendarDays(required, currentStartTime);
          const extraDays = Math.max(0, diffDays);

          if (extraDays > 0) {
            setAllocationNodesSnapshot(results);
            setAllocationExtraDays(extraDays);
            setAllocationMode('auto');
            setAllocationVisible(true);
            delayShowTimelineModal = true;
          } else if (required.getTime() < currentStartTime.getTime()) {
            if (showUI && bitable.ui.showToast) {
              await bitable.ui.showToast({ toastType: ToastType.warning, message: '当前起始日期晚于倒推要求起始日期，可能无法满足客户期望日期' });
            }
          }
        }
      }

      const nextAdjustments = remapTimelineAdjustmentsToNewResults(timelineResults, results);
      setTimelineAdjustments(nextAdjustments);
      pendingRecalculateAfterCalculateAdjustmentsRef.current = nextAdjustments;
      pendingRecalculateAfterCalculateRef.current = true;
      setTimelineResults(results);
      if (!isBackward && mode === 'generate' && showUI && currentExpectedDate && results.length > 0) {
        let lastCompletionDate: Date | null = null;
        for (let i = results.length - 1; i >= 0; i--) {
          const end = results[i]?.estimatedEnd;
          if (!end || end === '时效值为0') continue;
          const parsed = typeof end === 'string' ? parseDate(end) : (end as any as Date);
          if (parsed && !isNaN(parsed.getTime())) {
            lastCompletionDate = parsed;
            break;
          }
        }

        if (lastCompletionDate) {
          const bufferDays = Math.max(0, Math.ceil(baseBufferDays));
          const computedDeliveryDate = new Date(lastCompletionDate);
          computedDeliveryDate.setDate(computedDeliveryDate.getDate() + bufferDays);
          const slackDays = differenceInCalendarDays(currentExpectedDate, computedDeliveryDate);
          if (slackDays > 0) {
            const suggested = bufferDays + slackDays;
            await new Promise<void>((resolve) => {
              Modal.confirm({
                title: '建议增加缓冲期',
                content: `客户交期 ${formatDate(currentExpectedDate)}（${getDayOfWeek(currentExpectedDate)}）晚于当前预计交付日期 ${formatDate(computedDeliveryDate)}（${getDayOfWeek(computedDeliveryDate)}），建议将缓冲期从 ${bufferDays} 天调整为 ${suggested} 天以对齐。`,
                okText: '增加缓冲期',
                cancelText: '不调整',
                onOk: () => {
                  setBaseBufferDays(suggested);
                  setHasAppliedSuggestedBuffer(true);
                  setLastSuggestedApplied(suggested);
                  resolve();
                },
                onCancel: () => resolve()
              });
            });
          }
        }
      }
      if (showUI && !delayShowTimelineModal) {
        setTimelineVisible(true);
      } else if (showUI && delayShowTimelineModal) {
        setTimelineVisible(false);
      }
      
      console.log('按流程顺序计算的时效结果:', results);
      
      return results; // 返回计算结果
      
    } catch (error) {
      console.error('计算时效失败:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '计算时效失败，请检查表格配置'
        });
      }
      throw error; // 重新抛出错误
    } finally {
      setTimelineLoading(false);
    }
  };
  
  // 复合调整处理函数：根据缓冲期和交期余量状态决定调整方式
  const handleComplexAdjustment = (nodeKey: string, nodeIndex: number, adjustment: number) => {
    const nextAdjustments = handleTimelineAdjustment(nodeKey, nodeIndex, adjustment);
    if (!nextAdjustments) return;
    const deficit = computeBufferDeficitDaysUsingEndDelta(nextAdjustments);
    const prevDeficit = lastBufferDeficitRef.current;
    lastBufferDeficitRef.current = deficit;
    if (adjustment > 0 && deficit > 0 && Math.ceil(deficit) > Math.ceil(prevDeficit)) {
      Modal.warning({
        title: '缓冲期不足',
        content: `当前缓冲期无法覆盖本次超期，缺口 ${Math.ceil(deficit)} 天`,
      });
    }
  };
  
  // 调整时效值的函数
  const handleTimelineAdjustment = (nodeKey: string, nodeIndex: number, adjustment: number) => {
    const newAdjustments = { ...timelineAdjustments };
    const currentAdjustment = newAdjustments[nodeKey] || 0;
    const newAdjustment = currentAdjustment + adjustment;
    
    // 允许调整后的时效值为负数，用于向前回退结束时间
    const baseValue = (typeof timelineResults[nodeIndex]?.timelineValue === 'number')
      ? timelineResults[nodeIndex]!.timelineValue
      : (typeof timelineResults[nodeIndex]?.adjustedTimelineValue === 'number')
        ? timelineResults[nodeIndex]!.adjustedTimelineValue
        : 0;
    
    // 检查当前调整的节点是否为周转周期节点
    const currentNodeName = timelineResults[nodeIndex]?.nodeName;
    const isTurnoverNode = currentNodeName === '周转周期';
    
    // 如果调整的是周转周期节点，直接返回（禁用手动调整）
    if (isTurnoverNode) {
      return null;
    }
    
    newAdjustments[nodeKey] = newAdjustment;
    
    setTimelineAdjustments(newAdjustments);
    
    // 使用智能重算逻辑，只重算被调整的节点及其后续节点
    const hasAnyNonZeroAdjustment = Object.values(newAdjustments).some(v => v !== 0);
    recalculateTimeline(newAdjustments, !hasAnyNonZeroAdjustment);
    return newAdjustments;
  };

  // 获取重新计算后的时间线结果（不更新状态，逻辑对齐页面的重算口径）
  const getRecalculatedTimeline = (
    adjustments: Record<string, number>,
    opts?: { ignoreActualCompletionDates?: boolean; actualCompletionDatesOverride?: { [key: number]: Date | null } }
  ) => {
    const updatedResults = [...timelineResults];
    let cumulativeStartTime = startTime ? new Date(startTime) : new Date(); // 从起始时间开始

    for (let i = 0; i < updatedResults.length; i++) {
      const result = updatedResults[i];
      const baseTimelineValue = (typeof result.timelineValue === 'number')
        ? result.timelineValue
        : (typeof result.adjustedTimelineValue === 'number')
          ? result.adjustedTimelineValue
          : 0;
      const nodeKey = buildTimelineAdjustmentKey(result, i);
      const adjustment = adjustments[nodeKey] || 0;
      const adjustedTimelineValue = baseTimelineValue + adjustment;

      // 计算当前节点的开始时间
      let nodeStartTime = new Date(cumulativeStartTime);

      // 应用起始日期调整规则（与页面重算逻辑一致）
      if (result.startDateRule) {
        let ruleJson = '';
        if (typeof result.startDateRule === 'string') {
          ruleJson = result.startDateRule;
        } else if (result.startDateRule && result.startDateRule.text) {
          ruleJson = result.startDateRule.text;
        }
        if (ruleJson.trim()) {
          nodeStartTime = adjustStartDateByRule(nodeStartTime, ruleJson);
        }
      }

      // 应用 JSON 日期调整规则（与页面重算逻辑一致）
      let ruleDescription = result.ruleDescription || '';
      if (result.dateAdjustmentRule) {
        let ruleText = '';
        if (typeof result.dateAdjustmentRule === 'string') {
          ruleText = result.dateAdjustmentRule;
        } else if (Array.isArray(result.dateAdjustmentRule)) {
          ruleText = result.dateAdjustmentRule
            .filter((item: any) => item.type === 'text')
            .map((item: any) => item.text)
            .join('');
        } else if (result.dateAdjustmentRule && result.dateAdjustmentRule.text) {
          ruleText = result.dateAdjustmentRule.text;
        }
        if (ruleText && ruleText.trim() !== '') {
          const adjustmentResult = adjustStartDateByJsonRule(nodeStartTime, ruleText);
          nodeStartTime = adjustmentResult.adjustedDate;
          if (adjustmentResult.description) {
            ruleDescription = adjustmentResult.description;
          }
        }
      }

      // 获取节点的计算方式、休息日与排除日期（与页面重算逻辑一致）
      const nodeWeekendDays = result.weekendDaysConfig || [];
      const nodeCalculationMethod = result.calculationMethod || '外部';
      const nodeExcludedDates = Array.isArray(result.excludedDates) ? result.excludedDates : [];

      // 计算节点的结束时间（允许负时效值向前回退）
      let nodeEndTime: Date;
      if (adjustedTimelineValue !== 0) {
        const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
        if (nodeCalculationMethod === '内部') {
          nodeEndTime = addInternalBusinessTime(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        } else {
          nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        }
      } else {
        nodeEndTime = new Date(nodeStartTime);
      }

      if (shouldForceEndTimeTo18(result) && nodeEndTime && !isNaN(nodeEndTime.getTime())) {
        nodeEndTime = setTimeOfDay(nodeEndTime, 18, 0);
      }

      const actualEnd = opts?.ignoreActualCompletionDates
        ? null
        : (opts?.actualCompletionDatesOverride ?? actualCompletionDates)?.[i] ?? null;

      // 计算跳过的天数及日期范围内的自定义跳过日期
      const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
      const skippedWeekends = calculateSkippedWeekends(adjustedStartTime, nodeEndTime, nodeWeekendDays);
      const estimatedStartStr = formatDate(adjustedStartTime);
      let estimatedEndStr = adjustedTimelineValue !== 0 ? formatDate(nodeEndTime) : '时效值为0';
      if (actualEnd && actualEnd instanceof Date && !isNaN(actualEnd.getTime())) {
        estimatedEndStr = formatDate(actualEnd);
      }
      const actualDays = calculateActualDays(estimatedStartStr, estimatedEndStr);
      const excludedDatesInRange = calculateExcludedDatesInRange(adjustedStartTime, nodeEndTime, nodeExcludedDates);

      // 更新结果
      updatedResults[i] = {
        ...result,
        adjustedTimelineValue,
        estimatedStart: estimatedStartStr,
        estimatedEnd: estimatedEndStr,
        adjustment,
        calculationMethod: nodeCalculationMethod,
        skippedWeekends,
        actualDays,
        actualExcludedDates: excludedDatesInRange.dates,
        actualExcludedDatesCount: excludedDatesInRange.count,
        adjustmentDescription: result.adjustmentDescription,
        ruleDescription
      };

      // 更新累积开始时间：使用当前节点的预计完成时间
      if (adjustedTimelineValue !== 0) {
        if (actualEnd && actualEnd instanceof Date && !isNaN(actualEnd.getTime())) {
          cumulativeStartTime = new Date(actualEnd);
        } else {
          cumulativeStartTime = new Date(nodeEndTime);
        }
      }
    }

    return updatedResults;
  };

  const getRecalculatedTimelineBackward = (adjustments: Record<string, number>) => {
    const updatedResults = [...timelineResults];
    const fallbackEnd = (() => {
      if (expectedDate && !isNaN(expectedDate.getTime())) return new Date(expectedDate);
      const lastEndStr = updatedResults.length > 0 ? updatedResults[updatedResults.length - 1]?.estimatedEnd : null;
      const parsed = typeof lastEndStr === 'string' ? parseDate(lastEndStr) : null;
      return parsed && !isNaN(parsed.getTime()) ? parsed : new Date();
    })();

    let cumulativeEndTime = new Date(fallbackEnd);

    for (let i = updatedResults.length - 1; i >= 0; i--) {
      const result = updatedResults[i];
      const baseTimelineValue = (typeof result.timelineValue === 'number')
        ? result.timelineValue
        : (typeof result.adjustedTimelineValue === 'number')
          ? result.adjustedTimelineValue
          : 0;
      const nodeKey = buildTimelineAdjustmentKey(result, i);
      const adjustment = adjustments[nodeKey] || 0;
      const adjustedTimelineValue = baseTimelineValue + adjustment;

      const nodeWeekendDays = result.weekendDaysConfig || [];
      const nodeCalculationMethod = result.calculationMethod || '外部';
      const nodeExcludedDates = Array.isArray(result.excludedDates) ? result.excludedDates : [];

      let nodeEndTime = new Date(cumulativeEndTime);
      if (shouldForceEndTimeTo18(result) && nodeEndTime && !isNaN(nodeEndTime.getTime())) {
        nodeEndTime = setTimeOfDay(nodeEndTime, 18, 0);
      }
      let nodeStartTime: Date;

      if (adjustedTimelineValue !== 0) {
        if (nodeCalculationMethod === '内部') {
          nodeStartTime = addInternalBusinessTime(nodeEndTime, -adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        } else {
          nodeStartTime = addBusinessDaysWithHolidays(nodeEndTime, -adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        }
      } else {
        nodeStartTime = new Date(nodeEndTime);
      }

      const skippedWeekends = calculateSkippedWeekends(nodeStartTime, nodeEndTime, nodeWeekendDays);
      const estimatedStartStr = formatDate(nodeStartTime);
      const estimatedEndStr = formatDate(nodeEndTime);
      const actualDays = calculateActualDays(estimatedStartStr, estimatedEndStr);
      const excludedDatesInRange = calculateExcludedDatesInRange(nodeStartTime, nodeEndTime, nodeExcludedDates);

      updatedResults[i] = {
        ...result,
        adjustedTimelineValue,
        estimatedStart: estimatedStartStr,
        estimatedEnd: estimatedEndStr,
        adjustment,
        calculationMethod: nodeCalculationMethod,
        skippedWeekends,
        actualDays,
        actualExcludedDates: excludedDatesInRange.dates,
        actualExcludedDatesCount: excludedDatesInRange.count,
      };

      if (adjustedTimelineValue !== 0) {
        cumulativeEndTime = new Date(nodeStartTime);
      }
    }

    return updatedResults;
  };

  const buildAutoAllocationDraft = (nodes: any[], extraDays: number, excluded?: {[key: number]: boolean}) => {
    const roundedExtra = Math.max(0, Math.round(Number(extraDays) * 100) / 100);
    if (!Array.isArray(nodes) || nodes.length === 0 || roundedExtra === 0) return {};

    const isTurnover = (node: any) => node?.nodeName === '周转周期';
    const toBase = (node: any) => {
      const v = typeof node?.timelineValue === 'number'
        ? node.timelineValue
        : (typeof node?.adjustedTimelineValue === 'number' ? node.adjustedTimelineValue : 0);
      return Number.isFinite(v) ? v : 0;
    };

    const eligible = nodes
      .map((n, i) => ({ i, base: toBase(n), turnover: isTurnover(n), excluded: !!excluded?.[i] }))
      .filter(x => !x.turnover && !x.excluded);

    if (eligible.length === 0) return {};

    const positive = eligible.filter(x => x.base > 0);
    const pool = positive.length > 0 ? positive : eligible;
    const totalBase = pool.reduce((s, x) => s + (x.base > 0 ? x.base : 1), 0);

    const draft: {[key: number]: number} = {};
    let remaining = roundedExtra;
    for (let k = 0; k < pool.length; k++) {
      const idx = pool[k].i;
      if (k === pool.length - 1) {
        draft[idx] = Math.max(0, Math.round(remaining * 100) / 100);
        break;
      }
      const weight = pool[k].base > 0 ? pool[k].base : 1;
      const raw = (roundedExtra * weight) / totalBase;
      const val = Math.max(0, Math.round(raw * 100) / 100);
      draft[idx] = val;
      remaining = Math.max(0, Math.round((remaining - val) * 100) / 100);
    }

    return draft;
  };

  useEffect(() => {
    if (!allocationVisible) return;
    if (allocationMode !== 'auto') return;
    const nodes = allocationNodesSnapshot.length > 0 ? allocationNodesSnapshot : timelineResults;
    const draft = buildAutoAllocationDraft(nodes, allocationExtraDays, allocationExcluded);
    setAllocationDraft(draft);
  }, [allocationVisible, allocationMode, allocationExtraDays, allocationNodesSnapshot, timelineResults, allocationExcluded]);

  const applyAllocationDraft = async () => {
    const sum = Object.values(allocationDraft || {}).reduce((s, v) => s + (Number(v) || 0), 0);
    const roundedSum = Math.round(sum * 100) / 100;
    const roundedTarget = Math.round((Number(allocationExtraDays) || 0) * 100) / 100;
    const diff = Math.round((roundedTarget - roundedSum) * 100) / 100;

    if (Math.abs(diff) > 0.01) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.warning, message: `当前分配合计${roundedSum}天，与盈余${roundedTarget}天不一致` });
      }
      return;
    }

    const merged: Record<string, number> = { ...(timelineAdjustments || {}) };
    const nodes = allocationNodesSnapshot.length > 0 ? allocationNodesSnapshot : timelineResults;
    for (const [k, v] of Object.entries(allocationDraft || {})) {
      const idx = parseInt(k, 10);
      const val = Math.max(0, Math.round((Number(v) || 0) * 100) / 100);
      const node = nodes?.[idx];
      const nodeKey = buildTimelineAdjustmentKey(node, idx);
      merged[nodeKey] = Math.round(((Number(merged[nodeKey]) || 0) + val) * 100) / 100;
    }

    setTimelineAdjustments(merged);
    setAllocationVisible(false);
    recalculateTimeline(merged, true);
    setTimelineVisible(true);

    if (bitable.ui.showToast) {
      await bitable.ui.showToast({ toastType: ToastType.success, message: '已应用盈余分配' });
    }
  };

  // 获取有效的最后完成日期（忽略 '时效值为0'），若不存在则返回最后一项的日期
  const getLastValidCompletionDateFromResults = (results: any[]): Date | null => {
    if (!Array.isArray(results) || results.length === 0) return null;
    for (let i = results.length - 1; i >= 0; i--) {
      const endStr = results[i]?.estimatedEnd;
      if (endStr && typeof endStr === 'string' && endStr !== '时效值为0') {
        const d = new Date(endStr);
        if (!isNaN(d.getTime())) return d;
      }
    }
    const fallbackStr = results[results.length - 1]?.estimatedEnd;
    const fallback = fallbackStr ? new Date(fallbackStr) : null;
    return fallback && !isNaN(fallback.getTime()) ? fallback : null;
  };

  const computeLastNodeEndDeltaDays = (adjustments: Record<string, number>): number => {
    try {
      if (timelineDirection === 'backward') return 0;
      const baseline = getRecalculatedTimeline({}, { ignoreActualCompletionDates: true });
      const current = getRecalculatedTimeline(adjustments);

      const pickLastNodeEnd = (results: any[]): Date | null => {
        if (!Array.isArray(results) || results.length === 0) return null;
        const endStr = results[results.length - 1]?.estimatedEnd;
        if (endStr && typeof endStr === 'string' && endStr !== '时效值为0') {
          const d = new Date(endStr);
          if (!isNaN(d.getTime())) return d;
        }
        return getLastValidCompletionDateFromResults(results);
      };

      const baselineLast = pickLastNodeEnd(baseline);
      const currentLast = pickLastNodeEnd(current);
      if (!baselineLast || !currentLast) return 0;

      const dayMs = 1000 * 60 * 60 * 24;
      return Math.ceil((currentLast.getTime() - baselineLast.getTime()) / dayMs);
    } catch {
      return 0;
    }
  };

  const computeBufferDeficitDaysUsingEndDelta = (adjustments: Record<string, number>): number => {
    const deltaDays = computeLastNodeEndDeltaDays(adjustments);
    const base = Math.max(0, Math.ceil(baseBufferDays));
    return Math.max(0, deltaDays - base);
  };

  const computeExpectedDeliveryDateTsFromResults = (
    results: any[],
    adjustments: Record<string, number>,
    baseBufferDaysOverride?: number
  ): number | null => {
    if (timelineDirection === 'backward') return null;
    const lastCompletionDate = getLastValidCompletionDateFromResults(results);
    if (!lastCompletionDate) return null;
    const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments, baseBufferDaysOverride);
    const deliveryDate = new Date(lastCompletionDate);
    deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
    const ts = deliveryDate.getTime();
    return Number.isFinite(ts) ? ts : null;
  };

  const handleExpectedDeliveryDateLockChange = (checked: boolean) => {
    const nextLocked = !!checked;
    setIsExpectedDeliveryDateLocked(nextLocked);
    if (!nextLocked) {
      setLockedExpectedDeliveryDateTs(null);
      return;
    }
    if (!expectedDate || isNaN(expectedDate.getTime())) {
      setIsExpectedDeliveryDateLocked(false);
      setLockedExpectedDeliveryDateTs(null);
      if (bitable.ui.showToast) {
        bitable.ui.showToast({ toastType: ToastType.warning, message: '请先选择客户期望日期，再开启锁交付' });
      }
      return;
    }
    setLockedExpectedDeliveryDateTs(expectedDate.getTime());
  };

  const computeDynamicBufferDaysUsingEndDelta = (adjustments: Record<string, number>, baseBufferDaysOverride?: number): number => {
    try {
      if (timelineDirection === 'backward') return 0;
      const deltaDays = computeLastNodeEndDeltaDays(adjustments);
      const base = Math.max(0, Math.ceil(baseBufferDaysOverride ?? baseBufferDays));
      const remaining = base - deltaDays;
      return Math.max(0, Math.min(base, remaining));
    } catch {
      const base = Math.max(0, Math.ceil(baseBufferDaysOverride ?? baseBufferDays));
      return base;
    }
  };

  // 重新计算时间线的函数
  const recalculateTimeline = (adjustments: Record<string, number>, forceRecalculateAll: boolean = false) => {
    if (timelineDirection === 'backward') {
      const updated = getRecalculatedTimelineBackward(adjustments);
      setTimelineResults(updated);
      const firstStartStr = updated.length > 0 ? updated[0]?.estimatedStart : null;
      const parsed = typeof firstStartStr === 'string' ? parseDate(firstStartStr) : null;
      if (parsed && !isNaN(parsed.getTime())) {
        setCalculatedRequiredStartTime(parsed);
      } else {
        setCalculatedRequiredStartTime(null);
      }
      return;
    }

    const updatedResults = [...timelineResults];
    
    // 找到第一个被调整的节点索引
    const adjustedIndices = updatedResults
      .map((r, i) => {
        const key = buildTimelineAdjustmentKey(r, i);
        const v = adjustments[key] || 0;
        return v !== 0 ? i : -1;
      })
      .filter(i => i >= 0);
    
    // 找到有实际完成时间的节点
    const actualCompletionIndices = Object.keys(actualCompletionDates)
      .map(k => parseInt(k))
      .filter(i => actualCompletionDates[i] !== null && actualCompletionDates[i] !== undefined);
    
    // 如果没有调整且不是强制重算，但有实际完成时间，需要重新计算
    if (adjustedIndices.length === 0 && !forceRecalculateAll && actualCompletionIndices.length === 0) {
      return; // 没有调整也没有实际完成时间，直接返回
    }
    
    // 确定第一个需要重新计算的节点索引
    let firstAdjustedIndex: number;
    
    if (forceRecalculateAll) {
      firstAdjustedIndex = 0;
    } else if (actualCompletionIndices.length > 0) {
      // 如果有实际完成时间，从最早有实际完成时间的节点的下一个节点开始重新计算
      const earliestActualCompletionIndex = Math.min(...actualCompletionIndices);
      const earliestAdjustmentIndex = adjustedIndices.length > 0 ? Math.min(...adjustedIndices) : Infinity;
      firstAdjustedIndex = Math.min(earliestActualCompletionIndex + 1, earliestAdjustmentIndex);
      console.log(`检测到实际完成时间，从节点 ${firstAdjustedIndex} 开始重新计算`);
    } else {
      firstAdjustedIndex = adjustedIndices.length > 0 ? Math.min(...adjustedIndices) : 0;
    }
    
    // 确保索引不超出范围
    firstAdjustedIndex = Math.max(0, Math.min(firstAdjustedIndex, updatedResults.length - 1));
    
    // 确定累积开始时间
    let cumulativeStartTime: Date;
    if (firstAdjustedIndex === 0) {
      // 如果调整的是第一个节点，从起始时间开始
      cumulativeStartTime = startTime ? new Date(startTime) : new Date();
    } else {
      // 如果调整的不是第一个节点，从前一个节点的结束时间开始
      const previousResult = updatedResults[firstAdjustedIndex - 1];
      const previousIndex = firstAdjustedIndex - 1;
      
      // 检查前一个节点是否有实际完成时间
      if (actualCompletionDates[previousIndex]) {
        // 使用实际完成时间作为下一个节点的开始时间
        cumulativeStartTime = new Date(actualCompletionDates[previousIndex]!);
        console.log(`节点 ${previousIndex} 使用实际完成时间: ${formatDate(cumulativeStartTime)}`);
      } else {
        // 使用预计完成时间
        const prevEndParsed = typeof previousResult.estimatedEnd === 'string'
          ? parseDate(previousResult.estimatedEnd)
          : previousResult.estimatedEnd as any as Date;
        // 当无法解析前一节点的预计完成时，安全回退到全局起始时间（或当前时间），避免使用未初始化的累计时间
        cumulativeStartTime = (prevEndParsed && !isNaN(prevEndParsed.getTime()))
          ? new Date(prevEndParsed)
          : (startTime ? new Date(startTime) : new Date());
      }
    }
    
    // 只重新计算从第一个调整节点开始的后续节点
    for (let i = firstAdjustedIndex; i < updatedResults.length; i++) {
      const result = updatedResults[i];
      const baseTimelineValue = (typeof result.timelineValue === 'number')
        ? result.timelineValue
        : (typeof result.adjustedTimelineValue === 'number')
          ? result.adjustedTimelineValue
          : 0;
      const nodeKey = buildTimelineAdjustmentKey(result, i);
      const adjustment = adjustments[nodeKey] || 0;
      const adjustedTimelineValue = baseTimelineValue + adjustment;
      
      // 计算当前节点的开始时间
      let nodeStartTime = new Date(cumulativeStartTime);
      
      // 重新应用起始日期调整规则
      if (result.startDateRule) {
        let ruleJson = '';
        if (typeof result.startDateRule === 'string') {
          ruleJson = result.startDateRule;
        } else if (result.startDateRule && result.startDateRule.text) {
          ruleJson = result.startDateRule.text;
        }
        
        if (ruleJson.trim()) {
          nodeStartTime = adjustStartDateByRule(nodeStartTime, ruleJson);
        }
      }
      
      // 重新应用JSON格式日期调整规则
      let ruleDescription = result.ruleDescription; // 保持原有描述作为默认值
      if (result.dateAdjustmentRule) {
        let ruleText = '';
        if (typeof result.dateAdjustmentRule === 'string') {
          ruleText = result.dateAdjustmentRule;
        } else if (Array.isArray(result.dateAdjustmentRule)) {
          ruleText = result.dateAdjustmentRule
            .filter((item: any) => item.type === 'text')
            .map((item: any) => item.text)
            .join('');
        } else if (result.dateAdjustmentRule && result.dateAdjustmentRule.text) {
          ruleText = result.dateAdjustmentRule.text;
        }
        
        if (ruleText && ruleText.trim() !== '') {
          const adjustmentResult = adjustStartDateByJsonRule(nodeStartTime, ruleText);
          nodeStartTime = adjustmentResult.adjustedDate;
          // 更新规则描述
          if (adjustmentResult.description) {
            ruleDescription = adjustmentResult.description;
          }
        }
      }
      
      const nodeWeekendDays = result.weekendDaysConfig || []; // 使用节点特定的休息日配置
      const nodeCalculationMethod = result.calculationMethod || '外部'; // 获取节点的计算方式
      
      let nodeEndTime: Date;
      const nodeExcludedDates = Array.isArray(result.excludedDates) ? result.excludedDates : [];
      if (adjustedTimelineValue !== 0) {
        const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
        if (nodeCalculationMethod === '内部') {
          nodeEndTime = addInternalBusinessTime(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        } else {
          nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, adjustedTimelineValue, nodeWeekendDays, nodeExcludedDates);
        }
      } else {
        nodeEndTime = new Date(nodeStartTime);
      }

      if (shouldForceEndTimeTo18(result) && nodeEndTime && !isNaN(nodeEndTime.getTime())) {
        nodeEndTime = setTimeOfDay(nodeEndTime, 18, 0);
      }
      
      // 计算跳过的天数
      const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates);
      const skippedWeekends = calculateSkippedWeekends(adjustedStartTime, nodeEndTime, nodeWeekendDays);
      const estimatedStartStr = formatDate(adjustedStartTime);
      let estimatedEndStr = adjustedTimelineValue !== 0 ? formatDate(nodeEndTime) : '时效值为0';
      // 若当前节点存在实际完成时间，则用于展示与跨度计算，以实现与后续节点的实际连线
      if (actualCompletionDates[i]) {
        estimatedEndStr = formatDate(actualCompletionDates[i]!);
      }
      const actualDays = calculateActualDays(estimatedStartStr, estimatedEndStr);
      
      // 计算时间范围内实际跳过的自定义日期
      const excludedDatesInRange = calculateExcludedDatesInRange(adjustedStartTime, nodeEndTime, nodeExcludedDates);
      
      // 更新结果
        updatedResults[i] = {
          ...result,
          adjustedTimelineValue: adjustedTimelineValue,
          estimatedStart: estimatedStartStr,
          estimatedEnd: estimatedEndStr,
          adjustment: adjustment,
          calculationMethod: nodeCalculationMethod, // 保持计算方式
          skippedWeekends: skippedWeekends,
          actualDays: actualDays,
          // 更新时间范围内实际跳过的日期
          actualExcludedDates: excludedDatesInRange.dates,
          actualExcludedDatesCount: excludedDatesInRange.count,
          adjustmentDescription: result.adjustmentDescription, // 保持调整规则描述
          ruleDescription: ruleDescription // 添加更新后的规则描述
        };
      
      // 更新累积时间：优先使用当前节点的实际完成时间，否则使用预计完成时间
      if (adjustedTimelineValue !== 0) {
        if (actualCompletionDates[i]) {
          // 如果当前节点有实际完成时间，使用实际完成时间
          cumulativeStartTime = new Date(actualCompletionDates[i]!);
        } else {
          // 否则使用预计完成时间
          cumulativeStartTime = new Date(nodeEndTime);
        }
      }
    }
    
    setTimelineResults(updatedResults);
  };

  // 添加快照还原状态标志
  const [isRestoringSnapshot, setIsRestoringSnapshot] = useState(false);

  const [hasAppliedSuggestedBuffer, setHasAppliedSuggestedBuffer] = useState(false);
  const [lastSuggestedApplied, setLastSuggestedApplied] = useState<number | null>(null);

  // 初始状态快照（仅捕获一次）
  const initialSnapshotRef = useRef<any>(null);
  const hasCapturedInitialSnapshotRef = useRef<boolean>(false);

  // 当起始时间变更时，重新以最新起始时间为基准重算全流程
  useEffect(() => {
    if (timelineDirection !== 'forward') return;
    if (timelineResults.length > 0 && !isRestoringSnapshot) {
      recalculateTimeline(timelineAdjustments, true); // 强制重算所有节点
    }
  }, [startTime, isRestoringSnapshot, timelineDirection]);

  useEffect(() => {
    if (timelineDirection !== 'backward') return;
    if (!expectedDate) return;
    if (timelineResults.length > 0 && !isRestoringSnapshot) {
      recalculateTimeline(timelineAdjustments, true);
    }
  }, [expectedDate, isRestoringSnapshot, timelineDirection]);

  // 当实际完成日期变化时，以最新状态进行重算，避免首次选择不触发或使用旧值
  useEffect(() => {
    if (timelineResults.length > 0 && !isRestoringSnapshot) {
      recalculateTimeline(timelineAdjustments, false);
    }
  }, [actualCompletionDates, isRestoringSnapshot]);

  useEffect(() => {
    if (!pendingRecalculateAfterExcludedDatesRef.current) return;
    if (isRestoringSnapshot) return;
    pendingRecalculateAfterExcludedDatesRef.current = false;
    if (timelineResults.length > 0) {
      recalculateTimeline(timelineAdjustments, true);
    }
  }, [timelineResults, isRestoringSnapshot]);

  useEffect(() => {
    if (!pendingRecalculateAfterCalculateRef.current) return;
    if (isRestoringSnapshot) return;
    pendingRecalculateAfterCalculateRef.current = false;
    const adjustments = pendingRecalculateAfterCalculateAdjustmentsRef.current || timelineAdjustments;
    pendingRecalculateAfterCalculateAdjustmentsRef.current = null;
    if (timelineResults.length > 0) {
      recalculateTimeline(adjustments, true);
    }
  }, [timelineResults, isRestoringSnapshot]);

  // 捕获初始状态快照（在首次生成/还原出完整时间线后）
  useEffect(() => {
    if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) {
      initialSnapshotRef.current = {
        timelineDirection,
        startTime,
        expectedDate,
        selectedLabels,
        timelineResults,
        timelineAdjustments,
        baseBufferDays,
        lockedExpectedDeliveryDateTs,
        isExpectedDeliveryDateLocked,
        actualCompletionDates,
        completionDateAdjustment,
        hasAppliedSuggestedBuffer,
        lastSuggestedApplied,
        deliveryMarginDeductions,
      };
      hasCapturedInitialSnapshotRef.current = true;
      console.log('已捕获初始状态快照');
    }
  }, [timelineResults, startTime, expectedDate]);

  // 重置调整的函数
  const resetTimelineAdjustments = () => {
    setTimelineAdjustments({});
    setDeliveryMarginDeductions(0); // 同时重置交期余量扣减
    setCompletionDateAdjustment(0); // 重置最后流程完成日期调整
    setActualCompletionDates({}); // 重置实际完成日期
    setBaseBufferDays(14); // 重置固定缓冲期为默认值
    setIsExpectedDeliveryDateLocked(false);
    try { lastBufferDeficitRef.current = 0; } catch {}
    setHasAppliedSuggestedBuffer(false); // 重置建议缓冲期应用标志
    setLastSuggestedApplied(null); // 清空上次建议值
    recalculateTimeline({}, true); // 强制重算所有节点
  };

  // 一键还原到最初状态
  const resetToInitialState = async () => {
    try {
      if (!hasCapturedInitialSnapshotRef.current || !initialSnapshotRef.current) {
        // 若未捕获到初始快照，则退化为仅重置调整
        resetTimelineAdjustments();
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({ toastType: ToastType.warning, message: '未检测到初始快照，已重置调整项' });
        }
        return;
      }
      const s = initialSnapshotRef.current;
      setIsRestoringSnapshot(true);
      if (s.timelineDirection === 'forward' || s.timelineDirection === 'backward') {
        setTimelineDirection(s.timelineDirection);
      }
      setStartTime(s.startTime || null);
      setExpectedDate(s.expectedDate || null);
      setSelectedLabels(s.selectedLabels || {});
      const normalizedAdjustments = normalizeTimelineAdjustmentsFromSnapshot(s.timelineAdjustments || {}, s.timelineResults || []);
      setTimelineAdjustments(normalizedAdjustments);
      setBaseBufferDays(s.baseBufferDays ?? 14);
      const shouldLock = s.isExpectedDeliveryDateLocked === true || (s.isExpectedDeliveryDateLocked === undefined && s.lockedExpectedDeliveryDateTs !== null && s.lockedExpectedDeliveryDateTs !== undefined);
      if (shouldLock && typeof s.lockedExpectedDeliveryDateTs === 'number' && Number.isFinite(s.lockedExpectedDeliveryDateTs)) {
        setLockedExpectedDeliveryDateTs(s.lockedExpectedDeliveryDateTs);
        setIsExpectedDeliveryDateLocked(true);
      } else {
        setLockedExpectedDeliveryDateTs(null);
        setIsExpectedDeliveryDateLocked(false);
      }
      setActualCompletionDates(s.actualCompletionDates || {});
      setCompletionDateAdjustment(s.completionDateAdjustment || 0);
      setHasAppliedSuggestedBuffer(!!s.hasAppliedSuggestedBuffer && s.hasAppliedSuggestedBuffer);
      setLastSuggestedApplied(s.lastSuggestedApplied ?? null);
      setDeliveryMarginDeductions(s.deliveryMarginDeductions || 0);
      setTimelineResults(Array.isArray(s.timelineResults) ? s.timelineResults : []);
      recalculateTimeline(normalizedAdjustments, true);
      setIsRestoringSnapshot(false);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.success, message: '已恢复至最初状态' });
      }
    } catch (e) {
      console.error('恢复初始状态失败:', e);
      setIsRestoringSnapshot(false);
    }
  };
  
  // 已移除未使用的 getTimelineLabelFieldId 辅助函数

  // 写入货期记录表的函数
  const writeToDeliveryRecordTable = async (
    timelineResults: any[],
    processRecordIds: string[],
    timelineAdjustments: Record<string, number> = {},
    overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]}; baseBufferDays?: number }
  ) => {
    let recordCells: any[] | undefined;
    try {
      console.log('=== 开始写入货期记录表 ===');
      console.log('当前模式:', mode);
      console.log('timelineResults数量:', timelineResults?.length || 0);
      console.log('processRecordIds数量:', processRecordIds?.length || 0);
      console.log('timelineAdjustments:', timelineAdjustments);
      console.log('timelineResults详情:', timelineResults);
      console.log('processRecordIds详情:', processRecordIds);
      
      // 获取货期记录表
      console.log('正在获取货期记录表...');
      const deliveryRecordTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
      console.log('成功获取货期记录表');
      
      // 检查字段是否存在
      console.log('正在检查和获取所有必需字段...');
      const fieldsToCheck = [
        DELIVERY_FOREIGN_ID_FIELD_ID,
        DELIVERY_LABELS_FIELD_ID,
        DELIVERY_STYLE_FIELD_ID,
        DELIVERY_COLOR_FIELD_ID,
        DELIVERY_CREATE_TIME_FIELD_ID,
        DELIVERY_EXPECTED_DATE_FIELD_ID,
        DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID,
        DELIVERY_NODE_DETAILS_FIELD_ID,
        DELIVERY_ADJUSTMENT_INFO_FIELD_ID,  // 添加货期调整信息字段
        DELIVERY_START_TIME_FIELD_ID,       // 新增：起始时间字段
        DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID
      ];
      console.log('需要检查的字段ID列表:', fieldsToCheck);
      
      // 获取各个字段
      console.log('正在获取各个字段对象...');
      const [
        foreignIdField,
        labelsField,
        styleField,
        colorField,
        text2Field,
        createTimeField,
        expectedDateField,
        nodeDetailsField,
        customerExpectedDateField,
        adjustmentInfoField,
        versionField,
        startTimeField,
        snapshotField,
        recordIdsTextField,
        factoryDepartureDateField
      ] = await Promise.all([
        deliveryRecordTable.getField(DELIVERY_FOREIGN_ID_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_LABELS_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_STYLE_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_COLOR_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_TEXT2_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_CREATE_TIME_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_EXPECTED_DATE_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_NODE_DETAILS_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_ADJUSTMENT_INFO_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_VERSION_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_RECORD_IDS_FIELD_ID),
        deliveryRecordTable.getField(DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID)
      ]);
      console.log('成功获取所有字段对象');
      
      // 检查标签汇总字段的类型
      const labelsFieldDebug = labelsField as any;
      console.log('标签汇总字段信息:', {
        id: labelsFieldDebug?.id,
        name: labelsFieldDebug?.name,
        type: labelsFieldDebug?.type,
        property: labelsFieldDebug?.property
      });
      
      // 获取foreign_id：调整模式严格使用快照数据，生成模式优先使用选择记录
      console.log('=== 开始获取foreign_id ===');
      let foreignId = overrides?.foreignId ?? '';
      
      // 使用全局状态
      const currentSelectedRecords = selectedRecords;
      const currentRecordDetails = recordDetails;
      
      if (!foreignId && mode === 'adjust') {
        // 调整模式：严格使用快照回填的foreign_id，即使为空也不回退
        foreignId = currentForeignId ?? '';
        console.log('调整模式：严格使用快照恢复的foreign_id:', foreignId);
      } else if (!foreignId && currentSelectedRecords.length > 0) {
        // 生成模式：从选择记录获取
        console.log('生成模式：从选择记录获取foreign_id');
        console.log('selectedRecords[0]:', currentSelectedRecords[0]);
        
        // 生成模式：从数据库获取
        const table = await bitable.base.getTable(TABLE_ID);
        const firstRecord = await table.getRecordById(currentSelectedRecords[0]);
        console.log('获取到的记录:', firstRecord);
        const fieldValue = firstRecord.fields['fldpvBfeC0'];
        console.log('fldpvBfeC0字段值:', fieldValue);
        
        foreignId = extractText(fieldValue);
      }
      
      // 生成模式的回退逻辑：记录详情
      if (!foreignId && mode !== 'adjust' && currentRecordDetails.length > 0) {
        const first = currentRecordDetails[0];
        const val = first.fields['fldpvBfeC0'];
        foreignId = extractText(val);
      }
      // 生成模式的最后回退：快照状态
      if (!foreignId && mode !== 'adjust' && currentForeignId) {
        foreignId = currentForeignId;
      }
      
      // 获取款式与颜色：调整模式优先使用快照数据，生成模式优先使用记录详情
      let style = overrides?.style ?? '';
      let color = overrides?.color ?? '';
      
      if (!style && !color && mode === 'adjust') {
        // 调整模式：严格使用快照回填的数据，即使为空也不回退
        style = currentStyleText;
        color = currentColorText;
        console.log('调整模式：严格使用快照恢复的款式:', style, '颜色:', color);
      } else {
        // 生成模式：优先使用记录详情
        if (!style && !color && currentRecordDetails.length > 0) {
          const first = currentRecordDetails[0];
          style = extractText(first.fields['fld6Uw95kt']) || currentStyleText || '';
          color = extractText(first.fields['flde85ni4O']) || currentColorText || '';
        } else {
          // 回退：使用快照回填的状态
          style = style || currentStyleText || '';
          color = color || currentColorText || '';
          // 若仍为空且有选择记录，仅做一次读取
          if ((!style || !color) && currentSelectedRecords.length > 0) {
            const table = await bitable.base.getTable(TABLE_ID);
            const firstRecord = await table.getRecordById(currentSelectedRecords[0]);
            style = style || extractText(firstRecord.fields['fld6Uw95kt']);
            color = color || extractText(firstRecord.fields['flde85ni4O']);
          }
        }
      }
      
      // 获取文本2：调整模式优先使用快照数据；生成模式在批量模式下填写
      let text2 = '';
      if (mode === 'adjust') {
        // 调整模式：严格使用快照回填的数据，即使为空也不回退
        text2 = currentText2; // 直接使用快照值，不使用 || '' 的回退逻辑
        console.log('调整模式：严格使用快照恢复的文本2:', text2);
      } else {
        // 生成模式：文本2字段保持为空
        text2 = '';
        console.log('生成模式：文本2字段保持为空');
      }
      
      // 获取标签汇总：批量模式优先使用传入的labels
      const selectedLabelValues = Object.values(overrides?.selectedLabels ?? selectedLabels).flat().filter(Boolean);
      
      const baseBufferDaysToUse = Math.max(0, Math.ceil(overrides?.baseBufferDays ?? baseBufferDays));

      // 获取预计交付日期（交期余量的日期版本：最后流程完成日期 + 基础缓冲期）
      let expectedDeliveryDate = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments, baseBufferDaysToUse);
      if (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) {
        expectedDeliveryDate = lockedExpectedDeliveryDateTs;
      }
      
      // 获取客户期望日期：批量模式优先使用传入的expectedDate
      let customerExpectedDate = null;
      const expectedDateToUse = overrides?.expectedDate ?? expectedDate;
      if (expectedDateToUse) {
        customerExpectedDate = expectedDateToUse.getTime();
      }

      let factoryDepartureDate = null as number | null;
      const reservationInbound = timelineResults.find(r => r?.nodeName === '预约入库');
      const reservationEnd = reservationInbound?.estimatedEnd;
      if (reservationEnd instanceof Date && !isNaN(reservationEnd.getTime())) {
        factoryDepartureDate = reservationEnd.getTime();
      } else if (typeof reservationEnd === 'number' && Number.isFinite(reservationEnd)) {
        factoryDepartureDate = reservationEnd;
      } else if (typeof reservationEnd === 'string' && reservationEnd.trim() !== '') {
        const parsed = parseDate(reservationEnd);
        if (parsed) {
          factoryDepartureDate = parsed.getTime();
        }
      }
      
      // 创建当前时间戳
      const currentTime = new Date().getTime();
      
      // 计算版本号（数字）并格式化货期调整信息
      let versionNumber = 1;
      if (mode === 'adjust' && currentVersionNumber !== null) {
        versionNumber = currentVersionNumber + 1;
      }

      let adjustmentInfo = `版本：V${versionNumber}`;
      if (Object.keys(timelineAdjustments).length > 0) {
        const nodeNameByKey = new Map<string, string>();
        for (let i = 0; i < timelineResults.length; i++) {
          const r = timelineResults[i];
          const k = buildTimelineAdjustmentKey(r, i);
          if (!nodeNameByKey.has(k)) nodeNameByKey.set(k, r?.nodeName || k);
        }
        const adjustmentTexts = Object.entries(timelineAdjustments).map(([nodeKey, adjustment]) => {
          const nodeName = nodeNameByKey.get(nodeKey) || nodeKey;
          return `${nodeName}: ${adjustment > 0 ? '+' : ''}${adjustment.toFixed(1)} 天`;
        });
        adjustmentInfo += `\n当前调整：\n${adjustmentTexts.join('\n')}`;
      }
      
      // 在创建Cell之前进行数据校验（移除冗余日志）

      // ===== 构建完整快照（保持与流程数据表写入时的一致内容）并写入到货期记录表 =====
      const expectedDateTimestamp = expectedDateToUse ? expectedDateToUse.getTime() : null;
      const expectedDateString = expectedDateToUse ? format(expectedDateToUse, DATE_FORMATS.STORAGE_FORMAT) : null;
      const currentStartTime = overrides?.startTime ?? startTime;
      const currentSelectedLabels = overrides?.selectedLabels ?? selectedLabels;

      // 与快照字段保持相同的命名
      const styleText = style || '';
      const colorText = color || '';

      const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments, baseBufferDaysToUse);

      // 检查是否达到最终限制
      let hasReachedFinalLimit = false;
      const currentExpectedDate = expectedDate;
      if (dynamicBufferDays === 0 && currentExpectedDate && timelineResults.length > 0) {
        const lastNode = timelineResults[timelineResults.length - 1];
        if (lastNode && lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) {
          try {
            const lastCompletionDate = new Date(lastNode.estimatedEnd);
            const daysToExpected = Math.ceil((currentExpectedDate.getTime() - lastCompletionDate.getTime()) / (1000 * 60 * 60 * 24));
            hasReachedFinalLimit = daysToExpected <= 0;
          } catch (error) {
            console.warn('计算最终限制状态失败:', error);
          }
        }
      }

      // 为快照提供基础缓冲与节点调整总量（用于兼容历史字段），尽管动态缓冲期已改为“自然日差”口径
      const baseBuferDays = baseBufferDaysToUse;
      const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);

      const globalSnapshot = {
        version: versionNumber,
        foreignId,
        styleText,
        colorText,
        text2,
        mode,
        timelineDirection,
        excludedDatesOverride,
        excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride,
        lockedExpectedDeliveryDateTs,
        isExpectedDeliveryDateLocked,
        selectedLabels: currentSelectedLabels,
        expectedDateTimestamp,
        expectedDateString,
        startTimestamp: currentStartTime ? currentStartTime.getTime() : undefined,
        startString: currentStartTime ? formatDate(currentStartTime, 'STORAGE_FORMAT') : undefined,
        timelineAdjustments,
        generationModeState: {
          currentForeignId,
          currentStyleText,
          currentColorText,
          currentText2,
          currentVersionNumber: versionNumber,
          recordDetails: recordDetails || [],
          hasSelectedLabels: Object.values(selectedLabels).some(value => {
            return Array.isArray(value) ? value.length > 0 : Boolean(value);
          }),
          labelSelectionComplete: Object.keys(selectedLabels).length > 0
        },
        ...(timelineDirection !== 'backward'
          ? {
              bufferManagement: {
                baseDays: baseBuferDays,
                totalAdjustments,
                dynamicBufferDays,
                hasReachedFinalLimit,
                hasAppliedSuggestedBuffer,
                lastSuggestedApplied: lastSuggestedApplied ?? 0
              },
            }
          : {}),
        chainAdjustmentSystem: {
          enabled: true,
          lastCalculationTime: new Date().getTime(),
          adjustmentHistory: timelineAdjustments
        },
        timelineCalculationState: {
          calculationTimestamp: new Date().getTime(),
          totalNodes: timelineResults.length,
          hasValidResults: timelineResults.length > 0,
          lastCalculationMode: mode,
          timelineDirection,
          excludedDatesOverride,
          excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride
        },
        totalNodes: timelineResults.length,
        isGlobalSnapshot: true
      };

      // 选择用于快照的最后一个有效节点（与流程写入时的节点快照结构一致）
      let selectedIndex = -1;
      for (let i = timelineResults.length - 1; i >= 0; i--) {
        const r = timelineResults[i];
        if (r.estimatedEnd && !r.estimatedEnd.includes('未找到') && r.estimatedEnd.trim() !== '') {
          selectedIndex = i;
          break;
        }
      }
      if (selectedIndex === -1) selectedIndex = 0; // 无有效结束时间时兜底为第一个

      const selectedResult = timelineResults[selectedIndex];
      let nodeStartTs = null as number | null;
      let nodeEndTs = null as number | null;
      if (selectedResult.estimatedStart && !selectedResult.estimatedStart.includes('未找到')) {
        try { nodeStartTs = new Date(selectedResult.estimatedStart).getTime(); } catch {}
      }
      if (selectedResult.estimatedEnd && !selectedResult.estimatedEnd.includes('未找到')) {
        try { nodeEndTs = new Date(selectedResult.estimatedEnd).getTime(); } catch {}
      }

      const nodeSnapshot = {
        processOrder: selectedResult.processOrder,
        nodeName: selectedResult.nodeName,
        matchedLabels: selectedResult.matchedLabels,
        timelineValue: selectedResult.timelineValue,
        estimatedStart: selectedResult.estimatedStart,
        estimatedEnd: selectedResult.estimatedEnd,
        estimatedStartTimestamp: nodeStartTs,
        estimatedEndTimestamp: nodeEndTs,
        timelineRecordId: selectedResult.timelineRecordId,
        allMatchedRecords: selectedResult.allMatchedRecords,
        isAccumulated: selectedResult.isAccumulated,
        weekendDaysConfig: selectedResult.weekendDaysConfig,
        excludedDates: selectedResult.excludedDates,
        actualExcludedDates: selectedResult.actualExcludedDates,
        actualExcludedDatesCount: selectedResult.actualExcludedDatesCount,
        calculationMethod: selectedResult.calculationMethod,
        ruleDescription: selectedResult.ruleDescription,
        skippedWeekends: selectedResult.skippedWeekends,
        actualDays: selectedResult.actualDays,
        adjustedTimelineValue: selectedResult.adjustedTimelineValue,
        adjustment: selectedResult.adjustment,
        adjustmentDescription: selectedResult.adjustmentDescription,
        startDateRule: selectedResult.startDateRule,
        dateAdjustmentRule: selectedResult.dateAdjustmentRule,
        nodeCalculationState: {
          hasValidTimelineValue: typeof selectedResult.timelineValue === 'number' && selectedResult.timelineValue !== 0,
          hasValidStartTime: Boolean(nodeStartTs),
          hasValidEndTime: Boolean(nodeEndTs),
          calculationTimestamp: new Date().getTime(),
          originalTimelineValue: selectedResult.timelineValue,
          finalAdjustedValue: (selectedResult.adjustedTimelineValue ?? selectedResult.timelineValue)
        },
        chainAdjustmentNode: {
          nodeIndex: selectedIndex,
          hasAdjustment: selectedResult.adjustment !== undefined && selectedResult.adjustment !== 0,
          adjustmentValue: selectedResult.adjustment || 0,
          isChainSource: selectedResult.adjustment !== undefined && selectedResult.adjustment !== 0,
          affectedByChain: selectedIndex > 0
        },
        isNodeSnapshot: true
      };

      const completeSnapshot = {
        ...globalSnapshot,
        ...nodeSnapshot,
        timelineResults: timelineResults,
        currentNodeIndex: selectedIndex,
        currentNodeName: selectedResult.nodeName,
        isCompleteSnapshot: true,
        snapshotType: 'complete'
      };
      const snapshotJson = JSON.stringify(completeSnapshot);

      // 使用createCell方法创建各个字段的Cell
      const startTimestamp = currentStartTime ? currentStartTime.getTime() : currentTime;

      const recordIdsText = (restoredRecordIdsText && restoredRecordIdsText.trim() !== '')
        ? restoredRecordIdsText.trim()
        : '';

      const [
        foreignIdCell,
        labelsCell,
        styleCell,
        colorCell,
        text2Cell,
        createTimeCell,
        startTimeCell,
        snapshotCell,
        expectedDateCell,
        customerExpectedDateCell,
        factoryDepartureDateCell,
        nodeDetailsCell,
        adjustmentInfoCell,
        versionCell,
        recordIdsCell
      ] = await Promise.all([
        foreignIdField.createCell(foreignId),
        selectedLabelValues.length > 0 ? labelsField.createCell(selectedLabelValues) : Promise.resolve(null),
        styleField.createCell(style),
        colorField.createCell(color),
        text2Field.createCell(text2),
        createTimeField.createCell(currentTime),
        startTimeField.createCell(startTimestamp),
        snapshotField.createCell(snapshotJson),
        expectedDeliveryDate ? expectedDateField.createCell(expectedDeliveryDate) : Promise.resolve(null),
        customerExpectedDate ? customerExpectedDateField.createCell(customerExpectedDate) : Promise.resolve(null),
        factoryDepartureDate ? factoryDepartureDateField.createCell(factoryDepartureDate) : Promise.resolve(null),
        processRecordIds.length > 0 ? nodeDetailsField.createCell({ recordIds: processRecordIds }) : Promise.resolve(null),
        adjustmentInfo ? adjustmentInfoField.createCell(adjustmentInfo) : Promise.resolve(null),
        versionField.createCell(versionNumber),
        recordIdsTextField.createCell(recordIdsText)
      ]);
      
      // 组合所有Cell到一个记录中
      recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell, snapshotCell, recordIdsCell];
      
      // 只有当数据存在时才添加对应的Cell
      if (labelsCell) recordCells.push(labelsCell);
      if (expectedDateCell) recordCells.push(expectedDateCell);
      if (customerExpectedDateCell) recordCells.push(customerExpectedDateCell);
      if (factoryDepartureDateCell) recordCells.push(factoryDepartureDateCell);
      if (nodeDetailsCell) recordCells.push(nodeDetailsCell);
      if (adjustmentInfoCell) recordCells.push(adjustmentInfoCell);
      
      // 添加记录到货期记录表
      const addedRecord = await deliveryRecordTable.addRecord(recordCells);
      
      return addedRecord;
    } catch (error: any) {
      console.error('写入货期记录表详细错误:', {
        error: error,
        message: error?.message,
        stack: error?.stack,
        recordCellsLength: Array.isArray(recordCells) ? recordCells.length : 'recordCells未定义'
      });
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '写入货期记录表失败: ' + (error as Error).message
        });
      }
      throw error;
    }
  };

  // 写入流程数据表的函数
  const writeToProcessDataTable = async (timelineResults: any[], overrides?: { foreignId?: string; style?: string; color?: string }): Promise<string[]> => {
    try {
      console.log('=== 开始写入流程数据表 ===');
      console.log('当前模式:', mode);
      console.log('timelineResults数量:', timelineResults?.length || 0);
      
      
      // 获取流程数据表和流程配置表
      console.log('正在获取流程数据表和流程配置表...');
      const processDataTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID);
      const processConfigTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID);
      console.log('成功获取数据表');
      
      // 获取所有需要的字段
      console.log('正在获取所有必需字段...');
      const [
        foreignIdField,
        processNameField,
        processOrderField,
        startDateField,
        endDateField,
        versionField,
        timelinessField,
        processStyleField,
        processColorField,
        processGroupField
      ] = await Promise.all([
        processDataTable.getField(FOREIGN_ID_FIELD_ID),
        processDataTable.getField(PROCESS_NAME_FIELD_ID),
        processDataTable.getField(PROCESS_ORDER_FIELD_ID_DATA),
        processDataTable.getField(ESTIMATED_START_DATE_FIELD_ID),
        processDataTable.getField(ESTIMATED_END_DATE_FIELD_ID),
        processDataTable.getField(PROCESS_VERSION_FIELD_ID),
        processDataTable.getField(PROCESS_TIMELINESS_FIELD_ID),
        processDataTable.getField(PROCESS_STYLE_FIELD_ID),
        processDataTable.getField(PROCESS_COLOR_FIELD_ID),
        processDataTable.getField(PROCESS_GROUP_FIELD_ID_DATA)
      ]);
      console.log('成功获取所有字段');
      
      // 获取foreign_id - 支持批量模式直接传递数据
      console.log('=== 开始获取foreign_id ===');
      let foreignId = overrides?.foreignId ?? null;
      
      // 使用全局状态
      const currentSelectedRecords = selectedRecords;
      const currentRecordDetails = recordDetails;
      
      console.log('selectedRecords数量:', currentSelectedRecords?.length || 0);
      console.log('recordDetails数量:', currentRecordDetails?.length || 0);
      console.log('selectedRecords:', currentSelectedRecords);
      console.log('recordDetails:', currentRecordDetails);
      
      if (currentSelectedRecords.length > 0 && currentRecordDetails.length > 0) {
        // 从第一个选择的记录的详情中获取fldpvBfeC0字段的值
        const firstRecord = currentRecordDetails[0];
        if (firstRecord && firstRecord.fields && firstRecord.fields['fldpvBfeC0']) {
          const fieldValue = firstRecord.fields['fldpvBfeC0'];
          foreignId = extractText(fieldValue);
          console.log('从fldpvBfeC0字段获取到的foreign_id:', foreignId);
        } else {
          console.warn('未在记录详情中找到fldpvBfeC0字段');
          console.log('第一个记录的字段:', firstRecord?.fields);
        }
      }
      
      // 快照回填：在调整模式通过快照还原时使用当前foreign_id状态
      if (!foreignId && currentForeignId) {
        foreignId = currentForeignId;
        console.log('使用快照恢复的foreign_id:', foreignId);
      }
      
      if (!foreignId) {
        console.warn('未找到foreign_id，跳过写入流程数据表');
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.warning,
            message: '未找到foreign_id字段，无法写入流程数据表'
          });
        }
        return [];
      }

      // 获取款式与颜色：与货期记录写入逻辑保持一致
      let style = overrides?.style ?? '';
      let color = overrides?.color ?? '';

      if (mode === 'adjust') {
        // 调整模式：严格使用快照回填的数据
        style = currentStyleText;
        color = currentColorText;
        console.log('调整模式：流程数据款式/颜色来自快照', { style, color });
      } else {
        // 生成模式：优先使用记录详情
        if (currentRecordDetails.length > 0) {
          const first = currentRecordDetails[0];
          try { style = extractText(first.fields['fld6Uw95kt']); } catch { style = first?.fields?.['fld6Uw95kt'] || ''; }
          try { color = extractText(first.fields['flde85ni4O']); } catch { color = first?.fields?.['flde85ni4O'] || ''; }
          if (!style) style = currentStyleText || '';
          if (!color) color = currentColorText || '';
        } else {
          // 回退：使用快照回填的状态
          style = currentStyleText || '';
          color = currentColorText || '';
          // 若仍为空且有选择记录，仅做一次读取
          if ((!style || !color) && currentSelectedRecords.length > 0) {
            try {
              const table = await bitable.base.getTable(TABLE_ID);
              const firstRecord = await table.getRecordById(currentSelectedRecords[0]);
              style = style || extractText(firstRecord.fields['fld6Uw95kt']);
              color = color || extractText(firstRecord.fields['flde85ni4O']);
            } catch (e) {
              console.warn('读取源表款式/颜色失败，保持现有值', e);
            }
          }
        }
      }

      
      
      // 构建页面快照JSON（确保可一模一样还原）
      // 计算版本号：仅在调整模式下递增
      let versionNumber = 1;
      if (mode === 'adjust' && currentVersionNumber !== null) {
        versionNumber = currentVersionNumber + 1;
      }


      // 使用createCell方法准备要写入的记录数据
      const recordValueList: Array<{ fields: Record<string, any> }> = [];
      const fallbackCellRows: any[] = [];

      let selectOptions: Array<{ id: string; name: string }> = [];
      try {
        if ((processNameField as any)?.getOptions) {
          selectOptions = await (processNameField as any).getOptions();
        }
      } catch {}
      if (!selectOptions || selectOptions.length === 0) {
        const propOptions = (processNameField as any)?.property?.options;
        if (Array.isArray(propOptions)) {
          selectOptions = propOptions;
        }
      }
      const optionNameToId = new Map<string, string>();
      for (const opt of (selectOptions || [])) {
        if (opt && typeof (opt as any).name === 'string' && typeof (opt as any).id === 'string') {
          optionNameToId.set((opt as any).name, (opt as any).id);
        }
      }

      const _processGroupField = processGroupField;
        
        for (let index = 0; index < timelineResults.length; index++) {
          const result = timelineResults[index];

          const hasValidTimelineValue = typeof result.timelineValue === 'number' && Number.isFinite(result.timelineValue) && result.timelineValue > 0;
          if (!hasValidTimelineValue) {
            console.log(`跳过节点 "${result.nodeName}" - 未匹配到时效值`);
            continue;
          }
          
          // 检查是否有有效的预计完成时间（只检查结束时间）
          const hasValidEndTime = (
            result.estimatedEnd && 
            !result.estimatedEnd.includes('未找到') && 
            result.estimatedEnd.trim() !== ''
          );
          
          // 如果没有有效的预计完成时间，跳过这个节点
          if (!hasValidEndTime) {
            console.log(`跳过节点 "${result.nodeName}" - 未找到有效的预计完成时间`);
            continue;
          }
          
          // 转换日期格式为时间戳（毫秒）
          let startTimestamp = null;
          let endTimestamp = null;
          
          if (result.estimatedStart && !result.estimatedStart.includes('未找到')) {
            try {
              startTimestamp = new Date(result.estimatedStart).getTime();
            } catch (error) {
              console.error(`转换开始时间失败:`, error);
            }
          }
          
          if (result.estimatedEnd && !result.estimatedEnd.includes('未找到')) {
            try {
              endTimestamp = new Date(result.estimatedEnd).getTime();
            } catch (error) {
              console.error(`转换结束时间失败:`, error);
            }
          }
          
          
          const nodeName = result.nodeName;
          const optionId = typeof nodeName === 'string' ? optionNameToId.get(nodeName) : undefined;

          const rawGroupName =
            (typeof result.processGroupInstanceName === 'string' && result.processGroupInstanceName.trim() !== '')
              ? result.processGroupInstanceName
              : (typeof result.processGroup === 'string'
                ? result.processGroup
                : extractText(result.processGroup));
          const groupName = (rawGroupName || '').trim();

          if (optionId) {
            const fields: Record<string, any> = {
              [FOREIGN_ID_FIELD_ID]: foreignId,
              [PROCESS_NAME_FIELD_ID]: { id: optionId, text: nodeName },
              [PROCESS_ORDER_FIELD_ID_DATA]: result.processOrder,
              [PROCESS_STYLE_FIELD_ID]: style,
              [PROCESS_COLOR_FIELD_ID]: color,
              [PROCESS_VERSION_FIELD_ID]: versionNumber,
              [PROCESS_TIMELINESS_FIELD_ID]: result.timelineValue
            };

            if (startTimestamp) fields[ESTIMATED_START_DATE_FIELD_ID] = startTimestamp;
            if (endTimestamp) fields[ESTIMATED_END_DATE_FIELD_ID] = endTimestamp;

            if (groupName) {
              (fields as any)[PROCESS_GROUP_FIELD_ID_DATA] = groupName;
            }

            recordValueList.push({ fields });
          } else {
            const foreignIdCell = await foreignIdField.createCell(foreignId);
            const processNameCell = await processNameField.createCell(nodeName);
            const processOrderCell = await processOrderField.createCell(result.processOrder);
            const startDateCell = startTimestamp ? await startDateField.createCell(startTimestamp) : null;
            const endDateCell = endTimestamp ? await endDateField.createCell(endTimestamp) : null;
            const styleCell = await processStyleField.createCell(style);
            const colorCell = await processColorField.createCell(color);
            const versionCell = await versionField.createCell(versionNumber);
            const timelinessCell = await timelinessField.createCell(result.timelineValue);
            const processGroupCell = groupName ? await _processGroupField.createCell(groupName) : null;

            const recordCells = [
              foreignIdCell,
              processNameCell,
              processOrderCell,
              styleCell,
              colorCell,
              versionCell,
              timelinessCell
            ];
            if (startDateCell) recordCells.push(startDateCell);
            if (endDateCell) recordCells.push(endDateCell);
            if (processGroupCell) recordCells.push(processGroupCell);
            fallbackCellRows.push(recordCells);
          }
        }
      
      console.log('准备写入记录数:', recordValueList.length + fallbackCellRows.length);
      
      // 在添加记录的部分，收集记录ID
      const addedRecordIds: string[] = [];
      
      if (recordValueList.length > 0 || fallbackCellRows.length > 0) {
        if (recordValueList.length > 0) {
          try {
            const addedRecords = await processDataTable.addRecords(recordValueList as any);
            addedRecordIds.push(...(addedRecords as any));
          } catch (error) {
            console.error('批量写入(IRecordValue)失败，尝试逐条写入:', error);
            for (const rv of recordValueList) {
              try {
                const addedRecord = await processDataTable.addRecord(rv as any);
                addedRecordIds.push(addedRecord as any);
              } catch (e) {
                console.error('逐条写入(IRecordValue)失败:', e, rv);
              }
            }
          }
        }

        if (fallbackCellRows.length > 0) {
          try {
            const addedRecords2 = await (processDataTable as any).addRecordsByCell(fallbackCellRows);
            addedRecordIds.push(...(addedRecords2 as any));
          } catch (error) {
            console.error('回退批量写入(ICell[])失败，尝试逐条写入:', error);
            for (const recordCells of fallbackCellRows) {
              const addedRecord = await processDataTable.addRecord(recordCells);
              addedRecordIds.push(addedRecord as any);
            }
          }
        }
        
        console.log(`成功写入 ${addedRecordIds.length} 条流程数据`);
        
        // 显示成功提示
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.success,
            message: `成功写入 ${addedRecordIds.length} 条流程数据到流程数据表`
          });
        }
        
        return addedRecordIds; // 返回记录ID列表
      } else {
        console.warn('没有有效的记录可以写入');
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.warning,
            message: '没有有效的流程数据可以写入 - 未匹配到时效值'
          });
        }
        return [];
      }
      
    } catch (error) {
      console.error('写入流程数据表失败:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: `写入流程数据表失败: ${(error as Error).message}`
        });
      }
      return [];
    }
  };

  

  // 保存时效数据的核心逻辑（从确定并保存按钮提取）
  const saveTimelineData = async () => {
    try {
      // 使用全局状态
      const currentTimelineResults = timelineResults;
      const currentTimelineAdjustments = timelineAdjustments;
      
      if (currentTimelineResults.length > 0) {
        // 写入流程数据表
        const processRecordIds = await writeToProcessDataTable(currentTimelineResults);
        
        // 写入货期记录表
        const deliveryRecord = await writeToDeliveryRecordTable(currentTimelineResults, processRecordIds, currentTimelineAdjustments);
        const deliveryRecordId = typeof deliveryRecord === 'string'
          ? deliveryRecord
          : (deliveryRecord && typeof deliveryRecord === 'object'
            ? ((deliveryRecord as any).id || (deliveryRecord as any).recordId || (deliveryRecord as any).record_id || '')
            : '');

        if (mode === 'adjust') {
          setCurrentDeliveryRecordId(deliveryRecordId || null);
          if (currentVersionNumber !== null) {
            setCurrentVersionNumber(currentVersionNumber + 1);
          }
        }
        
        if (bitable.ui.showToast) {
          await bitable.ui.showToast({
            toastType: ToastType.success,
            message: Object.keys(currentTimelineAdjustments).length > 0 
              ? '已保存调整后的时效数据到流程数据表和货期记录表' 
              : '已保存计算的时效数据到流程数据表和货期记录表'
          });
        }
        
        return true; // 保存成功
      }
      return false; // 没有数据需要保存
    } catch (error) {
      console.error('保存数据时出错:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '保存数据失败，请重试'
        });
      }
      throw error; // 重新抛出错误
    }
  };

  const copyToClipboard = async (text: string) => {
    if (!text) return;

    const fallbackCopy = async () => {
      const doc: any = typeof document !== 'undefined' ? document : null;
      const textarea = doc?.createElement?.('textarea');
      if (!textarea) {
        throw new Error('无法访问剪贴板');
      }
      textarea.value = text;
      textarea.style.position = 'fixed';
      textarea.style.opacity = '0';
      textarea.style.left = '-9999px';
      doc.body.appendChild(textarea);
      textarea.focus();
      textarea.select();
      const ok = doc.execCommand?.('copy');
      doc.body.removeChild(textarea);
      if (!ok) {
        throw new Error('复制失败');
      }
    };

    try {
      const nav: any = typeof navigator !== 'undefined' ? navigator : null;
      if (nav?.clipboard?.writeText) {
        try {
          await nav.clipboard.writeText(text);
        } catch {
          await fallbackCopy();
        }
      } else {
        await fallbackCopy();
      }
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.success, message: '已复制' });
      }
    } catch (e: any) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.error, message: e?.message ? `复制失败：${e.message}` : '复制失败' });
      }
    }
  };
  const handleBatchProcess = async (range?: { start: number; end: number }) => {
    try {
      resetGlobalState({ resetMode: false });
      setModeSelectionVisible(false);
      setBatchLoading(true);
      setBatchProcessedCount(0);
      setBatchSuccessCount(0);
      setBatchFailureCount(0);
      setBatchProgressList([]);
      batchAbortRef.current = false;
      const batchTable = await bitable.base.getTable(BATCH_TABLE_ID);
      const fieldMetaList = await batchTable.getFieldMetaList();
      const nameToId = new Map<string, string>();
      for (const meta of (fieldMetaList || [])) {
        if (!meta) continue;
        const nm = (meta as any).name;
        const id = (meta as any).id;
        if (typeof nm === 'string' && typeof id === 'string' && nm.trim() && id.trim()) {
          nameToId.set(nm, id);
        }
      }
      const rows = await fetchAllRecordsByPage(batchTable, undefined);

      const rowsWithNo = rows.map((row: any, idx: number) => {
        const f = row?.fields || {};
        const no = parseBatchRowNumber(f[BATCH_ROW_NUMBER_FIELD_ID]);
        return { row, idx, no };
      });
      const hasRowNo = rowsWithNo.some(r => typeof r.no === 'number');
      const ordered = hasRowNo
        ? [...rowsWithNo].sort((a, b) => {
            const na = typeof a.no === 'number' ? a.no : Infinity;
            const nb = typeof b.no === 'number' ? b.no : Infinity;
            if (na !== nb) return na - nb;
            return a.idx - b.idx;
          })
        : rowsWithNo;

      const allNos = hasRowNo ? ordered.map(r => (typeof r.no === 'number' ? r.no : null)).filter((v): v is number => v !== null) : [];
      const minNo = allNos.length > 0 ? Math.min(...allNos) : 1;
      const maxNo = allNos.length > 0 ? Math.max(...allNos) : ordered.length;
      const requestedStart = range?.start && range.start > 0 ? range.start : (hasRowNo ? minNo : 1);
      const requestedEnd = range?.end && range.end > 0 ? range.end : (hasRowNo ? maxNo : ordered.length);
      const start = Math.min(requestedStart, requestedEnd);
      const end = Math.max(requestedStart, requestedEnd);

      const selected = hasRowNo
        ? ordered.filter(r => typeof r.no === 'number' && r.no >= Math.max(minNo, start) && r.no <= Math.min(maxNo, end))
        : ordered.slice(Math.max(0, Math.min(start, ordered.length) - 1), Math.max(0, Math.min(end, ordered.length)));

      setBatchProcessingTotal(selected.length);
      let processed = 0;
      for (let j = 0; j < selected.length; j++) {
        if (batchAbortRef.current) {
          break;
        }
        const { row, idx, no } = selected[j];
        const displayIndex = typeof no === 'number' ? no : (idx + 1);
        const f = row.fields || {};
        const getText = (name: string) => extractText(f[nameToId.get(name) || '']);
        const foreignId = getText('foreignId');
        const styleText = getText('styleText');
        const colorText = getText('colorText');
        const rawStart = f[nameToId.get('startTimestamp') || ''];
        const rawExpected = f[nameToId.get('expectedDateTimestamp') || ''];
        let startDate: Date | null = null;
        let expectedDateObj: Date | null = null;
        if (typeof rawStart === 'number') startDate = new Date(rawStart);
        else if (Array.isArray(rawStart) && rawStart.length > 0) {
          const item = rawStart[0];
          if (typeof item === 'number') startDate = new Date(item);
          else startDate = parseDate(extractText(rawStart));
        } else if (typeof rawStart === 'string') startDate = parseDate(rawStart);
        else if (rawStart && typeof rawStart === 'object') {
          const v = (rawStart as any).value || (rawStart as any).text || (rawStart as any).name || '';
          if (typeof v === 'number') startDate = new Date(v); else startDate = parseDate(v);
        }
        if (typeof rawExpected === 'number') expectedDateObj = new Date(rawExpected);
        else if (Array.isArray(rawExpected) && rawExpected.length > 0) {
          const item = rawExpected[0];
          if (typeof item === 'number') expectedDateObj = new Date(item);
          else expectedDateObj = parseDate(extractText(rawExpected));
        } else if (typeof rawExpected === 'string') expectedDateObj = parseDate(rawExpected);
        else if (rawExpected && typeof rawExpected === 'object') {
          const v = (rawExpected as any).value || (rawExpected as any).text || (rawExpected as any).name || '';
          if (typeof v === 'number') expectedDateObj = new Date(v); else expectedDateObj = parseDate(v);
        }

        const normalizeExcludedDates = (raw: any): string[] => {
          const out: string[] = [];
          const pushToken = (token: any) => {
            if (token == null) return;
            if (typeof token === 'number' && Number.isFinite(token)) {
              const d = new Date(token);
              if (!isNaN(d.getTime())) out.push(format(d, 'yyyy-MM-dd'));
              return;
            }
            const text = (typeof token === 'string') ? token : extractText(token);
            if (!text) return;
            const parts = String(text).split(/[,，;；、\n\r\t ]+/).map(s => s.trim()).filter(Boolean);
            for (const p of parts) {
              const head = p.slice(0, 10);
              if (/^\d{4}-\d{2}-\d{2}$/.test(head)) {
                out.push(head);
                continue;
              }
              const parsed = parseDate(p);
              if (parsed && !isNaN(parsed.getTime())) out.push(format(parsed, 'yyyy-MM-dd'));
            }
          };
          const walk = (v: any) => {
            if (Array.isArray(v)) {
              v.forEach(walk);
              return;
            }
            if (v && typeof v === 'object' && Array.isArray((v as any).value)) {
              walk((v as any).value);
              return;
            }
            pushToken(v);
          };
          walk(raw);
          return Array.from(new Set(out)).filter(Boolean);
        };

        const batchExcludedDates = normalizeExcludedDates(f['fldQNxtHnd']);
        const splitVals = (s: string) => (s || '').split(/[,，、]+/).map(v => v.trim()).filter(Boolean);
        const normalizeToStringList = (raw: any): string[] => {
          if (!raw) return [];
          if (Array.isArray(raw)) {
            return raw.flatMap((item: any) => normalizeToStringList(item));
          }
          if (typeof raw === 'string') return splitVals(raw);
          if (typeof raw === 'number') return [String(raw)];
          if (raw && typeof raw === 'object') return splitVals(extractText(raw));
          return [];
        };
        const labels: { [key: string]: string | string[] } = {};
        for (let i2 = 1; i2 <= 10; i2++) {
          const key = `标签${i2}`;
          const raw = f[nameToId.get(key) || ''];
          const list = normalizeToStringList(raw);
          if (list.length > 0) {
            if (i2 === 7 || i2 === 8 || i2 === 10) labels[key] = list;
            else labels[key] = list.join('，');
          }
        }
        {
          const requiredLabelKeys = Array.from({ length: 10 }, (_, k) => `标签${k + 1}`);
          const missing = requiredLabelKeys.filter(k => {
            const val = (labels as any)[k];
            if (Array.isArray(val)) return val.length === 0;
            return !(typeof val === 'string' && val.trim().length > 0);
          });
          if (missing.length > 0) {
            setBatchProcessedCount(p => p + 1);
            setBatchFailureCount(fCount => fCount + 1);
            setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: `标签不完整：${missing.join('、')}` }]);
            continue;
          }
        }
        setBatchCurrentRowInfo({ index: displayIndex, foreignId: foreignId || '', style: styleText || '', color: colorText || '' });
        setCurrentForeignId(foreignId || '');
        setCurrentStyleText(styleText || '');
        setCurrentColorText(colorText || '');
        setExpectedDate(expectedDateObj || null);
        setStartTime(startDate || null);
        setSelectedLabels(labels);
        try {
          const results = await handleCalculateTimeline(true, { selectedLabels: labels, expectedDate: expectedDateObj || null, startTime: startDate || null, excludedDates: batchExcludedDates }, false);
          if (results && results.length > 0) {
            const baseForSuggestion = 14;
            const suggestedBaseBufferDays = (() => {
              if (!expectedDateObj || isNaN(expectedDateObj.getTime())) return baseForSuggestion;
              const lastCompletionDate = getLastValidCompletionDateFromResults(results);
              if (!lastCompletionDate) return baseForSuggestion;
              const computedDeliveryDate = new Date(lastCompletionDate);
              computedDeliveryDate.setDate(computedDeliveryDate.getDate() + baseForSuggestion);
              const slackDays = differenceInCalendarDays(expectedDateObj, computedDeliveryDate);
              return slackDays > 0 ? (baseForSuggestion + slackDays) : baseForSuggestion;
            })();
            const processRecordIds = await writeToProcessDataTable(results, { foreignId, style: styleText, color: colorText });
            const deliveryRecordId = await writeToDeliveryRecordTable(
              results,
              processRecordIds,
              {},
              {
                foreignId,
                style: styleText,
                color: colorText,
                expectedDate: expectedDateObj || null,
                startTime: startDate || null,
                selectedLabels: labels,
                baseBufferDays: suggestedBaseBufferDays
              }
            );
            try {
              const candidateNames = ['状态','record_id','记录ID','货期记录ID','deliveryRecordId'];
              let statusFieldId = '';
              for (const nm of candidateNames) { const id = nameToId.get(nm) || ''; if (id) { statusFieldId = id; break; } }
              if (!statusFieldId) statusFieldId = 'fldKTpPL9s';
              const rowRecordId = (row.id || (row as any).recordId || (row as any)._id || (row as any).record_id);
              const deliveryRecordIdStr = typeof deliveryRecordId === 'string' 
                ? deliveryRecordId 
                : ((deliveryRecordId && ((deliveryRecordId as any).id || (deliveryRecordId as any).recordId)) ? (((deliveryRecordId as any).id || (deliveryRecordId as any).recordId) as string) : '');
              if (statusFieldId && rowRecordId && deliveryRecordIdStr) {
                const statusField = await batchTable.getField(statusFieldId);
                try {
                  await (statusField as any).setValue(rowRecordId, deliveryRecordIdStr);
                } catch (eSet) {
                  try {
                    const statusCell = await statusField.createCell(deliveryRecordIdStr);
                    try {
                      await (batchTable as any).updateRecord(rowRecordId, [statusCell]);
                    } catch (e1) {
                      const fn = (batchTable as any).setRecord || (batchTable as any).updateRecordById;
                      if (typeof fn === 'function') {
                        await fn.call(batchTable, rowRecordId, [statusCell]);
                      } else {
                        throw e1;
                      }
                    }
                  } catch (e2) {
                    throw e2;
                }
              }
              setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'success', message: `记录ID: ${deliveryRecordIdStr}` }]);
              } else {
                setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: '未找到状态字段或记录ID为空' }]);
              }
            } catch (statusErr: any) {
              console.warn('回写批量状态字段失败', statusErr);
              setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: `状态写入失败: ${statusErr?.message || '未知错误'}` }]);
            }
            processed++;
            setBatchProcessedCount(p => p + 1);
            setBatchSuccessCount(s => s + 1);
          } else {
            setBatchProcessedCount(p => p + 1);
            setBatchFailureCount(fCount => fCount + 1);
            setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: '时效结果为空' }]);
          }
        } catch (rowErr: any) {
          setBatchProcessedCount(p => p + 1);
          setBatchFailureCount(fCount => fCount + 1);
          setBatchProgressList(list => [...list, { index: displayIndex, foreignId: foreignId || '', status: 'failed', message: rowErr?.message || '处理失败' }]);
        }
      }
      if (bitable.ui.showToast) {
        const aborted = batchAbortRef.current;
        await bitable.ui.showToast({ toastType: ToastType.success, message: aborted ? `批量已中止，已处理 ${processed} 条记录` : `批量生成完成，共处理 ${processed} 条记录` });
      }
    } catch (error) {
      console.error('批量处理失败:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({ toastType: ToastType.error, message: '批量处理失败，请检查数据表配置' });
      }
    } finally {
      setBatchLoading(false);
    }
  };

  // 为批量处理优化的时效计算函数
  // 已移除批量时效计算函数 calculateTimelineForBatch



  // 执行定价数据查询
  const executeQuery = async (packId: string, packType: string) => {
    if (queryLoading) return;
    setQueryLoading(true);
    try {
      // 使用 apiService 中的函数
      const data = await executePricingQuery(packId, packType, selectedLabels);
      setQueryResults(data);
      
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.success,
          message: '查询成功'
        });
      }
      
    } catch (error: any) {
      console.error('数据库查询出错:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: `数据库查询失败: ${error.message}`
        });
      }
    } finally {
      setQueryLoading(false);
    }
  };

  // 执行二次工艺查询
  const executeSecondaryProcessQueryLocal = async (packId: string, packType: string) => {
    if (secondaryProcessLoading) return;
    setSecondaryProcessLoading(true);
    try {
      const data = await executeSecondaryProcessQuery(packId, packType);
      setSecondaryProcessResults(data);
      
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.success,
          message: '二次工艺查询成功'
        });
      }
    } catch (error: any) {
      console.error('二次工艺查询出错:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: `二次工艺查询失败: ${error.message}`
        });
      }
    } finally {
      setSecondaryProcessLoading(false);
    }
  };

  // 执行定价详情查询
  const executePricingDetailsQueryLocal = async (packId: string) => {
    if (pricingDetailsLoading) return;
    setPricingDetailsLoading(true);
    try {
      const data = await executePricingDetailsQuery(packId);
      setPricingDetailsResults(data);
      
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.success,
          message: '定价详情查询成功'
        });
      }
    } catch (error: any) {
      console.error('定价详情查询出错:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: `定价详情查询失败: ${error.message}`
        });
      }
    } finally {
      setPricingDetailsLoading(false);
    }
  };

  // 处理数据库查询
  const handleQueryDatabase = async (record: any) => {
    // 从记录字段中提取 packId 和 packType
    let packId = '';
    let packType = '';
    
    // 提取 pack_id (fldpvBfeC0)
    if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
      packId = record.fields.fldpvBfeC0[0].text;
    }
    
    // 提取 pack_type (fldSAF9qXe)
    if (record.fields.fldSAF9qXe && record.fields.fldSAF9qXe.text) {
      packType = record.fields.fldSAF9qXe.text;
    }
    
    if (!packId || !packType) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '缺少必要的查询参数 (pack_id 或 pack_type)'
        });
      }
      return;
    }
    
    await executeQuery(packId, packType);
  };

  // 处理二次工艺查询
  const handleSecondaryProcessQuery = async (record: any) => {
    // 从记录字段中提取 packId 和 packType
    let packId = '';
    let packType = '';
    
    // 提取 pack_id (fldpvBfeC0)
    if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
      packId = record.fields.fldpvBfeC0[0].text;
    }
    
    // 提取 pack_type (fldSAF9qXe)
    if (record.fields.fldSAF9qXe && record.fields.fldSAF9qXe.text) {
      packType = record.fields.fldSAF9qXe.text;
    }
    
    if (!packId || !packType) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '缺少必要的查询参数 (pack_id 或 pack_type)'
        });
      }
      return;
    }
    
    await executeSecondaryProcessQueryLocal(packId, packType);
  };

  // 处理定价详情查询
  const handlePricingDetailsQuery = async (record: any) => {
    // 从记录字段中提取 packId
    let packId = '';
    
    // 提取 pack_id (fldpvBfeC0)
    if (record.fields.fldpvBfeC0 && Array.isArray(record.fields.fldpvBfeC0) && record.fields.fldpvBfeC0.length > 0) {
      packId = record.fields.fldpvBfeC0[0].text;
    }
    
    if (!packId) {
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '缺少必要的查询参数 (pack_id)'
        });
      }
      return;
    }
    
    await executePricingDetailsQueryLocal(packId);
  };



  // 获取记录详情的函数
  const fetchRecordDetails = async (recordIdList: string[]) => {

    try {
      const table = await bitable.base.getTable(TABLE_ID);
      
      // 并行获取所有记录详情
      const recordPromises = recordIdList.map(recordId => 
        table.getRecordById(recordId)
      );
      
      const records = await Promise.all(recordPromises);
      
      const recordValList = records.map((record, index) => {
        console.log(`记录 ${recordIdList[index]} 的详情:`, record);
        console.log(`记录 ${recordIdList[index]} 的fldpvBfeC0字段:`, record.fields['fldpvBfeC0']);
        return {
          id: recordIdList[index],
          fields: record.fields
        };
      });
      
      setRecordDetails(recordValList);
    } catch (error) {
      console.error('获取记录详情失败:', error);
    }
  };

  // 选择记录
  const handleSelectRecords = async () => {
    // 切换版单数据时重置全局变量，清空旧结果与回填状态
    resetGlobalState();
    setLoading(true);
    // 清空标签选择
    setSelectedLabels({});
    setExpectedDate(null);
    
    try {
      // 修改这里：使用正确的 API
      const recordIdList = await bitable.ui.selectRecordIdList(TABLE_ID, VIEW_ID);
      
      setSelectedRecords(recordIdList);
      
      // 获取记录的详细信息
      if (recordIdList.length > 0) {
        // 并行获取记录详情和字段元数据
        const table = await bitable.base.getTable(TABLE_ID);
        const [recordPromises, fieldMetaList] = await Promise.all([
          Promise.all(recordIdList.map(recordId => table.getRecordById(recordId))),
          table.getFieldMetaList()
        ]);
        
        const recordValList = recordPromises.map((record, index) => ({
          id: recordIdList[index],
          fields: record.fields
        }));
        
        if (recordValList.length > 0) {
          const firstRecord = recordValList[0];
          const extractedLabels: {[key: string]: string} = {};
          
          // 建立字段名到字段ID的映射
          const fieldNameToId: {[key: string]: string} = {};
          for (const fieldMeta of fieldMetaList) {
            if (!fieldMeta || typeof (fieldMeta as any).name !== 'string' || typeof (fieldMeta as any).id !== 'string') {
              continue;
            }
            fieldNameToId[(fieldMeta as any).name] = (fieldMeta as any).id;
          }
          
          // 提取标签值的辅助函数
          const extractFieldValue = (fieldName: string) => {
            const fieldId = fieldNameToId[fieldName];
            if (fieldId && firstRecord.fields[fieldId]) {
              const fieldValue: any = firstRecord.fields[fieldId] as any;
              
              // 优先处理数组格式（公式字段）
              if (Array.isArray(fieldValue) && fieldValue.length > 0) {
                const firstItem: any = fieldValue[0] as any;
                if (typeof firstItem === 'string') {
                  return firstItem;
                } else if (firstItem && (firstItem.text || firstItem.name)) {
                  return firstItem.text || firstItem.name;
                }
              }
              // 处理对象格式（普通字段）
              else if (typeof fieldValue === 'object' && fieldValue !== null) {
                if (fieldValue.text) {
                  return fieldValue.text;
                } else if (fieldValue.name) {
                  return fieldValue.name;
                }
              }
              // 处理字符串格式
              else if (typeof fieldValue === 'string') {
                return fieldValue.trim();
              }
            }
            return '';
          };
          
          // 直接通过字段ID提取fld6Uw95kt的值
          const getFieldValueById = (fieldId: string) => {
            if (fieldId && firstRecord.fields[fieldId]) {
              const fieldValue: any = firstRecord.fields[fieldId] as any;
              
              // 优先处理数组格式（公式字段）
              if (Array.isArray(fieldValue) && fieldValue.length > 0) {
                const firstItem: any = fieldValue[0] as any;
                if (typeof firstItem === 'string') {
                  return firstItem;
                } else if (firstItem && (firstItem.text || firstItem.name)) {
                  return firstItem.text || firstItem.name;
                }
              }
              // 处理对象格式（普通字段）
              else if (typeof fieldValue === 'object' && fieldValue !== null) {
                if (fieldValue.text) {
                  return fieldValue.text;
                } else if (fieldValue.name) {
                  return fieldValue.name;
                }
              }
              // 处理字符串格式
              else if (typeof fieldValue === 'string') {
                return fieldValue.trim();
              }
            }
            return '';
          };
          
          // 提取fld6Uw95kt字段的值
          const mainRecordDisplayValue = getFieldValueById('fld6Uw95kt') || firstRecord.id;
          
          // 将这个值存储到recordDetails中，以便在UI中使用
          const updatedRecordValList = recordValList.map((record, index) => ({
            ...record,
            displayValue: index === 0 ? mainRecordDisplayValue : record.id
          }));
          
          setRecordDetails(updatedRecordValList);
          
          // 提取各个标签的值
          const label2Value = extractFieldValue('品类名称');
          const label3Value = extractFieldValue('大类名称');
          const label4Value = extractFieldValue('中类名称');
          const label5Value = extractFieldValue('小类名称');
          const label6Value = extractFieldValue('工艺难易度');
          const extractFieldValuesById = (fieldId: string): string[] => {
            const v: any = firstRecord.fields[fieldId] as any;
            if (!v) return [];
            if (Array.isArray(v)) {
              return v
                .flatMap((item: any) => {
                  const raw = typeof item === 'string' ? item : (item?.text || item?.name || '');
                  return raw.split(/[,，、]+/);
                })
                .map((s: string) => (s || '').trim())
                .filter(Boolean) as string[];
            }
            if (typeof v === 'string') {
              return v
                .split(/[,，、]+/)
                .map((s: string) => s.trim())
                .filter(Boolean);
            }
            if (typeof v === 'object' && v !== null) {
              const s = (v.text || v.name || '').trim();
              return s
                ? s.split(/[,，、]+/).map((x: string) => x.trim()).filter(Boolean)
                : [];
            }
            return [];
          };
          
          // 设置提取到的标签值
          const newSelectedLabels: {[key: string]: string | string[]} = {};
          if (label2Value) newSelectedLabels['标签2'] = label2Value;
          if (label3Value) newSelectedLabels['标签3'] = label3Value;
          if (label4Value) newSelectedLabels['标签4'] = label4Value;
          if (label5Value) newSelectedLabels['标签5'] = label5Value;
          if (label6Value) newSelectedLabels['标签6'] = label6Value;
          const label8Vals = extractFieldValuesById('fldLeU2Qhq');
          if (label8Vals.length > 0) {
            const opts = labelOptions['标签8'] || [];
            const matched = label8Vals.filter(val => opts.some(o => o.value === val || o.label === val));
            if (matched.length > 0) {
              newSelectedLabels['标签8'] = matched;
            }
          }
          const label7Vals = extractFieldValuesById('fldt9v2oJM');
          if (label7Vals.length > 0) {
            const opts = labelOptions['标签7'] || [];
            const matched = label7Vals.filter(val => opts.some(o => o.value === val || o.label === val));
            if (matched.length > 0) {
              newSelectedLabels['标签7'] = matched;
            }
          }

          const label12Vals = extractFieldValuesById('fldWuCeQDS');
          if (label12Vals.length > 0) {
            const uniqVals = Array.from(new Set(label12Vals.map(v => (v || '').trim()).filter(Boolean)));
            const opts = labelOptions['标签12'] || [];
            const optSet = new Set<string>();
            for (const o of opts) {
              const v = typeof o?.value === 'string' ? o.value.trim() : '';
              const l = typeof o?.label === 'string' ? o.label.trim() : '';
              if (v) optSet.add(v);
              if (l) optSet.add(l);
            }
            const matched = optSet.size > 0 ? uniqVals.filter(v => optSet.has(v)) : uniqVals;
            if (matched.length > 0) {
              newSelectedLabels['标签12'] = matched;
            }
          }
          
          // 添加标签10的自动填充
          newSelectedLabels['标签10'] = ['开货版不打版'];
          
          // 保留用户手动选择的标签1、7、8、9
          setSelectedLabels(prev => ({
            ...prev,
            ...newSelectedLabels
          }));
          
          console.log('自动提取的标签值:', newSelectedLabels);
          
          // 显示提取结果的提示
          if (Object.keys(newSelectedLabels).length > 0 && bitable.ui.showToast) {
            await bitable.ui.showToast({
              toastType: ToastType.success,
              message: `已自动提取 ${Object.keys(newSelectedLabels).length} 个标签值`
            });
          }
          
        }
      } else {
        setRecordDetails([]);
      }
    } catch (error) {
      console.error('选择记录时出错:', error);
      if (bitable.ui.showToast) {
        await bitable.ui.showToast({
          toastType: ToastType.error,
          message: '选择记录时出错，请重试'
        });
      }
    } finally {
      setLoading(false);
    }
  };

  // 清空选中的记录
  const handleClearRecords = () => {
    setSelectedRecords([]);
    setRecordDetails([]);
    setQueryResults([]);
    setSecondaryProcessResults([]);
    setPricingDetailsResults([]);
    // 同时清空标签选择
    setSelectedLabels({});
    setExpectedDate(null);
  };

  // 定价数据表格列定义
  const columns = [
    {
      title: 'ID',
      dataIndex: 'id',
      key: 'id',
    },
    {
      title: 'Pack ID',
      dataIndex: 'pack_id',
      key: 'pack_id',
    },
    {
      title: 'Pack Type',
      dataIndex: 'pack_type',
      key: 'pack_type',
    },
    {
      title: '物料编码',
      dataIndex: 'material_code',
      key: 'material_code',
    },
    {
      title: '一级分类',
      dataIndex: 'category1_name',
      key: 'category1_name',
    },
    {
      title: '二级分类',
      dataIndex: 'category2_name',
      key: 'category2_name',
    },
    {
      title: '三级分类',
      dataIndex: 'category3_name',
      key: 'category3_name',
    },
    {
      title: '品类属性',
      dataIndex: '品类属性',
      key: '品类属性',
    },
  ];

  // 二次工艺表格列定义
  const secondaryProcessColumns = [
    {
      title: 'ID',
      dataIndex: 'id',
      key: 'id',
    },
    {
      title: 'Foreign ID',
      dataIndex: 'foreign_id',
      key: 'foreign_id',
    },
    {
      title: 'Pack Type',
      dataIndex: 'pack_type',
      key: 'pack_type',
    },
    {
      title: '项目',
      dataIndex: 'costs_item',
      key: 'costs_item',
    },
    {
      title: '二次工艺',
      dataIndex: 'costs_type',
      key: 'costs_type',
    },
    {
      title: '备注',
      dataIndex: 'remarks',
      key: 'remarks',
    },
  ];

  // 定价详情表格列定义
  const pricingDetailsColumns = [
    {
      title: 'Pack ID',
      dataIndex: 'pack_id',
      key: 'pack_id',
    },
    {
      title: '倍率',
      dataIndex: '倍率',
      key: '倍率',
    },
    {
      title: '加工费',
      dataIndex: '加工费',
      key: '加工费',
    },
    {
      title: '总价',
      dataIndex: '总价',
      key: '总价',
    },
  ];

  return (
    <div style={{ padding: '20px' }}>
      {/* 入口选择弹窗 */}
      <Modal
        title="请选择功能入口"
        visible={modeSelectionVisible}
        footer={null}
        onCancel={() => setModeSelectionVisible(false)}
        maskClosable={false}
      >
        <div style={{ display: 'flex', gap: 24, justifyContent: 'center', padding: '12px 8px 8px', flexWrap: 'wrap' }}>
          <div style={{ width: 280, cursor: 'pointer' }} onClick={() => chooseMode('generate')}>
            <Card 
              style={{ 
                width: '100%',
                borderRadius: 12,
                boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
                border: '1px solid #e5e7eb',
                background: 'linear-gradient(180deg, #fff, #f9fbff)'
              }} 
            >
              <Title heading={3} style={{ marginBottom: 8 }}>生成流程日期</Title>
              <Text type='tertiary'>基于业务数据计算并生成节点时间线</Text>
              <div style={{ marginTop: 16 }}>
                <Button type='primary' theme='solid' size='large' style={{ width: '100%' }} onClick={() => chooseMode('generate')}>进入</Button>
              </div>
            </Card>
          </div>
          <div style={{ width: 280, cursor: 'pointer' }} onClick={() => chooseMode('adjust')}>
            <Card 
              style={{ 
                width: '100%',
                borderRadius: 12,
                boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
                border: '1px solid #e5e7eb',
                background: 'linear-gradient(180deg, #fff, #f9fbff)'
              }} 
            >
              <Title heading={3} style={{ marginBottom: 8 }}>调整流程日期</Title>
              <Text type='tertiary'>读取货期记录，精确还原时间线</Text>
              <div style={{ marginTop: 16 }}>
                <Button type='primary' theme='solid' size='large' style={{ width: '100%' }} onClick={() => chooseMode('adjust')}>进入</Button>
              </div>
            </Card>
          </div>
          <Card 
            style={{ 
              width: 280, 
              cursor: 'pointer', 
              borderRadius: 12,
              boxShadow: '0 8px 24px rgba(0,0,0,0.06)',
              border: '1px solid #e5e7eb',
              background: 'linear-gradient(180deg, #fff, #f9fbff)'
            }} 
          >
            <Title heading={3} style={{ marginBottom: 8 }}>批量生成</Title>
            <Text type='tertiary'>从批量生成数据表读取并写入记录</Text>
            <div style={{ marginTop: 16 }}>
              <Button type='primary' theme='solid' size='large' style={{ width: '100%' }} onClick={openBatchModal}>进入</Button>
            </div>
          </Card>
        </div>
      </Modal>

      <Modal
        title="批量生成"
        visible={batchModalVisible}
        onCancel={async () => { batchAbortRef.current = true; setBatchModalVisible(false); }}
        footer={null}
        maskClosable={false}
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <Card style={{ padding: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <Text>起始行</Text>
              <InputNumber min={1} value={batchStartRow} onChange={(v) => setBatchStartRow(typeof v === 'number' ? v : 1)} style={{ width: 120 }} disabled={batchLoading} />
              <Text>结束行</Text>
              <InputNumber min={1} value={batchEndRow} onChange={(v) => setBatchEndRow(typeof v === 'number' ? v : 1)} style={{ width: 120 }} disabled={batchLoading} />
              <Text type='tertiary'>总行数：{batchTotalRows}</Text>
              <Space>
                <Button loading={batchLoading} type='primary' theme='solid' onClick={() => handleBatchProcess({ start: batchStartRow, end: batchEndRow })}>开始</Button>
                <Button type='danger' onClick={() => { batchAbortRef.current = true; }} disabled={!batchLoading}>中止</Button>
                <Button onClick={() => setBatchModalVisible(false)} disabled={batchLoading}>关闭</Button>
              </Space>
            </div>
          </Card>

          {(batchLoading || batchProcessedCount > 0) && (
            <Card style={{ padding: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
                <div style={{ flex: 1, paddingRight: 12 }}>
                  <Progress percent={batchProcessingTotal > 0 ? Math.round((batchProcessedCount / batchProcessingTotal) * 100) : 0} showInfo />
                </div>
                <div className="progress-indicator">
                  <div className="progress-spinner" />
                  <Text>进度：{batchProcessedCount}/{batchProcessingTotal}</Text>
                  <Text type='tertiary'>成功 {batchSuccessCount}，失败 {batchFailureCount}</Text>
                </div>
              </div>
              {batchCurrentRowInfo && (
                <div style={{ display: 'flex', gap: 16, flexWrap: 'wrap', marginBottom: 12 }}>
                  <Text>当前处理行：{batchCurrentRowInfo.index}</Text>
                  <Text>款号：{batchCurrentRowInfo.foreignId || '-'}</Text>
                  <Text>款式：{batchCurrentRowInfo.style || '-'}</Text>
                  <Text>颜色：{batchCurrentRowInfo.color || '-'}</Text>
                </div>
              )}
              <List
                dataSource={batchProgressList}
                renderItem={item => (
                  <List.Item style={{ padding: '6px 0' }}>
                    <Space>
                      <Text>行 {item.index}</Text>
                      <Text>款号 {item.foreignId || '-'}</Text>
                      <Text type={item.status === 'success' ? 'success' : 'danger'}>{item.status === 'success' ? '成功' : '失败'}</Text>
                      {item.message && <Text type='tertiary'>{item.message}</Text>}
                    </Space>
                  </List.Item>
                )}
              />
            </Card>
          )}
        </div>
      </Modal>

      {mode === 'generate' && (
        <div style={{ marginBottom: '24px' }}>
          <Title heading={2} style={{ 
            background: 'linear-gradient(135deg, #3b82f6, #1d4ed8)', 
            WebkitBackgroundClip: 'text', 
            WebkitTextFillColor: 'transparent',
            marginBottom: '8px'
          }}>
            数据查询工具
          </Title>
          <Text type="tertiary" style={{ fontSize: '14px' }}>基于业务数据计算并生成节点时间线</Text>
        </div>
      )}
      {/* 已移除：批量模式标题区块 */}
      {mode === 'adjust' && (
        <div style={{ marginBottom: '24px' }}>
          <Title heading={2} style={{ 
            background: 'linear-gradient(135deg, #f59e0b, #d97706)', 
            WebkitBackgroundClip: 'text', 
            WebkitTextFillColor: 'transparent',
            marginBottom: '8px'
          }}>
            调整流程日期
          </Title>
          <Text type="tertiary" style={{ fontSize: '14px' }}>读取货期记录，精确还原时间线</Text>
        </div>
      )}
      {/* 功能入口切换与调整入口 */}
      {mode !== null && (
        <div style={{ margin: '18px 0 14px' }}>
          <Space spacing={16} align='center'>
            <Select value={mode} onChange={(v) => {
              const next = v as any;
              setMode(next);
              setIsExpectedDeliveryDateLocked(false);
              setLockedExpectedDeliveryDateTs(null);
              setExcludedDatesOverride([]);
              setExcludedDatesOverrideText('');
            }}
              optionList={[
                { value: 'generate', label: '生成流程日期' }, 
                { value: 'adjust', label: '调整流程日期' }
              ]} />
            {mode === 'adjust' && (
              <Space spacing={12} align='center'>
                <Button type="primary" onClick={async () => {
                  try {
                    // 新增：每次在调整模式读取记录前重置关键状态，避免跨单据串值
                    // 保留当前模式不变
                    resetGlobalState({ resetMode: false });
                    const selection = await bitable.base.getSelection();
                    const recordId = selection?.recordId || '';
                    const tableId = selection?.tableId || '';
                    if (!recordId) {
                      if (bitable.ui.showToast) {
                        await bitable.ui.showToast({ toastType: ToastType.warning, message: '请先在数据表中选中一条货期记录' });
                      }
                      return;
                    }
                    if (tableId === DELIVERY_RECORD_TABLE_ID) {
                      setSelectedDeliveryRecordId(recordId);
                      await loadProcessDataFromDeliveryRecord(recordId);
                    } else if (tableId === OMS_BOARD_TABLE_ID) {
                      const matchedDeliveryRecordId = await getDeliveryRecordIdFromOmsRecord(recordId);
                      if (!matchedDeliveryRecordId) {
                        if (bitable.ui.showToast) {
                          await bitable.ui.showToast({ toastType: ToastType.warning, message: 'OMS看板记录缺少货期记录ID（fldjEIP9yC）' });
                        }
                        return;
                      }
                      setSelectedDeliveryRecordId(matchedDeliveryRecordId);
                      await loadProcessDataFromDeliveryRecord(matchedDeliveryRecordId);

                      /*
                      // 原始逻辑：从OMS看板用“货期计划 + 计划版本”匹配货期记录（已停用）
                      const omsTable = await bitable.base.getTable(OMS_BOARD_TABLE_ID);
                      const omsRecord = await omsTable.getRecordById(recordId);
                      const planTextRaw = omsRecord?.fields?.[OMS_PLAN_TEXT_FIELD_ID];
                      const planVersionRaw = omsRecord?.fields?.[OMS_PLAN_VERSION_FIELD_ID];
                      const planText = extractText(planTextRaw)?.trim();
                      let planVersion: number | null = null;
                      if (typeof planVersionRaw === 'number') {
                        planVersion = planVersionRaw;
                      } else if (typeof planVersionRaw === 'string') {
                        const m = planVersionRaw.match(/\d+/);
                        if (m) planVersion = parseInt(m[0], 10);
                      } else if (planVersionRaw && typeof planVersionRaw === 'object') {
                        const v = (planVersionRaw as any).value ?? (planVersionRaw as any).text;
                        if (typeof v === 'number') planVersion = v;
                        else if (typeof v === 'string') {
                          const m = v.match(/\d+/);
                          if (m) planVersion = parseInt(m[0], 10);
                        }
                      }

                      if (!planText || planVersion === null) {
                        if (bitable.ui.showToast) {
                          await bitable.ui.showToast({ toastType: ToastType.warning, message: 'OMS看板记录缺少货期计划或计划版本' });
                        }
                        return;
                      }

                      const matchedDeliveryRecordId = await findDeliveryRecordIdByPlan(planText, planVersion);
                      if (!matchedDeliveryRecordId) {
                        if (bitable.ui.showToast) {
                          await bitable.ui.showToast({ toastType: ToastType.warning, message: '未能在货期记录表中匹配到对应记录' });
                        }
                        return;
                      }
                      setSelectedDeliveryRecordId(matchedDeliveryRecordId);
                      await loadProcessDataFromDeliveryRecord(matchedDeliveryRecordId);
                      */
                    } else {
                      if (bitable.ui.showToast) {
                        await bitable.ui.showToast({ toastType: ToastType.warning, message: '请在货期记录或OMS看板表中选择记录' });
                      }
                      return;
                    }
                  } catch (e) {
                    console.error('读取当前选中记录失败:', e);
                    if (bitable.ui.showToast) {
                      await bitable.ui.showToast({ toastType: ToastType.error, message: '读取当前选中记录失败' });
                    }
                  }
                }}>读取当前选中记录并还原流程数据</Button>
                <Button
                  onClick={() => {
                    const base = Array.isArray(timelineResults) ? timelineResults : [];
                    const groups = Array.from(
                      new Set(
                        base
                          .map(r => (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim())
                          .filter(Boolean)
                      )
                    );
                    if (groups.length <= 1) return;

                    const groupSet = new Set(groups);
                    const existing = Array.isArray(groupOrderConfig)
                      ? groupOrderConfig.filter(inst => groupSet.has((inst?.groupName || '').trim()))
                      : [];
                    const draft = existing.length > 0 ? existing : deriveGroupOrderDraftByProcessOrder(base);
                    setGroupOrderDraft(draft);
                    pendingGroupConfigCalcRef.current = null;
                    setGroupConfigVisible(true);
                  }}
                  disabled={
                    (() => {
                      const base = Array.isArray(timelineResults) ? timelineResults : [];
                      const groups = Array.from(
                        new Set(
                          base
                            .map(r => (typeof r?.processGroup === 'string' ? r.processGroup : extractText(r?.processGroup)).trim())
                            .filter(Boolean)
                        )
                      );
                      return groups.length <= 1;
                    })()
                  }
                >
                  流程组顺序/复版
                </Button>
                {currentDeliveryRecordId && (
                  <Space spacing={8} align="center">
                    <Text type="tertiary">款号</Text>
                    <Input
                      size="small"
                      style={{ width: 160 }}
                      value={currentStyleText || ''}
                      disabled
                    />
                    <Text type="tertiary">颜色</Text>
                    <Input
                      size="small"
                      style={{ width: 160 }}
                      value={currentColorText || ''}
                      disabled
                    />
                    <Text type="tertiary">最近保存记录ID</Text>
                    <Input
                      size="small"
                      style={{ width: 260 }}
                      value={currentDeliveryRecordId}
                      disabled
                    />
                    <Button size="small" onClick={() => copyToClipboard(currentDeliveryRecordId)}>
                      复制
                    </Button>
                  </Space>
                )}
              </Space>
            )}
            {/* 批量处理功能 - 只在批量模式下显示 */}
          </Space>
        </div>
      )}
      
      <Modal
        title="倒推盈余分配"
        visible={allocationVisible}
        maskClosable={false}
        onCancel={() => {
          setAllocationVisible(false);
          setTimelineVisible(true);
        }}
        footer={
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <Space>
              <Button
                onClick={() => {
                  const nodes = allocationNodesSnapshot.length > 0 ? allocationNodesSnapshot : timelineResults;
                  const draft = buildAutoAllocationDraft(nodes, allocationExtraDays, allocationExcluded);
                  setAllocationDraft(draft);
                }}
              >
                按比例重算
              </Button>
              <Button
                onClick={() => {
                  setAllocationDraft({});
                }}
              >
                清空分配
              </Button>
            </Space>
            <Space>
              <Button onClick={() => { setAllocationVisible(false); setTimelineVisible(true); }}>暂不分配</Button>
              <Button type="primary" onClick={applyAllocationDraft}>应用分配</Button>
            </Space>
          </div>
        }
        width={900}
      >
        {(() => {
          const nodes = allocationNodesSnapshot.length > 0 ? allocationNodesSnapshot : timelineResults;
          const sum = Object.values(allocationDraft || {}).reduce((s, v) => s + (Number(v) || 0), 0);
          const roundedSum = Math.round(sum * 100) / 100;
          const roundedTarget = Math.round((Number(allocationExtraDays) || 0) * 100) / 100;
          const remainingRaw = Math.round((roundedTarget - roundedSum) * 100) / 100;
          const remaining = Math.max(0, remainingRaw);
          const overAllocated = Math.max(0, Math.round((0 - remainingRaw) * 100) / 100);
          const statusColor = overAllocated > 0 ? '#dc2626' : (remaining === 0 ? '#16a34a' : '#d97706');

          return (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
              <Card>
                <div style={{ display: 'flex', flexWrap: 'wrap', gap: 16, alignItems: 'center' }}>
                  <Text>客户期望日期：{expectedDate ? formatDate(expectedDate, 'CHINESE_DATE') : '-'}</Text>
                  <Text>业务起始日期：{startTime ? formatDate(startTime) : '-'}</Text>
                  <Text>倒推要求起始日期：{calculatedRequiredStartTime ? formatDate(calculatedRequiredStartTime) : '-'}</Text>
                  <Text strong style={{ color: statusColor }}>
                    盈余：{roundedTarget} 天（剩余 {remaining}{overAllocated > 0 ? `，已超配 ${overAllocated}` : ''}）
                  </Text>
                </div>
                <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <Text strong>分配方式</Text>
                  <Select
                    style={{ width: 240 }}
                    value={allocationMode}
                    onChange={(v) => setAllocationMode(v as 'auto' | 'manual')}
                    optionList={[
                      { value: 'auto', label: '系统自动分配（按时效比例）' },
                      { value: 'manual', label: '业务自行分配' },
                    ]}
                  />
                </div>
              </Card>

              <Table
                pagination={false}
                size="small"
                columns={[
                  {
                    title: '节点',
                    dataIndex: 'nodeName',
                    key: 'nodeName',
                    width: 220,
                    render: (_: any, row: any) => {
                      const idx = row.index as number;
                      const nodeName = row.nodeName as string;
                      const isTurnoverNode = nodeName === '周转周期';
                      const excluded = !!allocationExcluded?.[idx] || isTurnoverNode;
                      return (
                        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
                          <span style={{ opacity: excluded ? 0.5 : 1 }}>{nodeName}</span>
                          <Button
                            size="small"
                            type="tertiary"
                            disabled={isTurnoverNode}
                            onClick={() => {
                              const currentlyExcluded = excluded;
                              setAllocationExcluded(prev => {
                                const next = { ...(prev || {}) };
                                if (currentlyExcluded) {
                                  delete next[idx];
                                } else {
                                  next[idx] = true;
                                }
                                return next;
                              });
                              setAllocationDraft(prev => {
                                const next = { ...(prev || {}) };
                                if (!currentlyExcluded) {
                                  next[idx] = 0;
                                }
                                return next;
                              });
                            }}
                          >
                            {excluded ? '+' : '-'}
                          </Button>
                        </span>
                      );
                    }
                  },
                  { title: '基准时效(天)', dataIndex: 'base', key: 'base', width: 120, render: (v) => (Number(v) || 0).toFixed(2) },
                  {
                    title: allocationMode === 'manual' ? '分配盈余(天)' : '系统分配(天)',
                    dataIndex: 'allocated',
                    key: 'allocated',
                    width: 160,
                    render: (_: any, row: any) => {
                      const idx = row.index as number;
                      const isTurnoverNode = row.nodeName === '周转周期';
                      const isExcluded = !!allocationExcluded?.[idx];
                      const val = Number(allocationDraft[idx]) || 0;

                      if (allocationMode === 'manual') {
                        const otherSum = Math.round((roundedSum - val) * 100) / 100;
                        const maxAllowed = Math.max(0, Math.round((roundedTarget - otherSum) * 100) / 100);
                        const maxForControl = maxAllowed < val ? val : maxAllowed;
                        return (
                          <InputNumber
                            min={0}
                            max={maxForControl}
                            step={0.1}
                            value={val}
                            disabled={isTurnoverNode || isExcluded}
                            onChange={(v) => {
                              const raw = Math.round((Number(v) || 0) * 100) / 100;
                              const n = Math.max(0, Math.min(raw, maxAllowed));
                              setAllocationDraft(prev => ({ ...(prev || {}), [idx]: n }));
                            }}
                            style={{ width: 140 }}
                          />
                        );
                      }

                      return <span>{val.toFixed(2)}</span>;
                    }
                  }
                ]}
                dataSource={nodes.map((n, index) => {
                  const base = typeof n?.timelineValue === 'number'
                    ? n.timelineValue
                    : (typeof n?.adjustedTimelineValue === 'number' ? n.adjustedTimelineValue : 0);
                  return {
                    key: index,
                    index,
                    nodeName: n?.nodeName || `节点${index + 1}`,
                    base: Number.isFinite(base) ? base : 0,
                    allocated: Number(allocationDraft[index]) || 0,
                  };
                })}
              />
            </div>
          );
        })()}
      </Modal>

      <Modal
        title="流程组执行顺序"
        visible={groupConfigVisible}
        maskClosable={false}
        onCancel={() => {
          setGroupConfigVisible(false);
          setGroupOrderDraft([]);
        }}
        footer={
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <Text type="tertiary">
              拖拽条目调整流程组顺序，上方的流程组将优先执行
            </Text>
            <Space>
              <Button
                onClick={() => {
                  setGroupConfigVisible(false);
                  setGroupOrderDraft([]);
                  pendingGroupConfigCalcRef.current = null;
                }}
              >
                取消
              </Button>
              <Button
                type="primary"
                disabled={!Array.isArray(groupOrderDraft) || groupOrderDraft.length === 0}
                onClick={() => {
                  const nextConfig = Array.isArray(groupOrderDraft) ? groupOrderDraft : [];
                  setGroupOrderConfig(nextConfig);
                  setGroupConfigVisible(false);
                  if (!pendingGroupConfigCalcRef.current && mode === 'adjust') {
                    const next = applyGroupOrderConfigToTimelineResults(timelineResults, nextConfig);
                    pendingRecalculateAfterCalculateAdjustmentsRef.current = timelineAdjustments;
                    pendingRecalculateAfterCalculateRef.current = true;
                    setTimelineResults(next);
                    setTimelineVisible(true);
                  }
                }}
              >
                应用顺序并继续计算
              </Button>
            </Space>
          </div>
        }
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {groupOrderDraft.length === 0 ? (
            <Text type="tertiary">未检测到可配置的流程组</Text>
          ) : (
            <div>
              {groupOrderDraft.map((inst, index) => (
                <div
                  key={inst.id}
                  style={{
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'space-between',
                    padding: '8px 12px',
                    marginBottom: 8,
                    borderRadius: 8,
                    border: '1px solid #e5e7eb',
                    background: '#f9fafb',
                    cursor: 'move',
                    opacity: draggingGroupIndex === index ? 0.6 : 1,
                  }}
                  draggable
                  onDragStart={() => setDraggingGroupIndex(index)}
                  onDragOver={(e) => {
                    e.preventDefault();
                    if (draggingGroupIndex === null || draggingGroupIndex === index) return;
                    setGroupOrderDraft(prev => {
                      const next = [...prev];
                      const [removed] = next.splice(draggingGroupIndex, 1);
                      next.splice(index, 0, removed);
                      return next;
                    });
                    setDraggingGroupIndex(index);
                  }}
                  onDragEnd={() => setDraggingGroupIndex(null)}
                >
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    <Tag color="blue" style={{ minWidth: 32, textAlign: 'center' }}>
                      {index + 1}
                    </Tag>
                    <Text>{inst.displayName}</Text>
                    <Button
                      size="small"
                      type="tertiary"
                      onClick={() => {
                        setGroupOrderDraft(prev => {
                          const baseName = (inst.groupName || '').trim();
                          if (!baseName) return prev;
                          const currentCount = prev.filter(x => (x.groupName || '').trim() === baseName).length;
                          const nextIndex = currentCount + 1;
                          const displayName = nextIndex <= 1 ? baseName : `${baseName}${nextIndex}`;
                          const duplicated = createProcessGroupInstance(baseName, displayName);
                          const next = [...prev];
                          next.splice(index + 1, 0, duplicated);
                          return next;
                        });
                      }}
                    >
                      复版
                    </Button>
                  </div>
                  <span style={{ cursor: 'grab', color: '#9ca3af', fontSize: 18 }}>☰</span>
                </div>
              ))}
            </div>
          )}
        </div>
      </Modal>

      <Modal
        title="跳过日期调整（按节点）"
        visible={excludedDatesAdjustVisible}
        maskClosable={false}
        onCancel={closeExcludedDatesAdjustModal}
        footer={
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <Text type="tertiary">
              共 {Array.isArray(timelineResults) ? timelineResults.length : 0} 个节点，
              已配置 {Object.values(excludedDatesByNodeDraft || {}).filter(v => Array.isArray(v) && v.length > 0).length} 个节点
            </Text>
            <Space>
              <Button onClick={closeExcludedDatesAdjustModal}>取消</Button>
              <Button
                type="primary"
                onClick={() => {
                  const next = normalizeExcludedDatesByNodeMap(excludedDatesByNodeDraft || {});
                  excludedDatesByNodeOverrideRef.current = next;
                  setExcludedDatesByNodeOverride(next);
                  pendingRecalculateAfterExcludedDatesRef.current = true;
                  setTimelineResults(prev => {
                    const base = Array.isArray(prev) ? prev : [];
                    return base.map((r: any, idx: number) => {
                      const nodeKey = buildExcludedDatesNodeKey(
                        r?.nodeName,
                        r?.processOrder,
                        idx,
                        r?.processGroupInstanceId || r?.processGroupInstanceName
                      );
                      if (!Object.prototype.hasOwnProperty.call(next, nodeKey)) return r;
                      const list = Array.isArray(next[nodeKey]) ? next[nodeKey] : [];
                      return { ...r, excludedDates: list };
                    });
                  });
                  setExcludedDatesAdjustVisible(false);
                }}
              >
                应用并重算
              </Button>
            </Space>
          </div>
        }
        style={{ width: 980 }}
        bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}
      >
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {(Array.isArray(timelineResults) ? timelineResults : []).map((r: any, idx: number) => {
            const nodeKey = buildExcludedDatesNodeKey(
              r?.nodeName,
              r?.processOrder,
              idx,
              r?.processGroupInstanceId || r?.processGroupInstanceName
            );
            const dates = Array.isArray(excludedDatesByNodeDraft?.[nodeKey]) ? excludedDatesByNodeDraft[nodeKey] : [];
            const groups = groupDatesByMonth(dates);
            const pickerValue = excludedDatesAddDraft?.[nodeKey] ?? null;
            return (
              <Card
                key={nodeKey}
                className="card-enhanced"
                bodyStyle={{ padding: 12 }}
              >
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
                  <Text strong style={{ fontSize: 14 }}>
                    {typeof r?.processOrder !== 'undefined' ? `#${r.processOrder} ` : ''}{r?.nodeName || nodeKey}{r?.processGroupInstanceName ? `（${r.processGroupInstanceName}）` : ''}
                  </Text>
                  <Space>
                    <DatePicker
                      style={{ width: 160 }}
                      value={pickerValue ?? undefined}
                      placeholder="选择日期"
                      format="yyyy-MM-dd"
                      onChange={(v) => {
                        let d: Date | null = null;
                        if (v instanceof Date) d = v;
                        else if (typeof v === 'string') {
                          const parsed = parseDate(v);
                          d = parsed && !isNaN(parsed.getTime()) ? parsed : null;
                        }
                        setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: d }));
                      }}
                    />
                    <Button
                      onClick={() => {
                        const d = excludedDatesAddDraft?.[nodeKey] ?? null;
                        addExcludedDateToDraft(nodeKey, d);
                        setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: null }));
                      }}
                    >
                      添加
                    </Button>
                    <Button type="tertiary" onClick={() => clearExcludedDatesDraftForNode(nodeKey)}>清空</Button>
                  </Space>
                </div>

                <div style={{ marginTop: 10 }}>
                  {groups.length === 0 ? (
                    <Text type="tertiary">无跳过日期</Text>
                  ) : (
                    groups.map(g => (
                      <div key={g.month} style={{ marginBottom: 10 }}>
                        <Text style={{ display: 'block', marginBottom: 6, color: '#595959' }}>{g.month}</Text>
                        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
                          {g.dates.map(d => (
                            <Tag
                              key={d}
                              closable
                              onClose={() => removeExcludedDateFromDraft(nodeKey, d)}
                              style={{ backgroundColor: '#fff7e6', borderColor: '#ffd591', color: '#ad4e00' }}
                            >
                              {d}
                            </Tag>
                          ))}
                        </div>
                      </div>
                    ))
                  )}
                </div>
              </Card>
            );
          })}
        </div>
      </Modal>

      {/* 时效计算结果模态框 */}
      <Modal
        title={
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, flexWrap: 'wrap' }}>
            <span style={{ fontSize: 16, fontWeight: 600 }}>预计开始和完成时间</span>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, whiteSpace: 'nowrap', overflowX: 'auto', maxWidth: '70%' }}>
              <Text type="tertiary">款号</Text>
              <Input
                size="small"
                style={{ width: 160 }}
                placeholder="请输入款号"
                value={currentStyleText}
                disabled={!styleColorEditable}
                onChange={(val) => setCurrentStyleText(val)}
              />
              <Text type="tertiary" style={{ marginLeft: 8 }}>颜色</Text>
              <Input
                size="small"
                style={{ width: 160 }}
                placeholder="请输入颜色"
                value={currentColorText}
                disabled={!styleColorEditable}
                onChange={(val) => setCurrentColorText(val)}
              />
              <Button
                size="small"
                type="tertiary"
                onClick={() => setStyleColorEditable(prev => !prev)}
                style={{ marginLeft: 8 }}
              >
                <span style={{ fontSize: 14, lineHeight: 1 }}>✏️</span>
              </Button>
              {mode === 'adjust' && (
                <>
                  <Text type="tertiary" style={{ marginLeft: 8 }}>锁交付</Text>
                  <Switch
                    checked={isExpectedDeliveryDateLocked}
                    onChange={handleExpectedDeliveryDateLockChange}
                    style={{ marginLeft: 4 }}
                  />
                </>
              )}
            </div>
          </div>
        }
        visible={timelineVisible}
        onCancel={() => {
          setTimelineVisible(false);
          setTimelineAdjustments({}); // 关闭时重置调整
          setDeliveryMarginDeductions(0); // 关闭时重置交期余量扣减
          setCompletionDateAdjustment(0); // 关闭时重置最后流程完成日期调整
          setStyleColorEditable(false); // 关闭弹窗后恢复为锁定状态
          if (mode !== 'adjust') {
            setLockedExpectedDeliveryDateTs(null);
            setIsExpectedDeliveryDateLocked(false);
          }
        }}
        footer={
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <Space align="center">
              <Text>{mode === 'adjust' && isExpectedDeliveryDateLocked ? '基础缓冲期(天)(不影响已锁交付)：' : '基础缓冲期(天)：'}</Text>
              <InputNumber
                min={0}
                step={1}
                value={baseBufferDays}
                onChange={(val) => {
                  const n = Number(val);
                  setBaseBufferDays(Number.isFinite(n) && n >= 0 ? Math.ceil(n) : 0);
                }}
                style={{ width: 90 }}
              />
              <Text>剩余缓冲期(天)：</Text>
              <InputNumber
                value={computeDynamicBufferDaysUsingEndDelta(timelineAdjustments)}
                disabled
                style={{ width: 90 }}
              />
              <Button onClick={resetToInitialState}>
                重置所有调整
              </Button>
            </Space>
            <Button 
              type="primary" 
              onClick={async () => {
                try {
                  await saveTimelineData();
                } catch (error) {
                  // 错误已在saveTimelineData中处理
                }
                setTimelineVisible(false);
              }}
            >
              确定并保存
            </Button>
          </div>
        }
        width={900}
      >
        <div style={{ maxHeight: '80vh', overflowY: 'auto' }}>
          {timelineResults.length === 0 ? (
            <div style={{ textAlign: 'center', padding: '40px' }}>
              <Text type="tertiary">未找到匹配的时效数据</Text>
            </div>
          ) : (
            <div>
              <div style={{ marginBottom: '12px', display: 'flex', alignItems: 'center', gap: '12px' }}>
                <Text strong>起始时间</Text>
                <DatePicker
                  style={{ width: 280 }}
                  placeholder="请选择起始时间"
                  value={startTime ?? undefined}
                  onChange={(date) => {
                    if (date instanceof Date) {
                      setStartTime(date);
                    } else if (typeof date === 'string') {
                      const parsed = new Date(date);
                      setStartTime(isNaN(parsed.getTime()) ? null : parsed);
                    } else {
                      setStartTime(null);
                    }
                  }}
                  type="dateTime"
                  format="yyyy-MM-dd HH:mm"
                />
              </div>
              {timelineResults.map((result, index) => {
                const nodeKey = buildTimelineAdjustmentKey(result, index);
                const adjustment = timelineAdjustments[nodeKey] || 0;
                const baseValue = (typeof result.timelineValue === 'number')
                  ? result.timelineValue
                  : (typeof result.adjustedTimelineValue === 'number')
                    ? result.adjustedTimelineValue
                    : 0;
                const adjustedValue = baseValue + adjustment;
                
                // 检查是否为周转周期节点
                const isTurnoverNode = result.nodeName === '周转周期';
                
                // 检查是否存在周转周期为零的情况
                const hasTurnoverNodeWithZero = timelineResults.some((r, i) => {
                  if (r.nodeName === '周转周期') {
                    const k = buildTimelineAdjustmentKey(r, i);
                    const adj = timelineAdjustments[k] || 0;
                    const baseVal = (typeof r.timelineValue === 'number')
                      ? r.timelineValue
                      : (typeof r.adjustedTimelineValue === 'number')
                        ? r.adjustedTimelineValue
                        : 0;
                    return (baseVal + adj) === 0;
                  }
                  return false;
                });
                
                // 当前节点是否为零值的周转周期节点
                const isCurrentTurnoverZero = isTurnoverNode && adjustedValue === 0;
                
                // 计算动态缓冲期（按“最后完成时间变动天数”扣减缓冲期）
                const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
                
                return (
                  <Card key={index} style={{ marginBottom: '8px', padding: '12px', position: 'relative' }}>
                    <div style={{ position: 'absolute', top: '12px', left: '12px', zIndex: 1, display: 'flex', alignItems: 'center', gap: '16px' }}>
  <Title heading={6} style={{ margin: 0, color: '#1890ff', fontSize: '18px' }}>
    {result.processOrder && `${result.processOrder}. `}{result.nodeName}{result.processGroupInstanceName ? `（${result.processGroupInstanceName}）` : ''}
    {result.isAccumulated && result.allMatchedRecords ? (
      <span style={{ 
        marginLeft: '8px', 
        fontSize: '12px', 
        color: '#000000', 
        fontWeight: 'normal' 
      }}>
        (累加 {result.allMatchedRecords.length} 条记录: {result.allMatchedRecords.map((record: any) => record.recordId).join(', ')})
      </span>
    ) : result.timelineRecordId ? (
      <span style={{ 
        marginLeft: '8px', 
        fontSize: '12px', 
        color: '#000000', 
        fontWeight: 'normal' 
      }}>
        ({result.timelineRecordId})
      </span>
    ) : (
      <span style={{ 
        marginLeft: '8px', 
        fontSize: '12px', 
        color: '#ff4d4f', 
        fontWeight: 'normal' 
      }}>
        (无匹配记录)
      </span>
    )}
  </Title>
  
  <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
    <Text style={{ fontSize: '14px', color: '#666', whiteSpace: 'nowrap' }}>实际完成：</Text>
        <DatePicker
          size="small"
          type="dateTime"
          format="yyyy-MM-dd HH:mm"
          placeholder="选择日期时间"
          style={{ width: '200px' }}
          value={(actualCompletionDates[index] as Date | null) ?? undefined}
          onChange={(date) => {
        // 自动填充默认时分：仅在用户未指定具体时分时套用预计完成的时分
            let nextDate: Date | null;
            if (date instanceof Date) {
              nextDate = new Date(date);
            } else if (typeof date === 'string') {
              const parsed = new Date(date);
              nextDate = isNaN(parsed.getTime()) ? null : parsed;
            } else {
              nextDate = null;
            }
        try {
          if (nextDate) {
            const expectedEndStr = timelineResults?.[index]?.estimatedEnd;
            if (expectedEndStr && typeof expectedEndStr === 'string' && !expectedEndStr.includes('未找到')) {
              const expectedEnd = parseDate(expectedEndStr);
              if (expectedEnd && !isNaN(expectedEnd.getTime())) {
                const h = nextDate.getHours();
                const m = nextDate.getMinutes();
                const s = nextDate.getSeconds();
                // 当用户未选择具体时间（默认为00:00:00）时，应用预计完成的时分
                if (h === 0 && m === 0 && s === 0) {
                  nextDate.setHours(expectedEnd.getHours(), expectedEnd.getMinutes(), 0, 0);
                }
              }
            }
          }
        } catch {}

        setActualCompletionDates(prev => ({
          ...prev,
          [index]: nextDate
        }));
        
        // 自动设置调整量：按工作日规则（考虑内部/外部、休息日、跳过日期）
        try {
          const currentResult = timelineResults[index];
          const startStr = currentResult?.estimatedStart;
          if (startStr && nextDate) {
            const startDate = parseDate(startStr);
            if (startDate && !isNaN(startDate.getTime())) {
              const calcRaw = currentResult?.calculationMethod || '外部';
              const calcMethod = (calcRaw === '内部' || calcRaw === 'internal') ? '内部' : '外部';
              const weekendDays = currentResult?.weekendDaysConfig || currentResult?.weekendDays || [];
              const excludedDates = Array.isArray(currentResult?.excludedDates) ? currentResult!.excludedDates : [];

              // 与正向计算一致：先对起始时间应用工作时间调整
              const adjustedStart = adjustToNextWorkingHour(startDate, calcMethod, weekendDays, excludedDates);
              const targetDate = new Date(nextDate);

              // 如果用户只调整了时分（同一天），不触发工作日反推，避免日期意外跳变
              const expectedEndStrForAdjust = timelineResults?.[index]?.estimatedEnd;
              const expectedEndForAdjust = expectedEndStrForAdjust ? parseDate(expectedEndStrForAdjust) : null;
              if (expectedEndForAdjust && !isNaN(expectedEndForAdjust.getTime())) {
                const sameDay = expectedEndForAdjust.getFullYear() === targetDate.getFullYear()
                  && expectedEndForAdjust.getMonth() === targetDate.getMonth()
                  && expectedEndForAdjust.getDate() === targetDate.getDate();
                if (sameDay) {
                  // 仅时间微调：保持当前工作日调整量不变，并让预计完成对齐到实际完成时分
                  try {
                    const newEndStr = formatDate(targetDate);
                    const newSkippedWeekends = calculateSkippedWeekends(adjustedStart, targetDate, weekendDays);
                    const newActualDays = calculateActualDays(formatDate(adjustedStart), newEndStr);
                    setTimelineResults(prev => {
                      const updated = [...prev];
                      const prevItem = updated[index];
                      if (prevItem) {
                        updated[index] = {
                          ...prevItem,
                          estimatedEnd: newEndStr,
                          skippedWeekends: newSkippedWeekends,
                          actualDays: newActualDays
                        };
                      }
                      return updated;
                    });
                  } catch {}
                  return;
                }
              }

              // 使用二分搜索反推工作日数（按0.5天粒度），使得正向计算的结束时间尽量贴近目标日期
              const dayMs = 1000 * 60 * 60 * 24;
              const approxNatural = (targetDate.getTime() - adjustedStart.getTime()) / dayMs;
              const endFor = (bd: number): Date => {
                if (bd === 0) return new Date(adjustedStart);
                return calcMethod === '内部'
                  ? addInternalBusinessTime(new Date(adjustedStart), bd, weekendDays, excludedDates)
                  : addBusinessDaysWithHolidays(new Date(adjustedStart), bd, weekendDays, excludedDates);
              };

              let lo = approxNatural - 50;
              let hi = approxNatural + 50;
              for (let it = 0; it < 40; it++) {
                const mid = (lo + hi) / 2;
                const end = endFor(mid);
                if (end.getTime() < targetDate.getTime()) {
                  lo = mid;
                } else {
                  hi = mid;
                }
              }
              const desiredBusinessDays = Math.round(hi * 2) / 2; // 与UI保持0.5粒度一致

              // 目标调整量 = 目标工作日数 - 基准时效值
              const baseValue = (typeof currentResult.timelineValue === 'number')
                ? currentResult.timelineValue
                : (typeof currentResult.adjustedTimelineValue === 'number')
                  ? currentResult.adjustedTimelineValue
                  : 0;

              const desiredAdjustmentAbs = desiredBusinessDays - baseValue;
              const currentAdj = timelineAdjustments[nodeKey] || 0;
              const deltaToApply = desiredAdjustmentAbs - currentAdj;
              if (deltaToApply !== 0) {
                const updated = handleTimelineAdjustment(nodeKey, index, deltaToApply);
                // 若该节点不允许调整，交由实际完成日期的useEffect联动重算
                if (!updated) {
                  return;
                }
                const deficit = computeBufferDeficitDaysUsingEndDelta(updated);
                const prevDeficit = lastBufferDeficitRef.current;
                lastBufferDeficitRef.current = deficit;
                if (deficit > 0 && Math.ceil(deficit) > Math.ceil(prevDeficit)) {
                  Modal.warning({
                    title: '缓冲期不足',
                    content: `当前缓冲期无法覆盖本次超期，缺口 ${Math.ceil(deficit)} 天`,
                  });
                }
              }
            }
          }
        } catch (e) {
          console.warn('自动调整量计算失败:', e);
        }

        // 重算由依赖actualCompletionDates的useEffect触发，避免使用旧状态
      }}
    />
    <Button
      size="small"
      disabled={isTurnoverNode}
      onClick={() => {
        try {
          const baseTimelineValue = (typeof result.timelineValue === 'number')
            ? result.timelineValue
            : (typeof result.adjustedTimelineValue === 'number' ? result.adjustedTimelineValue : 0);
          const currentAdj = timelineAdjustments[nodeKey] || 0;
          const targetAdj = -baseTimelineValue;
          const deltaToApply = targetAdj - currentAdj;
          if (deltaToApply !== 0) {
            handleTimelineAdjustment(nodeKey, index, deltaToApply);
          }
          setActualCompletionDates(prev => {
            const next = { ...(prev || {}) };
            next[index] = null;
            return next;
          });
        } catch (e) {
          console.warn('节点置零失败:', e);
        }
      }}
      style={{ height: '24px', padding: '0 8px' }}
      title={isTurnoverNode ? '周转周期节点不支持置零' : '将该节点时效置为0天'}
    >
      置零
    </Button>
    <Button
      size="small"
      disabled={isTurnoverNode || (timelineAdjustments[nodeKey] || 0) === 0}
      onClick={() => {
        try {
          const currentAdj = timelineAdjustments[nodeKey] || 0;
          if (currentAdj !== 0) {
            handleTimelineAdjustment(nodeKey, index, -currentAdj);
          }
        } catch (e) {
          console.warn('恢复节点失败:', e);
        }
      }}
      style={{ height: '24px', padding: '0 8px' }}
      title={isTurnoverNode ? '周转周期节点不支持恢复' : '恢复该节点的时效调整为0'}
    >
      恢复
    </Button>
  </div>
</div>
                    
                    <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr 1fr 1fr', gap: '12px', marginTop: '40px' }}>
                      <div>
                        <Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}>时效值调整：</Text>
                    <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
                      <Button 
                        size="small" 
                        onClick={() => handleComplexAdjustment(nodeKey, index, -0.5)}
                        disabled={adjustedValue <= 0 || isCurrentTurnoverZero}
                        style={{ minWidth: '28px', height: '24px', fontSize: '13px' }}
                        title={isCurrentTurnoverZero ? '周转周期为零，无法调整' : ''}
                      >
                        -0.5
                      </Button>
                      <Button 
                        size="small" 
                        onClick={() => handleComplexAdjustment(nodeKey, index, -1)}
                        disabled={adjustedValue <= 0 || isCurrentTurnoverZero}
                        style={{ minWidth: '24px', height: '24px', fontSize: '13px' }}
                        title={isCurrentTurnoverZero ? '周转周期为零，无法调整' : ''}
                      >
                        -1
                      </Button>
                      <div style={{ 
                        minWidth: '70px', 
                        textAlign: 'center',
                        padding: '2px 6px',
                        border: '1px solid #d9d9d9',
                        borderRadius: '4px',
                        backgroundColor: adjustment !== 0 ? '#fff7e6' : '#f5f5f5',
                        fontSize: '13px'
                      }}>
                        <Text style={{ 
                          color: adjustedValue > 0 ? '#52c41a' : '#ff4d4f',
                          fontWeight: 'bold',
                          fontSize: '13px'
                        }}>
                          {adjustedValue.toFixed(1)} 工作日
                        </Text>
                        {adjustment !== 0 && (
                          <div style={{ fontSize: '10px', color: '#666', lineHeight: '1.2' }}>
                            原值: {baseValue.toFixed(1)}
                            <br />
                            调整: {adjustment > 0 ? '+' : ''}{adjustment.toFixed(1)}
                          </div>
                        )}
                      </div>
                      <Button 
                        size="small" 
                        onClick={() => handleComplexAdjustment(nodeKey, index, 1)}
                        disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero)}
                        style={{ minWidth: '24px', height: '24px', fontSize: '13px' }}
                        title={
                          isCurrentTurnoverZero ? '周转周期为零，无法调整' : 
                          (hasTurnoverNodeWithZero && !isTurnoverNode) ? '周转周期为零，其他节点无法增加' : 
                          (isTurnoverNode && !isCurrentTurnoverZero) ? '周转周期节点会自动调整，无法手动修改' : 
                          ''
                        }
                      >
                        +1
                      </Button>
                      <Button 
                        size="small" 
                        onClick={() => handleComplexAdjustment(nodeKey, index, 0.5)}
                        disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero)}
                        style={{ minWidth: '28px', height: '24px', fontSize: '13px' }}
                        title={
                          isCurrentTurnoverZero ? '周转周期为零，无法调整' : 
                          (hasTurnoverNodeWithZero && !isTurnoverNode) ? '周转周期为零，其他节点无法增加' : 
                          (isTurnoverNode && !isCurrentTurnoverZero) ? '周转周期节点会自动调整，无法手动修改' : 
                          ''
                        }
                      >
                        +0.5
                      </Button>
                    </div>
                      </div>
                      
                      <div>
                        <Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}>预计开始：</Text>
                        <Text style={{ fontSize: '14px' }}>{formatDate(result.estimatedStart)}</Text>
                        <div style={{ fontSize: '12px', color: '#666', marginTop: '1px' }}>
          <Text>{getDayOfWeek(result.estimatedStart)}</Text>
        </div>
                      </div>
                      
                      <div>
                        <Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}>预计完成：</Text>
                        <Text style={{ 
                          color: result.estimatedEnd.includes('未找到') || result.estimatedEnd.includes('时效值为0') ? '#ff4d4f' : '#52c41a',
                          fontSize: '14px'
                        }}>
                          {result.estimatedEnd === '时效值为0' ? result.estimatedEnd : formatDate(result.estimatedEnd)}
                        </Text>
                        <div style={{ fontSize: '12px', color: '#666', marginTop: '1px' }}>
          <Text>{getDayOfWeek(result.estimatedEnd)}</Text>
        </div>
                      </div>
                      
                      <div>
                        <Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}>实际跨度：</Text>
                        <Text style={{ color: '#1890ff', fontSize: '14px' }}>
                          {calculateActualDays(result.estimatedStart, result.estimatedEnd)} 自然日
                        </Text>
                        <div style={{ fontSize: '12px', color: '#666', marginTop: '1px' }}>
                          <Text>含 {adjustedValue.toFixed(1)} 工作日</Text>
                        </div>
                      </div>
                      
                      <div>
                        <Text strong style={{ display: 'block', marginBottom: '2px', fontSize: '15px' }}>计算方式：</Text>
                        <Text style={{ 
                          color: result.calculationMethod === '内部' ? '#1890ff' : '#52c41a',
                          fontSize: '14px'
                        }}>
                          {result.calculationMethod || '外部'}
                          {result.ruleDescription && (
                            <span style={{ 
                              marginLeft: '8px', 
                              color: '#ff7a00',
                              fontSize: '12px',
                              fontStyle: 'italic'
                            }}>
                              ({result.ruleDescription})
                            </span>
                          )}
                          <span 
                            style={{ 
                              marginLeft: '4px', 
                              cursor: 'help',
                              color: '#666',
                              fontSize: '12px',
                              display: 'inline-flex',
                              alignItems: 'center',
                              justifyContent: 'center',
                              width: '14px',
                              height: '14px',
                              borderRadius: '50%',
                              backgroundColor: '#f0f0f0',
                              border: '1px solid #d9d9d9'
                            }}
                            title={`计算方式详情：\n${result.calculationMethod === '内部' ? '内部计算 (9小时工作制)' : '外部计算 (24小时制)'}${result.ruleDescription ? `\n应用规则：${result.ruleDescription}` : ''}\n已跳过周末：${result.skippedWeekends || 0} 天\n${result.weekendDaysConfig && result.weekendDaysConfig.length > 0 ? `休息日配置：${result.weekendDaysConfig.map((day: number) => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][day]).join(', ')}` : '休息日配置：无固定休息日'}${result.calculationMethod === '内部' ? '\n工作时间：9:00-18:00 (9小时制)' : ''}${(Array.isArray(result.actualExcludedDates) && result.actualExcludedDates.length > 0) ? `\n\n跳过的具体日期：\n${result.actualExcludedDates.join('\n')}` : ''}`}
                          >
                            ?
                          </span>
                        </Text>
                        <div style={{ fontSize: '12px', color: '#666', marginTop: '2px' }}>
                          <Text>跳过日期：{(result.actualExcludedDatesCount && result.actualExcludedDatesCount > 0) ? `${result.actualExcludedDatesCount} 天` : '无'}</Text>
                        </div>
                      </div>
                    </div>
                  </Card>
                );
              })}
              
              {/* 计算公式展示卡片 */}
              {timelineResults.length > 0 && (
                <Card style={{ 
                  marginBottom: '16px', 
                  padding: '16px', 
                  background: 'linear-gradient(135deg, #e6f7ff 0%, #f0f9ff 100%)',
                  border: '2px solid #1890ff',
                  borderRadius: '12px',
                  boxShadow: '0 4px 12px rgba(24, 144, 255, 0.15)'
                }}>
                  <div style={{ textAlign: 'center' }}>
                    <Title heading={5} style={{ 
                      margin: '0 0 16px 0', 
                      color: '#1890ff',
                      fontSize: '20px',
                      fontWeight: 'bold'
                    }}>
                      📅 交期余量计算
                    </Title>
                    {/* 汇总两行：1）最后完成+缓冲期=结束日期  2）客户期望日期（可更改） */}
                    <div style={{
                      marginBottom: '12px',
                      padding: '8px 12px',
                      backgroundColor: 'rgba(255,255,255,0.85)',
                      borderRadius: '8px',
                      border: '1px solid rgba(24, 144, 255, 0.2)'
                    }}>
                      {(() => {
                        // 计算有效的最后流程完成日期
                        let effectiveLastProcess = null as any;
                        let lastCompletionDate = null as Date | null;
                        for (let i = timelineResults.length - 1; i >= 0; i--) {
                          const process = timelineResults[i];
                          const processDate = typeof process.estimatedEnd === 'string'
                            ? parseDate(process.estimatedEnd)
                            : (process.estimatedEnd as any as Date);
                          if (processDate && !isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') {
                            effectiveLastProcess = process;
                            lastCompletionDate = processDate;
                            break;
                          }
                        }
                        if (!effectiveLastProcess) {
                          effectiveLastProcess = timelineResults[timelineResults.length - 1];
                          const fallback = typeof effectiveLastProcess.estimatedEnd === 'string'
                            ? parseDate(effectiveLastProcess.estimatedEnd)
                            : (effectiveLastProcess.estimatedEnd as any as Date);
                          lastCompletionDate = fallback || new Date();
                        }

                        // 缓冲期（动态）与调整后的最后完成日期
                        const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
                        const adjustedCompletionDate = new Date(lastCompletionDate!);
                        adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment);
                        const deliveryDate = new Date(adjustedCompletionDate);
                        deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
                        const lockedDeliveryDate = (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null)
                          ? new Date(lockedExpectedDeliveryDateTs)
                          : null;

                        return (
                          <>
                            {/* 第一行：最后流程完成日期 + 缓冲期 = 结束日期（优化为紧凑分段展示） */}
                            <div style={{
                              display: 'flex',
                              alignItems: 'center',
                              justifyContent: 'flex-start',
                              gap: 6,
                              flexWrap: 'nowrap',
                              whiteSpace: 'nowrap',
                              overflowX: 'auto'
                            }}>
                              <span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}>最后流程完成日期</span>
                              <span style={{
                                fontSize: 13,
                                color: '#333',
                                backgroundColor: 'rgba(0,0,0,0.04)',
                                border: '1px solid rgba(0,0,0,0.08)',
                                borderRadius: 6,
                                padding: '4px 8px'
                              }}>
                                {formatDate(adjustedCompletionDate)}（{getDayOfWeek(adjustedCompletionDate)}）
                              </span>
                              <span style={{ fontSize: 16, color: '#1890ff', fontWeight: 700 }}>+</span>
                              <span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}>缓冲期</span>
                              <span style={{
                                fontSize: 13,
                                color: '#1890ff',
                                backgroundColor: 'rgba(24, 144, 255, 0.08)',
                                border: '1px solid rgba(24, 144, 255, 0.2)',
                                borderRadius: 16,
                                padding: '3px 8px',
                                fontWeight: 700,
                                whiteSpace: 'nowrap'
                              }}>
                                {dynamicBufferDays}天
                              </span>
                              <span style={{ fontSize: 16, color: '#1890ff', fontWeight: 700 }}>=</span>
                              <span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}>
                                {lockedDeliveryDate ? '结束日期（未锁）' : '结束日期'}
                              </span>
                              <span style={{
                                fontSize: 13,
                                color: '#52c41a',
                                backgroundColor: 'rgba(82, 196, 26, 0.08)',
                                border: '1px solid rgba(82, 196, 26, 0.24)',
                                borderRadius: 6,
                                padding: '3px 8px',
                                fontWeight: 700
                              }}>
                                {formatDate(deliveryDate)}（{getDayOfWeek(deliveryDate)}）
                              </span>
                            </div>

                            {lockedDeliveryDate && (
                              <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: 8, flexWrap: 'nowrap', whiteSpace: 'nowrap', overflowX: 'auto' }}>
                                <Text style={{ fontSize: 13, color: '#333', fontWeight: 600, whiteSpace: 'nowrap' }}>结束日期（已锁定）</Text>
                                <span style={{
                                  fontSize: 13,
                                  color: '#722ed1',
                                  backgroundColor: 'rgba(114, 46, 209, 0.08)',
                                  border: '1px solid rgba(114, 46, 209, 0.24)',
                                  borderRadius: 6,
                                  padding: '3px 8px',
                                  fontWeight: 700,
                                  whiteSpace: 'nowrap'
                                }}>
                                  {formatDate(lockedDeliveryDate)}（{getDayOfWeek(lockedDeliveryDate)}）
                                </span>
                              </div>
                            )}

                            {/* 第二行：客户期望日期（可更改，优化展示为标签样式） */}
                            <div style={{ marginTop: 8, display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: 8, flexWrap: 'nowrap', whiteSpace: 'nowrap', overflowX: 'auto' }}>
                              <Text style={{ fontSize: 13, color: '#333', fontWeight: 600, whiteSpace: 'nowrap' }}>客户期望日期（可更改）</Text>
                              <DatePicker
                                style={{ width: '200px' }}
                                placeholder="请选择客户期望日期"
                                value={expectedDate ?? undefined}
                                disabled={mode === 'adjust' && isExpectedDeliveryDateLocked}
                                onChange={(date) => {
                                  if (date instanceof Date) {
                                    setExpectedDate(date);
                                  } else if (typeof date === 'string') {
                                    const parsed = new Date(date);
                                    setExpectedDate(isNaN(parsed.getTime()) ? null : parsed);
                                  } else {
                                    setExpectedDate(null);
                                  }
                                }}
                                format="yyyy-MM-dd"
                                disabledDate={(date) => {
                                  const today = new Date();
                                  today.setHours(0, 0, 0, 0);
                                  if (!(date instanceof Date)) return false;
                                  return date < today;
                                }}
                              />
                              {expectedDate && (
                                <span style={{
                                  fontSize: 13,
                                  color: '#722ed1',
                                  backgroundColor: 'rgba(114, 46, 209, 0.08)',
                                  border: '1px solid rgba(114, 46, 209, 0.24)',
                                  borderRadius: 6,
                                  padding: '4px 8px',
                                  fontWeight: 600,
                                  whiteSpace: 'nowrap'
                                }}>
                                  {formatDate(expectedDate)}（{getDayOfWeek(expectedDate)}）
                                </span>
                              )}
                            </div>
                          </>
                        );
                      })()}
                    </div>
                  </div>
                </Card>
              )}
              
              <div style={{ marginTop: '16px', padding: '12px', backgroundColor: '#f6f8fa', borderRadius: '6px' }}>
              <Text strong>📊 计算说明：</Text>
                <Text style={{ display: 'block', marginTop: '4px' }}>
                  共找到 {timelineResults.length} 个匹配的节点。时间按流程顺序计算，上一个节点的完成时间等于下一个节点的开始时间。每个节点使用其特定的休息日配置和计算方式进行工作日计算。
                </Text>
                <div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#e6f7ff', borderRadius: '4px' }}>
                  <Text strong style={{ color: '#1890ff' }}>🗓️ 计算规则说明：</Text>
                  <Text style={{ display: 'block', marginTop: '4px', fontSize: '14px' }}>
                    • <strong>内部计算</strong>：按9小时工作制（9:00-18:00），超时自动顺延至下个工作日
                    <br />
                    • <strong>外部计算</strong>：按24小时制计算，适用于外部供应商等不受工作时间限制的节点
                    <br />
                    • 根据每个节点的休息日配置自动跳过相应的休息日
                    <br />
                    • 可为每个节点配置“跳过日期”，这些日期将不参与工作日计算
                    <br />
                    • 时效值以"工作日"为单位计算，确保预期时间的准确性
                    <br />
                    • 使用 +1/-1 按钮调整整天，+0.5/-0.5 按钮调整半天，系统会自动重新计算所有后续节点
                    <br />
                    • 不同节点可配置不同的休息日和计算方式（如：内部节点按工作时间，外部节点按自然时间）
                  </Text>
                </div>
                <div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fffbe6', borderRadius: '4px' }}>
                  <Space align="center" spacing={8} style={{ width: '100%', justifyContent: 'space-between' }}>
                    <Text strong style={{ color: '#fa8c16' }}>跳过日期</Text>
                    <Space align="center" spacing={8}>
                      <Text type="tertiary">
                        已配置 {Object.values(excludedDatesByNodeOverride || {}).filter(v => Array.isArray(v) && v.length > 0).length} 个节点
                      </Text>
                      <Button size="small" onClick={openExcludedDatesAdjustModal}>按节点调整</Button>
                    </Space>
                  </Space>
                </div>
                {Object.keys(timelineAdjustments).length > 0 && (
                  <div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fff7e6', borderRadius: '4px' }}>
                    <Text strong style={{ color: '#fa8c16' }}>当前调整：</Text>
                    <div style={{ marginTop: '4px' }}>
                      {(() => {
                        const nodeNameByKey = new Map<string, string>();
                        for (let i = 0; i < timelineResults.length; i++) {
                          const r = timelineResults[i];
                          const k = buildTimelineAdjustmentKey(r, i);
                          if (!nodeNameByKey.has(k)) nodeNameByKey.set(k, r?.nodeName || k);
                        }
                        return Object.entries(timelineAdjustments).map(([nodeKey, adjustment]) => {
                          const nodeName = nodeNameByKey.get(nodeKey) || nodeKey;
                        return (
                          <Text key={nodeKey} style={{ display: 'block', fontSize: '12px' }}>
                            {nodeName}: {adjustment > 0 ? '+' : ''}{adjustment.toFixed(1)} 天
                          </Text>
                        );
                        });
                      })()}
                    </div>
                  </div>
                )}
              </div>
            </div>
          )}
        </div>
      </Modal>
      

      
      {/* 标签选择部分，生成/调整模式均可使用 */}
      {(mode === 'generate' || labelAdjustmentFlow) && labelOptions && Object.keys(labelOptions).length > 0 && (
        <Card title="标签选择" className="card-enhanced" style={{ marginBottom: '24px' }}>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '16px' }}>
            {Array.from({ length: 12 }, (_, i) => i + 1).map(num => {
              const labelKey = `标签${num}`;
              const options = labelOptions[labelKey] || [];
              const isMultiSelect = labelKey === '标签7' || labelKey === '标签8' || labelKey === '标签10' || labelKey === '标签12';
              const val = selectedLabels[labelKey];
              const isMissing = Array.isArray(val) ? (val as string[]).length === 0 : !(typeof val === 'string' && (val as string).trim().length > 0);
              const label7Val = selectedLabels['标签7'];
              const label7Values = Array.isArray(label7Val)
                ? (label7Val as any[]).map(s => String(s ?? '').trim()).filter(Boolean)
                : (typeof label7Val === 'string' ? [label7Val.trim()].filter(Boolean) : []);
              const shouldRequireLabel12 = label7Values.length > 0 && !(label7Values.length === 1 && label7Values[0] === '无(二次工艺)');
              const isRequired = labelKey !== '标签11' && (labelKey === '标签12' ? shouldRequireLabel12 : true);
              
              return (
                <div key={labelKey}>
                  <Text strong style={{ display: 'block', marginBottom: '8px' }}>{labelKey}（{isRequired ? '必填' : '非必填'}）</Text>
                  <Select
                    className="select-enhanced"
                    style={{ width: '100%', borderColor: isRequired && isMissing ? '#ff4d4f' : undefined }}
                    placeholder={`请选择${labelKey}${isRequired ? '（必填）' : ''}`}
                    value={selectedLabels[labelKey]}
                    onChange={(value) => {
                      const normalizeOne = (v: any) => {
                        if (typeof v === 'string') return v;
                        if (typeof v === 'number') return String(v);
                        if (v && typeof v === 'object') {
                          if (typeof v.value === 'string') return v.value;
                          if (typeof v.value === 'number') return String(v.value);
                          if (typeof v.text === 'string') return v.text;
                          if (typeof v.label === 'string') return v.label;
                        }
                        return '';
                      };

                      if (isMultiSelect) {
                        const arr = Array.isArray(value) ? value : (value == null ? [] : [value]);
                        const out = arr.map(normalizeOne).map(s => s.trim()).filter(Boolean);
                        handleLabelChange(labelKey, out);
                        return;
                      }

                      if (Array.isArray(value)) {
                        const first = value.length > 0 ? normalizeOne(value[0]).trim() : '';
                        handleLabelChange(labelKey, first);
                        return;
                      }

                      const single = normalizeOne(value).trim();
                      handleLabelChange(labelKey, single);
                    }}
                    multiple={isMultiSelect}
                    filter
                  >
                    {options.map((option, optionIndex) => (
                      <Select.Option key={optionIndex} value={option.value}>
                        {option.label}
                      </Select.Option>
                    ))}
                  </Select>
                  {labelKey === '标签7' && (
                    <Text type="tertiary" style={{ marginTop: '6px', display: 'block' }}>
                      此标签用作生产端工厂货期时效
                    </Text>
                  )}
                  {labelKey === '标签12' && (
                    <Text type="tertiary" style={{ marginTop: '6px', display: 'block' }}>
                      此标签用作前置流程节点，若选择版单不自动带出，建议提前询问一下设计跟单
                    </Text>
                  )}
                  {isRequired && isMissing && (
                    <Text type="danger" style={{ marginTop: '6px', display: 'block' }}>该标签为必填</Text>
                  )}
                </div>
              );
            })}
          </div>
          
          <div style={{ marginTop: '24px' }}>
            <Text strong style={{ display: 'block', marginBottom: '8px' }}>计算方向</Text>
            <Select
              style={{ width: '300px' }}
              value={timelineDirection}
              onChange={(value) => setTimelineDirection(value as 'forward' | 'backward')}
            >
              <Select.Option value="forward">正推（从起始时间开始）</Select.Option>
              <Select.Option value="backward">倒推（以客户期望日期为目标）</Select.Option>
            </Select>
          </div>

          <div style={{ marginTop: '24px' }}>
            <Text strong style={{ display: 'block', marginBottom: '8px' }}>起始日期</Text>
            <DatePicker
              className="input-enhanced"
              style={{ width: '300px' }}
              placeholder="请选择起始日期"
              value={startTime ?? undefined}
              onChange={(date) => {
                if (date instanceof Date) {
                  setStartTime(date);
                } else if (typeof date === 'string') {
                  const parsed = new Date(date);
                  setStartTime(isNaN(parsed.getTime()) ? null : parsed);
                } else {
                  setStartTime(null);
                }
              }}
              type="dateTime"
              format="yyyy-MM-dd HH:mm"
            />
            {timelineDirection === 'backward' && (
              <Text type="secondary" style={{ marginLeft: '12px' }}>
                倒推模式必填
              </Text>
            )}
          </div>

          {/* 客户期望日期选择 */}
          <div style={{ marginTop: '24px' }}>
            <Text strong style={{ display: 'block', marginBottom: '8px' }}>客户期望日期</Text>
            <DatePicker
              className="input-enhanced"
              style={{ width: '300px' }}
              placeholder="请选择客户期望日期"
              value={expectedDate ?? undefined}
              onChange={(date) => {
                if (date instanceof Date) {
                  setExpectedDate(date);
                } else if (typeof date === 'string') {
                  const parsed = new Date(date);
                  setExpectedDate(isNaN(parsed.getTime()) ? null : parsed);
                } else {
                  setExpectedDate(null);
                }
              }}
              format="yyyy-MM-dd"
              disabledDate={(date) => {
                // 禁用今天之前的日期
                const today = new Date();
                today.setHours(0, 0, 0, 0);
                if (!(date instanceof Date)) return false;
                return date < today;
              }}
            />
            {expectedDate && (
              <Text type="secondary" style={{ marginLeft: '12px' }}>
                已选择：{formatDate(expectedDate, 'CHINESE_DATE')}
              </Text>
            )}
          </div>
          
          {Object.keys(selectedLabels).length > 0 && (
            <div style={{ 
              marginTop: '24px', 
              padding: '20px',
              background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)',
              borderRadius: '12px',
              border: '1px solid #bae6fd'
            }}>
              <Space vertical spacing="loose" style={{ width: '100%' }}>
                <Button 
                  type='primary'
                  className="btn-gradient-calculate"
                  size="large"
                  onClick={() => labelAdjustmentFlow 
                    ? handleCalculateTimeline(true, { selectedLabels, expectedDate, startTime }, true) 
                    : handleCalculateTimeline()}
                  loading={timelineLoading}
                  disabled={
                    timelineLoading
                    || (timelineDirection === 'backward' && (!expectedDate || !startTime))
                    || Array.from({ length: 10 }, (_, i) => `标签${i + 1}`).some(key => {
                      const val = (selectedLabels as any)[key];
                      if (Array.isArray(val)) return val.length === 0;
                      return !(typeof val === 'string' && val.trim().length > 0);
                    })
                  }
                  style={{ minWidth: '160px' }}
                >
                  {labelAdjustmentFlow ? '重新生成计划' : '计算预计时间'}
                </Button>
                <Text type="secondary" style={{ fontSize: '14px' }}>
                  已选择 {Object.keys(selectedLabels).length} 个标签
                </Text>
              </Space>
              
              <div style={{ marginTop: '16px' }}>
                <Text strong style={{ color: '#0369a1' }}>当前选择的标签：</Text>
                <div style={{ marginTop: '8px', display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
                  {Object.entries(selectedLabels).map(([key, value]) => {
                    const displayValue = Array.isArray(value) ? value.join(', ') : value;
                    return (
                      <span 
                        key={key}
                        style={{
                          background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
                          color: 'white',
                          padding: '4px 12px',
                          borderRadius: '20px',
                          fontSize: '12px',
                          fontWeight: 500
                        }}
                      >
                        {key}: {displayValue}
                      </span>
                    );
                  })}
                </div>
              </div>
            </div>
          )}
        </Card>
      )}

      {/* 批量处理配置已移除 */}
      
      {mode === 'generate' && (
        <main className="main" style={{ padding: '20px' }}>
          <Card className="card-enhanced">
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '24px' }}>
              <div>
                <Title heading={3} style={{ 
                  margin: 0,
                  background: 'linear-gradient(135deg, #1890ff 0%, #722ed1 100%)',
                  WebkitBackgroundClip: 'text',
                  WebkitTextFillColor: 'transparent',
                  backgroundClip: 'text'
                }}>版单数据</Title>
                <Text type='secondary' style={{ fontSize: '14px', marginTop: '4px' }}>
                  选择需要生成时效的版单记录
                </Text>
                {selectedRecords.length > 0 && (
                  <Text type='secondary' style={{ fontSize: '12px', marginTop: '4px' }}>
                    已选择 {selectedRecords.length} 条记录
                  </Text>
                )}
              </div>
              
              <Space>
                <Button 
                  type='primary' 
                  className="btn-gradient-select"
                  size="large"
                  onClick={handleSelectRecords}
                  loading={loading}
                  disabled={loading}
                >
                  {selectedRecords.length > 0 ? '重新选择' : '选择记录'}
                </Button>
                
                {selectedRecords.length > 0 && (
                  <Button 
                    type='secondary' 
                    onClick={handleClearRecords}
                    size='small'
                  >
                    清空选择
                  </Button>
                )}
              </Space>
            </div>
            
            {/* 已选择记录的详细信息 */}
            {selectedRecords.length > 0 && recordDetails.length > 0 && (
              <div style={{ 
                padding: '16px', 
                background: 'linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%)', 
                borderRadius: '12px',
                border: '1px solid #dee2e6',
                marginBottom: '20px',
                boxShadow: '0 2px 8px rgba(0,0,0,0.06)'
              }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
                  <div>
                    <Text strong style={{ color: '#495057' }}>主记录:</Text>
                    <span style={{ 
                      marginLeft: '8px', 
                      fontSize: '12px',
                      background: 'linear-gradient(135deg, #007bff 0%, #0056b3 100%)',
                      color: 'white',
                      padding: '2px 8px',
                      borderRadius: '12px',
                      fontWeight: 500
                    }}>
                      {recordDetails[0].displayValue || recordDetails[0].id}
                    </span>
                  </div>
                  {recordDetails.length > 1 && (
                    <div>
                      <Text type='secondary'>+ 其他 {recordDetails.length - 1} 条</Text>
                    </div>
                  )}
                </div>
              </div>
            )}
            
            {/* 加载状态 */}
            {loading && (
              <div style={{ textAlign: 'center', padding: '20px' }}>
                <Spin size="large" />
                <div style={{ marginTop: '10px' }}>
                  <Text>正在加载版单数据...</Text>
                </div>
              </div>
            )}
            
            {/* 空状态提示 */}
            {selectedRecords.length === 0 && !loading && (
              <div style={{ 
                textAlign: 'center', 
                padding: '20px', 
                backgroundColor: '#fafafa',
                borderRadius: '6px',
                border: '1px dashed #d9d9d9'
              }}>
                <Text type="tertiary">请选择版单记录开始操作</Text>
              </div>
            )}
          </Card>
          
          {/* 批量处理配置已移除 */}
        </main>
      )}
      {mode === 'generate' && (
        <>
          {/* 面料数据查询结果 */}
          {queryResults.length > 0 && (
            <>
              <Divider />
              <Title heading={4}>面料数据查询结果 ({queryResults.length} 条)</Title>
              <Table 
                columns={columns} 
                dataSource={queryResults.map((item, index) => ({ ...item, key: index }))}
                pagination={{ pageSize: 10 }}
                style={{ marginTop: '10px' }}
              />
            </>
          )}
          {/* 二次工艺查询结果 */}
          {secondaryProcessResults.length > 0 && (
            <>
              <Divider />
              <Title heading={4}>二次工艺查询结果 ({secondaryProcessResults.length} 条)</Title>
              <Table 
                columns={secondaryProcessColumns} 
                dataSource={secondaryProcessResults.map((item, index) => ({ ...item, key: index }))}
                pagination={{ pageSize: 10 }}
                style={{ marginTop: '10px' }}
              />
            </>
          )}
          {/* 工艺价格查询结果 */}
          {pricingDetailsResults.length > 0 && (
            <>
              <Divider />
              <Title heading={4}>工艺价格查询结果 ({pricingDetailsResults.length} 条)</Title>
              <Table 
                columns={pricingDetailsColumns} 
                dataSource={pricingDetailsResults.map((item, index) => ({ ...item, key: index }))}
                pagination={{ pageSize: 10 }}
                style={{ marginTop: '10px' }}
              />
            </>
          )}
        </>
      )}
    </div>
  );
}
