1
This commit is contained in:
2026-01-06 12:22:46 +08:00
parent a9c2e7d967
commit 618b7bddc7

View File

@ -1,5 +1,5 @@
import { bitable, FieldType, ToastType } 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, Switch } from '@douyinfe/semi-ui'; import { Button, Typography, List, Card, Space, Divider, Spin, Table, Select, Modal, DatePicker, InputNumber, Input, Progress, Switch, Tag } from '@douyinfe/semi-ui';
import { useState, useEffect, useRef } from 'react'; import { useState, useEffect, useRef } from 'react';
import { addDays, format, differenceInCalendarDays } from 'date-fns'; import { addDays, format, differenceInCalendarDays } from 'date-fns';
import { zhCN } from 'date-fns/locale'; import { zhCN } from 'date-fns/locale';
@ -49,6 +49,14 @@ export default function App() {
const [labelAdjustmentFlow, setLabelAdjustmentFlow] = useState(false); const [labelAdjustmentFlow, setLabelAdjustmentFlow] = useState(false);
const [selectedLabels, setSelectedLabels] = useState<{[key: string]: string | string[]}>({}); const [selectedLabels, setSelectedLabels] = useState<{[key: string]: string | string[]}>({});
const [labelLoading, setLabelLoading] = useState(false); const [labelLoading, setLabelLoading] = useState(false);
const [excludedDatesOverride, setExcludedDatesOverride] = useState<string[]>([]);
const [excludedDatesOverrideText, setExcludedDatesOverrideText] = useState<string>('');
const [excludedDatesByNodeOverride, setExcludedDatesByNodeOverride] = useState<Record<string, string[]>>({});
const excludedDatesByNodeOverrideRef = useRef<Record<string, string[]>>({});
const pendingRecalculateAfterExcludedDatesRef = useRef(false);
const [excludedDatesAdjustVisible, setExcludedDatesAdjustVisible] = useState(false);
const [excludedDatesByNodeDraft, setExcludedDatesByNodeDraft] = useState<Record<string, string[]>>({});
const [excludedDatesAddDraft, setExcludedDatesAddDraft] = useState<Record<string, Date | null>>({});
// 客户期望日期状态 // 客户期望日期状态
const [expectedDate, setExpectedDate] = useState<Date | null>(null); const [expectedDate, setExpectedDate] = useState<Date | null>(null);
@ -130,6 +138,13 @@ export default function App() {
setSelectedRecords([]); setSelectedRecords([]);
setRecordDetails([]); setRecordDetails([]);
setSelectedLabels({}); setSelectedLabels({});
setExcludedDatesOverride([]);
setExcludedDatesOverrideText('');
setExcludedDatesByNodeOverride({});
excludedDatesByNodeOverrideRef.current = {};
setExcludedDatesAdjustVisible(false);
setExcludedDatesByNodeDraft({});
setExcludedDatesAddDraft({});
setExpectedDate(null); setExpectedDate(null);
setStartTime(null); setStartTime(null);
setCalculatedRequiredStartTime(null); setCalculatedRequiredStartTime(null);
@ -223,6 +238,10 @@ export default function App() {
ensureStyleColorDefaults(); ensureStyleColorDefaults();
} }
}, [timelineVisible]); }, [timelineVisible]);
useEffect(() => {
excludedDatesByNodeOverrideRef.current = excludedDatesByNodeOverride || {};
}, [excludedDatesByNodeOverride]);
const VIEW_ID = 'vewb28sjuX'; const VIEW_ID = 'vewb28sjuX';
// 标签表ID // 标签表ID
@ -350,6 +369,212 @@ export default function App() {
} }
}; };
const normalizeExcludedDatesOverride = (raw: any): string[] => {
const parts: string[] = [];
if (Array.isArray(raw)) {
for (const el of raw) {
const s = typeof el === 'string' ? el : extractText(el);
if (s) parts.push(s);
}
} else if (typeof raw === 'string') {
parts.push(...raw.split(/[\,\n\r\s]+/));
} else if (raw && typeof raw === 'object') {
const s = extractText(raw);
if (s) parts.push(...s.split(/[\,\n\r\s]+/));
}
const out: string[] = [];
const seen = new Set<string>();
for (const p of parts) {
const t = (p || '').trim();
if (!t) continue;
const d = parseDate(t);
if (!d || isNaN(d.getTime())) continue;
const normalized = formatDate(d, 'DISPLAY_DATE_ONLY');
if (!normalized) continue;
if (!seen.has(normalized)) {
seen.add(normalized);
out.push(normalized);
}
}
out.sort();
return out;
};
const restoreExcludedDatesOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
const candidates: any[] = [
snapshot?.excludedDatesOverride,
snapshot?.timelineCalculationState?.excludedDatesOverride,
snapshot?.timelineCalculationState?.excludedDates,
];
for (const c of candidates) {
const normalized = normalizeExcludedDatesOverride(c);
if (normalized.length > 0) {
setExcludedDatesOverride(normalized);
setExcludedDatesOverrideText(normalized.join('\n'));
return;
}
}
const results = Array.isArray(timelineResultsCandidate)
? timelineResultsCandidate
: (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);
if (results.length > 0) {
const union: string[] = [];
for (const r of results) {
const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
union.push(...arr);
}
const normalized = normalizeExcludedDatesOverride(union);
if (normalized.length > 0) {
setExcludedDatesOverride(normalized);
setExcludedDatesOverrideText(normalized.join('\n'));
return;
}
}
setExcludedDatesOverride([]);
setExcludedDatesOverrideText('');
};
const handleExcludedDatesOverrideTextChange = (next: any) => {
const text = typeof next === 'string'
? next
: (next?.target?.value ?? '');
setExcludedDatesOverrideText(text);
setExcludedDatesOverride(normalizeExcludedDatesOverride(text));
};
const buildExcludedDatesNodeKey = (nodeName: any, processOrder: any, indexFallback?: number) => {
const name = (typeof nodeName === 'string' ? nodeName : extractText(nodeName)).trim();
const order = (typeof processOrder === 'number' || typeof processOrder === 'string')
? String(processOrder)
: '';
if (name && order) return `${order}::${name}`;
if (name) return name;
if (indexFallback !== undefined) return `#${indexFallback + 1}`;
return '#unknown';
};
const buildExcludedDatesByNodeFromTimeline = (results: any[]): Record<string, string[]> => {
const map: Record<string, string[]> = {};
for (let i = 0; i < results.length; i++) {
const r = results[i];
const key = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, i);
const arr = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
map[key] = normalizeExcludedDatesOverride(arr);
}
return map;
};
const normalizeExcludedDatesByNodeMap = (raw: any): Record<string, string[]> => {
const out: Record<string, string[]> = {};
if (!raw) return out;
if (typeof raw !== 'object') return out;
for (const [k, v] of Object.entries(raw)) {
const key = (k || '').trim();
if (!key) continue;
const normalized = normalizeExcludedDatesOverride(v);
out[key] = normalized;
}
return out;
};
const restoreExcludedDatesByNodeOverrideFromSnapshot = (snapshot: any, timelineResultsCandidate?: any[]) => {
const candidates: any[] = [
snapshot?.excludedDatesByNodeOverride,
snapshot?.excludedDatesByNode,
snapshot?.timelineCalculationState?.excludedDatesByNodeOverride,
snapshot?.timelineCalculationState?.excludedDatesByNode,
];
for (const c of candidates) {
const normalized = normalizeExcludedDatesByNodeMap(c);
if (Object.keys(normalized).length > 0) {
excludedDatesByNodeOverrideRef.current = normalized;
setExcludedDatesByNodeOverride(normalized);
return;
}
}
const results = Array.isArray(timelineResultsCandidate)
? timelineResultsCandidate
: (Array.isArray(snapshot?.timelineResults) ? snapshot.timelineResults : []);
if (results.length > 0) {
const derived = buildExcludedDatesByNodeFromTimeline(results);
excludedDatesByNodeOverrideRef.current = derived;
setExcludedDatesByNodeOverride(derived);
return;
}
excludedDatesByNodeOverrideRef.current = {};
setExcludedDatesByNodeOverride({});
};
const groupDatesByMonth = (dates: string[]) => {
const groups = new Map<string, string[]>();
for (const d of dates) {
const ds = (d || '').trim();
if (!ds) continue;
const month = ds.slice(0, 7);
if (!groups.has(month)) groups.set(month, []);
groups.get(month)!.push(ds);
}
const out = Array.from(groups.entries())
.map(([month, ds]) => ({
month,
dates: Array.from(new Set(ds)).sort(),
}))
.sort((a, b) => a.month.localeCompare(b.month));
return out;
};
const openExcludedDatesAdjustModal = () => {
const baseResults = Array.isArray(timelineResults) ? timelineResults : [];
const draft: Record<string, string[]> = {};
for (let i = 0; i < baseResults.length; i++) {
const r = baseResults[i];
const key = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, i);
const existing = excludedDatesByNodeOverrideRef.current?.[key];
const fromResult = Array.isArray(r?.excludedDates) ? r.excludedDates : [];
draft[key] = normalizeExcludedDatesOverride(existing ?? fromResult);
}
setExcludedDatesByNodeDraft(draft);
setExcludedDatesAddDraft({});
setExcludedDatesAdjustVisible(true);
};
const closeExcludedDatesAdjustModal = () => {
setExcludedDatesAdjustVisible(false);
setExcludedDatesByNodeDraft({});
setExcludedDatesAddDraft({});
};
const removeExcludedDateFromDraft = (nodeKey: string, date: string) => {
setExcludedDatesByNodeDraft(prev => {
const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
const next = current.filter(d => d !== date);
return { ...(prev || {}), [nodeKey]: next };
});
};
const addExcludedDateToDraft = (nodeKey: string, date: Date | null) => {
if (!(date instanceof Date) || isNaN(date.getTime())) return;
const normalized = formatDate(date, 'DISPLAY_DATE_ONLY');
if (!normalized) return;
setExcludedDatesByNodeDraft(prev => {
const current = Array.isArray(prev?.[nodeKey]) ? prev[nodeKey] : [];
const next = Array.from(new Set([...current, normalized])).sort();
return { ...(prev || {}), [nodeKey]: next };
});
};
const clearExcludedDatesDraftForNode = (nodeKey: string) => {
setExcludedDatesByNodeDraft(prev => ({ ...(prev || {}), [nodeKey]: [] }));
};
// 根据货期记录ID读取节点详情并还原流程数据 // 根据货期记录ID读取节点详情并还原流程数据
const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => { const loadProcessDataFromDeliveryRecord = async (deliveryRecordId: string) => {
if (!deliveryRecordId) { if (!deliveryRecordId) {
@ -434,6 +659,8 @@ export default function App() {
const snapshot = JSON.parse(deliverySnapStr); const snapshot = JSON.parse(deliverySnapStr);
restoreTimelineDirectionFromSnapshot(snapshot); restoreTimelineDirectionFromSnapshot(snapshot);
restoreBaseBufferDaysFromSnapshot(snapshot); restoreBaseBufferDaysFromSnapshot(snapshot);
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
// 恢复页面与全局状态 // 恢复页面与全局状态
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels); if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
@ -487,6 +714,8 @@ export default function App() {
setIsExpectedDeliveryDateLocked(false); setIsExpectedDeliveryDateLocked(false);
} }
} }
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
if (snapshot.expectedDateTimestamp) { if (snapshot.expectedDateTimestamp) {
setExpectedDate(new Date(snapshot.expectedDateTimestamp)); setExpectedDate(new Date(snapshot.expectedDateTimestamp));
} else if (snapshot.expectedDateString) { } else if (snapshot.expectedDateString) {
@ -594,6 +823,8 @@ export default function App() {
setIsRestoringSnapshot(true); setIsRestoringSnapshot(true);
restoreTimelineDirectionFromSnapshot(snapshot); restoreTimelineDirectionFromSnapshot(snapshot);
restoreBaseBufferDaysFromSnapshot(snapshot); restoreBaseBufferDaysFromSnapshot(snapshot);
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot?.timelineResults);
if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels); if (snapshot.selectedLabels) setSelectedLabels(snapshot.selectedLabels);
if (!mode && snapshot.mode) setMode(snapshot.mode); if (!mode && snapshot.mode) setMode(snapshot.mode);
@ -782,6 +1013,8 @@ export default function App() {
if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum); if (vNum !== null && !isNaN(vNum)) setCurrentVersionNumber(vNum);
} }
if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments); if (snapshot.timelineAdjustments) setTimelineAdjustments(snapshot.timelineAdjustments);
restoreExcludedDatesOverrideFromSnapshot(snapshot, snapshot.timelineResults);
restoreExcludedDatesByNodeOverrideFromSnapshot(snapshot, snapshot.timelineResults);
if (snapshot.expectedDateTimestamp) { if (snapshot.expectedDateTimestamp) {
setExpectedDate(new Date(snapshot.expectedDateTimestamp)); setExpectedDate(new Date(snapshot.expectedDateTimestamp));
} else if (snapshot.expectedDateString) { } else if (snapshot.expectedDateString) {
@ -1084,6 +1317,8 @@ export default function App() {
// 重组完整的 timelineResults // 重组完整的 timelineResults
restoreTimelineDirectionFromSnapshot(globalSnapshotData); restoreTimelineDirectionFromSnapshot(globalSnapshotData);
restoreBaseBufferDaysFromSnapshot(globalSnapshotData); restoreBaseBufferDaysFromSnapshot(globalSnapshotData);
restoreExcludedDatesOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
restoreExcludedDatesByNodeOverrideFromSnapshot(globalSnapshotData, nodeSnapshots);
setTimelineResults(nodeSnapshots); setTimelineResults(nodeSnapshots);
setTimelineVisible(true); setTimelineVisible(true);
@ -1902,7 +2137,7 @@ export default function App() {
try { try {
const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID); const deliveryTable = await bitable.base.getTable(DELIVERY_RECORD_TABLE_ID);
const versionField: any = await deliveryTable.getField(DELIVERY_VERSION_FIELD_ID); const versionField: any = await deliveryTable.getField(DELIVERY_VERSION_FIELD_ID);
const planFilter = { const planFilter: any = {
conjunction: 'and', conjunction: 'and',
conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }] conditions: [{ fieldId: DELIVERY_RECORD_IDS_FIELD_ID, operator: 'is', value: planText }]
}; };
@ -1910,38 +2145,115 @@ export default function App() {
? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }] ? [{ fieldId: DELIVERY_CREATE_TIME_FIELD_ID, desc: true }]
: undefined; : undefined;
let token: any = undefined;
const eps = 1e-9; const eps = 1e-9;
for (let i = 0; i < 10000; i++) { const normalizePlanText = (s: string) => s.replace(/\s+/g, '').trim();
const res: any = await versionField.getFieldValueListByPage({ const parseVersionFromRaw = (raw: any): number | null => {
pageSize: 200, if (typeof raw === 'number' && Number.isFinite(raw)) return raw;
pageToken: token, const s = typeof raw === 'string' ? raw : extractText(raw);
filter: planFilter, const m = (s || '').match(/\d+(?:\.\d+)?/);
sort, return m ? parseFloat(m[0]) : null;
stringValue: true };
});
const fieldValues: any[] = Array.isArray(res?.fieldValues) ? res.fieldValues : [];
for (const fv of fieldValues) { {
const recordId = fv?.recordId; try {
const raw = fv?.value; let token: any = undefined;
let v: number | null = null; for (let i = 0; i < 10000; i++) {
if (typeof raw === 'number') { const res: any = await deliveryTable.getRecordsByPage({
v = raw; pageSize: 200,
} else { pageToken: token,
const s = typeof raw === 'string' ? raw : extractText(raw); filter: planFilter,
const m = (s || '').match(/\d+(?:\.\d+)?/); sort,
if (m) v = parseFloat(m[0]); });
} const recs: any[] = Array.isArray(res?.records) ? res.records : [];
if (v !== null && Math.abs(v - planVersion) < eps) {
return recordId || null;
}
}
const nextToken = res?.pageToken; for (const r of recs) {
const hasMore = !!res?.hasMore; const recordId = r?.recordId || r?.id || null;
token = nextToken; if (!recordId) continue;
if (!hasMore && !nextToken) break;
const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
if (!recordPlanText) continue;
if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;
const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
const recordVersion = parseVersionFromRaw(recordVersionRaw);
if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
return recordId;
}
}
const nextToken = res?.pageToken;
const hasMore = !!res?.hasMore;
token = nextToken;
if (!hasMore && !nextToken) break;
}
} catch {}
}
{
try {
let token: any = undefined;
for (let i = 0; i < 10000; i++) {
const res: any = await versionField.getFieldValueListByPage({
pageSize: 200,
pageToken: token,
filter: planFilter,
sort,
stringValue: true
});
const fieldValues: any[] = Array.isArray(res?.fieldValues) ? res.fieldValues : [];
for (const fv of fieldValues) {
const recordId = fv?.recordId;
const v = parseVersionFromRaw(fv?.value);
if (v !== null && Math.abs(v - planVersion) < eps) {
return recordId || null;
}
}
const nextToken = res?.pageToken;
const hasMore = !!res?.hasMore;
token = nextToken;
if (!hasMore && !nextToken) break;
}
} catch {}
}
{
try {
let token: any = undefined;
for (let i = 0; i < 10000; i++) {
const res: any = await deliveryTable.getRecordsByPage({
pageSize: 200,
pageToken: token,
sort,
});
const recs: any[] = Array.isArray(res?.records) ? res.records : [];
for (const r of recs) {
const recordId = r?.recordId || r?.id || null;
if (!recordId) continue;
const recordPlanRaw = (r?.fields || {})[DELIVERY_RECORD_IDS_FIELD_ID];
const recordPlanText = typeof recordPlanRaw === 'string' ? recordPlanRaw : extractText(recordPlanRaw);
if (!recordPlanText) continue;
if (normalizePlanText(recordPlanText) !== normalizePlanText(planText)) continue;
const recordVersionRaw = (r?.fields || {})[DELIVERY_VERSION_FIELD_ID];
const recordVersion = parseVersionFromRaw(recordVersionRaw);
if (recordVersion !== null && Math.abs(recordVersion - planVersion) < eps) {
return recordId;
}
}
const nextToken = res?.pageToken;
const hasMore = !!res?.hasMore;
token = nextToken;
if (!hasMore && !nextToken) break;
}
} catch {}
} }
return null; return null;
} catch (error) { } catch (error) {
@ -1986,6 +2298,8 @@ export default function App() {
const currentExpectedDate = overrideData?.expectedDate || expectedDate; const currentExpectedDate = overrideData?.expectedDate || expectedDate;
const currentStartTime = overrideData?.startTime || startTime; const currentStartTime = overrideData?.startTime || startTime;
const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : []; const currentExcludedDates = Array.isArray(overrideData?.excludedDates) ? overrideData!.excludedDates : [];
const globalExcludedDates = normalizeExcludedDatesOverride(currentExcludedDates);
const excludedByNode = excludedDatesByNodeOverrideRef.current || {};
const isBackward = timelineDirection === 'backward'; const isBackward = timelineDirection === 'backward';
console.log('=== handleCalculateTimeline - 使用的数据 ==='); console.log('=== handleCalculateTimeline - 使用的数据 ===');
@ -2381,7 +2695,16 @@ export default function App() {
for (let i = 0; i < nodesToProcess.length; i++) { for (let i = 0; i < nodesToProcess.length; i++) {
const processNode = nodesToProcess[i]; const processNode = nodesToProcess[i];
const nodeExcludedDates = Array.from(new Set([...(processNode.excludedDates || []), ...currentExcludedDates])).filter(Boolean); const nodeKey = buildExcludedDatesNodeKey(processNode?.nodeName, processNode?.processOrder, i);
const overrideList = Object.prototype.hasOwnProperty.call(excludedByNode, nodeKey)
? excludedByNode[nodeKey]
: undefined;
const baseList = Array.isArray(processNode?.excludedDates) ? processNode.excludedDates : [];
const selectedList = Array.isArray(overrideList) ? overrideList : baseList;
const nodeExcludedDates = Array.from(new Set([
...normalizeExcludedDatesOverride(selectedList),
...globalExcludedDates,
])).filter(Boolean);
let timelineValue = null; let timelineValue = null;
let matchedTimelineRecord = null; let matchedTimelineRecord = null;
@ -3479,6 +3802,15 @@ export default function App() {
} }
}, [actualCompletionDates, isRestoringSnapshot]); }, [actualCompletionDates, isRestoringSnapshot]);
useEffect(() => {
if (!pendingRecalculateAfterExcludedDatesRef.current) return;
if (isRestoringSnapshot) return;
pendingRecalculateAfterExcludedDatesRef.current = false;
if (timelineResults.length > 0) {
recalculateTimeline(timelineAdjustments, true);
}
}, [timelineResults, isRestoringSnapshot]);
// 捕获初始状态快照(在首次生成/还原出完整时间线后) // 捕获初始状态快照(在首次生成/还原出完整时间线后)
useEffect(() => { useEffect(() => {
if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) { if (!hasCapturedInitialSnapshotRef.current && timelineResults.length > 0) {
@ -3821,6 +4153,8 @@ export default function App() {
text2, text2,
mode, mode,
timelineDirection, timelineDirection,
excludedDatesOverride,
excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride,
lockedExpectedDeliveryDateTs, lockedExpectedDeliveryDateTs,
isExpectedDeliveryDateLocked, isExpectedDeliveryDateLocked,
selectedLabels: currentSelectedLabels, selectedLabels: currentSelectedLabels,
@ -3863,7 +4197,9 @@ export default function App() {
totalNodes: timelineResults.length, totalNodes: timelineResults.length,
hasValidResults: timelineResults.length > 0, hasValidResults: timelineResults.length > 0,
lastCalculationMode: mode, lastCalculationMode: mode,
timelineDirection timelineDirection,
excludedDatesOverride,
excludedDatesByNodeOverride: excludedDatesByNodeOverrideRef.current || excludedDatesByNodeOverride
}, },
totalNodes: timelineResults.length, totalNodes: timelineResults.length,
isGlobalSnapshot: true isGlobalSnapshot: true
@ -5362,6 +5698,8 @@ export default function App() {
setMode(next); setMode(next);
setIsExpectedDeliveryDateLocked(false); setIsExpectedDeliveryDateLocked(false);
setLockedExpectedDeliveryDateTs(null); setLockedExpectedDeliveryDateTs(null);
setExcludedDatesOverride([]);
setExcludedDatesOverrideText('');
}} }}
optionList={[ optionList={[
{ value: 'generate', label: '生成流程日期' }, { value: 'generate', label: '生成流程日期' },
@ -5645,6 +5983,120 @@ export default function App() {
})()} })()}
</Modal> </Modal>
<Modal
title="跳过日期调整(按节点)"
visible={excludedDatesAdjustVisible}
maskClosable={false}
onCancel={closeExcludedDatesAdjustModal}
footer={
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Text type="tertiary">
{Array.isArray(timelineResults) ? timelineResults.length : 0}
{Object.values(excludedDatesByNodeDraft || {}).filter(v => Array.isArray(v) && v.length > 0).length}
</Text>
<Space>
<Button onClick={closeExcludedDatesAdjustModal}></Button>
<Button
type="primary"
onClick={() => {
const next = normalizeExcludedDatesByNodeMap(excludedDatesByNodeDraft || {});
excludedDatesByNodeOverrideRef.current = next;
setExcludedDatesByNodeOverride(next);
pendingRecalculateAfterExcludedDatesRef.current = true;
setTimelineResults(prev => {
const base = Array.isArray(prev) ? prev : [];
return base.map((r: any, idx: number) => {
const nodeKey = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, idx);
if (!Object.prototype.hasOwnProperty.call(next, nodeKey)) return r;
const list = Array.isArray(next[nodeKey]) ? next[nodeKey] : [];
return { ...r, excludedDates: list };
});
});
setExcludedDatesAdjustVisible(false);
}}
>
</Button>
</Space>
</div>
}
style={{ width: 980 }}
bodyStyle={{ maxHeight: '70vh', overflowY: 'auto' }}
>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{(Array.isArray(timelineResults) ? timelineResults : []).map((r: any, idx: number) => {
const nodeKey = buildExcludedDatesNodeKey(r?.nodeName, r?.processOrder, idx);
const dates = Array.isArray(excludedDatesByNodeDraft?.[nodeKey]) ? excludedDatesByNodeDraft[nodeKey] : [];
const groups = groupDatesByMonth(dates);
const pickerValue = excludedDatesAddDraft?.[nodeKey] ?? null;
return (
<Card
key={nodeKey}
className="card-enhanced"
bodyStyle={{ padding: 12 }}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
<Text strong style={{ fontSize: 14 }}>
{typeof r?.processOrder !== 'undefined' ? `#${r.processOrder} ` : ''}{r?.nodeName || nodeKey}
</Text>
<Space>
<DatePicker
style={{ width: 160 }}
value={pickerValue ?? undefined}
placeholder="选择日期"
format="yyyy-MM-dd"
onChange={(v) => {
let d: Date | null = null;
if (v instanceof Date) d = v;
else if (typeof v === 'string') {
const parsed = parseDate(v);
d = parsed && !isNaN(parsed.getTime()) ? parsed : null;
}
setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: d }));
}}
/>
<Button
onClick={() => {
const d = excludedDatesAddDraft?.[nodeKey] ?? null;
addExcludedDateToDraft(nodeKey, d);
setExcludedDatesAddDraft(prev => ({ ...(prev || {}), [nodeKey]: null }));
}}
>
</Button>
<Button type="tertiary" onClick={() => clearExcludedDatesDraftForNode(nodeKey)}></Button>
</Space>
</div>
<div style={{ marginTop: 10 }}>
{groups.length === 0 ? (
<Text type="tertiary"></Text>
) : (
groups.map(g => (
<div key={g.month} style={{ marginBottom: 10 }}>
<Text style={{ display: 'block', marginBottom: 6, color: '#595959' }}>{g.month}</Text>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 8 }}>
{g.dates.map(d => (
<Tag
key={d}
closable
onClose={() => removeExcludedDateFromDraft(nodeKey, d)}
style={{ backgroundColor: '#fff7e6', borderColor: '#ffd591', color: '#ad4e00' }}
>
{d}
</Tag>
))}
</div>
</div>
))
)}
</div>
</Card>
);
})}
</div>
</Modal>
{/* 时效计算结果模态框 */} {/* 时效计算结果模态框 */}
<Modal <Modal
title={ title={
@ -6346,6 +6798,17 @@ export default function App() {
</Text> </Text>
</div> </div>
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fffbe6', borderRadius: '4px' }}>
<Space align="center" spacing={8} style={{ width: '100%', justifyContent: 'space-between' }}>
<Text strong style={{ color: '#fa8c16' }}></Text>
<Space align="center" spacing={8}>
<Text type="tertiary">
{Object.values(excludedDatesByNodeOverride || {}).filter(v => Array.isArray(v) && v.length > 0).length}
</Text>
<Button size="small" onClick={openExcludedDatesAdjustModal}></Button>
</Space>
</Space>
</div>
{Object.keys(timelineAdjustments).length > 0 && ( {Object.keys(timelineAdjustments).length > 0 && (
<div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fff7e6', borderRadius: '4px' }}> <div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#fff7e6', borderRadius: '4px' }}>
<Text strong style={{ color: '#fa8c16' }}></Text> <Text strong style={{ color: '#fa8c16' }}></Text>