4
4
This commit is contained in:
625
src/App.tsx
625
src/App.tsx
@ -1,5 +1,5 @@
|
||||
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 { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber, Input, Progress, Switch } from '@douyinfe/semi-ui';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { addDays, format, differenceInCalendarDays } from 'date-fns';
|
||||
import { zhCN } from 'date-fns/locale';
|
||||
@ -78,6 +78,8 @@ export default function App() {
|
||||
const [actualCompletionDates, setActualCompletionDates] = useState<{[key: number]: Date | null}>({});
|
||||
// 基础缓冲期天数(可配置),用于计算动态缓冲期,默认14天
|
||||
const [baseBufferDays, setBaseBufferDays] = useState<number>(14);
|
||||
const [lockedExpectedDeliveryDateTs, setLockedExpectedDeliveryDateTs] = useState<number | null>(null);
|
||||
const [isExpectedDeliveryDateLocked, setIsExpectedDeliveryDateLocked] = useState<boolean>(false);
|
||||
// 快照回填来源(foreign_id、款式、颜色、文本2)
|
||||
const [currentForeignId, setCurrentForeignId] = useState<string | null>(null);
|
||||
const [currentStyleText, setCurrentStyleText] = useState<string>('');
|
||||
@ -103,6 +105,7 @@ export default function App() {
|
||||
const [batchProgressList, setBatchProgressList] = useState<{ index: number; foreignId: string; status: 'success' | 'failed'; message?: string }[]>([]);
|
||||
const [batchCurrentRowInfo, setBatchCurrentRowInfo] = useState<{ index: number; foreignId: string; style: string; color: string } | null>(null);
|
||||
const batchAbortRef = useRef<boolean>(false);
|
||||
const lastBufferDeficitRef = useRef<number>(0);
|
||||
// 删除未使用的 deliveryRecords 状态
|
||||
const [selectedDeliveryRecordId, setSelectedDeliveryRecordId] = useState<string>('');
|
||||
// 从货期记录读取到的record_ids(用于保存回写)
|
||||
@ -145,12 +148,17 @@ export default function App() {
|
||||
setIsRestoringSnapshot(false);
|
||||
setRestoredRecordIds([]);
|
||||
setRestoredRecordIdsText('');
|
||||
setLockedExpectedDeliveryDateTs(null);
|
||||
setIsExpectedDeliveryDateLocked(false);
|
||||
|
||||
// 重置初始快照捕获状态
|
||||
try {
|
||||
hasCapturedInitialSnapshotRef.current = false;
|
||||
initialSnapshotRef.current = null;
|
||||
} catch {}
|
||||
try {
|
||||
lastBufferDeficitRef.current = 0;
|
||||
} catch {}
|
||||
|
||||
// 移除:批量模式当前记录信息
|
||||
|
||||
@ -275,6 +283,7 @@ export default function App() {
|
||||
// 切换功能时重置全局变量,但保留新的mode
|
||||
resetGlobalState({ resetMode: false });
|
||||
setMode(m);
|
||||
setIsExpectedDeliveryDateLocked(m === 'adjust');
|
||||
setModeSelectionVisible(false);
|
||||
};
|
||||
|
||||
@ -358,8 +367,26 @@ export default function App() {
|
||||
const recordIdsField: any = await deliveryTable.getField(DELIVERY_RECORD_IDS_FIELD_ID);
|
||||
const snapshotField: any = await deliveryTable.getField(DELIVERY_SNAPSHOT_JSON_FIELD_ID);
|
||||
const startTimeField: any = await deliveryTable.getField(DELIVERY_START_TIME_FIELD_ID);
|
||||
const expectedDeliveryDateField: any = await deliveryTable.getField(DELIVERY_EXPECTED_DATE_FIELD_ID);
|
||||
|
||||
const nodeDetailsVal = await nodeDetailsField.getValue(deliveryRecordId);
|
||||
try {
|
||||
const expectedDeliveryVal = await expectedDeliveryDateField.getValue(deliveryRecordId);
|
||||
let ts: number | null = null;
|
||||
if (typeof expectedDeliveryVal === 'number') {
|
||||
ts = expectedDeliveryVal;
|
||||
} else if (Array.isArray(expectedDeliveryVal) && expectedDeliveryVal.length > 0 && typeof expectedDeliveryVal[0] === 'number') {
|
||||
ts = expectedDeliveryVal[0];
|
||||
} else {
|
||||
const extracted = extractText(expectedDeliveryVal);
|
||||
if (extracted && extracted.trim() !== '') {
|
||||
const parsed = new Date(extracted);
|
||||
if (!isNaN(parsed.getTime())) ts = parsed.getTime();
|
||||
}
|
||||
}
|
||||
setLockedExpectedDeliveryDateTs(ts);
|
||||
setIsExpectedDeliveryDateLocked(true);
|
||||
} catch {}
|
||||
// 读取record_ids文本字段并保留原始文本(用于原样写回)
|
||||
try {
|
||||
const recordIdsTextVal = await recordIdsField.getValue(deliveryRecordId);
|
||||
@ -439,6 +466,22 @@ export default function App() {
|
||||
}
|
||||
|
||||
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
|
||||
{
|
||||
const snapLockedTs = snapshot?.lockedExpectedDeliveryDateTs;
|
||||
if (snapLockedTs === null) {
|
||||
setLockedExpectedDeliveryDateTs(null);
|
||||
} else {
|
||||
const hasLockedTs = snapLockedTs !== undefined && snapLockedTs !== null;
|
||||
if (hasLockedTs) setLockedExpectedDeliveryDateTs(snapLockedTs);
|
||||
}
|
||||
const hasLockedTs = snapLockedTs !== undefined && snapLockedTs !== null;
|
||||
const snapLockedFlag = snapshot?.isExpectedDeliveryDateLocked;
|
||||
if (typeof snapLockedFlag === 'boolean') {
|
||||
setIsExpectedDeliveryDateLocked(snapLockedFlag);
|
||||
} else if (hasLockedTs) {
|
||||
setIsExpectedDeliveryDateLocked(true);
|
||||
}
|
||||
}
|
||||
if (snapshot.expectedDateTimestamp) {
|
||||
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
||||
} else if (snapshot.expectedDateString) {
|
||||
@ -580,6 +623,22 @@ export default function App() {
|
||||
}
|
||||
|
||||
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
|
||||
{
|
||||
const snapLockedTs = snapshot?.lockedExpectedDeliveryDateTs;
|
||||
if (snapLockedTs === null) {
|
||||
setLockedExpectedDeliveryDateTs(null);
|
||||
} else {
|
||||
const hasLockedTs = snapLockedTs !== undefined && snapLockedTs !== null;
|
||||
if (hasLockedTs) setLockedExpectedDeliveryDateTs(snapLockedTs);
|
||||
}
|
||||
const hasLockedTs = snapLockedTs !== undefined && snapLockedTs !== null;
|
||||
const snapLockedFlag = snapshot?.isExpectedDeliveryDateLocked;
|
||||
if (typeof snapLockedFlag === 'boolean') {
|
||||
setIsExpectedDeliveryDateLocked(snapLockedFlag);
|
||||
} else if (hasLockedTs) {
|
||||
setIsExpectedDeliveryDateLocked(true);
|
||||
}
|
||||
}
|
||||
if (snapshot.expectedDateTimestamp) {
|
||||
setExpectedDate(new Date(snapshot.expectedDateTimestamp));
|
||||
} else if (snapshot.expectedDateString) {
|
||||
@ -913,6 +972,9 @@ export default function App() {
|
||||
colorText: nodeSnapshot.colorText,
|
||||
text2: nodeSnapshot.text2,
|
||||
mode: nodeSnapshot.mode,
|
||||
timelineDirection: nodeSnapshot.timelineDirection,
|
||||
lockedExpectedDeliveryDateTs: nodeSnapshot.lockedExpectedDeliveryDateTs,
|
||||
isExpectedDeliveryDateLocked: nodeSnapshot.isExpectedDeliveryDateLocked,
|
||||
selectedLabels: nodeSnapshot.selectedLabels,
|
||||
expectedDateTimestamp: nodeSnapshot.expectedDateTimestamp,
|
||||
expectedDateString: nodeSnapshot.expectedDateString,
|
||||
@ -1024,6 +1086,22 @@ export default function App() {
|
||||
setTimelineResults(nodeSnapshots);
|
||||
setTimelineVisible(true);
|
||||
|
||||
{
|
||||
const snapLockedTs = (globalSnapshotData as any)?.lockedExpectedDeliveryDateTs;
|
||||
if (snapLockedTs === null) {
|
||||
setLockedExpectedDeliveryDateTs(null);
|
||||
} else {
|
||||
const hasLockedTs = snapLockedTs !== undefined && snapLockedTs !== null;
|
||||
if (hasLockedTs) setLockedExpectedDeliveryDateTs(snapLockedTs);
|
||||
}
|
||||
const snapLockedFlag = (globalSnapshotData as any)?.isExpectedDeliveryDateLocked;
|
||||
if (typeof snapLockedFlag === 'boolean') {
|
||||
setIsExpectedDeliveryDateLocked(snapLockedFlag);
|
||||
} else if (snapLockedTs !== undefined && snapLockedTs !== null) {
|
||||
setIsExpectedDeliveryDateLocked(true);
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复智能缓冲期状态(如果存在)
|
||||
if (globalSnapshotData.bufferManagement) {
|
||||
console.log('恢复智能缓冲期状态:', globalSnapshotData.bufferManagement);
|
||||
@ -2713,50 +2791,6 @@ export default function App() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!isBackward && mode === 'generate' && showUI && currentExpectedDate && !isNaN(currentExpectedDate.getTime()) && results.length > 0) {
|
||||
const findLastCompletionDate = (): Date | null => {
|
||||
for (let i = results.length - 1; i >= 0; i--) {
|
||||
const endStr = results[i]?.estimatedEnd;
|
||||
if (typeof endStr !== 'string' || endStr.trim() === '' || endStr.includes('未找到') || endStr.includes('时效值为0')) continue;
|
||||
const d = parseDate(endStr);
|
||||
if (d && !isNaN(d.getTime())) return d;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const lastCompletionDate = findLastCompletionDate();
|
||||
if (lastCompletionDate) {
|
||||
const deliveryDate = new Date(lastCompletionDate);
|
||||
deliveryDate.setDate(deliveryDate.getDate() + Math.max(0, Math.ceil(baseBufferDays)));
|
||||
const increaseDays = differenceInCalendarDays(currentExpectedDate, deliveryDate);
|
||||
|
||||
if (increaseDays > 0) {
|
||||
const shouldApply = await new Promise<boolean>((resolve) => {
|
||||
Modal.confirm({
|
||||
title: '是否增加缓冲期使其一致?',
|
||||
content: `请业务确认:OMS 获取小ERP的预计交付日期。\n当前预计交付日期:${formatDate(deliveryDate, 'CHINESE_DATE')}\n客户期望日期:${formatDate(currentExpectedDate, 'CHINESE_DATE')}\n是否增加缓冲期 +${increaseDays} 天使其一致?`,
|
||||
okText: '确认增加',
|
||||
cancelText: '不调整',
|
||||
onOk: () => resolve(true),
|
||||
onCancel: () => resolve(false),
|
||||
});
|
||||
});
|
||||
|
||||
if (shouldApply) {
|
||||
setBaseBufferDays((prev) => Math.max(0, Math.ceil(prev) + increaseDays));
|
||||
setHasAppliedSuggestedBuffer(true);
|
||||
setLastSuggestedApplied(increaseDays);
|
||||
if (bitable.ui.showToast) {
|
||||
await bitable.ui.showToast({
|
||||
toastType: ToastType.success,
|
||||
message: `已自动增加缓冲期 +${increaseDays} 天,使预计交付日期对齐客户期望日期`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setTimelineResults(results);
|
||||
if (showUI && !delayShowTimelineModal) {
|
||||
setTimelineVisible(true);
|
||||
@ -2784,87 +2818,16 @@ export default function App() {
|
||||
|
||||
// 复合调整处理函数:根据缓冲期和交期余量状态决定调整方式
|
||||
const handleComplexAdjustment = (nodeIndex: number, adjustment: number) => {
|
||||
// 计算当前状态
|
||||
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
|
||||
const baseBuferDays = baseBufferDays;
|
||||
|
||||
// 智能缓冲期计算逻辑(缓冲期扣减=最后完成时间变动的自然天数)
|
||||
let dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
|
||||
|
||||
// 只有在缓冲期为0时才检查最终限制:最后节点预计完成时间是否已达到客户期望日期
|
||||
let hasReachedFinalLimit = false;
|
||||
if (dynamicBufferDays === 0 && expectedDate && timelineResults.length > 0) {
|
||||
// 获取有效的最后流程完成日期
|
||||
let effectiveLastProcess = null;
|
||||
let lastCompletionDate = null;
|
||||
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const process = timelineResults[i];
|
||||
const processDate = new Date(process.estimatedEnd);
|
||||
|
||||
if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') {
|
||||
effectiveLastProcess = process;
|
||||
lastCompletionDate = processDate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!effectiveLastProcess) {
|
||||
effectiveLastProcess = timelineResults[timelineResults.length - 1];
|
||||
lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd);
|
||||
}
|
||||
|
||||
// 计算最后流程完成日期(包含调整)
|
||||
const adjustedCompletionDate = new Date(lastCompletionDate as Date);
|
||||
adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment);
|
||||
|
||||
// 检查是否已达到客户期望日期
|
||||
const timeDiffToExpected = expectedDate.getTime() - adjustedCompletionDate.getTime();
|
||||
const daysToExpected = Math.ceil(timeDiffToExpected / (1000 * 60 * 60 * 24));
|
||||
hasReachedFinalLimit = daysToExpected <= 0;
|
||||
}
|
||||
|
||||
// 如果已达到最终限制且是正向调整,则禁止操作
|
||||
if (hasReachedFinalLimit && adjustment > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 调整逻辑:
|
||||
// 1. 优先使用缓冲期
|
||||
// 2. 缓冲期为0时,通过调整节点时效值实现连锁传递
|
||||
if (adjustment > 0) {
|
||||
// 正数调整:需要扣减
|
||||
if (dynamicBufferDays > 0) {
|
||||
// 1. 缓冲期 > 0,优先扣减缓冲期(通过调整节点时间)
|
||||
const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment);
|
||||
// 如果调整失败,不进行后续操作
|
||||
if (!updatedResults) return;
|
||||
} else {
|
||||
// 2. 缓冲期 = 0,通过调整节点时效值实现连锁传递
|
||||
const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment);
|
||||
// 如果调整失败,不进行后续操作
|
||||
if (!updatedResults) return;
|
||||
}
|
||||
} else if (adjustment < 0) {
|
||||
// 负数调整:需要恢复
|
||||
if (completionDateAdjustment > 0) {
|
||||
// 1. 优先恢复最后流程完成日期的调整
|
||||
const restoreAmount = Math.min(-adjustment, completionDateAdjustment);
|
||||
setCompletionDateAdjustment(completionDateAdjustment - restoreAmount);
|
||||
const remainingRestore = -adjustment - restoreAmount;
|
||||
|
||||
if (remainingRestore > 0) {
|
||||
// 2. 如果还有剩余恢复量,恢复缓冲期(通过调整节点时间)
|
||||
const updatedResults = handleTimelineAdjustment(nodeIndex, -remainingRestore);
|
||||
// 如果调整失败,不进行后续操作
|
||||
if (!updatedResults) return;
|
||||
}
|
||||
} else if (totalAdjustments > 0) {
|
||||
// 直接恢复缓冲期
|
||||
const updatedResults = handleTimelineAdjustment(nodeIndex, adjustment);
|
||||
// 如果调整失败,不进行后续操作
|
||||
if (!updatedResults) return;
|
||||
}
|
||||
const nextAdjustments = handleTimelineAdjustment(nodeIndex, adjustment);
|
||||
if (!nextAdjustments) return;
|
||||
const deficit = computeBufferDeficitDaysUsingEndDelta(nextAdjustments);
|
||||
const prevDeficit = lastBufferDeficitRef.current;
|
||||
lastBufferDeficitRef.current = deficit;
|
||||
if (adjustment > 0 && deficit > 0 && Math.ceil(deficit) > Math.ceil(prevDeficit)) {
|
||||
Modal.warning({
|
||||
title: '缓冲期不足',
|
||||
content: `当前缓冲期无法覆盖本次超期,缺口 ${Math.ceil(deficit)} 天`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@ -2892,32 +2855,11 @@ export default function App() {
|
||||
|
||||
newAdjustments[nodeIndex] = newAdjustment;
|
||||
|
||||
// 查找周转周期节点的索引
|
||||
const turnoverNodeIndex = timelineResults.findIndex(result => result.nodeName === '周转周期');
|
||||
|
||||
// 如果存在周转周期节点,进行反向调整
|
||||
if (turnoverNodeIndex !== -1 && turnoverNodeIndex !== nodeIndex) {
|
||||
const turnoverCurrentAdjustment = newAdjustments[turnoverNodeIndex] || 0;
|
||||
const turnoverBaseValue = (typeof timelineResults[turnoverNodeIndex]?.timelineValue === 'number')
|
||||
? timelineResults[turnoverNodeIndex]!.timelineValue
|
||||
: (typeof timelineResults[turnoverNodeIndex]?.adjustedTimelineValue === 'number')
|
||||
? timelineResults[turnoverNodeIndex]!.adjustedTimelineValue
|
||||
: 0;
|
||||
|
||||
// 计算周转周期的反向调整值
|
||||
const turnoverNewAdjustment = turnoverCurrentAdjustment - adjustment;
|
||||
|
||||
// 确保周转周期调整后的值不小于0
|
||||
if (turnoverBaseValue + turnoverNewAdjustment >= 0) {
|
||||
newAdjustments[turnoverNodeIndex] = turnoverNewAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
setTimelineAdjustments(newAdjustments);
|
||||
|
||||
// 使用智能重算逻辑,只重算被调整的节点及其后续节点
|
||||
recalculateTimeline(newAdjustments);
|
||||
return timelineResults;
|
||||
return newAdjustments;
|
||||
};
|
||||
|
||||
// 获取重新计算后的时间线结果(不更新状态,逻辑对齐页面的重算口径)
|
||||
@ -3183,14 +3125,12 @@ export default function App() {
|
||||
return fallback && !isNaN(fallback.getTime()) ? fallback : null;
|
||||
};
|
||||
|
||||
// 依据“最后流程节点的预计完成日期差(自然日)”计算剩余缓冲期
|
||||
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => {
|
||||
const computeLastNodeEndDeltaDays = (adjustments: { [key: number]: number }): number => {
|
||||
try {
|
||||
if (timelineDirection === 'backward') return 0;
|
||||
const baseline = getRecalculatedTimeline({}); // 原始计划(不含任何调整)
|
||||
const current = getRecalculatedTimeline(adjustments); // 当前计划(包含时效值调整)
|
||||
const baseline = getRecalculatedTimeline({});
|
||||
const current = getRecalculatedTimeline(adjustments);
|
||||
|
||||
// 严格取最后流程节点的预计完成日期;若不可用则回退到最后一个有效完成日期
|
||||
const pickLastNodeEnd = (results: any[]): Date | null => {
|
||||
if (!Array.isArray(results) || results.length === 0) return null;
|
||||
const endStr = results[results.length - 1]?.estimatedEnd;
|
||||
@ -3203,19 +3143,54 @@ export default function App() {
|
||||
|
||||
const baselineLast = pickLastNodeEnd(baseline);
|
||||
const currentLast = pickLastNodeEnd(current);
|
||||
if (!baselineLast || !currentLast) {
|
||||
return Math.max(0, Math.min(baseBufferDays, baseBufferDays));
|
||||
}
|
||||
if (!baselineLast || !currentLast) return 0;
|
||||
|
||||
// 仅使用“预计完成日期”差,不叠加 completionDateAdjustment
|
||||
const dayMs = 1000 * 60 * 60 * 24;
|
||||
const deltaDays = Math.ceil((currentLast.getTime() - baselineLast.getTime()) / dayMs);
|
||||
// 缓冲期剩余 = clamp(基础缓冲期 - 差值, 0, 基础缓冲期)
|
||||
const remaining = baseBufferDays - deltaDays;
|
||||
return Math.max(0, Math.min(baseBufferDays, remaining));
|
||||
return Math.ceil((currentLast.getTime() - baselineLast.getTime()) / dayMs);
|
||||
} catch {
|
||||
// 兜底:如出现异常,保持基础缓冲期
|
||||
return Math.max(0, Math.min(baseBufferDays, baseBufferDays));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
const computeBufferDeficitDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => {
|
||||
const deltaDays = computeLastNodeEndDeltaDays(adjustments);
|
||||
const base = Math.max(0, Math.ceil(baseBufferDays));
|
||||
return Math.max(0, deltaDays - base);
|
||||
};
|
||||
|
||||
const computeExpectedDeliveryDateTsFromResults = (
|
||||
results: any[],
|
||||
adjustments: { [key: number]: number }
|
||||
): number | null => {
|
||||
if (timelineDirection === 'backward') return null;
|
||||
const lastCompletionDate = getLastValidCompletionDateFromResults(results);
|
||||
if (!lastCompletionDate) return null;
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(adjustments);
|
||||
const deliveryDate = new Date(lastCompletionDate);
|
||||
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
|
||||
const ts = deliveryDate.getTime();
|
||||
return Number.isFinite(ts) ? ts : null;
|
||||
};
|
||||
|
||||
const handleExpectedDeliveryDateLockChange = (checked: boolean) => {
|
||||
const nextLocked = !!checked;
|
||||
setIsExpectedDeliveryDateLocked(nextLocked);
|
||||
if (!nextLocked) return;
|
||||
if (lockedExpectedDeliveryDateTs !== null && lockedExpectedDeliveryDateTs !== undefined) return;
|
||||
const computed = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments);
|
||||
if (computed !== null) setLockedExpectedDeliveryDateTs(computed);
|
||||
};
|
||||
|
||||
const computeDynamicBufferDaysUsingEndDelta = (adjustments: { [key: number]: number }): number => {
|
||||
try {
|
||||
if (timelineDirection === 'backward') return 0;
|
||||
const deltaDays = computeLastNodeEndDeltaDays(adjustments);
|
||||
const base = Math.max(0, Math.ceil(baseBufferDays));
|
||||
const remaining = base - deltaDays;
|
||||
return Math.max(0, Math.min(base, remaining));
|
||||
} catch {
|
||||
const base = Math.max(0, Math.ceil(baseBufferDays));
|
||||
return base;
|
||||
}
|
||||
};
|
||||
|
||||
@ -3453,6 +3428,8 @@ export default function App() {
|
||||
timelineResults,
|
||||
timelineAdjustments,
|
||||
baseBufferDays,
|
||||
lockedExpectedDeliveryDateTs,
|
||||
isExpectedDeliveryDateLocked,
|
||||
actualCompletionDates,
|
||||
completionDateAdjustment,
|
||||
hasAppliedSuggestedBuffer,
|
||||
@ -3471,6 +3448,8 @@ export default function App() {
|
||||
setCompletionDateAdjustment(0); // 重置最后流程完成日期调整
|
||||
setActualCompletionDates({}); // 重置实际完成日期
|
||||
setBaseBufferDays(14); // 重置固定缓冲期为默认值
|
||||
setIsExpectedDeliveryDateLocked(false);
|
||||
try { lastBufferDeficitRef.current = 0; } catch {}
|
||||
setHasAppliedSuggestedBuffer(false); // 重置建议缓冲期应用标志
|
||||
setLastSuggestedApplied(null); // 清空上次建议值
|
||||
recalculateTimeline({}, true); // 强制重算所有节点
|
||||
@ -3497,6 +3476,8 @@ export default function App() {
|
||||
setSelectedLabels(s.selectedLabels || {});
|
||||
setTimelineAdjustments(s.timelineAdjustments || {});
|
||||
setBaseBufferDays(s.baseBufferDays ?? 14);
|
||||
setLockedExpectedDeliveryDateTs(s.lockedExpectedDeliveryDateTs ?? null);
|
||||
setIsExpectedDeliveryDateLocked(!!s.isExpectedDeliveryDateLocked);
|
||||
setActualCompletionDates(s.actualCompletionDates || {});
|
||||
setCompletionDateAdjustment(s.completionDateAdjustment || 0);
|
||||
setHasAppliedSuggestedBuffer(!!s.hasAppliedSuggestedBuffer && s.hasAppliedSuggestedBuffer);
|
||||
@ -3684,36 +3665,9 @@ export default function App() {
|
||||
const selectedLabelValues = Object.values(overrides?.selectedLabels ?? selectedLabels).flat().filter(Boolean);
|
||||
|
||||
// 获取预计交付日期(交期余量的日期版本:最后流程完成日期 + 基础缓冲期)
|
||||
let expectedDeliveryDate = null;
|
||||
if (timelineResults.length > 0) {
|
||||
// 从后往前查找第一个有效的流程完成日期(与交期余量计算逻辑一致)
|
||||
let effectiveLastProcess = null;
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const process = timelineResults[i];
|
||||
if (process.estimatedEnd &&
|
||||
!process.estimatedEnd.includes('未找到') &&
|
||||
!process.estimatedEnd.includes('时效值为0') &&
|
||||
process.estimatedEnd !== 'Invalid Date') {
|
||||
effectiveLastProcess = process;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (effectiveLastProcess) {
|
||||
try {
|
||||
const lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd);
|
||||
|
||||
// 计算动态缓冲期:按“最后完成时间自然日差”扣减基础缓冲期
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
|
||||
|
||||
// 加上动态缓冲期
|
||||
const deliveryDate = new Date(lastCompletionDate);
|
||||
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
|
||||
expectedDeliveryDate = deliveryDate.getTime();
|
||||
} catch (error) {
|
||||
console.error('转换预计交付日期失败:', error);
|
||||
}
|
||||
}
|
||||
let expectedDeliveryDate = computeExpectedDeliveryDateTsFromResults(timelineResults, timelineAdjustments);
|
||||
if (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null) {
|
||||
expectedDeliveryDate = lockedExpectedDeliveryDateTs;
|
||||
}
|
||||
|
||||
// 获取客户期望日期:批量模式优先使用传入的expectedDate
|
||||
@ -3797,6 +3751,8 @@ export default function App() {
|
||||
text2,
|
||||
mode,
|
||||
timelineDirection,
|
||||
lockedExpectedDeliveryDateTs,
|
||||
isExpectedDeliveryDateLocked,
|
||||
selectedLabels: currentSelectedLabels,
|
||||
expectedDateTimestamp,
|
||||
expectedDateString,
|
||||
@ -5269,7 +5225,11 @@ export default function App() {
|
||||
{mode !== null && (
|
||||
<div style={{ margin: '18px 0 14px' }}>
|
||||
<Space spacing={16} align='center'>
|
||||
<Select value={mode} onChange={(v) => setMode(v as any)}
|
||||
<Select value={mode} onChange={(v) => {
|
||||
const next = v as any;
|
||||
setMode(next);
|
||||
setIsExpectedDeliveryDateLocked(next === 'adjust');
|
||||
}}
|
||||
optionList={[
|
||||
{ value: 'generate', label: '生成流程日期' },
|
||||
{ value: 'adjust', label: '调整流程日期' }
|
||||
@ -5584,6 +5544,16 @@ export default function App() {
|
||||
>
|
||||
<span style={{ fontSize: 14, lineHeight: 1 }}>✏️</span>
|
||||
</Button>
|
||||
{mode === 'adjust' && (
|
||||
<>
|
||||
<Text type="tertiary" style={{ marginLeft: 8 }}>锁交付</Text>
|
||||
<Switch
|
||||
checked={isExpectedDeliveryDateLocked}
|
||||
onChange={handleExpectedDeliveryDateLockChange}
|
||||
style={{ marginLeft: 4 }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -5593,14 +5563,18 @@ export default function App() {
|
||||
setTimelineAdjustments({}); // 关闭时重置调整
|
||||
setDeliveryMarginDeductions(0); // 关闭时重置交期余量扣减
|
||||
setCompletionDateAdjustment(0); // 关闭时重置最后流程完成日期调整
|
||||
setHasAppliedSuggestedBuffer(false); // 关闭时允许下次重新应用建议缓冲期
|
||||
setLastSuggestedApplied(null); // 关闭时清空上次建议值
|
||||
setStyleColorEditable(false); // 关闭弹窗后恢复为锁定状态
|
||||
if (mode !== 'adjust') {
|
||||
setLockedExpectedDeliveryDateTs(null);
|
||||
setIsExpectedDeliveryDateLocked(false);
|
||||
}
|
||||
}}
|
||||
footer={
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Space align="center">
|
||||
<Text>缓冲期(天):</Text>
|
||||
<Text>
|
||||
{mode === 'adjust' && isExpectedDeliveryDateLocked ? '缓冲期(天)(不影响已锁交付):' : '缓冲期(天):'}
|
||||
</Text>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={1}
|
||||
@ -5611,124 +5585,6 @@ export default function App() {
|
||||
}}
|
||||
style={{ width: 90 }}
|
||||
/>
|
||||
{/* 建议缓冲期增量:根据实际完成偏差与交期余量自动反算 */}
|
||||
{(() => {
|
||||
try {
|
||||
// 仅当用户本次会话发生改动时显示建议
|
||||
const initialSnap = initialSnapshotRef.current;
|
||||
const hasAdjustmentChanges = Object.values(timelineAdjustments || {}).some(v => (Number(v) || 0) !== 0) || (Number(completionDateAdjustment) || 0) !== 0;
|
||||
const toMillisMap = (obj: any) => {
|
||||
const entries = Object.entries(obj || {});
|
||||
return Object.fromEntries(entries.map(([k, v]) => [k, v ? new Date(v as any).getTime() : null]));
|
||||
};
|
||||
const currentActualMap = toMillisMap(actualCompletionDates);
|
||||
const initialActualMap = toMillisMap(initialSnap?.actualCompletionDates || {});
|
||||
const hasActualChanges = JSON.stringify(currentActualMap) !== JSON.stringify(initialActualMap);
|
||||
const hasUserSessionChanges = hasAdjustmentChanges || hasActualChanges;
|
||||
if (!hasUserSessionChanges) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 计算动态缓冲期(按“最后完成时间自然日差”扣减基础缓冲期)
|
||||
const baseBuferDays = baseBufferDays;
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
|
||||
|
||||
// 获取有效的最后流程完成日期
|
||||
let effectiveLastProcess: any = null;
|
||||
let lastCompletionDate: Date | null = null;
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const process = timelineResults[i];
|
||||
const processDate = new Date(process.estimatedEnd);
|
||||
if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') {
|
||||
effectiveLastProcess = process;
|
||||
lastCompletionDate = processDate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!effectiveLastProcess && timelineResults.length > 0) {
|
||||
effectiveLastProcess = timelineResults[timelineResults.length - 1];
|
||||
lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd);
|
||||
}
|
||||
|
||||
// 基线计划(不考虑实际完成、也不考虑调整):使用原始时效值重算
|
||||
const baseline = getRecalculatedTimeline({});
|
||||
let baselineLast: Date | null = null;
|
||||
for (let i = baseline.length - 1; i >= 0; i--) {
|
||||
const p = baseline[i];
|
||||
const d = new Date(p.estimatedEnd);
|
||||
if (!isNaN(d.getTime()) && p.estimatedEnd !== '时效值为0') { baselineLast = d; break; }
|
||||
}
|
||||
if (!baselineLast && baseline.length > 0) {
|
||||
const d = new Date(baseline[baseline.length - 1].estimatedEnd);
|
||||
if (!isNaN(d.getTime())) baselineLast = d;
|
||||
}
|
||||
|
||||
// 当前计划(考虑实际完成):使用现有timelineResults
|
||||
let currentLast: Date | null = null;
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const p = timelineResults[i];
|
||||
const d = new Date(p.estimatedEnd);
|
||||
if (!isNaN(d.getTime()) && p.estimatedEnd !== '时效值为0') { currentLast = d; break; }
|
||||
}
|
||||
if (!currentLast && timelineResults.length > 0) {
|
||||
const d = new Date(timelineResults[timelineResults.length - 1].estimatedEnd);
|
||||
if (!isNaN(d.getTime())) currentLast = d;
|
||||
}
|
||||
|
||||
// 建议口径1:自然日偏差(当前计划 vs 原始计划)扣除默认缓冲期
|
||||
let suggestedInt = 0;
|
||||
let slipDays = 0;
|
||||
if (baselineLast && currentLast) {
|
||||
const dayMs = 1000 * 60 * 60 * 24;
|
||||
slipDays = Math.ceil((currentLast.getTime() - baselineLast.getTime()) / dayMs);
|
||||
const deficitCalendar = Math.max(0, slipDays - baseBuferDays);
|
||||
suggestedInt = Math.ceil(deficitCalendar);
|
||||
}
|
||||
|
||||
// 建议口径2:工作日总调整量(仅统计 external 节点的天数调整)扣除默认缓冲期
|
||||
const totalWorkingAdjustments = Object.entries(timelineAdjustments).reduce((sum, [k, adj]) => {
|
||||
const i = parseInt(k, 10);
|
||||
const method = timelineResults[i]?.calculationMethod || 'external';
|
||||
const val = Number(adj) || 0;
|
||||
return method === 'internal' ? sum : sum + val;
|
||||
}, 0);
|
||||
const deficitWorking = Math.max(0, totalWorkingAdjustments - baseBuferDays);
|
||||
const suggestedByWorking = Math.ceil(deficitWorking);
|
||||
|
||||
// 取两种口径的较大值,避免出现工作日调整很大但自然日差较小的误差
|
||||
suggestedInt = Math.max(suggestedInt, suggestedByWorking);
|
||||
const displayInt = Math.max(0, suggestedInt);
|
||||
if (displayInt > 0) {
|
||||
return (
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||
<Text style={{ color: '#fa8c16' }}>建议增加缓冲期:+{displayInt}天</Text>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
if (!hasAppliedSuggestedBuffer) {
|
||||
// 应用当前建议值
|
||||
setBaseBufferDays(Math.max(0, Math.ceil(baseBufferDays) + displayInt));
|
||||
setHasAppliedSuggestedBuffer(true);
|
||||
setLastSuggestedApplied(displayInt);
|
||||
} else {
|
||||
// 撤销上次应用的建议值(使用记录的 lastSuggestedApplied 兜底当前显示值)
|
||||
const rollback = (lastSuggestedApplied ?? displayInt);
|
||||
setBaseBufferDays(prev => Math.max(0, Math.ceil(prev) - rollback));
|
||||
setHasAppliedSuggestedBuffer(false);
|
||||
setLastSuggestedApplied(null);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{hasAppliedSuggestedBuffer ? '撤销建议' : '应用建议'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
})()}
|
||||
<Button onClick={resetToInitialState}>
|
||||
重置所有调整
|
||||
</Button>
|
||||
@ -5807,105 +5663,8 @@ export default function App() {
|
||||
const isCurrentTurnoverZero = isTurnoverNode && adjustedValue === 0;
|
||||
|
||||
// 计算动态缓冲期(按“最后完成时间变动天数”扣减缓冲期)
|
||||
const totalAdjustments = Object.values(timelineAdjustments).reduce((sum, adj) => sum + adj, 0);
|
||||
const baseBuferDays = baseBufferDays;
|
||||
const dynamicBufferDays = computeDynamicBufferDaysUsingEndDelta(timelineAdjustments);
|
||||
|
||||
// 新的复合限制逻辑:
|
||||
// 1. 如果缓冲期 > 0,允许操作
|
||||
// 2. 如果缓冲期 = 0,进一步判断交期余量
|
||||
// 3. 如果缓冲期 = 0 且交期余量 <= 0,禁止操作
|
||||
let hasNegativeBuffer = false;
|
||||
|
||||
// 计算交期余量(仅用于显示,不用于限制)
|
||||
let deliveryMargin = 0;
|
||||
if (timelineResults.length > 0) {
|
||||
// 获取有效的最后流程完成日期(与交期余量计算逻辑保持一致)
|
||||
let effectiveLastProcess = null;
|
||||
let lastCompletionDate: Date | null = null;
|
||||
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const process = timelineResults[i];
|
||||
const processDate = new Date(process.estimatedEnd);
|
||||
|
||||
if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') {
|
||||
effectiveLastProcess = process;
|
||||
lastCompletionDate = processDate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!effectiveLastProcess) {
|
||||
effectiveLastProcess = timelineResults[timelineResults.length - 1];
|
||||
lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 执行复合限制判断 - 只有在缓冲期为0时才检查最终限制
|
||||
let canIncrease = false;
|
||||
if (dynamicBufferDays > 0) {
|
||||
// 1. 缓冲期 > 0,允许操作
|
||||
canIncrease = true;
|
||||
} else {
|
||||
// 2. 缓冲期 = 0,检查最终限制:最后节点预计完成时间是否已达到客户期望日期
|
||||
if (expectedDate && timelineResults.length > 0) {
|
||||
// 获取有效的最后流程完成日期
|
||||
let effectiveLastProcess = null;
|
||||
let lastCompletionDate: Date | null = null;
|
||||
|
||||
for (let i = timelineResults.length - 1; i >= 0; i--) {
|
||||
const process = timelineResults[i];
|
||||
const processDate = new Date(process.estimatedEnd);
|
||||
|
||||
if (!isNaN(processDate.getTime()) && process.estimatedEnd !== '时效值为0') {
|
||||
effectiveLastProcess = process;
|
||||
lastCompletionDate = processDate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!effectiveLastProcess) {
|
||||
effectiveLastProcess = timelineResults[timelineResults.length - 1];
|
||||
lastCompletionDate = new Date(effectiveLastProcess.estimatedEnd);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
hasNegativeBuffer = !canIncrease;
|
||||
|
||||
return (
|
||||
<Card key={index} style={{ marginBottom: '8px', padding: '12px', position: 'relative' }}>
|
||||
<div style={{ position: 'absolute', top: '12px', left: '12px', zIndex: 1, display: 'flex', alignItems: 'center', gap: '16px' }}>
|
||||
@ -6070,6 +5829,15 @@ export default function App() {
|
||||
if (!updated) {
|
||||
return;
|
||||
}
|
||||
const deficit = computeBufferDeficitDaysUsingEndDelta(updated);
|
||||
const prevDeficit = lastBufferDeficitRef.current;
|
||||
lastBufferDeficitRef.current = deficit;
|
||||
if (deficit > 0 && Math.ceil(deficit) > Math.ceil(prevDeficit)) {
|
||||
Modal.warning({
|
||||
title: '缓冲期不足',
|
||||
content: `当前缓冲期无法覆盖本次超期,缺口 ${Math.ceil(deficit)} 天`,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6132,13 +5900,13 @@ export default function App() {
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => handleComplexAdjustment(index, 1)}
|
||||
disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero) || hasNegativeBuffer}
|
||||
disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero)}
|
||||
style={{ minWidth: '24px', height: '24px', fontSize: '13px' }}
|
||||
title={
|
||||
isCurrentTurnoverZero ? '周转周期为零,无法调整' :
|
||||
(hasTurnoverNodeWithZero && !isTurnoverNode) ? '周转周期为零,其他节点无法增加' :
|
||||
(isTurnoverNode && !isCurrentTurnoverZero) ? '周转周期节点会自动调整,无法手动修改' :
|
||||
hasNegativeBuffer ? `缓冲期已耗尽且交期余量不足,不允许增加时效` : ''
|
||||
''
|
||||
}
|
||||
>
|
||||
+1
|
||||
@ -6146,13 +5914,13 @@ export default function App() {
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => handleComplexAdjustment(index, 0.5)}
|
||||
disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero) || hasNegativeBuffer}
|
||||
disabled={isCurrentTurnoverZero || (hasTurnoverNodeWithZero && !isTurnoverNode) || (isTurnoverNode && !isCurrentTurnoverZero)}
|
||||
style={{ minWidth: '28px', height: '24px', fontSize: '13px' }}
|
||||
title={
|
||||
isCurrentTurnoverZero ? '周转周期为零,无法调整' :
|
||||
(hasTurnoverNodeWithZero && !isTurnoverNode) ? '周转周期为零,其他节点无法增加' :
|
||||
(isTurnoverNode && !isCurrentTurnoverZero) ? '周转周期节点会自动调整,无法手动修改' :
|
||||
hasNegativeBuffer ? `缓冲期已耗尽且交期余量不足,不允许增加时效` : ''
|
||||
''
|
||||
}
|
||||
>
|
||||
+0.5
|
||||
@ -6293,6 +6061,9 @@ export default function App() {
|
||||
adjustedCompletionDate.setDate(adjustedCompletionDate.getDate() + completionDateAdjustment);
|
||||
const deliveryDate = new Date(adjustedCompletionDate);
|
||||
deliveryDate.setDate(deliveryDate.getDate() + dynamicBufferDays);
|
||||
const lockedDeliveryDate = (mode === 'adjust' && isExpectedDeliveryDateLocked && lockedExpectedDeliveryDateTs !== null)
|
||||
? new Date(lockedExpectedDeliveryDateTs)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -6332,7 +6103,9 @@ export default function App() {
|
||||
{dynamicBufferDays}天
|
||||
</span>
|
||||
<span style={{ fontSize: 16, color: '#1890ff', fontWeight: 700 }}>=</span>
|
||||
<span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}>结束日期</span>
|
||||
<span style={{ fontSize: 13, color: '#666', fontWeight: 600 }}>
|
||||
{lockedDeliveryDate ? '结束日期(未锁)' : '结束日期'}
|
||||
</span>
|
||||
<span style={{
|
||||
fontSize: 13,
|
||||
color: '#52c41a',
|
||||
@ -6346,6 +6119,24 @@ export default function App() {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{lockedDeliveryDate && (
|
||||
<div style={{ marginTop: 8, display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: 8, flexWrap: 'nowrap', whiteSpace: 'nowrap', overflowX: 'auto' }}>
|
||||
<Text style={{ fontSize: 13, color: '#333', fontWeight: 600, whiteSpace: 'nowrap' }}>结束日期(已锁定)</Text>
|
||||
<span style={{
|
||||
fontSize: 13,
|
||||
color: '#722ed1',
|
||||
backgroundColor: 'rgba(114, 46, 209, 0.08)',
|
||||
border: '1px solid rgba(114, 46, 209, 0.24)',
|
||||
borderRadius: 6,
|
||||
padding: '3px 8px',
|
||||
fontWeight: 700,
|
||||
whiteSpace: 'nowrap'
|
||||
}}>
|
||||
{formatDate(lockedDeliveryDate)}({getDayOfWeek(lockedDeliveryDate)})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 第二行:客户期望日期(可更改,优化展示为标签样式) */}
|
||||
<div style={{ marginTop: 8, display: 'flex', alignItems: 'center', justifyContent: 'flex-start', gap: 8, flexWrap: 'nowrap', whiteSpace: 'nowrap', overflowX: 'auto' }}>
|
||||
<Text style={{ fontSize: 13, color: '#333', fontWeight: 600, whiteSpace: 'nowrap' }}>客户期望日期(可更改)</Text>
|
||||
|
||||
Reference in New Issue
Block a user