From 96e5edd283a366e88915602969ae55f6142bc474 Mon Sep 17 00:00:00 2001 From: mairuiming Date: Wed, 29 Oct 2025 15:03:02 +0800 Subject: [PATCH] 2 2 --- src/App.tsx | 1552 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1514 insertions(+), 38 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 82dade6..8fd1806 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import { bitable, FieldType } from '@lark-base-open/js-sdk'; -import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker } from '@douyinfe/semi-ui'; +import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber } from '@douyinfe/semi-ui'; import { useState, useEffect } from 'react'; import { addDays, format } from 'date-fns'; import { zhCN } from 'date-fns/locale'; @@ -36,8 +36,8 @@ export default function App() { // 客户期望日期状态 const [expectedDate, setExpectedDate] = useState(null); - // 起始时间状态(默认当前时间,用于驱动所有节点时间) - const [startTime, setStartTime] = useState(new Date()); + // 起始时间状态(从货期记录表获取,新记录则使用当前时间) + const [startTime, setStartTime] = useState(null); // 预览相关状态(已移除未使用的 previewLoading 状态) @@ -58,6 +58,18 @@ export default function App() { // 删除未使用的 deliveryRecords 状态 const [selectedDeliveryRecordId, setSelectedDeliveryRecordId] = useState(''); + // 批量处理相关状态 + const [batchProcessing, setBatchProcessing] = useState(false); + const [batchProgress, setBatchProgress] = useState({ current: 0, total: 0 }); + const [batchRowCount, setBatchRowCount] = useState(10); // 默认处理10行 + + // 批量处理表和视图选择状态 + 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); // 指定的数据表ID和视图ID const TABLE_ID = 'tblPIJ7unndydSMu'; @@ -82,6 +94,9 @@ export default function App() { const TIMELINE_NODE_FIELD_ID = 'fldeIZzokl'; // 时效表中的节点名称字段ID const CALCULATION_METHOD_FIELD_ID = 'fldxfLZNUu'; // 时效计算方式字段ID + // 新表ID(批量生成表) + const BATCH_TABLE_ID = 'tbl673YuipMIJnXL'; // 新创建的多维表格ID + // 已移除:调整模式不再加载货期记录列表 // 入口选择处理 @@ -178,13 +193,40 @@ export default function App() { } else if (snapshot.expectedDateString) { setExpectedDate(new Date(snapshot.expectedDateString)); } - // 回填起始时间(优先时间戳,其次字符串) + + // 优先从快照恢复起始时间,如果快照中没有则从当前货期记录中获取 + let startTimeRestored = false; if (snapshot.startTimestamp) { setStartTime(new Date(snapshot.startTimestamp)); + startTimeRestored = true; } else if (snapshot.startString) { const parsed = new Date(snapshot.startString); - if (!isNaN(parsed.getTime())) setStartTime(parsed); + if (!isNaN(parsed.getTime())) { + setStartTime(parsed); + startTimeRestored = true; + } } + + // 如果快照中没有起始时间信息,则从当前选中的货期记录中获取 + if (!startTimeRestored) { + const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; + if (startTimeValue) { + let extractedStartTime: Date | null = null; + if (typeof startTimeValue === 'number') { + extractedStartTime = new Date(startTimeValue); + } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) { + const timestamp = startTimeValue[0]; + if (typeof timestamp === 'number') { + extractedStartTime = new Date(timestamp); + } + } + + if (extractedStartTime && !isNaN(extractedStartTime.getTime())) { + setStartTime(extractedStartTime); + } + } + } + if (Array.isArray(snapshot.timelineResults)) { setTimelineResults(snapshot.timelineResults); setTimelineVisible(true); @@ -226,6 +268,24 @@ export default function App() { }; }); + // 如果没有快照恢复起始时间,则从当前货期记录中获取起始时间 + const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; + if (startTimeValue) { + let extractedStartTime: Date | null = null; + if (typeof startTimeValue === 'number') { + extractedStartTime = new Date(startTimeValue); + } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) { + const timestamp = startTimeValue[0]; + if (typeof timestamp === 'number') { + extractedStartTime = new Date(timestamp); + } + } + + if (extractedStartTime && !isNaN(extractedStartTime.getTime())) { + setStartTime(extractedStartTime); + } + } + const sorted = results.sort((a, b) => (a.processOrder || 0) - (b.processOrder || 0)); setTimelineResults(sorted); setTimelineVisible(true); @@ -752,15 +812,105 @@ export default function App() { } }; - // 组件加载时获取标签数据 + // 组件加载时获取标签数据和初始化起始时间 useEffect(() => { const initializeData = async () => { await fetchLabelOptions(); + await initializeStartTime(); + await loadAvailableTables(); // 加载可用表列表 }; initializeData(); }, []); + // 初始化起始时间:从货期记录表获取,新记录则使用当前时间 + const initializeStartTime = async () => { + try { + const selection = await bitable.base.getSelection(); + const recordId = selection?.recordId || ''; + const tableId = selection?.tableId || ''; + + if (recordId && tableId === DELIVERY_RECORD_TABLE_ID) { + // 如果选中的是货期记录表的记录,从中获取起始时间 + const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); + const deliveryRecord = await deliveryTable.getRecordById(recordId); + const startTimeValue = deliveryRecord?.fields?.[DELIVERY_START_TIME_FIELD_ID]; + + if (startTimeValue) { + let extractedStartTime: Date | null = null; + if (typeof startTimeValue === 'number') { + extractedStartTime = new Date(startTimeValue); + } else if (Array.isArray(startTimeValue) && startTimeValue.length > 0) { + const timestamp = startTimeValue[0]; + if (typeof timestamp === 'number') { + extractedStartTime = new Date(timestamp); + } + } + + if (extractedStartTime && !isNaN(extractedStartTime.getTime())) { + setStartTime(extractedStartTime); + return; + } + } + } + + // 如果没有找到有效的起始时间,使用当前时间 + setStartTime(new Date()); + } catch (error) { + console.error('初始化起始时间失败:', error); + // 出错时使用当前时间作为默认值 + setStartTime(new Date()); + } + }; + + // 加载可用表列表 + 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) => { + setSelectedBatchTableId(tableId); + setSelectedBatchViewId(''); // 重置视图选择 + loadAvailableViews(tableId); // 加载新表的视图列表 + }; + // 处理标签选择变化 const handleLabelChange = (labelKey: string, value: string | string[]) => { setSelectedLabels(prev => ({ @@ -1677,6 +1827,14 @@ export default function App() { const versionField = await deliveryRecordTable.getField(DELIVERY_VERSION_FIELD_ID); const startTimeField = await deliveryRecordTable.getField(DELIVERY_START_TIME_FIELD_ID); + // 检查标签汇总字段的类型 + console.log('标签汇总字段信息:', { + id: labelsField.id, + name: labelsField.name, + type: labelsField.type, + property: labelsField.property + }); + // 获取foreign_id:优先使用选择记录,其次记录详情,最后快照回填 let foreignId = ''; if (selectedRecords.length > 0) { @@ -1934,7 +2092,7 @@ export default function App() { conditions: [{ fieldId: FOREIGN_ID_FIELD_ID, operator: 'is', - value: [foreignId] + value: [actualForeignId] }] } }); @@ -2157,6 +2315,957 @@ export default function App() { } }; + // 批量处理函数 + const handleBatchProcess = async (rowCount: number) => { + // 检查是否选择了表 + 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; + } + } + + console.log('字段映射:', fieldMapping); + console.log('选项映射:', optionMapping); + + let successCount = 0; + let errorCount = 0; + const errors: string[] = []; + + // 逐条处理记录 + 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) { + extractedLabels[labelKey] = values; + } + } else if (typeof fieldValue === 'string') { + // 单行文本或选项ID + if (optionMapping[fieldId] && optionMapping[fieldId][fieldValue]) { + extractedLabels[labelKey] = optionMapping[fieldId][fieldValue]; + } else { + extractedLabels[labelKey] = fieldValue; + } + } else if (fieldValue && fieldValue.text) { + // 富文本等 + extractedLabels[labelKey] = fieldValue.text; + } 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) { + extractedLabels[labelKey] = values; + } + } else if (typeof fieldValue.value === 'string') { + extractedLabels[labelKey] = fieldValue.value; + } else if (fieldValue.value && fieldValue.value.name) { + // 单选字段的选项对象 + extractedLabels[labelKey] = fieldValue.value.name; + } + } 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) { + extractedLabels[labelKey] = values; + } + } else if (typeof fieldValue.displayValue === 'string') { + extractedLabels[labelKey] = fieldValue.displayValue; + } else if (fieldValue.displayValue && fieldValue.displayValue.name) { + // 单选字段的选项对象 + extractedLabels[labelKey] = fieldValue.displayValue.name; + } + } else if (fieldValue.name) { + // 直接的选项对象,有name属性 + extractedLabels[labelKey] = fieldValue.name; + } + } + } + } + + // 提取起始日期 + 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); + } + } + } + + // 提取款式、颜色、foreign_id字段 + let extractedStyle: any = null; + let extractedColor: any = null; + let extractedForeignId: any = null; + + // 提取款式字段 + const styleFieldId = fieldMapping['款式']; + if (styleFieldId && recordFields[styleFieldId]) { + const styleValue = recordFields[styleFieldId]; + if (Array.isArray(styleValue) && styleValue.length > 0) { + // 处理数组格式,转换为指定的text格式 + const firstValue = styleValue[0]; + if (typeof firstValue === 'string') { + extractedStyle = [{ type: "text", text: firstValue }]; + } else if (firstValue && firstValue.text) { + extractedStyle = [{ type: "text", text: firstValue.text }]; + } + } else if (typeof styleValue === 'string') { + extractedStyle = [{ type: "text", text: styleValue }]; + } else if (styleValue && styleValue.text) { + extractedStyle = [{ type: "text", text: styleValue.text }]; + } + } + + // 提取颜色字段 + const colorFieldId = fieldMapping['颜色']; + if (colorFieldId && recordFields[colorFieldId]) { + const colorValue = recordFields[colorFieldId]; + if (Array.isArray(colorValue) && colorValue.length > 0) { + // 处理数组格式,转换为指定的text格式 + const firstValue = colorValue[0]; + if (typeof firstValue === 'string') { + extractedColor = [{ type: "text", text: firstValue }]; + } else if (firstValue && firstValue.text) { + extractedColor = [{ type: "text", text: firstValue.text }]; + } + } else if (typeof colorValue === 'string') { + extractedColor = [{ type: "text", text: colorValue }]; + } else if (colorValue && colorValue.text) { + extractedColor = [{ type: "text", text: colorValue.text }]; + } + } + + // 提取foreign_id字段 + const foreignIdFieldId = fieldMapping['foreign_id']; + if (foreignIdFieldId && recordFields[foreignIdFieldId]) { + const foreignIdValue = recordFields[foreignIdFieldId]; + if (Array.isArray(foreignIdValue) && foreignIdValue.length > 0) { + // 处理数组格式,转换为指定的text格式 + const firstValue = foreignIdValue[0]; + if (typeof firstValue === 'string') { + extractedForeignId = [{ type: "text", text: firstValue }]; + } else if (firstValue && firstValue.text) { + extractedForeignId = [{ type: "text", text: firstValue.text }]; + } + } else if (typeof foreignIdValue === 'string') { + extractedForeignId = [{ type: "text", text: foreignIdValue }]; + } else if (foreignIdValue && foreignIdValue.text) { + extractedForeignId = [{ type: "text", text: foreignIdValue.text }]; + } + } + + console.log(`记录 ${i + 1} 提取的数据:`, { + labels: extractedLabels, + startTime: extractedStartTime, + expectedDate: extractedExpectedDate, + style: extractedStyle, + color: extractedColor, + foreignId: extractedForeignId + }); + + 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 calculateTimelineForBatch(extractedLabels, extractedStartTime, extractedExpectedDate, record.id, extractedStyle, extractedColor, extractedForeignId); + + successCount++; + + // 添加延迟避免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} 条` + }); + } + + 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 }); + } + }; + + // 为批量处理优化的时效计算函数 + const calculateTimelineForBatch = async ( + labels: {[key: string]: string | string[]}, + startTime: Date | null, + expectedDate: Date | null, + sourceRecordId: string, + style?: any, + color?: any, + foreignId?: any + ) => { + try { + // 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(); + for (const [labelKey, selectedValue] of Object.entries(labels)) { + if (selectedValue) { + const values = Array.isArray(selectedValue) ? selectedValue : [selectedValue]; + values.forEach(value => { + if (typeof value === 'string' && value.trim()) { + businessLabelValues.add(value.trim()); + } + }); + } + } + + // 3. 匹配流程节点 + const matchedProcessNodes: any[] = []; + + 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; + + // 处理流程标签匹配 + let isMatched = true; + const matchedLabels: string[] = []; + + if (processLabels && Array.isArray(processLabels)) { + // 改为OR逻辑:只要有一个标签匹配就认为节点匹配 + isMatched = false; + + for (const labelItem of processLabels) { + const labelText = typeof labelItem === 'string' ? labelItem : (labelItem?.text || ''); + + if (labelText && businessLabelValues.has(labelText.trim())) { + matchedLabels.push(labelText.trim()); + isMatched = true; // 只要有一个匹配就设为true + } + } + } + + 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 + }); + } + } + + // 按流程顺序排序 + 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 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) { + const normalizedProcessName = processNode.nodeName.trim().toLowerCase(); + const candidateRecords = timelineIndexByNode.get(normalizedProcessName) || []; + + let timelineValue = null; + let matchedTimelineRecord = null; + + // 在候选记录中进行标签匹配 + for (const candidate of candidateRecords) { + const { record: timelineRecord, fields: timelineFields } = candidate; + + // 进行标签匹配逻辑(简化版) + let isLabelsMatched = true; + + // 这里可以添加更详细的标签匹配逻辑 + // 为了简化,我们假设找到节点名称匹配就使用第一个记录 + + if (isLabelsMatched) { + const timelineValueField = timelineFields[TIMELINE_FIELD_ID]; + const calculationMethodField = timelineFields[CALCULATION_METHOD_FIELD_ID]; + + 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; + } + + // 获取计算方式 + let calculationMethod = '外部'; + if (calculationMethodField) { + if (typeof calculationMethodField === 'string') { + calculationMethod = calculationMethodField; + } else if (calculationMethodField && typeof calculationMethodField === 'object' && calculationMethodField.text) { + calculationMethod = calculationMethodField.text; + } + } + + // 转换时效值 + let convertedTimelineValue = 0; + if (calculationMethod === '内部') { + convertedTimelineValue = Math.round((candidateTimelineValue / 9) * 1000) / 1000; + } else { + convertedTimelineValue = Math.round((candidateTimelineValue / 24) * 1000) / 1000; + } + + timelineValue = convertedTimelineValue; + matchedTimelineRecord = timelineRecord; + break; + } + } + } + + // 计算节点时间 + let nodeStartTime = new Date(cumulativeStartTime); + let nodeEndTime = new Date(nodeStartTime); + + if (timelineValue && timelineValue > 0) { + // 使用现有的时间计算函数 + const adjustedStartTime = adjustToNextWorkingHour(nodeStartTime, '外部', processNode.weekendDays, processNode.excludedDates || []); + nodeEndTime = addBusinessDaysWithHolidays(adjustedStartTime, timelineValue, processNode.weekendDays, processNode.excludedDates || []); + nodeStartTime = adjustedStartTime; + } + + results.push({ + processOrder: processNode.processOrder, + nodeName: processNode.nodeName, + matchedLabels: processNode.matchedLabels, + timelineValue: timelineValue, + estimatedStart: formatDate(nodeStartTime, 'STORAGE_FORMAT'), + estimatedEnd: formatDate(nodeEndTime, 'STORAGE_FORMAT'), + timelineRecordId: matchedTimelineRecord ? (matchedTimelineRecord.id || matchedTimelineRecord.recordId) : null, + calculationMethod: '外部', + weekendDaysConfig: processNode.weekendDays, + skippedWeekends: 0, + actualDays: calculateActualDays(formatDate(nodeStartTime, 'STORAGE_FORMAT'), formatDate(nodeEndTime, 'STORAGE_FORMAT')), + startDateRule: processNode.startDateRule, + dateAdjustmentRule: processNode.dateAdjustmentRule + }); + + // 更新累积时间 + if (timelineValue && timelineValue > 0) { + cumulativeStartTime = new Date(nodeEndTime); + } + } + + console.log('批量计算结果:', results); + + // 6. 写入数据到货期记录表和流程数据表 + if (results.length > 0) { + // 写入流程数据表 + const processRecordIds = await writeToProcessDataTableForBatch(results, sourceRecordId, labels, style, color, foreignId); + + // 写入货期记录表 + await writeToDeliveryRecordTableForBatch(results, processRecordIds, {}, sourceRecordId, labels, startTime, expectedDate, style, color, foreignId); + } + + } catch (error) { + console.error('批量时效计算失败:', error); + throw error; + } + }; + + // 为批量处理优化的流程数据表写入函数 + const writeToProcessDataTableForBatch = async ( + timelineResults: any[], + sourceRecordId: string, + labels: {[key: string]: string | string[]}, + style?: any, + color?: any, + foreignId?: any + ): Promise => { + try { + // 获取流程数据表 + const processDataTable = await bitable.base.getTable(PROCESS_DATA_TABLE_ID); + + // 获取所有需要的字段 + 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 snapshotField = await processDataTable.getField(PROCESS_SNAPSHOT_JSON_FIELD_ID); + const versionField = await processDataTable.getField(PROCESS_VERSION_FIELD_ID); + const timelinessField = await processDataTable.getField(PROCESS_TIMELINESS_FIELD_ID); + + // 使用传入的foreignId参数,如果没有则使用sourceRecordId作为foreign_id + const actualForeignId = foreignId || sourceRecordId; + + // 先删除该foreign_id的所有现有记录 + try { + const existingRecords = await processDataTable.getRecords({ + pageSize: 5000, + filter: { + conjunction: 'And', + conditions: [{ + fieldId: FOREIGN_ID_FIELD_ID, + operator: 'is', + value: [foreignId] + }] + } + }); + + if (existingRecords.records && existingRecords.records.length > 0) { + const recordIdsToDelete = existingRecords.records.map(record => record.id); + await processDataTable.deleteRecords(recordIdsToDelete); + console.log(`已删除 ${recordIdsToDelete.length} 条现有记录`); + } + } catch (deleteError) { + console.warn('删除现有记录时出错:', deleteError); + } + + // 构建页面快照JSON + const versionNumber = 1; // 批量处理默认版本号为1 + + const pageSnapshot = { + version: versionNumber, + foreignId, + mode: 'batch', + selectedLabels: labels, + timelineResults + }; + const snapshotJson = JSON.stringify(pageSnapshot); + + // 准备要写入的记录数据 + const recordCellsToAdd = []; + + for (const result of timelineResults) { + // 检查是否有有效的预计完成时间 + const hasValidEndTime = ( + result.estimatedEnd && + !result.estimatedEnd.includes('未找到') && + result.estimatedEnd.trim() !== '' + ); + + if (!hasValidEndTime) { + continue; + } + + // 转换日期格式为时间戳 + let startTimestamp = null; + let endTimestamp = null; + + if (result.estimatedStart && !result.estimatedStart.includes('未找到')) { + try { + startTimestamp = new Date(result.estimatedStart).getTime(); + } catch (error) { + console.error(`转换开始时间失败:`, error); + } + } + + if (result.estimatedEnd && !result.estimatedEnd.includes('未找到')) { + try { + endTimestamp = new Date(result.estimatedEnd).getTime(); + } catch (error) { + console.error(`转换结束时间失败:`, error); + } + } + + // 使用createCell方法创建每个字段的Cell + // 使用传入的foreignId参数,如果没有则使用sourceRecordId + const actualForeignId = foreignId || sourceRecordId; + const foreignIdCell = await foreignIdField.createCell(actualForeignId); + 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 snapshotCell = await snapshotField.createCell(snapshotJson); + const versionCell = await versionField.createCell(versionNumber); + const timelinessCell = (typeof result.timelineValue === 'number') + ? await timelinessField.createCell(result.timelineValue) + : null; + + const recordCells = [ + foreignIdCell, + processNameCell, + processOrderCell, + snapshotCell, + versionCell + ]; + + if (startDateCell) recordCells.push(startDateCell); + if (endDateCell) recordCells.push(endDateCell); + if (timelinessCell) recordCells.push(timelinessCell); + + recordCellsToAdd.push(recordCells); + } + + // 批量写入记录 + const addedRecordIds: string[] = []; + + if (recordCellsToAdd.length > 0) { + try { + const addedRecords = await processDataTable.addRecords(recordCellsToAdd); + addedRecordIds.push(...addedRecords); + } catch (error) { + console.error('批量写入失败,尝试逐条写入:', error); + for (const recordCells of recordCellsToAdd) { + const addedRecord = await processDataTable.addRecord(recordCells); + addedRecordIds.push(addedRecord); + } + } + + return addedRecordIds; + } else { + return []; + } + + } catch (error) { + console.error('批量写入流程数据表失败:', error); + throw error; + } + }; + + // 为批量处理优化的货期记录表写入函数 + const writeToDeliveryRecordTableForBatch = async ( + timelineResults: any[], + processRecordIds: string[], + timelineAdjustments: {[key: number]: number} = {}, + sourceRecordId: string, + labels: {[key: string]: string | string[]}, + startTime: Date | null, + expectedDate: Date | null, + style?: any, + color?: any, + foreignId?: any + ) => { + try { + // 获取货期记录表 + const deliveryRecordTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); + + // 获取各个字段 + 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 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); + + // 使用传入的foreignId参数,如果没有则使用sourceRecordId作为foreign_id + const actualForeignId = foreignId || sourceRecordId; + + // 获取标签汇总 - 批量模式下正确处理标签数据 + // 批量模式下,labels参数包含从当前记录中提取的标签数据 + // 需要将这些标签数据合并为标签汇总 + + console.log('传入的labels参数:', labels); + console.log('labels参数类型:', typeof labels); + console.log('labels参数键值:', Object.keys(labels)); + console.log('labels参数值详情:', JSON.stringify(labels, null, 2)); + + // 从当前记录的标签数据中提取所有标签值 + const selectedLabelValues: string[] = []; + + console.log('开始处理标签数据...'); + + // 遍历当前记录的标签数据 + Object.entries(labels).forEach(([labelKey, labelValue]) => { + console.log(`处理标签字段 ${labelKey}:`, labelValue, '类型:', typeof labelValue); + + if (labelValue) { + if (Array.isArray(labelValue)) { + console.log(`${labelKey} 是数组,长度:`, labelValue.length); + // 多选标签,展开所有值 + labelValue.forEach((value, index) => { + console.log(` 数组项 ${index}:`, value, '类型:', typeof value); + if (typeof value === 'string' && value.trim() !== '') { + // 处理可能包含逗号分隔的复合标签值 + const splitValues = value.split(',').map(v => v.trim()).filter(v => v !== ''); + console.log(` 分割后的值:`, splitValues); + selectedLabelValues.push(...splitValues); + } + }); + } else if (typeof labelValue === 'string' && labelValue.trim() !== '') { + console.log(`${labelKey} 是字符串:`, labelValue); + // 单选标签,处理可能包含逗号分隔的复合标签值 + const splitValues = labelValue.split(',').map(v => v.trim()).filter(v => v !== ''); + console.log(` 分割后的值:`, splitValues); + selectedLabelValues.push(...splitValues); + } + } else { + console.log(`${labelKey} 为空或无效`); + } + }); + + console.log('提取的所有标签值:', selectedLabelValues); + + // 去重处理 + const uniqueLabelValues = [...new Set(selectedLabelValues)]; + + console.log('当前记录的标签数据:', labels); + console.log('处理后的标签汇总:', uniqueLabelValues); + + // 获取预计交付日期(最后节点的预计完成时间) + let expectedDeliveryDate = null; + if (timelineResults.length > 0) { + const lastNode = timelineResults[timelineResults.length - 1]; + if (lastNode.estimatedEnd && !lastNode.estimatedEnd.includes('未找到')) { + try { + expectedDeliveryDate = new Date(lastNode.estimatedEnd).getTime(); + } catch (error) { + console.error('转换预计交付日期失败:', error); + } + } + } + + // 获取客户期望日期 + let customerExpectedDate = null; + if (expectedDate) { + customerExpectedDate = expectedDate.getTime(); + } + + // 创建当前时间戳 + const currentTime = new Date().getTime(); + const versionNumber = 1; // 批量处理默认版本号为1 + + let adjustmentInfo = `版本:V${versionNumber}`; + if (Object.keys(timelineAdjustments).length > 0) { + const adjustmentTexts = Object.entries(timelineAdjustments).map(([nodeIndex, adjustment]) => { + const nodeName = timelineResults[parseInt(nodeIndex)]?.nodeName || `节点${parseInt(nodeIndex) + 1}`; + return `${nodeName}: ${adjustment > 0 ? '+' : ''}${adjustment.toFixed(1)} 天`; + }); + adjustmentInfo += `\n当前调整:\n${adjustmentTexts.join('\n')}`; + } + + // 创建所有必要的Cell + const foreignIdCell = await foreignIdField.createCell(actualForeignId); + const styleCell = await styleField.createCell(style || ''); + const colorCell = await colorField.createCell(color || ''); + const createTimeCell = await createTimeField.createCell(currentTime); + const startTimeCell = await startTimeField.createCell(startTime ? startTime.getTime() : currentTime); + const versionCell = await versionField.createCell(versionNumber); + + // 创建可选的Cell + const expectedDateCell = expectedDeliveryDate ? await expectedDateField.createCell(expectedDeliveryDate) : null; + const customerExpectedDateCell = customerExpectedDate ? await customerExpectedDateField.createCell(customerExpectedDate) : null; + const nodeDetailsCell = processRecordIds.length > 0 ? await nodeDetailsField.createCell({ recordIds: processRecordIds }) : null; + const adjustmentInfoCell = adjustmentInfo ? await adjustmentInfoField.createCell(adjustmentInfo) : null; + + // 只有当数据存在时才添加对应的Cell + console.log('uniqueLabelValues长度:', uniqueLabelValues.length); + console.log('uniqueLabelValues内容:', uniqueLabelValues); + + // 使用与正常模式相同的createCell方法创建标签汇总字段 + const labelsCell = uniqueLabelValues.length > 0 ? await labelsField.createCell(uniqueLabelValues) : null; + + if (labelsCell) { + console.log('标签汇总字段Cell创建成功'); + } else { + console.log('标签汇总字段为空,不创建Cell'); + } + + // 组合所有Cell到一个记录中 + const recordCells = [foreignIdCell, styleCell, colorCell, createTimeCell, startTimeCell, versionCell]; + + // 只有当数据存在时才添加对应的Cell + if (labelsCell) recordCells.push(labelsCell); + if (expectedDateCell) recordCells.push(expectedDateCell); + if (customerExpectedDateCell) recordCells.push(customerExpectedDateCell); + if (nodeDetailsCell) recordCells.push(nodeDetailsCell); + if (adjustmentInfoCell) recordCells.push(adjustmentInfoCell); + + console.log('准备写入的RecordCells数量:', recordCells.length); + console.log('标签汇总字段Cell是否存在:', !!labelsCell); + + // 使用Cell数组格式添加记录(与正常模式相同) + console.log('即将调用 deliveryRecordTable.addRecord...'); + const addedRecord = await deliveryRecordTable.addRecord(recordCells); + + console.log('货期记录表写入成功,记录ID:', addedRecord); + + // 验证写入的记录 + try { + console.log('开始验证写入的记录...'); + const writtenRecord = await deliveryRecordTable.getRecordById(addedRecord); + console.log('写入后的记录验证:', writtenRecord); + console.log('写入后的标签汇总字段:', writtenRecord.fields[DELIVERY_LABELS_FIELD_ID]); + + // 检查标签汇总字段是否为空 + const labelsFieldValue = writtenRecord.fields[DELIVERY_LABELS_FIELD_ID]; + if (!labelsFieldValue || (Array.isArray(labelsFieldValue) && labelsFieldValue.length === 0)) { + console.error('警告:标签汇总字段写入后为空!'); + console.error('原始数据:', uniqueLabelValues); + console.error('字段ID:', DELIVERY_LABELS_FIELD_ID); + } else { + console.log('标签汇总字段写入成功,包含', Array.isArray(labelsFieldValue) ? labelsFieldValue.length : 1, '个值'); + } + } catch (verifyError) { + console.error('验证写入记录失败:', verifyError); + } + return addedRecord; + } catch (error) { + console.error('批量写入货期记录表失败:', error); + throw error; + } + }; + // 执行定价数据查询 const executeQuery = async (packId: string, packType: string) => { setQueryLoading(true); @@ -2743,16 +3852,75 @@ export default function App() { + chooseMode('batch')} + > + 批量生成 + 批量生成多条流程记录 +
+ +
+
- {mode === 'generate' && 数据查询工具} + {mode === 'generate' && ( +
+ + 数据查询工具 + + 基于业务数据计算并生成节点时间线 +
+ )} + {mode === 'batch' && ( +
+ + 批量生成工具 + + 批量生成多条流程记录,提高工作效率 +
+ )} + {mode === 'adjust' && ( +
+ + 调整流程日期 + + 读取货期记录,精确还原时间线 +
+ )} {/* 功能入口切换与调整入口 */} {mode !== null && (
{/* 客户期望日期选择 */} -
+
客户期望日期 {Object.keys(selectedLabels).length > 0 && ( -
- +
+ - + 已选择 {Object.keys(selectedLabels).length} 个标签 -
- 当前选择的标签: -
+
+ 当前选择的标签: +
{Object.entries(selectedLabels).map(([key, value]) => { const displayValue = Array.isArray(value) ? value.join(', ') : value; return ( - {key}: {displayValue} - + ); })}
@@ -3185,15 +4366,160 @@ export default function App() { )} - - {/* ... existing code ... */} + {mode === 'batch' && ( +
+ {/* 批量处理配置 */} +
+ + 批量处理配置 + +
+ {/* 第一行:表和视图选择 */} +
+
+ 数据表: + +
+
+ 视图: + +
+
+ + {/* 第二行:处理行数和操作按钮 */} +
+
+ 处理行数: + setBatchRowCount(value || 10)} + min={1} + max={100} + style={{ width: 100 }} + /> +
+ +
+ {batchProcessing && ( +
+
+ + 进度: {batchProgress.current}/{batchProgress.total} + +
+ )} + +
+
+
+
+
+ )} {mode === 'generate' && (
- -
+ +
- 版单数据 + 版单数据 + + 选择需要生成时效的版单记录 + {selectedRecords.length > 0 && ( 已选择 {selectedRecords.length} 条记录 @@ -3204,6 +4530,8 @@ export default function App() { +
+
+
+
+ )} )} {mode === 'generate' && (