From 757c193df0e1874895a2abb3fc3a1116049e3d14 Mon Sep 17 00:00:00 2001 From: mairuiming Date: Wed, 17 Dec 2025 19:03:38 +0800 Subject: [PATCH] 5 5 --- src/App.tsx | 878 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 569 insertions(+), 309 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index c09acd9..daaafc8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { bitable, FieldType } from '@lark-base-open/js-sdk'; +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 } from '@douyinfe/semi-ui'; import { useState, useEffect, useRef } from 'react'; import { addDays, format } from 'date-fns'; @@ -78,6 +78,7 @@ export default function App() { const [styleColorEditable, setStyleColorEditable] = useState(false); const [currentText2, setCurrentText2] = useState(''); const [currentVersionNumber, setCurrentVersionNumber] = useState(null); + const [currentDeliveryRecordId, setCurrentDeliveryRecordId] = useState(null); // 功能入口模式与调整相关状态 const [mode, setMode] = useState<'generate' | 'adjust' | null>(null); const [modeSelectionVisible, setModeSelectionVisible] = useState(true); @@ -145,6 +146,7 @@ export default function App() { setCurrentColorText(''); setCurrentText2(''); setCurrentVersionNumber(null); + setCurrentDeliveryRecordId(null); setLabelAdjustmentFlow(false); // 可选:重置模式 @@ -274,7 +276,7 @@ export default function App() { const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => { if (!deliveryRecordId) { if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'warning', message: '请先选择一条货期记录' }); + await bitable.ui.showToast({ toastType: ToastType.warning, message: '请先选择一条货期记录' }); } return; } @@ -304,7 +306,7 @@ export default function App() { if (parsedFromText.length > 0) setRestoredRecordIds(parsedFromText); } } catch { - const parsedFromText = raw.split(/[\,\s]+/).map(s => s.trim()).filter(Boolean); + const parsedFromText = raw.split(/[\,\s]+/).map((s: string) => s.trim()).filter(Boolean); if (parsedFromText.length > 0) setRestoredRecordIds(parsedFromText); } } @@ -412,14 +414,14 @@ export default function App() { setLabelAdjustmentFlow(true); setTimelineVisible(false); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'info', message: '请在下方修改标签后点击重新生成计划' }); + await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' }); } }, onCancel: async () => { setLabelAdjustmentFlow(false); setTimelineVisible(true); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已按货期记录快照还原流程数据' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已按货期记录快照还原流程数据' }); } } }); @@ -450,7 +452,7 @@ export default function App() { if (!recordIds || recordIds.length === 0) { if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'warning', message: '该货期记录未包含节点详情或为空' }); + await bitable.ui.showToast({ toastType: ToastType.warning, message: '该货期记录未包含节点详情或为空' }); } setTimelineLoading(false); return; @@ -551,14 +553,14 @@ export default function App() { setLabelAdjustmentFlow(true); setTimelineVisible(false); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'info', message: '请在下方修改标签后点击重新生成计划' }); + await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' }); } }, onCancel: async () => { setLabelAdjustmentFlow(false); setTimelineVisible(true); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已按快照一模一样还原流程数据' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' }); } } }); @@ -696,14 +698,14 @@ export default function App() { setLabelAdjustmentFlow(true); setTimelineVisible(false); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'info', message: '请在下方修改标签后点击重新生成计划' }); + await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' }); } }, onCancel: async () => { setLabelAdjustmentFlow(false); setTimelineVisible(true); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已按快照一模一样还原流程数据' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' }); } } }); @@ -727,14 +729,14 @@ export default function App() { setLabelAdjustmentFlow(true); setTimelineVisible(false); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'info', message: '请在下方修改标签后点击重新生成计划' }); + await bitable.ui.showToast({ toastType: ToastType.info, message: '请在下方修改标签后点击重新生成计划' }); } }, onCancel: async () => { setLabelAdjustmentFlow(false); setTimelineVisible(true); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已按快照一模一样还原流程数据' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已按快照一模一样还原流程数据' }); } } }); @@ -784,7 +786,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: '已按完整快照还原流程数据' }); } @@ -988,7 +990,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: `已从 ${nodeSnapshots.length} 个节点快照还原完整流程数据(包含智能缓冲期和连锁调整状态)` }); } @@ -1063,12 +1065,12 @@ export default function App() { setTimelineVisible(true); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已从货期记录还原流程数据' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已从货期记录还原流程数据' }); } } catch (error) { console.error('从货期记录还原流程数据失败:', error); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'error', message: `还原失败: ${(error as Error).message}` }); + await bitable.ui.showToast({ toastType: ToastType.error, message: `还原失败: ${(error as Error).message}` }); } } finally { setTimelineLoading(false); @@ -1182,8 +1184,8 @@ export default function App() { } throw new Error(`无法解析日期格式: ${cleanStr}`); - } catch (error) { - console.error('日期解析失败:', { dateStr, error: error.message }); + } catch (error: any) { + console.error('日期解析失败:', { dateStr, error: error?.message }); return null; } }; @@ -1576,11 +1578,13 @@ export default function App() { const labelFields: {[key: string]: string} = {}; // 存储字段名到字段ID的映射 for (const fieldMeta of fieldMetaList) { - // 检查字段名是否匹配标签1-标签10的模式 - const match = fieldMeta.name.match(/^标签([1-9]|10)$/); + 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)$/); if (match) { const labelKey = `标签${match[1]}`; - labelFields[labelKey] = fieldMeta.id; + labelFields[labelKey] = (fieldMeta as any).id; } } @@ -1606,10 +1610,12 @@ export default function App() { const fieldOptions = await selectField.getOptions(); // 转换为我们需要的格式 - options[labelKey] = fieldOptions.map((option: any) => ({ - label: option.name, - value: option.name - })); + options[labelKey] = (fieldOptions || []) + .filter((option: any) => option && typeof option.name === 'string') + .map((option: any) => ({ + label: option.name, + value: option.name + })); } } catch (error) { console.warn(`获取${labelKey}字段选项失败:`, error); @@ -1624,7 +1630,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: `标签选项加载成功,共找到 ${Object.keys(labelFields).length} 个标签字段` }); } @@ -1632,7 +1638,7 @@ export default function App() { console.error('获取标签选项失败:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '获取标签选项失败,请检查表格配置' }); } @@ -1842,7 +1848,7 @@ export default function App() { }); if (missing.length > 0) { if (showUI && bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'warning', message: `请填写以下必填标签:${missing.join('、')}` }); + await bitable.ui.showToast({ toastType: ToastType.warning, message: `请填写以下必填标签:${missing.join('、')}` }); } return []; } @@ -1854,7 +1860,7 @@ export default function App() { if (currentSelectedRecords.length > 1) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'warning', + toastType: ToastType.warning, message: '计算时效功能仅支持单条记录,请重新选择单条记录后再试' }); } @@ -1865,7 +1871,7 @@ export default function App() { if (currentSelectedRecords.length === 0) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'warning', + toastType: ToastType.warning, message: '请先选择一条记录' }); } @@ -1876,7 +1882,7 @@ export default function App() { if (!currentExpectedDate) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'info', + toastType: ToastType.info, message: '建议选择客户期望日期以便更好地进行时效计算' }); } @@ -1899,16 +1905,37 @@ export default function App() { 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(); - for (const [labelKey, selectedValue] of Object.entries(currentSelectedLabels)) { - if (selectedValue) { - const values = Array.isArray(selectedValue) ? selectedValue : [selectedValue]; - values.forEach(value => { - if (typeof value === 'string' && value.trim()) { - businessLabelValues.add(value.trim()); - } - }); + for (const selectedValue of Object.values(currentSelectedLabels)) { + for (const token of extractLabelTokens(selectedValue)) { + businessLabelValues.add(token); } } @@ -1922,18 +1949,19 @@ export default function App() { let calculationMethodFieldId = ''; // 时效计算方式字段ID for (const fieldMeta of timelineFieldMetaList) { - const match = fieldMeta.name.match(/^标签([1-9]|10)$/); + 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)$/); if (match) { const labelKey = `标签${match[1]}`; - timelineLabelFields[labelKey] = fieldMeta.id; + timelineLabelFields[labelKey] = (fieldMeta as any).id; } - // 查找关系字段 - if (fieldMeta.name === '关系' || fieldMeta.id === 'fldaIDAhab') { - relationshipFieldId = fieldMeta.id; + if ((fieldMeta as any).name === '关系' || (fieldMeta as any).id === 'fldaIDAhab') { + relationshipFieldId = (fieldMeta as any).id; } - // 查找时效计算方式字段 - if (fieldMeta.name === '时效计算方式' || fieldMeta.id === 'fldxfLZNUu') { - calculationMethodFieldId = fieldMeta.id; + if ((fieldMeta as any).name === '时效计算方式' || (fieldMeta as any).id === 'fldxfLZNUu') { + calculationMethodFieldId = (fieldMeta as any).id; } } @@ -2137,7 +2165,7 @@ export default function App() { let timelineNodeNames: string[] = []; if (typeof timelineNodeName === 'string') { - timelineNodeNames = timelineNodeName.split(',').map(name => name.trim()); + timelineNodeNames = timelineNodeName.split(',').map((name: string) => name.trim()); } else if (Array.isArray(timelineNodeName)) { timelineNodeNames = timelineNodeName.map(item => { if (typeof item === 'string') { @@ -2148,7 +2176,7 @@ export default function App() { return ''; }).filter(name => name); } else if (timelineNodeName && timelineNodeName.text) { - timelineNodeNames = timelineNodeName.text.split(',').map(name => name.trim()); + timelineNodeNames = timelineNodeName.text.split(',').map((name: string) => name.trim()); } // 为每个节点名称建立索引 @@ -2195,21 +2223,7 @@ export default function App() { 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); - } + const timelineLabelTexts = extractLabelTokens(timelineLabelValue); // 如果时效表中该标签有值,则检查该标签的所有值是否都包含在业务标签中 if (timelineLabelTexts.length > 0) { @@ -2437,8 +2451,8 @@ export default function App() { } else if (Array.isArray(processNode.dateAdjustmentRule)) { // 处理富文本数组格式 ruleText = processNode.dateAdjustmentRule - .filter(item => item.type === 'text') - .map(item => item.text) + .filter((item: any) => item.type === 'text') + .map((item: any) => item.text) .join(''); } else if (processNode.dateAdjustmentRule && processNode.dateAdjustmentRule.text) { ruleText = processNode.dateAdjustmentRule.text; @@ -2556,7 +2570,7 @@ export default function App() { console.error('计算时效失败:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '计算时效失败,请检查表格配置' }); } @@ -2599,7 +2613,7 @@ export default function App() { } // 计算最后流程完成日期(包含调整) - const adjustedCompletionDate = new Date(lastCompletionDate); + const adjustedCompletionDate = new Date(lastCompletionDate as Date); adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); // 检查是否已达到客户期望日期 @@ -2743,8 +2757,8 @@ export default function App() { ruleText = result.dateAdjustmentRule; } else if (Array.isArray(result.dateAdjustmentRule)) { ruleText = result.dateAdjustmentRule - .filter(item => item.type === 'text') - .map(item => item.text) + .filter((item: any) => item.type === 'text') + .map((item: any) => item.text) .join(''); } else if (result.dateAdjustmentRule && result.dateAdjustmentRule.text) { ruleText = result.dateAdjustmentRule.text; @@ -2957,8 +2971,8 @@ export default function App() { ruleText = result.dateAdjustmentRule; } else if (Array.isArray(result.dateAdjustmentRule)) { ruleText = result.dateAdjustmentRule - .filter(item => item.type === 'text') - .map(item => item.text) + .filter((item: any) => item.type === 'text') + .map((item: any) => item.text) .join(''); } else if (result.dateAdjustmentRule && result.dateAdjustmentRule.text) { ruleText = result.dateAdjustmentRule.text; @@ -3100,7 +3114,7 @@ export default function App() { // 若未捕获到初始快照,则退化为仅重置调整 resetTimelineAdjustments(); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'warning', message: '未检测到初始快照,已重置调整项' }); + await bitable.ui.showToast({ toastType: ToastType.warning, message: '未检测到初始快照,已重置调整项' }); } return; } @@ -3120,7 +3134,7 @@ export default function App() { recalculateTimeline(s.timelineAdjustments || {}, true); setIsRestoringSnapshot(false); if (bitable.ui.showToast) { - await bitable.ui.showToast({ toastType: 'success', message: '已恢复至最初状态' }); + await bitable.ui.showToast({ toastType: ToastType.success, message: '已恢复至最初状态' }); } } catch (e) { console.error('恢复初始状态失败:', e); @@ -3137,6 +3151,7 @@ export default function App() { timelineAdjustments: {[key: number]: number} = {}, overrides?: { foreignId?: string; style?: string; color?: string; expectedDate?: Date | null; startTime?: Date | null; selectedLabels?: {[key: string]: string | string[]} } ) => { + let recordCells: any[] | undefined; try { console.log('=== 开始写入货期记录表 ==='); console.log('当前模式:', mode); @@ -3170,29 +3185,48 @@ export default function App() { // 获取各个字段 console.log('正在获取各个字段对象...'); - const foreignIdField = await deliveryRecordTable.getField(DELIVERY_FOREIGN_ID_FIELD_ID); - const labelsField = await deliveryRecordTable.getField(DELIVERY_LABELS_FIELD_ID); - const styleField = await deliveryRecordTable.getField(DELIVERY_STYLE_FIELD_ID); - const colorField = await deliveryRecordTable.getField(DELIVERY_COLOR_FIELD_ID); - const text2Field = await deliveryRecordTable.getField(DELIVERY_TEXT2_FIELD_ID); - const createTimeField = await deliveryRecordTable.getField(DELIVERY_CREATE_TIME_FIELD_ID); - const expectedDateField = await deliveryRecordTable.getField(DELIVERY_EXPECTED_DATE_FIELD_ID); - const nodeDetailsField = await deliveryRecordTable.getField(DELIVERY_NODE_DETAILS_FIELD_ID); - const customerExpectedDateField = await deliveryRecordTable.getField(DELIVERY_CUSTOMER_EXPECTED_DATE_FIELD_ID); - const adjustmentInfoField = await deliveryRecordTable.getField(DELIVERY_ADJUSTMENT_INFO_FIELD_ID); - const versionField = await deliveryRecordTable.getField(DELIVERY_VERSION_FIELD_ID); - const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID); - const snapshotField = await deliveryRecordTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID); - const recordIdsTextField = await deliveryRecordTable.getField(DELIVERY_RECORD_IDS_FIELD_ID); - const factoryDepartureDateField = await deliveryRecordTable.getField(DELIVERY_FACTORY_DEPARTURE_DATE_FIELD_ID); + 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: labelsField.id, - name: labelsField.name, - type: labelsField.type, - property: labelsField.property + id: labelsFieldDebug?.id, + name: labelsFieldDebug?.name, + type: labelsFieldDebug?.type, + property: labelsFieldDebug?.property }); // 获取foreign_id:调整模式严格使用快照数据,生成模式优先使用选择记录 @@ -3205,7 +3239,7 @@ export default function App() { if (!foreignId && mode === 'adjust') { // 调整模式:严格使用快照回填的foreign_id,即使为空也不回退 - foreignId = currentForeignId; + foreignId = currentForeignId ?? ''; console.log('调整模式:严格使用快照恢复的foreign_id:', foreignId); } else if (!foreignId && currentSelectedRecords.length > 0) { // 生成模式:从选择记录获取 @@ -3507,41 +3541,48 @@ export default function App() { const snapshotJson = JSON.stringify(completeSnapshot); // 使用createCell方法创建各个字段的Cell - const foreignIdCell = await foreignIdField.createCell(foreignId); - const labelsCell = selectedLabelValues.length > 0 ? await labelsField.createCell(selectedLabelValues) : null; - const styleCell = await styleField.createCell(style); - const colorCell = await colorField.createCell(color); - const text2Cell = await text2Field.createCell(text2); - const createTimeCell = await createTimeField.createCell(currentTime); - - // 调试日志:检查startTime参数 - console.log('保存 - startTime参数:', currentStartTime); - console.log('保存 - startTime类型:', typeof currentStartTime); - console.log('保存 - currentTime:', currentTime, '对应日期:', new Date(currentTime).toLocaleString()); - const startTimestamp = currentStartTime ? currentStartTime.getTime() : currentTime; - console.log('保存 - 最终使用的startTimestamp:', startTimestamp, '对应日期:', new Date(startTimestamp).toLocaleString()); - - const startTimeCell = await startTimeField.createCell(startTimestamp); - const snapshotCell = await snapshotField.createCell(snapshotJson); - const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null; - const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null; - const factoryDepartureDateCell = factoryDepartureDate ? await factoryDepartureDateField.createCell(factoryDepartureDate) : null; - // 对于关联记录字段,确保传入的是记录ID数组 - const nodeDetailsCell = processRecordIds.length > 0 ? - await nodeDetailsField.createCell({ recordIds: processRecordIds }) : null; - // 创建货期调整信息Cell - const adjustmentInfoCell = adjustmentInfo ? await adjustmentInfoField.createCell(adjustmentInfo) : null; - // 创建版本号Cell(数字) - const versionCell = await versionField.createCell(versionNumber); - // 写回保持“原始文本格式”;如果读取为空,则写入空字符串 + const recordIdsText = (restoredRecordIdsText && restoredRecordIdsText.trim() !== '') ? restoredRecordIdsText.trim() : ''; - const recordIdsCell = await recordIdsTextField.createCell(recordIdsText); + + 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到一个记录中 - const recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell, snapshotCell, recordIdsCell]; + recordCells = [foreignIdCell, styleCell, colorCell, text2Cell, createTimeCell, startTimeCell, versionCell, snapshotCell, recordIdsCell]; // 只有当数据存在时才添加对应的Cell if (labelsCell) recordCells.push(labelsCell); @@ -3555,16 +3596,16 @@ export default function App() { const addedRecord = await deliveryRecordTable.addRecord(recordCells); return addedRecord; - } catch (error) { + } catch (error: any) { console.error('写入货期记录表详细错误:', { error: error, - message: error.message, - stack: error.stack, - recordCellsLength: typeof recordCells !== 'undefined' ? recordCells.length : 'recordCells未定义' + message: error?.message, + stack: error?.stack, + recordCellsLength: Array.isArray(recordCells) ? recordCells.length : 'recordCells未定义' }); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '写入货期记录表失败: ' + (error as Error).message }); } @@ -3578,7 +3619,7 @@ export default function App() { console.log('=== 开始写入流程数据表 ==='); console.log('当前模式:', mode); console.log('timelineResults数量:', timelineResults?.length || 0); - console.log('timelineResults详情:', timelineResults); + // 获取流程数据表和流程配置表 console.log('正在获取流程数据表和流程配置表...'); @@ -3588,15 +3629,27 @@ export default function App() { // 获取所有需要的字段 console.log('正在获取所有必需字段...'); - const foreignIdField = await processDataTable.getField(FOREIGN_ID_FIELD_ID); - const processNameField = await processDataTable.getField(PROCESS_NAME_FIELD_ID); - const processOrderField = await processDataTable.getField(PROCESS_ORDER_FIELD_ID_DATA); - const startDateField = await processDataTable.getField(ESTIMATED_START_DATE_FIELD_ID); - const endDateField = await processDataTable.getField(ESTIMATED_END_DATE_FIELD_ID); - const versionField = await processDataTable.getField(PROCESS_VERSION_FIELD_ID); - const timelinessField = await processDataTable.getField(PROCESS_TIMELINESS_FIELD_ID); - const processStyleField = await processDataTable.getField(PROCESS_STYLE_FIELD_ID); - const processColorField = await processDataTable.getField(PROCESS_COLOR_FIELD_ID); + const [ + foreignIdField, + processNameField, + processOrderField, + startDateField, + endDateField, + versionField, + timelinessField, + processStyleField, + processColorField + ] = 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) + ]); console.log('成功获取所有字段'); // 获取foreign_id - 支持批量模式直接传递数据 @@ -3635,7 +3688,7 @@ export default function App() { console.warn('未找到foreign_id,跳过写入流程数据表'); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'warning', + toastType: ToastType.warning, message: '未找到foreign_id字段,无法写入流程数据表' }); } @@ -3688,11 +3741,30 @@ export default function App() { // 使用createCell方法准备要写入的记录数据 - const recordCellsToAdd = []; + const recordValueList: Array<{ fields: Record }> = []; + 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(); + 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); + } + } for (let index = 0; index < timelineResults.length; index++) { const result = timelineResults[index]; - console.log('处理节点数据:', result); const hasValidTimelineValue = typeof result.timelineValue === 'number' && Number.isFinite(result.timelineValue) && result.timelineValue > 0; if (!hasValidTimelineValue) { @@ -3734,56 +3806,83 @@ export default function App() { } - // 使用createCell方法创建每个字段的Cell - const foreignIdCell = await foreignIdField.createCell(foreignId); - // 直接使用节点名称创建单元格 - const processNameCell = await processNameField.createCell(result.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); - - // 组合所有Cell到一个记录中 - const versionCell = await versionField.createCell(versionNumber); - const timelinessCell = await timelinessField.createCell(result.timelineValue); - const recordCells = [ - foreignIdCell, - processNameCell, - processOrderCell, - styleCell, - colorCell, - versionCell, - timelinessCell - ]; - - // 只有当时间戳存在时才添加日期Cell - if (startDateCell) recordCells.push(startDateCell); - if (endDateCell) recordCells.push(endDateCell); - - console.log(`准备写入的Cell记录 - ${result.nodeName}:`, recordCells); - recordCellsToAdd.push(recordCells); + const nodeName = result.nodeName; + const optionId = typeof nodeName === 'string' ? optionNameToId.get(nodeName) : undefined; + + if (optionId) { + const fields: Record = { + [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; + + 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 recordCells = [ + foreignIdCell, + processNameCell, + processOrderCell, + styleCell, + colorCell, + versionCell, + timelinessCell + ]; + if (startDateCell) recordCells.push(startDateCell); + if (endDateCell) recordCells.push(endDateCell); + fallbackCellRows.push(recordCells); + } } - console.log('所有准备写入的Cell记录:', recordCellsToAdd); + console.log('准备写入记录数:', recordValueList.length + fallbackCellRows.length); // 在添加记录的部分,收集记录ID const addedRecordIds: string[] = []; - if (recordCellsToAdd.length > 0) { - try { - // 使用addRecords进行批量写入 - const addedRecords = await processDataTable.addRecords(recordCellsToAdd); - // 直接使用返回值,不需要map操作 - addedRecordIds.push(...addedRecords); - console.log(`批量写入成功,记录ID:`, addedRecords); - } catch (error) { - console.error('批量写入失败,尝试逐条写入:', error); - // 如果批量写入失败,回退到逐条写入 - for (const recordCells of recordCellsToAdd) { - const addedRecord = await processDataTable.addRecord(recordCells); - addedRecordIds.push(addedRecord); - console.log('成功添加记录:', addedRecord); + 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); + } } } @@ -3792,7 +3891,7 @@ export default function App() { // 显示成功提示 if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: `成功写入 ${addedRecordIds.length} 条流程数据到流程数据表` }); } @@ -3802,7 +3901,7 @@ export default function App() { console.warn('没有有效的记录可以写入'); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'warning', + toastType: ToastType.warning, message: '没有有效的流程数据可以写入 - 未匹配到时效值' }); } @@ -3813,7 +3912,7 @@ export default function App() { console.error('写入流程数据表失败:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: `写入流程数据表失败: ${(error as Error).message}` }); } @@ -3835,11 +3934,23 @@ export default function App() { const processRecordIds = await writeToProcessDataTable(currentTimelineResults); // 写入货期记录表 - await writeToDeliveryRecordTable(currentTimelineResults, processRecordIds, currentTimelineAdjustments); + 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: 'success', + toastType: ToastType.success, message: Object.keys(currentTimelineAdjustments).length > 0 ? '已保存调整后的时效数据到流程数据表和货期记录表' : '已保存计算的时效数据到流程数据表和货期记录表' @@ -3853,13 +3964,57 @@ export default function App() { console.error('保存数据时出错:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + 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 }); @@ -3924,13 +4079,24 @@ export default function App() { if (typeof v === 'number') expectedDateObj = new Date(v); else expectedDateObj = parseDate(v); } 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 v = getText(key); - if (v && v.trim()) { - if (i2 === 7 || i2 === 8 || i2 === 10) labels[key] = splitVals(v); - else labels[key] = v.trim(); + 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(','); } } { @@ -3976,7 +4142,7 @@ export default function App() { try { const statusCell = await statusField.createCell(deliveryRecordIdStr); try { - await batchTable.updateRecord(rowRecordId, [statusCell]); + await (batchTable as any).updateRecord(rowRecordId, [statusCell]); } catch (e1) { const fn = (batchTable as any).setRecord || (batchTable as any).updateRecordById; if (typeof fn === 'function') { @@ -3993,7 +4159,7 @@ export default function App() { } else { setBatchProgressList(list => [...list, { index: i + 1, foreignId: foreignId || '', status: 'failed', message: '未找到状态字段或记录ID为空' }]); } - } catch (statusErr) { + } catch (statusErr: any) { console.warn('回写批量状态字段失败', statusErr); setBatchProgressList(list => [...list, { index: i + 1, foreignId: foreignId || '', status: 'failed', message: `状态写入失败: ${statusErr?.message || '未知错误'}` }]); } @@ -4013,12 +4179,12 @@ export default function App() { } if (bitable.ui.showToast) { const aborted = batchAbortRef.current; - await bitable.ui.showToast({ toastType: 'success', message: aborted ? `批量已中止,已处理 ${processed} 条记录` : `批量生成完成,共处理 ${processed} 条记录` }); + 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: 'error', message: '批量处理失败,请检查数据表配置' }); + await bitable.ui.showToast({ toastType: ToastType.error, message: '批量处理失败,请检查数据表配置' }); } } finally { setBatchLoading(false); @@ -4041,7 +4207,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: '查询成功' }); } @@ -4050,7 +4216,7 @@ export default function App() { console.error('数据库查询出错:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: `数据库查询失败: ${error.message}` }); } @@ -4069,7 +4235,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: '二次工艺查询成功' }); } @@ -4077,7 +4243,7 @@ export default function App() { console.error('二次工艺查询出错:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: `二次工艺查询失败: ${error.message}` }); } @@ -4096,7 +4262,7 @@ export default function App() { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: '定价详情查询成功' }); } @@ -4104,7 +4270,7 @@ export default function App() { console.error('定价详情查询出错:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: `定价详情查询失败: ${error.message}` }); } @@ -4132,7 +4298,7 @@ export default function App() { if (!packId || !packType) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '缺少必要的查询参数 (pack_id 或 pack_type)' }); } @@ -4161,7 +4327,7 @@ export default function App() { if (!packId || !packType) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '缺少必要的查询参数 (pack_id 或 pack_type)' }); } @@ -4184,7 +4350,7 @@ export default function App() { if (!packId) { if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '缺少必要的查询参数 (pack_id)' }); } @@ -4260,18 +4426,21 @@ export default function App() { // 建立字段名到字段ID的映射 const fieldNameToId: {[key: string]: string} = {}; for (const fieldMeta of fieldMetaList) { - fieldNameToId[fieldMeta.name] = fieldMeta.id; + 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 = firstRecord.fields[fieldId]; + const fieldValue: any = firstRecord.fields[fieldId] as any; // 优先处理数组格式(公式字段) if (Array.isArray(fieldValue) && fieldValue.length > 0) { - const firstItem = fieldValue[0]; + const firstItem: any = fieldValue[0] as any; if (typeof firstItem === 'string') { return firstItem; } else if (firstItem && (firstItem.text || firstItem.name)) { @@ -4297,11 +4466,11 @@ export default function App() { // 直接通过字段ID提取fld6Uw95kt的值 const getFieldValueById = (fieldId: string) => { if (fieldId && firstRecord.fields[fieldId]) { - const fieldValue = firstRecord.fields[fieldId]; + const fieldValue: any = firstRecord.fields[fieldId] as any; // 优先处理数组格式(公式字段) if (Array.isArray(fieldValue) && fieldValue.length > 0) { - const firstItem = fieldValue[0]; + const firstItem: any = fieldValue[0] as any; if (typeof firstItem === 'string') { return firstItem; } else if (firstItem && (firstItem.text || firstItem.name)) { @@ -4342,7 +4511,7 @@ export default function App() { const label5Value = extractFieldValue('小类名称'); const label6Value = extractFieldValue('工艺难易度'); const extractFieldValuesById = (fieldId: string): string[] => { - const v = firstRecord.fields[fieldId]; + const v: any = firstRecord.fields[fieldId] as any; if (!v) return []; if (Array.isArray(v)) { return v @@ -4356,13 +4525,13 @@ export default function App() { if (typeof v === 'string') { return v .split(/[,,、]+/) - .map(s => s.trim()) + .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 => x.trim()).filter(Boolean) + ? s.split(/[,,、]+/).map((x: string) => x.trim()).filter(Boolean) : []; } return []; @@ -4406,7 +4575,7 @@ export default function App() { // 显示提取结果的提示 if (Object.keys(newSelectedLabels).length > 0 && bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'success', + toastType: ToastType.success, message: `已自动提取 ${Object.keys(newSelectedLabels).length} 个标签值` }); } @@ -4419,7 +4588,7 @@ export default function App() { console.error('选择记录时出错:', error); if (bitable.ui.showToast) { await bitable.ui.showToast({ - toastType: 'error', + toastType: ToastType.error, message: '选择记录时出错,请重试' }); } @@ -4553,40 +4722,40 @@ export default function App() { maskClosable={false} >
- chooseMode('generate')} - > - 生成流程日期 - 基于业务数据计算并生成节点时间线 -
- -
-
- chooseMode('adjust')} - > - 调整流程日期 - 读取货期记录,精确还原时间线 -
- -
-
+
chooseMode('generate')}> + + 生成流程日期 + 基于业务数据计算并生成节点时间线 +
+ +
+
+
+
chooseMode('adjust')}> + + 调整流程日期 + 读取货期记录,精确还原时间线 +
+ +
+
+
读取当前选中记录并还原流程数据 + {currentDeliveryRecordId && ( + + 款号 + + 颜色 + + 最近保存记录ID + + + + )} )} {/* 批量处理功能 - 只在批量模式下显示 */} @@ -4990,9 +5187,16 @@ export default function App() { { - setStartTime(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" @@ -5043,7 +5247,7 @@ export default function App() { if (timelineResults.length > 0) { // 获取有效的最后流程完成日期(与交期余量计算逻辑保持一致) let effectiveLastProcess = null; - let lastCompletionDate = null; + let lastCompletionDate: Date | null = null; for (let i = timelineResults.length - 1; i >= 0; i--) { const process = timelineResults[i]; @@ -5061,25 +5265,24 @@ export default function App() { lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); } - // 计算最后完成日期(考虑调整)+ 动态缓冲期 - const adjustedCompletionDate = new Date(lastCompletionDate); - adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); - - const deliveryDate = new Date(adjustedCompletionDate); - deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); - - if (expectedDate) { - // 有客户期望日期:交期余量 = 客户期望日期 - (最后流程完成 + 缓冲期) - const timeDiff = expectedDate.getTime() - deliveryDate.getTime(); - const baseDeliveryMargin = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); - deliveryMargin = baseDeliveryMargin - deliveryMarginDeductions; - } else { - // 无客户期望日期:交期余量 = (最后流程完成 + 缓冲期) - 今天 - const today = new Date(); - today.setHours(0, 0, 0, 0); - const timeDiff = deliveryDate.getTime() - today.getTime(); - const baseDeliveryMargin = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); - deliveryMargin = baseDeliveryMargin - deliveryMarginDeductions; + if (lastCompletionDate && !isNaN(lastCompletionDate.getTime())) { + const adjustedCompletionDate = new Date(lastCompletionDate); + adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); + + const deliveryDate = new Date(adjustedCompletionDate); + deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays); + + if (expectedDate) { + const timeDiff = expectedDate.getTime() - deliveryDate.getTime(); + const baseDeliveryMargin = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); + deliveryMargin = baseDeliveryMargin - deliveryMarginDeductions; + } else { + const today = new Date(); + today.setHours(0, 0, 0, 0); + const timeDiff = deliveryDate.getTime() - today.getTime(); + const baseDeliveryMargin = Math.ceil(timeDiff / (1000 * 60 * 60 * 24)); + deliveryMargin = baseDeliveryMargin - deliveryMarginDeductions; + } } } @@ -5093,7 +5296,7 @@ export default function App() { if (expectedDate && timelineResults.length > 0) { // 获取有效的最后流程完成日期 let effectiveLastProcess = null; - let lastCompletionDate = null; + let lastCompletionDate: Date | null = null; for (let i = timelineResults.length - 1; i >= 0; i--) { const process = timelineResults[i]; @@ -5111,14 +5314,16 @@ export default function App() { lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd); } - // 计算最后流程完成日期(包含调整) - const adjustedCompletionDate = new Date(lastCompletionDate); - adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); - - // 检查是否已达到客户期望日期 - const timeDiffToExpected = expectedDate.getTime() - adjustedCompletionDate.getTime(); - const daysToExpected = Math.ceil(timeDiffToExpected / (1000 * 60 * 60 * 24)); - canIncrease = daysToExpected > 0; // 只有当还有剩余天数时才允许调整 + if (lastCompletionDate && !isNaN(lastCompletionDate.getTime())) { + const adjustedCompletionDate = new Date(lastCompletionDate); + adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment); + + const timeDiffToExpected = expectedDate.getTime() - adjustedCompletionDate.getTime(); + const daysToExpected = Math.ceil(timeDiffToExpected / (1000 * 60 * 60 * 24)); + canIncrease = daysToExpected > 0; + } else { + canIncrease = true; + } } else { // 无客户期望日期时,理论上可以无限调整 canIncrease = true; @@ -5138,7 +5343,7 @@ export default function App() { color: '#000000', fontWeight: 'normal' }}> - (累加 {result.allMatchedRecords.length} 条记录: {result.allMatchedRecords.map(record => record.recordId).join(', ')}) + (累加 {result.allMatchedRecords.length} 条记录: {result.allMatchedRecords.map((record: any) => record.recordId).join(', ')}) ) : result.timelineRecordId ? ( 实际完成: - { + { // 自动填充默认时分:仅在用户未指定具体时分时套用预计完成的时分 - let nextDate = date ? new Date(date) : null; + 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; @@ -5200,7 +5413,7 @@ export default function App() { try { const currentResult = timelineResults[index]; const startStr = currentResult?.estimatedStart; - if (startStr && date) { + if (startStr && nextDate) { const startDate = parseDate(startStr); if (startDate && !isNaN(startDate.getTime())) { const calcRaw = currentResult?.calculationMethod || '外部'; @@ -5210,7 +5423,7 @@ export default function App() { // 与正向计算一致:先对起始时间应用工作时间调整 const adjustedStart = adjustToNextWorkingHour(startDate, calcMethod, weekendDays, excludedDates); - const targetDate = nextDate ? new Date(nextDate) : new Date(date); + const targetDate = new Date(nextDate); // 如果用户只调整了时分(同一天),不触发工作日反推,避免日期意外跳变 const expectedEndStrForAdjust = timelineResults?.[index]?.estimatedEnd; @@ -5435,7 +5648,7 @@ export default function App() { 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 => ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][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')}` : ''}`} + 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')}` : ''}`} > ? @@ -5564,12 +5777,22 @@ export default function App() { setExpectedDate(date)} + 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; }} /> @@ -5661,10 +5884,37 @@ export default function App() { style={{ width: '100%', borderColor: isMissing ? '#ff4d4f' : undefined }} placeholder={`请选择${labelKey}(必填)`} value={selectedLabels[labelKey]} - onChange={(value) => handleLabelChange(labelKey, value)} + 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 - showSearch > {options.map((option, optionIndex) => ( @@ -5687,13 +5937,23 @@ export default function App() { className="input-enhanced" style={{ width: '300px' }} placeholder="请选择客户期望日期" - value={expectedDate} - onChange={(date) => setExpectedDate(date)} + 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; }} /> @@ -5712,7 +5972,7 @@ export default function App() { borderRadius: '12px', border: '1px solid #bae6fd' }}> - +