diff --git a/src/App.tsx b/src/App.tsx index d14e700..d912aa0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -84,27 +84,7 @@ export default function App() { // 原始文本格式的record_ids(不做JSON化写回) const [restoredRecordIdsText, setRestoredRecordIdsText] = useState(''); - // 批量处理相关状态 - const [batchProcessing, setBatchProcessing] = useState(false); - const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0 }); - const [batchRowCount, setBatchRowCount] = useState(10); // 默认处理10行 - - // 批量处理当前记录信息(用于保存时传递正确的数据) - const [currentBatchRecord, setCurrentBatchRecord] = useState<{ - selectedRecords: string[], - recordDetails: any[], - labels: Record, - expectedDate: any, - startTime: any - } | null>(null); - - // 批量处理表和视图选择状态 - const [selectedBatchTableId, setSelectedBatchTableId] = useState(''); - const [selectedBatchViewId, setSelectedBatchViewId] = useState(''); - const [availableTables, setAvailableTables] = useState>([]); - const [availableViews, setAvailableViews] = useState>([]); - const [tablesLoading, setTablesLoading] = useState(false); - const [viewsLoading, setViewsLoading] = useState(false); + // 已移除:批量处理与表/视图选择相关状态 // 全局变量重置:在切换功能或切换版单/批量数据时,清空页面与计算相关状态 const resetGlobalState = (opts?: { resetMode?: boolean }) => { @@ -116,8 +96,6 @@ export default function App() { setLabelLoading(false); setAdjustLoading(false); setTimelineLoading(false); - setBatchProcessing(false); - setBatchProgress({ current: 0, total: 0 }); // 页面与计算数据 setSelectedRecords([]); @@ -142,8 +120,7 @@ export default function App() { initialSnapshotRef.current = null; } catch {} - // 当前记录与批量信息 - setCurrentBatchRecord(null); + // 移除:批量模式当前记录信息 // 当前回填状态 setCurrentForeignId(null); @@ -183,7 +160,7 @@ export default function App() { const CALCULATION_METHOD_FIELD_ID = 'fldxfLZNUu'; // 时效计算方式字段ID // 新表ID(批量生成表) - const BATCH_TABLE_ID = 'tbl673YuipMIJnXL'; // 新创建的多维表格ID + // 已移除:批量生成表ID // 已移除:调整模式不再加载货期记录列表 @@ -203,6 +180,9 @@ export default function App() { } return; } + // 新增:在读取新单据前重置关键状态,避免跨单据串值(缓冲期/实际完成日期/起始日期等) + // 保留当前模式不变 + resetGlobalState({ resetMode: false }); setTimelineLoading(true); try { const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); @@ -1412,7 +1392,6 @@ export default function App() { const initializeData = async () => { await fetchLabelOptions(); await initializeStartTime(); - await loadAvailableTables(); // 加载可用表列表 }; initializeData(); @@ -1541,55 +1520,7 @@ export default function App() { } }; - // 加载可用表列表 - const loadAvailableTables = async () => { - try { - setTablesLoading(true); - const tableMetaList = await bitable.base.getTableMetaList(); - const tables = tableMetaList.map(table => ({ - id: table.id, - name: table.name - })); - setAvailableTables(tables); - } catch (error) { - console.error('加载表列表失败:', error); - } finally { - setTablesLoading(false); - } - }; - - // 加载指定表的视图列表 - const loadAvailableViews = async (tableId: string) => { - if (!tableId) { - setAvailableViews([]); - return; - } - - try { - setViewsLoading(true); - const table = await bitable.base.getTable(tableId); - const viewMetaList = await table.getViewMetaList(); - const views = viewMetaList.map(view => ({ - id: view.id, - name: view.name - })); - setAvailableViews(views); - } catch (error) { - console.error('加载视图列表失败:', error); - setAvailableViews([]); - } finally { - setViewsLoading(false); - } - }; - - // 处理表选择变化 - const handleBatchTableChange = (tableId: string) => { - // 切换批量来源表时重置全局变量,避免旧状态残留 - resetGlobalState(); - setSelectedBatchTableId(tableId); - setSelectedBatchViewId(''); // 重置视图选择 - loadAvailableViews(tableId); // 加载新表的视图列表 - }; + // 已移除:批量表/视图加载与切换逻辑 // 处理标签选择变化 const handleLabelChange = (labelKey: string, value: string | string[]) => { @@ -2716,7 +2647,13 @@ export default function App() { console.log(`节点 ${previousIndex} 使用实际完成时间: ${formatDate(cumulativeStartTime)}`); } else { // 使用预计完成时间 - cumulativeStartTime = new Date(previousResult.estimatedEnd); + 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()); } } @@ -2793,7 +2730,11 @@ export default function App() { const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, nodeWeekendDays, nodeExcludedDates); const skippedWeekends = calculateSkippedWeekends(adjustedStartTime, nodeEndTime, nodeWeekendDays); const estimatedStartStr = formatDate(adjustedStartTime); - const estimatedEndStr = adjustedTimelineValue !== 0 ? formatDate(nodeEndTime) : '时效值为0'; + let estimatedEndStr = adjustedTimelineValue !== 0 ? formatDate(nodeEndTime) : '时效值为0'; + // 若当前节点存在实际完成时间,则用于展示与跨度计算,以实现与后续节点的实际连线 + if (actualCompletionDates[i]) { + estimatedEndStr = formatDate(actualCompletionDates[i]!); + } const actualDays = calculateActualDays(estimatedStartStr, estimatedEndStr); // 计算时间范围内实际跳过的自定义日期 @@ -2835,6 +2776,7 @@ export default function App() { const [isRestoringSnapshot, setIsRestoringSnapshot] = useState(false); const [hasAppliedSuggestedBuffer, setHasAppliedSuggestedBuffer] = useState(false); + const [lastSuggestedApplied, setLastSuggestedApplied] = useState(null); // 初始状态快照(仅捕获一次) const initialSnapshotRef = useRef(null); @@ -2867,6 +2809,7 @@ export default function App() { actualCompletionDates, completionDateAdjustment, hasAppliedSuggestedBuffer, + lastSuggestedApplied, deliveryMarginDeductions, }; hasCapturedInitialSnapshotRef.current = true; @@ -2882,6 +2825,7 @@ export default function App() { setActualCompletionDates({}); // 重置实际完成日期 setBaseBufferDays(14); // 重置固定缓冲期为默认值 setHasAppliedSuggestedBuffer(false); // 重置建议缓冲期应用标志 + setLastSuggestedApplied(null); // 清空上次建议值 recalculateTimeline({}, true); // 强制重算所有节点 }; @@ -2906,6 +2850,7 @@ export default function App() { 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(s.timelineAdjustments || {}, true); @@ -2925,14 +2870,7 @@ export default function App() { const writeToDeliveryRecordTable = async ( timelineResults: any[], processRecordIds: string[], - timelineAdjustments: {[key: number]: number} = {}, - batchData?: { - selectedRecords: string[], - recordDetails: any[], - labels?: Record, - expectedDate?: any, - startTime?: any - } + timelineAdjustments: {[key: number]: number} = {} ) => { try { console.log('=== 开始写入货期记录表 ==='); @@ -2994,9 +2932,9 @@ export default function App() { console.log('=== 开始获取foreign_id ==='); let foreignId = ''; - // 使用传递的数据或全局状态 - const currentSelectedRecords = batchData?.selectedRecords || selectedRecords; - const currentRecordDetails = batchData?.recordDetails || recordDetails; + // 使用全局状态 + const currentSelectedRecords = selectedRecords; + const currentRecordDetails = recordDetails; if (mode === 'adjust') { // 调整模式:严格使用快照回填的foreign_id,即使为空也不回退 @@ -3007,24 +2945,14 @@ export default function App() { console.log('生成模式:从选择记录获取foreign_id'); console.log('selectedRecords[0]:', currentSelectedRecords[0]); - if (batchData) { - // 批量模式:直接从传递的数据中获取 - const firstRecord = currentRecordDetails[0]; - if (firstRecord && firstRecord.fields && firstRecord.fields['fldpvBfeC0']) { - const fieldValue = firstRecord.fields['fldpvBfeC0']; - foreignId = extractText(fieldValue); - console.log('批量模式:从传递数据获取foreign_id:', foreignId); - } - } else { - // 生成模式:从数据库获取 - 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); - } + // 生成模式:从数据库获取 + 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); } // 生成模式的回退逻辑:记录详情 @@ -3074,27 +3002,13 @@ export default function App() { text2 = currentText2; // 直接使用快照值,不使用 || '' 的回退逻辑 console.log('调整模式:严格使用快照恢复的文本2:', text2); } else { - if (batchData && currentRecordDetails.length > 0) { - const first = currentRecordDetails[0]; - if (first?.fields?.['fldG6LZnmU']) { - text2 = extractText(first.fields['fldG6LZnmU']); - } - // 兜底:若批量数据中显式传递了text2,则使用之 - if (!text2 && (batchData as any).text2) { - try { text2 = extractText((batchData as any).text2); } catch { text2 = (batchData as any).text2; } - } - console.log('生成模式(批量):文本2来自批量数据:', text2); - } else { - // 非批量的生成模式:保持为空 - text2 = ''; - console.log('生成模式:文本2字段保持为空'); - } + // 生成模式:文本2字段保持为空 + text2 = ''; + console.log('生成模式:文本2字段保持为空'); } // 获取标签汇总:批量模式优先使用传入的labels - const selectedLabelValues = batchData?.labels - ? Object.values(batchData.labels).flat().filter(Boolean) - : Object.values(selectedLabels).flat().filter(Boolean); + const selectedLabelValues = Object.values(selectedLabels).flat().filter(Boolean); // 获取预计交付日期(交期余量的日期版本:最后流程完成日期 + 基础缓冲期) let expectedDeliveryDate = null; @@ -3131,11 +3045,7 @@ export default function App() { // 获取客户期望日期:批量模式优先使用传入的expectedDate let customerExpectedDate = null; - if (batchData?.expectedDate) { - try { - customerExpectedDate = new Date(batchData.expectedDate).getTime(); - } catch { /* 忽略转换错误,保持null */ } - } else if (expectedDate) { + if (expectedDate) { customerExpectedDate = expectedDate.getTime(); // 转换为时间戳 } @@ -3179,14 +3089,10 @@ export default function App() { // 在创建Cell之前进行数据校验(移除冗余日志) // ===== 构建完整快照(保持与流程数据表写入时的一致内容)并写入到货期记录表 ===== - const expectedDateTimestamp = (batchData?.expectedDate || expectedDate) - ? (batchData?.expectedDate || expectedDate).getTime() - : null; - const expectedDateString = (batchData?.expectedDate || expectedDate) - ? format((batchData?.expectedDate || expectedDate), DATE_FORMATS.STORAGE_FORMAT) - : null; - const currentStartTime = batchData?.startTime || startTime; - const currentSelectedLabels = batchData?.labels || selectedLabels; + const expectedDateTimestamp = expectedDate ? expectedDate.getTime() : null; + const expectedDateString = expectedDate ? format(expectedDate, DATE_FORMATS.STORAGE_FORMAT) : null; + const currentStartTime = startTime; + const currentSelectedLabels = selectedLabels; // 与快照字段保持相同的命名 const styleText = style || ''; @@ -3196,7 +3102,7 @@ export default function App() { // 检查是否达到最终限制 let hasReachedFinalLimit = false; - const currentExpectedDate = batchData?.expectedDate || expectedDate; + const currentExpectedDate = expectedDate; if (dynamicBufferDays === 0 && currentExpectedDate && timelineResults.length > 0) { const lastNode = timelineResults[timelineResults.length - 1]; if (lastNode && lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) { @@ -3243,7 +3149,9 @@ export default function App() { baseDays: baseBuferDays, totalAdjustments, dynamicBufferDays, - hasReachedFinalLimit + hasReachedFinalLimit, + hasAppliedSuggestedBuffer, + lastSuggestedApplied: lastSuggestedApplied ?? 0 }, chainAdjustmentSystem: { enabled: true, @@ -3344,14 +3252,12 @@ export default function App() { const createTimeCell = await createTimeField.createCell(currentTime); // 调试日志:检查startTime参数 - console.log('批量模式 - startTime参数:', startTime); - console.log('批量模式 - startTime类型:', typeof startTime); - console.log('批量模式 - currentTime:', currentTime, '对应日期:', new Date(currentTime).toLocaleString()); + console.log('保存 - startTime参数:', startTime); + console.log('保存 - startTime类型:', typeof startTime); + console.log('保存 - currentTime:', currentTime, '对应日期:', new Date(currentTime).toLocaleString()); - const startTimestamp = batchData?.startTime - ? new Date(batchData.startTime).getTime() - : (startTime ? startTime.getTime() : currentTime); - console.log('批量模式 - 最终使用的startTimestamp:', startTimestamp, '对应日期:', new Date(startTimestamp).toLocaleString()); + const startTimestamp = startTime ? startTime.getTime() : currentTime; + console.log('保存 - 最终使用的startTimestamp:', startTimestamp, '对应日期:', new Date(startTimestamp).toLocaleString()); const startTimeCell = await startTimeField.createCell(startTimestamp); const snapshotCell = await snapshotField.createCell(snapshotJson); @@ -3402,13 +3308,7 @@ export default function App() { }; // 写入流程数据表的函数 - const writeToProcessDataTable = async (timelineResults: any[], batchData?: { - selectedRecords: string[], - recordDetails: any[], - labels?: Record, - expectedDate?: any, - startTime?: any - }): Promise => { + const writeToProcessDataTable = async (timelineResults: any[]): Promise => { try { console.log('=== 开始写入流程数据表 ==='); console.log('当前模式:', mode); @@ -3438,9 +3338,9 @@ export default function App() { console.log('=== 开始获取foreign_id ==='); let foreignId = null; - // 使用传递的数据或全局状态 - const currentSelectedRecords = batchData?.selectedRecords || selectedRecords; - const currentRecordDetails = batchData?.recordDetails || recordDetails; + // 使用全局状态 + const currentSelectedRecords = selectedRecords; + const currentRecordDetails = recordDetails; console.log('selectedRecords数量:', currentSelectedRecords?.length || 0); console.log('recordDetails数量:', currentRecordDetails?.length || 0); @@ -3524,7 +3424,7 @@ export default function App() { conditions: [{ fieldId: FOREIGN_ID_FIELD_ID, operator: 'is', - value: [actualForeignId] + value: [foreignId] }] } }); @@ -3709,115 +3609,21 @@ export default function App() { } }; - // 批量处理函数 - // 批量模式:模拟生成模式的操作(简化版本) - const simulateGenerationModeForBatch = async ( - recordId: string, - extractedLabels: {[key: string]: string | string[]}, - extractedStartTime: Date | null, - extractedExpectedDate: Date | null, - style: any, - color: any, - foreignId: any, - text2: any - ) => { - console.log('=== 批量模式:模拟生成模式操作 ==='); - console.log('recordId:', recordId); - console.log('extractedLabels:', extractedLabels); - console.log('extractedStartTime:', extractedStartTime); - console.log('extractedExpectedDate:', extractedExpectedDate); - - // 1. 构造虚拟的 selectedRecords 和 recordDetails - const virtualSelectedRecords = [recordId]; - const virtualRecordDetails = [{ - id: recordId, - fields: { - 'fldpvBfeC0': foreignId, // 外键ID字段 - 'fld6Uw95kt': style, // 样式字段 - 'flde85ni4O': color, // 颜色字段 - 'fldG6LZnmU': text2 // 文本字段2 - } - }]; - - try { - // 不再设置全局状态,直接通过overrideData传递数据 - console.log('批量模式:直接调用计算逻辑,传递起始时间:', extractedStartTime); - - // 3. 直接调用时效计算,通过overrideData传递所有必要数据 - const calculatedResults = await handleCalculateTimeline(true, { - selectedRecords: virtualSelectedRecords, - recordDetails: virtualRecordDetails, - selectedLabels: extractedLabels, - expectedDate: extractedExpectedDate, - startTime: extractedStartTime // 直接传递起始时间,避免异步状态更新问题 - }, false); // showUI: false,批量模式下不显示时间轴UI - - console.log('批量模式:时效计算完成,结果:', calculatedResults); - - // 批量模式:更新UI显示最后处理记录的时间设置 - console.log('批量模式:更新UI显示时间设置'); - setExpectedDate(extractedExpectedDate); - setStartTime(extractedStartTime); - - // 批量模式:自动保存逻辑 - console.log('批量模式:开始自动保存流程'); - - // 检查计算结果 - if (!calculatedResults || calculatedResults.length === 0) { - console.error('批量模式:时效计算结果为空,无法保存'); - throw new Error('时效计算结果为空,请检查流程配置和标签匹配'); - } - - // 自动触发保存 - console.log('批量模式:开始自动保存数据'); - await saveTimelineData({ - timelineResults: calculatedResults, // 使用直接返回的结果 - selectedRecords: virtualSelectedRecords, - recordDetails: virtualRecordDetails, - timelineAdjustments: {}, // 批量模式下没有手动调整 - labels: extractedLabels, - expectedDate: extractedExpectedDate, - startTime: extractedStartTime - }); - - console.log('批量模式:自动保存完成'); - - } catch (error) { - console.error('批量模式:处理失败', error); - throw error; // 重新抛出错误让上层处理 - } - }; + // 保存时效数据的核心逻辑(从确定并保存按钮提取) - const saveTimelineData = async (batchData?: { - timelineResults?: any[], - selectedRecords?: string[], - recordDetails?: any[], - timelineAdjustments?: any, - labels?: Record, // 添加标签汇总 - expectedDate?: any, // 添加期望日期 - startTime?: any // 添加起始时间 - }) => { + const saveTimelineData = async () => { try { - // 使用传入的数据或全局状态 - const currentTimelineResults = batchData?.timelineResults || timelineResults; - const currentTimelineAdjustments = batchData?.timelineAdjustments || timelineAdjustments; + // 使用全局状态 + const currentTimelineResults = timelineResults; + const currentTimelineAdjustments = timelineAdjustments; if (currentTimelineResults.length > 0) { - // 构造批量数据参数 - const batchDataForWriting = batchData ? { - selectedRecords: batchData.selectedRecords || [], - recordDetails: batchData.recordDetails || [], - labels: batchData.labels, // 传递标签汇总 - expectedDate: batchData.expectedDate, // 传递期望日期 - startTime: batchData.startTime // 传递起始时间 - } : undefined; - // 写入流程数据表 - const processRecordIds = await writeToProcessDataTable(currentTimelineResults, batchDataForWriting); + const processRecordIds = await writeToProcessDataTable(currentTimelineResults); // 写入货期记录表 - await writeToDeliveryRecordTable(currentTimelineResults, processRecordIds, currentTimelineAdjustments, batchDataForWriting); + await writeToDeliveryRecordTable(currentTimelineResults, processRecordIds, currentTimelineAdjustments); if (bitable.ui.showToast) { await bitable.ui.showToast({ @@ -3842,1015 +3648,10 @@ export default function App() { throw error; // 重新抛出错误 } }; - - const handleBatchProcess = async (rowCount: number) => { - // 切换到批量处理流程时重置全局变量 - resetGlobalState(); - // 检查是否选择了表 - if (!selectedBatchTableId) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'warning', - message: '请先选择要处理的数据表' - }); - } - return; - } - - setBatchProcessing(true); - setBatchProgress({ current: 0, total: rowCount }); - - try { - // 使用选择的表而不是硬编码的表ID - const batchTable = await bitable.base.getTable(selectedBatchTableId); - - // 获取表中的记录(限制数量) - let records = []; - if (selectedBatchViewId) { - // 如果选择了视图,从视图中获取记录ID列表,然后获取记录 - const view = await batchTable.getViewById(selectedBatchViewId); - const recordIdList = await view.getVisibleRecordIdList(); - const limitedRecordIds = recordIdList.slice(0, rowCount).filter(id => id !== undefined); - - // 批量获取记录 - for (const recordId of limitedRecordIds) { - try { - const record = await batchTable.getRecordById(recordId); - // 将recordId添加到记录对象中,确保记录对象包含正确的ID - record.recordId = recordId; - records.push(record); - } catch (error) { - console.warn(`Failed to get record ${recordId}:`, error); - } - } - } else { - // 如果没有选择视图,从表中获取记录 - const recordsResult = await batchTable.getRecords({ - pageSize: rowCount - }); - records = recordsResult.records || []; - } - const actualCount = Math.min(records.length, rowCount); - - if (actualCount === 0) { - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'warning', - message: selectedBatchViewId ? '选择的视图中没有找到记录' : '选择的表中没有找到记录' - }); - } - return; - } - - setBatchProgress({ current: 0, total: actualCount }); - - // 获取新表的字段映射 - const fieldMetaList = await batchTable.getFieldMetaList(); - const fieldMapping: {[key: string]: string} = {}; - const optionMapping: {[fieldId: string]: {[optionId: string]: string}} = {}; - - // 建立字段映射和选项映射 - for (const fieldMeta of fieldMetaList) { - if (fieldMeta.name.match(/^标签([1-9]|10)$/)) { - fieldMapping[fieldMeta.name] = fieldMeta.id; - - // 如果是选择字段,建立选项ID到名称的映射 - if (fieldMeta.property && fieldMeta.property.dataType && fieldMeta.property.dataType.property && fieldMeta.property.dataType.property.options) { - optionMapping[fieldMeta.id] = {}; - for (const option of fieldMeta.property.dataType.property.options) { - if (option.id && option.name) { - optionMapping[fieldMeta.id][option.id] = option.name; - } - } - } - } else if (fieldMeta.name === '起始日期') { - fieldMapping['起始日期'] = fieldMeta.id; - } else if (fieldMeta.name === '客户期望日期') { - fieldMapping['客户期望日期'] = fieldMeta.id; - } else if (fieldMeta.id === 'fldGSdZgMI') { - // 根据字段ID直接映射起始时间字段 - fieldMapping['起始时间'] = fieldMeta.id; - } else if (fieldMeta.id === 'fldczh2nty') { - // 款式字段映射 - fieldMapping['款式'] = fieldMeta.id; - } else if (fieldMeta.id === 'fldk5fVYvW') { - // 颜色字段映射 - fieldMapping['颜色'] = fieldMeta.id; - } else if (fieldMeta.id === 'fldkKZecSv') { - // foreign_id字段映射 - fieldMapping['foreign_id'] = fieldMeta.id; - } else if (fieldMeta.name === 'oms看板record_id' || fieldMeta.id === 'fldlaYgpYO') { - // 文本2字段映射 - 支持有空格和无空格的字段名称,以及直接ID匹配 - fieldMapping['文本2'] = fieldMeta.id; - } - } - - console.log('字段映射:', fieldMapping); - console.log('选项映射:', optionMapping); - console.log('所有字段元数据:', fieldMetaList.map(f => ({ id: f.id, name: f.name }))); - - // 特别检查文本2字段 - const text2Fields = fieldMetaList.filter(f => f.name.includes('文本') || f.id === 'fldlaYgpYO'); - console.log('包含"文本"的字段或fldlaYgpYO:', text2Fields.map(f => ({ id: f.id, name: f.name }))); - - let successCount = 0; - let errorCount = 0; - const errors: string[] = []; - - // 跟踪最后成功处理的记录的时间信息 - let lastSuccessfulStartTime: Date | null = null; - let lastSuccessfulExpectedDate: Date | null = null; - - // 逐条处理记录 - for (let i = 0; i < actualCount; i++) { - const record = records[i]; - setBatchProgress({ current: i + 1, total: actualCount }); - - try { - // 获取记录ID - 现在应该能正确获取到recordId - const recordId = record.recordId || record.id || `record_${i + 1}`; - console.log(`处理第 ${i + 1} 条记录:`, recordId); - console.log(`记录对象结构:`, record); - - // 从记录中提取标签数据 - const extractedLabels: {[key: string]: string | string[]} = {}; - const recordFields = record.fields; - - // 提取标签1-10 - for (let labelNum = 1; labelNum <= 10; labelNum++) { - const labelKey = `标签${labelNum}`; - const fieldId = fieldMapping[labelKey]; - - if (fieldId && recordFields[fieldId]) { - const fieldValue = recordFields[fieldId]; - - // 处理不同类型的字段值 - if (Array.isArray(fieldValue)) { - // 多选字段或选项ID数组 - const values = fieldValue.map((item: any) => { - if (typeof item === 'string') { - // 如果是选项ID,从选项映射中查找对应的名称 - if (optionMapping[fieldId] && optionMapping[fieldId][item]) { - return optionMapping[fieldId][item]; - } - return item; - } - if (item && item.text) return item.text; - if (item && item.name) return item.name; - return ''; - }).filter(v => v); - - if (values.length > 0) { - // 处理可能包含逗号分隔的复合标签值,并与生成模式保持一致 - const expandedValues: string[] = []; - values.forEach(value => { - if (typeof value === 'string' && value.includes(',')) { - // 拆分逗号分隔的值 - const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); - expandedValues.push(...splitValues); - } else { - expandedValues.push(value); - } - }); - - // 与生成模式保持一致:单值返回字符串,多值返回数组 - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = expandedValues; - } else { - extractedLabels[labelKey] = expandedValues.length === 1 ? expandedValues[0] : expandedValues; - } - } - } else if (typeof fieldValue === 'string') { - // 单行文本或选项ID - let finalValue: string; - if (optionMapping[fieldId] && optionMapping[fieldId][fieldValue]) { - finalValue = optionMapping[fieldId][fieldValue]; - } else { - finalValue = fieldValue; - } - - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [finalValue]; - } else { - extractedLabels[labelKey] = finalValue; - } - } else if (fieldValue && fieldValue.text) { - // 富文本等 - const textValue = fieldValue.text; - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [textValue]; - } else { - extractedLabels[labelKey] = textValue; - } - } else if (fieldValue && typeof fieldValue === 'object') { - // 处理公式字段和其他复杂对象 - if (fieldValue.value !== undefined) { - // 公式字段通常有value属性 - if (Array.isArray(fieldValue.value)) { - const values = fieldValue.value.map((item: any) => { - if (typeof item === 'string') return item; - if (item && item.text) return item.text; - if (item && item.name) return item.name; // 处理选项的name属性 - return ''; - }).filter(v => v); - - if (values.length > 0) { - // 与生成模式保持一致:单值返回字符串,多值返回数组 - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = values; - } else { - extractedLabels[labelKey] = values.length === 1 ? values[0] : values; - } - } - } else if (typeof fieldValue.value === 'string') { - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [fieldValue.value]; - } else { - extractedLabels[labelKey] = fieldValue.value; - } - } else if (fieldValue.value && fieldValue.value.name) { - // 单选字段的选项对象 - const nameValue = fieldValue.value.name; - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [nameValue]; - } else { - extractedLabels[labelKey] = nameValue; - } - } - } else if (fieldValue.displayValue !== undefined) { - // 有些字段使用displayValue - if (Array.isArray(fieldValue.displayValue)) { - const values = fieldValue.displayValue.map((item: any) => { - if (typeof item === 'string') return item; - if (item && item.text) return item.text; - if (item && item.name) return item.name; // 处理选项的name属性 - return ''; - }).filter(v => v); - - if (values.length > 0) { - // 与生成模式保持一致:单值返回字符串,多值返回数组 - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = values; - } else { - extractedLabels[labelKey] = values.length === 1 ? values[0] : values; - } - } - } else if (typeof fieldValue.displayValue === 'string') { - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [fieldValue.displayValue]; - } else { - extractedLabels[labelKey] = fieldValue.displayValue; - } - } else if (fieldValue.displayValue && fieldValue.displayValue.name) { - // 单选字段的选项对象 - const nameValue = fieldValue.displayValue.name; - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [nameValue]; - } else { - extractedLabels[labelKey] = nameValue; - } - } - } else if (fieldValue.name) { - // 直接的选项对象,有name属性 - const nameValue = fieldValue.name; - // 特殊处理:标签7始终返回数组结构 - if (labelKey === '标签7') { - extractedLabels[labelKey] = [nameValue]; - } else { - extractedLabels[labelKey] = nameValue; - } - } - } - } - } - - // 提取起始日期 - let extractedStartTime: Date | null = null; - const startDateFieldId = fieldMapping['起始日期'] || fieldMapping['起始时间']; - if (startDateFieldId && recordFields[startDateFieldId]) { - const startDateValue = recordFields[startDateFieldId]; - if (typeof startDateValue === 'number') { - extractedStartTime = new Date(startDateValue); - } else if (typeof startDateValue === 'string') { - extractedStartTime = new Date(startDateValue); - } else if (Array.isArray(startDateValue) && startDateValue.length > 0) { - // 处理数组格式的时间戳,如 [1757260800000] - const timestamp = startDateValue[0]; - if (typeof timestamp === 'number') { - extractedStartTime = new Date(timestamp); - } - } - } - - // 提取客户期望日期 - let extractedExpectedDate: Date | null = null; - const expectedDateFieldId = fieldMapping['客户期望日期']; - if (expectedDateFieldId && recordFields[expectedDateFieldId]) { - const expectedDateValue = recordFields[expectedDateFieldId]; - if (typeof expectedDateValue === 'number') { - extractedExpectedDate = new Date(expectedDateValue); - } else if (typeof expectedDateValue === 'string') { - extractedExpectedDate = new Date(expectedDateValue); - } else if (Array.isArray(expectedDateValue) && expectedDateValue.length > 0) { - // 处理数组格式的时间戳,如 [1757260800000] - const timestamp = expectedDateValue[0]; - if (typeof timestamp === 'number') { - extractedExpectedDate = new Date(timestamp); - } - } - } - - // 统一的字段提取函数(复用生成模式的逻辑) - // 将提取的文本转换为富文本格式 - const createRichTextFormat = (text: string) => { - return text ? [{ type: "text", text: text }] : null; - }; - - // 提取款式、颜色、foreign_id字段 - let extractedStyle: any = null; - let extractedColor: any = null; - let extractedForeignId: any = null; - - // 提取款式字段 - const styleFieldId = fieldMapping['款式']; - if (styleFieldId && recordFields[styleFieldId]) { - const styleText = extractText(recordFields[styleFieldId]); - extractedStyle = createRichTextFormat(styleText); - } - - // 提取颜色字段 - const colorFieldId = fieldMapping['颜色']; - if (colorFieldId && recordFields[colorFieldId]) { - const colorText = extractText(recordFields[colorFieldId]); - extractedColor = createRichTextFormat(colorText); - } - - // 提取foreign_id字段 - const foreignIdFieldId = fieldMapping['foreign_id']; - if (foreignIdFieldId && recordFields[foreignIdFieldId]) { - const foreignIdText = extractText(recordFields[foreignIdFieldId]); - extractedForeignId = createRichTextFormat(foreignIdText); - } - - // 提取文本2字段 - let extractedText2: any = null; - const text2FieldId = fieldMapping['文本2']; - console.log('文本2字段映射:', { fieldName: '文本2', fieldId: text2FieldId }); - console.log('记录字段keys:', Object.keys(recordFields)); - console.log('fldlaYgpYO字段值:', recordFields['fldlaYgpYO']); - - if (text2FieldId && recordFields[text2FieldId]) { - extractedText2 = extractText(recordFields[text2FieldId]); - console.log('提取后的extractedText2:', extractedText2); - } else { - console.log('文本2字段未找到或无数据:', { - fieldId: text2FieldId, - hasField: !!recordFields[text2FieldId], - fieldValue: recordFields[text2FieldId] - }); - } - - console.log(`记录 ${i + 1} 提取的数据:`, { - labels: extractedLabels, - startTime: extractedStartTime, - expectedDate: extractedExpectedDate, - style: extractedStyle, - color: extractedColor, - foreignId: extractedForeignId, - text2: extractedText2 - }); - - console.log(`记录 ${i + 1} 字段映射:`, fieldMapping); - console.log(`记录 ${i + 1} 记录字段:`, Object.keys(recordFields)); - - // 如果没有提取到任何标签,跳过这条记录 - if (Object.keys(extractedLabels).length === 0) { - console.warn(`记录 ${i + 1} 没有找到任何标签数据,跳过处理`); - errorCount++; - errors.push(`记录 ${i + 1}: 没有找到标签数据`); - continue; - } - - // 使用模拟生成模式的方式进行时效计算和写入 - await simulateGenerationModeForBatch( - recordId, - extractedLabels, - extractedStartTime, - extractedExpectedDate, - extractedStyle, - extractedColor, - extractedForeignId, - extractedText2 - ); - - successCount++; - - // 记录最后成功处理的记录的时间信息 - lastSuccessfulStartTime = extractedStartTime; - lastSuccessfulExpectedDate = extractedExpectedDate; - - // 添加延迟避免API限制 - if (i < actualCount - 1) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - - } catch (error) { - console.error(`处理记录 ${i + 1} 失败:`, error); - errorCount++; - errors.push(`记录 ${i + 1}: ${(error as Error).message}`); - } - } - - // 显示处理结果 - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: successCount > 0 ? 'success' : 'error', - message: `批量处理完成!成功: ${successCount} 条,失败: ${errorCount} 条` - }); - } - - // 如果有成功处理的记录,更新UI时间显示为最后一条成功记录的时间 - if (successCount > 0 && lastSuccessfulStartTime && lastSuccessfulExpectedDate) { - console.log('批量处理完成:更新UI时间显示'); - console.log('最后成功记录的起始时间:', lastSuccessfulStartTime); - console.log('最后成功记录的期望日期:', lastSuccessfulExpectedDate); - - setStartTime(lastSuccessfulStartTime); - setExpectedDate(lastSuccessfulExpectedDate); - } - - if (errors.length > 0) { - console.error('批量处理错误详情:', errors); - } - - } catch (error) { - console.error('批量处理失败:', error); - if (bitable.ui.showToast) { - await bitable.ui.showToast({ - toastType: 'error', - message: `批量处理失败: ${(error as Error).message}` - }); - } - } finally { - setBatchProcessing(false); - setBatchProgress({ current: 0, total: 0 }); - } - }; + // 已移除批量处理主函数 handleBatchProcess // 为批量处理优化的时效计算函数 - const calculateTimelineForBatch = async ( - labels: {[key: string]: string | string[]}, - startTime: Date | null, - expectedDate: Date | null, - sourceRecordId: string, - style?: any, - color?: any, - foreignId?: any, - text2?: any - ) => { - try { - // 批量模式:输出当前数据结构,便于与生成模式对比 - try { - console.group('=== 批量模式:计算时效 - 当前数据结构 ==='); - console.log('sourceRecordId:', sourceRecordId); - console.log('labels:', labels); - console.log('startTime:', startTime); - console.log('expectedDate:', expectedDate); - console.log('style:', style); - console.log('color:', color); - console.log('foreignId:', foreignId); - console.log('text2:', text2); - console.groupEnd(); - } catch (logErr) { - console.warn('批量模式结构化日志输出失败:', logErr); - } - // 1. 获取流程配置表数据 - const processConfigTable = await bitable.base.getTable(PROCESS_CONFIG_TABLE_ID); - const processConfigRecords = await processConfigTable.getRecords({ - pageSize: 5000 - }); - - const processNodes = processConfigRecords.records || []; - - // 2. 构建业务选择的标签值集合 - const businessLabelValues = new Set(); - console.log('批量模式 - 开始处理标签数据:', labels); - - for (const [labelKey, selectedValue] of Object.entries(labels)) { - console.log(`批量模式 - 处理标签 ${labelKey}:`, selectedValue, '类型:', typeof selectedValue); - - if (selectedValue) { - const values = Array.isArray(selectedValue) ? selectedValue : [selectedValue]; - values.forEach((value, index) => { - console.log(`批量模式 - ${labelKey} 值${index}:`, value, '类型:', typeof value); - - if (typeof value === 'string' && value.trim()) { - // 处理可能包含逗号分隔的复合标签值 - const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); - console.log(`批量模式 - ${labelKey} 分割后的值:`, splitValues); - - splitValues.forEach(splitValue => { - businessLabelValues.add(splitValue); - console.log(`批量模式 - 添加到业务标签集合: "${splitValue}"`); - }); - } - }); - } - } - - console.log('批量模式 - 最终业务标签值集合:', Array.from(businessLabelValues)); - - // 3. 匹配流程节点 - const matchedProcessNodes: any[] = []; - console.log('批量模式 - 开始匹配流程节点,总节点数:', processNodes.length); - - for (const processNode of processNodes) { - const processFields = processNode.fields; - const nodeName = processFields[NODE_NAME_FIELD_ID]; - const processLabels = processFields[PROCESS_LABEL_FIELD_ID]; - const processOrder = processFields[PROCESS_ORDER_FIELD_ID]; - const weekendDays = processFields[WEEKEND_DAYS_FIELD_ID] || []; - const excludedDates = processFields[EXCLUDED_DATES_FIELD_ID] || []; - const startDateRule = processFields[START_DATE_RULE_FIELD_ID]; - const dateAdjustmentRule = processFields[DATE_ADJUSTMENT_RULE_FIELD_ID]; - - // 处理节点名称 - let nodeNameText = ''; - if (typeof nodeName === 'string') { - nodeNameText = nodeName; - } else if (nodeName && nodeName.text) { - nodeNameText = nodeName.text; - } - - if (!nodeNameText) continue; - - // 处理流程标签数据,与生成模式保持一致 - const processLabelTexts: string[] = []; - if (processLabels && Array.isArray(processLabels)) { - for (const labelItem of processLabels) { - const labelText = extractText(labelItem); - if (labelText && labelText.trim()) { - processLabelTexts.push(labelText.trim()); - } - } - } - - // 与生成模式保持一致:如果流程标签为空,跳过该节点 - if (processLabelTexts.length === 0) { - console.log(`批量模式 - 流程节点 "${nodeNameText}" 标签为空,跳过`); - continue; - } - - console.log(`批量模式 - 检查流程节点 "${nodeNameText}":`, { - 流程标签: processLabelTexts, - 流程顺序: processOrder - }); - - // 处理流程标签匹配 - let isMatched = false; - const matchedLabels: string[] = []; - - // 改为OR逻辑:只要有一个标签匹配就认为节点匹配 - for (const processLabelText of processLabelTexts) { - console.log(`批量模式 - 检查流程标签: "${processLabelText}"`); - - // 对流程标签也进行分割处理,以支持复合标签值 - const processLabelValues = processLabelText.split(',').map(v => v.trim()).filter(v => v !== ''); - console.log(`批量模式 - 流程标签分割后: [${processLabelValues.join(', ')}]`); - - // 检查分割后的每个流程标签值是否在业务标签集合中 - for (const processLabelValue of processLabelValues) { - if (businessLabelValues.has(processLabelValue)) { - matchedLabels.push(processLabelValue); - isMatched = true; - console.log(`批量模式 - 流程节点 "${nodeNameText}" 匹配成功,匹配的标签: "${processLabelValue}"`); - break; // 找到一个匹配就足够了 - } else { - console.log(`批量模式 - 流程标签值 "${processLabelValue}" 未匹配`); - } - } - - if (isMatched) break; // 已经匹配,不需要继续检查其他标签 - } - - if (isMatched) { - matchedProcessNodes.push({ - nodeName: nodeNameText, - processOrder: typeof processOrder === 'number' ? processOrder : parseInt(processOrder) || 0, - matchedLabels, - weekendDays: Array.isArray(weekendDays) ? weekendDays : [], - excludedDates: Array.isArray(excludedDates) ? excludedDates : [], - startDateRule, - dateAdjustmentRule - }); - console.log(`批量模式 - 添加匹配的流程节点: "${nodeNameText}"`); - } else { - console.log(`批量模式 - 流程节点 "${nodeNameText}" 未匹配`); - } - } - - // 按流程顺序排序 - matchedProcessNodes.sort((a, b) => a.processOrder - b.processOrder); - - if (matchedProcessNodes.length === 0) { - throw new Error('没有匹配的流程节点'); - } - - // 4. 获取时效数据并计算时间线 - const timelineTable = await bitable.base.getTable(TIMELINE_TABLE_ID); - const timelineRecords = await timelineTable.getRecords({ - pageSize: 5000 - }); - - // 获取时效表的标签字段映射(只需要获取一次) - const timelineFieldMetaList = await timelineTable.getFieldMetaList(); - const timelineLabelFields: {[key: string]: string} = {}; - - // 查找关系字段和计算方式字段 - let relationshipFieldId = ''; - let calculationMethodFieldId = ''; - - for (const fieldMeta of timelineFieldMetaList) { - const match = fieldMeta.name.match(/^标签([1-9]|10)$/); - if (match) { - const labelKey = `标签${match[1]}`; - timelineLabelFields[labelKey] = fieldMeta.id; - } - // 查找关系字段 - if (fieldMeta.name === '关系' || fieldMeta.id === 'fldaIDAhab') { - relationshipFieldId = fieldMeta.id; - } - // 查找时效计算方式字段 - if (fieldMeta.name === '时效计算方式' || fieldMeta.id === 'fldxfLZNUu') { - calculationMethodFieldId = fieldMeta.id; - } - } - - // 构建时效数据索引 - const timelineIndexByNode = new Map(); - - for (const timelineRecord of timelineRecords.records || []) { - const timelineFields = timelineRecord.fields; - const timelineNodeName = timelineFields[TIMELINE_NODE_FIELD_ID]; - - let timelineNodeNames: string[] = []; - - if (typeof timelineNodeName === 'string') { - timelineNodeNames = timelineNodeName.split(',').map(name => 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 => 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 - }); - } - } - - // 5. 计算每个节点的时效(遵循生成模式的匹配逻辑) - const results: any[] = []; - let cumulativeStartTime = startTime || new Date(); - - for (const processNode of matchedProcessNodes) { - console.log(`批量模式 - 开始处理流程节点: "${processNode.nodeName}"`); - - // 根据流程节点名称,在时效表中查找匹配的记录(与生成模式一致) - const normalizedProcessName = processNode.nodeName.trim().toLowerCase(); - const candidateRecords = timelineIndexByNode.get(normalizedProcessName) || []; - - console.log(`批量模式 - 节点 "${processNode.nodeName}" 找到 ${candidateRecords.length} 个候选时效记录`); - - let timelineValue = null; - let matchedTimelineRecord = null; - - // 收集所有匹配的候选记录(与生成模式保持一致) - const matchedCandidates: any[] = []; - - // 在候选记录中进行标签匹配,收集所有匹配的记录 - for (const candidate of candidateRecords) { - const { record: timelineRecord, fields: timelineFields } = candidate; - - // 进行完整的标签匹配逻辑(与生成模式保持一致) - let isLabelsMatched = true; - - // 检查时效表中每个有值的标签是否都包含在业务标签中 - for (const [labelKey, timelineFieldId] of Object.entries(timelineLabelFields)) { - const timelineLabelValue = timelineFields[timelineFieldId]; - - // 处理时效表中的标签值 - let timelineLabelTexts: string[] = []; - - if (typeof timelineLabelValue === 'string' && timelineLabelValue.trim()) { - timelineLabelTexts = [timelineLabelValue.trim()]; - } else if (timelineLabelValue && timelineLabelValue.text && timelineLabelValue.text.trim()) { - timelineLabelTexts = [timelineLabelValue.text.trim()]; - } else if (Array.isArray(timelineLabelValue) && timelineLabelValue.length > 0) { - // 多选字段,获取所有值 - timelineLabelTexts = timelineLabelValue.map(item => { - if (typeof item === 'string') return item.trim(); - if (item && item.text) return item.text.trim(); - return ''; - }).filter(text => text); - } - - // 如果时效表中该标签有值,则检查该标签的所有值是否都包含在业务标签中 - if (timelineLabelTexts.length > 0) { - let allValuesMatched = true; - - 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 calculationMethodField = timelineFields[calculationMethodFieldId]; - const relationshipField = timelineFields[relationshipFieldId]; - - 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 relationshipType = '默认'; - if (relationshipField) { - if (typeof relationshipField === 'string') { - relationshipType = relationshipField; - } else if (relationshipField && typeof relationshipField === 'object' && relationshipField.text) { - relationshipType = relationshipField.text; - } - } - - // 根据计算方式转换时效值(小时转天) - let convertedTimelineValue = 0; - if (calculationMethod === '内部') { - convertedTimelineValue = Math.round((candidateTimelineValue / 9) * 1000) / 1000; - } else { - convertedTimelineValue = Math.round((candidateTimelineValue / 24) * 1000) / 1000; - } - - // 添加到匹配候选列表 - matchedCandidates.push({ - record: timelineRecord, - timelineValue: convertedTimelineValue, - originalHours: candidateTimelineValue, - calculationMethod: calculationMethod, - relationshipType: relationshipType - }); - } - } - } - - // 处理匹配的候选记录(与生成模式保持一致) - let finalTimelineValue = 0; - let processingRule = '默认'; - - if (matchedCandidates.length > 0) { - // 获取主要关系类型(用于决定处理方式) - const relationshipTypes = matchedCandidates.map(c => c.relationshipType); - const primaryRelationshipType = relationshipTypes.find(type => type === '累加值' || type === '最大值') || '默认'; - - // 使用关系字段决定处理方式(累加值、最大值、默认) - 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; - } - - // 计算节点时间(与生成模式保持一致) - let nodeStartTime = new Date(cumulativeStartTime); - let nodeEndTime = new Date(nodeStartTime); - let nodeCalculationMethod = '外部'; // 默认计算方式 - - // 如果有匹配的候选记录,使用其计算方式 - if (matchedCandidates.length > 0) { - if (processingRule === '累加值') { - nodeCalculationMethod = matchedCandidates[0].calculationMethod; - } else if (processingRule === '最大值') { - const maxCandidate = matchedCandidates.find(c => c.timelineValue === finalTimelineValue); - nodeCalculationMethod = maxCandidate?.calculationMethod || matchedCandidates[0].calculationMethod; - } else { - nodeCalculationMethod = matchedCandidates[0].calculationMethod; - } - } - - if (timelineValue && timelineValue !== 0) { - // 根据计算方式调整开始时间 - const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, nodeCalculationMethod, processNode.weekendDays, processNode.excludedDates || []); - - let endDate: Date; - if (nodeCalculationMethod === '内部') { - // 使用内部工作时间计算 - endDate = addInternalBusinessTime(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); - } else { - // 使用原有的24小时制计算 - endDate = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); - } - - nodeStartTime = adjustedStartTime; - nodeEndTime = endDate; - } - - results.push({ - processOrder: processNode.processOrder, - nodeName: processNode.nodeName, - matchedLabels: processNode.matchedLabels, - timelineValue: timelineValue, - estimatedStart: formatDate(nodeStartTime), // 使用默认格式,与生成模式一致 - estimatedEnd: formatDate(nodeEndTime), // 使用默认格式,与生成模式一致 - timelineRecordId: matchedTimelineRecord ? (matchedTimelineRecord.id || matchedTimelineRecord.recordId) : null, - calculationMethod: nodeCalculationMethod, // 使用实际的计算方式 - weekendDaysConfig: processNode.weekendDays, - skippedWeekends: 0, - actualDays: calculateActualDays(formatDate(nodeStartTime), formatDate(nodeEndTime)), // 使用一致的格式 - startDateRule: processNode.startDateRule, - dateAdjustmentRule: processNode.dateAdjustmentRule, - // 新增:保存所有匹配记录的信息(用于累加情况) - 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 - }); - - // 更新累积时间 - if (timelineValue && timelineValue !== 0) { - cumulativeStartTime = new Date(nodeEndTime); - } - } - - console.log('批量计算结果:', results); - - // 6. 写入数据到货期记录表和流程数据表 - if (results.length > 0) { - console.log('=== 批量模式:开始写入数据 ==='); - console.log('批量写入 - sourceRecordId:', sourceRecordId); - console.log('批量写入 - labels:', labels); - console.log('批量写入 - expectedDate:', expectedDate); - console.log('批量写入 - style:', style); - console.log('批量写入 - color:', color); - console.log('批量写入 - foreignId:', foreignId); - console.log('批量写入 - text2:', text2); - console.log('批量写入 - startTime:', startTime); - - try { - // 为标准写入函数准备数据:构造临时的数据结构 - console.log('批量写入 - 设置临时状态以适配标准写入函数'); - - // 构造临时的recordDetails,包含当前记录的字段信息 - const tempRecordDetails = [{ - id: sourceRecordId, - fields: { - 'fldpvBfeC0': foreignId, // 外键ID字段 - 'fld6Uw95kt': style, // 样式字段 - 'flde85ni4O': color, // 颜色字段 - 'fldG6LZnmU': text2 // 文本字段2 - } - }]; - - // 构造批量数据对象(包含必要的业务字段) - const batchDataForWriting = { - selectedRecords: [sourceRecordId], - recordDetails: tempRecordDetails, - labels, // 标签汇总(对象) - expectedDate, // 客户期望日期(Date) - startTime // 起始时间(Date) - }; - - console.log('批量写入 - 临时数据构造完成,开始调用标准写入函数'); - console.log('批量写入 - batchDataForWriting:', batchDataForWriting); - - // 写入流程数据表 - 使用标准函数,传递批量数据 - console.log('批量写入 - 调用标准writeToProcessDataTable函数'); - const processRecordIds = await writeToProcessDataTable(results, batchDataForWriting); - console.log('批量写入 - 流程数据表写入完成,记录ID:', processRecordIds); - - // 写入货期记录表 - 使用标准函数,传递批量数据和空的调整信息 - console.log('批量写入 - 调用标准writeToDeliveryRecordTable函数'); - await writeToDeliveryRecordTable(results, processRecordIds, {}, batchDataForWriting); - console.log('批量写入 - 货期记录表写入完成'); - - // 批量写入成功后,设置全局状态以便快照保存 - console.log('批量写入 - 设置全局状态以便快照保存'); - setSelectedRecords([sourceRecordId]); - setRecordDetails(tempRecordDetails); - setSelectedLabels(labels); - setCurrentStyleText(extractText(style)); - setCurrentColorText(extractText(color)); - setCurrentText2(extractText(text2)); - setCurrentForeignId(extractText(foreignId)); - setExpectedDate(expectedDate); - setStartTime(startTime); - - } catch (error) { - console.error('批量写入失败:', error); - throw error; - } - - console.log('=== 批量模式:数据写入完成 ==='); - } - - } catch (error) { - console.error('批量时效计算失败:', error); - throw error; - } - }; + // 已移除批量时效计算函数 calculateTimelineForBatch @@ -5450,13 +4251,8 @@ export default function App() { border: '1px solid #e5e7eb', background: 'linear-gradient(180deg, #fff, #f9fbff)' }} - onClick={() => chooseMode('batch')} > - 批量生成 - 批量生成多条流程记录 -
- -
+ {/* 批量入口已移除 */} @@ -5474,19 +4270,7 @@ export default function App() { 基于业务数据计算并生成节点时间线 )} - {mode === 'batch' && ( -
- - 批量生成工具 - - 批量生成多条流程记录,提高工作效率 -
- )} + {/* 已移除:批量模式标题区块 */} {mode === 'adjust' && (
setMode(v as any)} optionList={[ { value: 'generate', label: '生成流程日期' }, - { value: 'adjust', label: '调整流程日期' }, - { value: 'batch', 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 || ''; @@ -5594,6 +4380,7 @@ export default function App() { setDeliveryMarginDeductions(0); // 关闭时重置交期余量扣减 setCompletionDateAdjustment(0); // 关闭时重置最后流程完成日期调整 setHasAppliedSuggestedBuffer(false); // 关闭时允许下次重新应用建议缓冲期 + setLastSuggestedApplied(null); // 关闭时清空上次建议值 }} footer={ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> @@ -5702,15 +4489,22 @@ export default function App() { <Text style={{ color: '#fa8c16' }}>建议增加缓冲期:+{displayInt}天</Text> <Button size="small" - disabled={hasAppliedSuggestedBuffer} onClick={() => { if (!hasAppliedSuggestedBuffer) { - setBaseBufferDays(Math.ceil(baseBufferDays) + displayInt); + // 应用当前建议值 + setBaseBufferDays(Math.max(0, Math.ceil(baseBufferDays) + displayInt)); setHasAppliedSuggestedBuffer(true); + setLastSuggestedApplied(displayInt); + } else { + // 撤销上次应用的建议值(使用记录的 lastSuggestedApplied 兜底当前显示值) + const rollback = (lastSuggestedApplied ?? displayInt); + setBaseBufferDays(prev => Math.max(0, Math.ceil(prev) - rollback)); + setHasAppliedSuggestedBuffer(false); + setLastSuggestedApplied(null); } }} > - {hasAppliedSuggestedBuffer ? '已应用' : '应用'} + {hasAppliedSuggestedBuffer ? '撤销建议' : '应用建议'} </Button> </div> ); @@ -5728,28 +4522,11 @@ export default function App() { type="primary" onClick={async () => { try { - // 如果有当前批量记录信息,使用批量数据进行保存 - if (currentBatchRecord) { - console.log('使用批量记录数据进行保存:', currentBatchRecord); - await saveTimelineData({ - timelineResults: timelineResults, - selectedRecords: currentBatchRecord.selectedRecords, - recordDetails: currentBatchRecord.recordDetails, - timelineAdjustments: timelineAdjustments, - labels: currentBatchRecord.labels, - expectedDate: currentBatchRecord.expectedDate, - startTime: currentBatchRecord.startTime - }); - } else { - // 普通模式保存 - await saveTimelineData(); - } + await saveTimelineData(); } catch (error) { // 错误已在saveTimelineData中处理 } setTimelineVisible(false); - // 清除批量记录信息 - setCurrentBatchRecord(null); }} > 确定并保存 @@ -5947,20 +4724,6 @@ export default function App() { size="small" type="dateTime" format="yyyy-MM-dd HH:mm" - defaultPickerValue={(() => { - try { - const expectedEndStr = timelineResults?.[index]?.estimatedEnd; - if (expectedEndStr && typeof expectedEndStr === 'string' && !expectedEndStr.includes('未找到')) { - const expectedEnd = new Date(expectedEndStr); - if (!isNaN(expectedEnd.getTime())) { - const dv = new Date(expectedEnd); - dv.setSeconds(0, 0); - return dv; - } - } - } catch {} - return undefined; - })()} placeholder="选择日期时间" style={{ width: '200px' }} value={actualCompletionDates[index] || null} @@ -5971,8 +4734,8 @@ export default function App() { if (nextDate) { const expectedEndStr = timelineResults?.[index]?.estimatedEnd; if (expectedEndStr && typeof expectedEndStr === 'string' && !expectedEndStr.includes('未找到')) { - const expectedEnd = new Date(expectedEndStr); - if (!isNaN(expectedEnd.getTime())) { + const expectedEnd = parseDate(expectedEndStr); + if (expectedEnd && !isNaN(expectedEnd.getTime())) { const h = nextDate.getHours(); const m = nextDate.getMinutes(); const s = nextDate.getSeconds(); @@ -5995,8 +4758,8 @@ export default function App() { const currentResult = timelineResults[index]; const startStr = currentResult?.estimatedStart; if (startStr && date) { - const startDate = new Date(startStr); - if (!isNaN(startDate.getTime())) { + const startDate = parseDate(startStr); + if (startDate && !isNaN(startDate.getTime())) { const calcRaw = currentResult?.calculationMethod || '外部'; const calcMethod = (calcRaw === '内部' || calcRaw === 'internal') ? '内部' : '外部'; const weekendDays = currentResult?.weekendDaysConfig || currentResult?.weekendDays || []; @@ -6004,7 +4767,38 @@ export default function App() { // 与正向计算一致:先对起始时间应用工作时间调整 const adjustedStart = adjustToNextWorkingHour(startDate, calcMethod, weekendDays, excludedDates); - const targetDate = new Date(date); + const targetDate = nextDate ? new Date(nextDate) : new Date(date); + + // 如果用户只调整了时分(同一天),不触发工作日反推,避免日期意外跳变 + 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; @@ -6231,355 +5025,130 @@ export default function App() { }}> 📅 交期余量计算 - -
{(() => { - // 获取有效的最后流程完成日期 - let effectiveLastProcess = null; - let lastCompletionDate = null; - - // 从后往前查找第一个有效的流程完成日期 + // 计算有效的最后流程完成日期 + 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 = new Date(process.estimatedEnd); - - // 检查日期是否有效且不是"时效值为0"的情况 - if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') { + 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]; - lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); + 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); + const adjustedCompletionDate = new Date(lastCompletionDate!); adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); - const deliveryDate = new Date(adjustedCompletionDate); deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); - - // 计算交期余量 - // 如果客户期望日期不为空:交期余量 = 客户期望日期 - (最后流程完成 + 缓冲期) - // 如果客户期望日期为空:交期余量 = (最后流程完成 + 缓冲期) - let timeDiff, baseDaysDiff, finalDaysDiff; - if (expectedDate) { - // 有客户期望日期:计算期望日期与交付日期的差值 - timeDiff = expectedDate.getTime() - deliveryDate.getTime(); - baseDaysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); - } else { - // 无客户期望日期:直接显示交付日期(最后流程完成 + 缓冲期) - const today = new Date(); - today.setHours(0, 0, 0, 0); - timeDiff = deliveryDate.getTime() - today.getTime(); - baseDaysDiff = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); - } - - // 计算最终交期余量:基础余量 - 扣减天数 - finalDaysDiff = baseDaysDiff - deliveryMarginDeductions; - + return ( <> - {expectedDate && ( - <> - {/* 客户期望日期 */} -
- - 客户期望日期 - - - {formatDate(expectedDate)} - - - {getDayOfWeek(expectedDate)} - -
- - {/* 减号 */} -
- - -
- - {/* 最后流程完成 */} -
- - {effectiveLastProcess === timelineResults[timelineResults.length - 1] - ? '最后流程完成' - : `有效流程完成 (${effectiveLastProcess.nodeName})`} - - - {formatDate(adjustedCompletionDate)} - - - {getDayOfWeek(adjustedCompletionDate)} - - {effectiveLastProcess !== timelineResults[timelineResults.length - 1] && ( - - (最后流程时效为0,使用此流程) - - )} - {completionDateAdjustment > 0 && ( - - (已调整 +{completionDateAdjustment}天) - - )} -
- - {/* 加号 */} -
- + -
- - {/* 缓冲期 */} -
- - 缓冲期 - - - {dynamicBufferDays}天 - - - 固定缓冲 - -
- - - - {/* 等号 */} -
- = -
- - )} - - {!expectedDate && ( - <> - {/* 最后流程完成 */} -
- - {effectiveLastProcess === timelineResults[timelineResults.length - 1] - ? '最后流程完成' - : `有效流程完成 (${effectiveLastProcess.nodeName})`} - - - {formatDate(effectiveLastProcess.estimatedEnd)} - - - {getDayOfWeek(effectiveLastProcess.estimatedEnd)} - - {effectiveLastProcess !== timelineResults[timelineResults.length - 1] && ( - - (最后流程时效为0,使用此流程) - - )} -
- - {/* 加号 */} -
- + -
- - {/* 缓冲期 */} -
- - 缓冲期 - - - {dynamicBufferDays}天 - - - 固定缓冲 - -
- - - - {/* 减号 */} -
- - -
- - {/* 今天 */} -
- - 今天 - - - {formatDate(new Date())} - - - {getDayOfWeek(new Date())} - -
- - {/* 等号 */} -
- = -
- - )} - - {/* 结果日期说明(位于等式下方) */} -
- - 结果日期(最后流程完成 + 缓冲期):{formatDate(deliveryDate)},{getDayOfWeek(deliveryDate)} - + {/* 第一行:最后流程完成日期 + 缓冲期 = 结束日期(优化为紧凑分段展示) */} +
+ 最后流程完成日期 + + {formatDate(adjustedCompletionDate)}({getDayOfWeek(adjustedCompletionDate)}) + + + + 缓冲期 + + {dynamicBufferDays}天 + + = + 结束日期 + + {formatDate(deliveryDate)}({getDayOfWeek(deliveryDate)}) +
- {/* 交期余量结果 */} -
= 0 ? '#f6ffed' : '#fff2f0', - borderRadius: '8px', - border: `2px solid ${finalDaysDiff >= 0 ? '#52c41a' : '#ff4d4f'}` - }}> - - 交期余量 - - = 0 ? '#52c41a' : '#ff4d4f', - display: 'block' - }}> - {finalDaysDiff >= 0 ? '+' : ''}{finalDaysDiff}天 - - = 0 ? '#52c41a' : '#ff4d4f', - fontWeight: 'bold' - }}> - {finalDaysDiff >= 0 ? '✅ 时间充裕' : '⚠️ 时间紧张'} - + {/* 第二行:客户期望日期(可更改,优化展示为标签样式) */} +
+ 客户期望日期(可更改) + setExpectedDate(date)} + format="yyyy-MM-dd" + disabledDate={(date) => { + const today = new Date(); + today.setHours(0, 0, 0, 0); + return date < today; + }} + /> + {expectedDate && ( + + {formatDate(expectedDate)}({getDayOfWeek(expectedDate)}) + + )}
); })()}
- -
- {(() => { - // 计算动态缓冲期(按“最后完成时间自然日差”扣减基础缓冲期) - const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments); - const baseBuferDays = baseBufferDays; - const deltaDays = baseBuferDays - dynamicBufferDays; // 自然日差(已按0..baseBuferDays夹取) - - return ( - - 💡 说明: - {expectedDate - ? `交期余量 = 客户期望日期 - (最后流程完成日期 + ${dynamicBufferDays}天缓冲期)` - : `交期余量 = (最后流程完成日期 + ${dynamicBufferDays}天缓冲期) - 今天` - } -
- {expectedDate - ? '• 正值表示有充裕时间,负值表示可能延期' - : '• 显示预计交付日期距离今天的天数' - } -
- • 缓冲期 = 基础{baseBuferDays}天 - 最后流程节点预计完成日期的自然日差(最小0天),包含质检、包装、物流等后续环节 - {deltaDays > 0 && ( - <> -
- • 当前自然日差:+{deltaDays}天,缓冲期相应减少 - - )} -
- ); - })()} -
)} @@ -6740,144 +5309,7 @@ export default function App() { )} - {mode === 'batch' && ( -
- {/* 批量处理配置 */} -
- - 批量处理配置 - -
- {/* 第一行:表和视图选择 */} -
-
- 数据表: - -
-
- 视图: - -
-
- - {/* 第二行:处理行数和操作按钮 */} -
-
- 处理行数: - setBatchRowCount(value || 10)} - min={1} - max={100} - style={{ width: 100 }} - /> -
- -
- {batchProcessing && ( -
-
- - 进度: {batchProgress.current}/{batchProgress.total} - -
- )} - -
-
-
-
-
- )} + {/* 批量处理配置已移除 */} {mode === 'generate' && (
@@ -6983,142 +5415,7 @@ export default function App() { )} - {/* 批量处理配置 - 移动到版单数据下方 */} - {mode === 'batch' && ( -
- - 批量处理配置 - -
- {/* 第一行:表和视图选择 */} -
-
- 数据表: - -
-
- 视图: - -
-
- - {/* 第二行:处理行数和操作按钮 */} -
-
- 处理行数: - setBatchRowCount(value || 10)} - min={1} - max={100} - style={{ width: 100 }} - /> -
- -
- {batchProcessing && ( -
-
- - 进度: {batchProgress.current}/{batchProgress.total} - -
- )} - -
-
-
-
- )} + {/* 批量处理配置已移除 */}
)} {mode === 'generate' && (