|
|
|
@ -0,0 +1,210 @@
|
|
|
|
|
|
|
|
import React, { useState, useEffect, useMemo } from 'react';
|
|
|
|
|
|
|
|
import { Modal, List, Message, Spin, Pagination } from '@arco-design/web-react';
|
|
|
|
|
|
|
|
import { ReactFlowProvider, ReactFlow, Background, Controls } from '@xyflow/react';
|
|
|
|
|
|
|
|
import '@xyflow/react/dist/style.css';
|
|
|
|
|
|
|
|
import { historyPage } from '@/api/appRes';
|
|
|
|
|
|
|
|
import { convertFlowData } from '@/utils/convertFlowData';
|
|
|
|
|
|
|
|
import { nodeTypes } from '@/components/FlowEditor/node';
|
|
|
|
|
|
|
|
import { useSelector } from 'react-redux';
|
|
|
|
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface HistoryVersionModalProps {
|
|
|
|
|
|
|
|
visible: boolean;
|
|
|
|
|
|
|
|
onCancel: () => void;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface HistoryItem {
|
|
|
|
|
|
|
|
appResId: string;
|
|
|
|
|
|
|
|
remarks: string;
|
|
|
|
|
|
|
|
res: {
|
|
|
|
|
|
|
|
appId: string;
|
|
|
|
|
|
|
|
compList: any;
|
|
|
|
|
|
|
|
events: any[];
|
|
|
|
|
|
|
|
main: {
|
|
|
|
|
|
|
|
appEventDefinition: any;
|
|
|
|
|
|
|
|
components: any;
|
|
|
|
|
|
|
|
flowId: string;
|
|
|
|
|
|
|
|
flowName: string;
|
|
|
|
|
|
|
|
sceneId: string;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
projectId: string;
|
|
|
|
|
|
|
|
subMap: any;
|
|
|
|
|
|
|
|
subs: any[];
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
timestamp: number;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const HistoryVersionModal: React.FC<HistoryVersionModalProps> = ({ visible, onCancel }) => {
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
|
|
const [historyList, setHistoryList] = useState<HistoryItem[]>([]);
|
|
|
|
|
|
|
|
const [selectedHistory, setSelectedHistory] = useState<HistoryItem | null>(null);
|
|
|
|
|
|
|
|
const [currPage, setCurrPage] = useState(1);
|
|
|
|
|
|
|
|
const [totalCount, setTotalCount] = useState(0);
|
|
|
|
|
|
|
|
const { currentAppData } = useSelector(state => state.ideContainer);
|
|
|
|
|
|
|
|
const pageSize = 10;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取历史版本列表
|
|
|
|
|
|
|
|
const fetchHistoryList = async (page = 1) => {
|
|
|
|
|
|
|
|
if (!currentAppData?.id) return;
|
|
|
|
|
|
|
|
console.log('currentAppData:', currentAppData);
|
|
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
const response: any = await historyPage({
|
|
|
|
|
|
|
|
appId: currentAppData?.id,
|
|
|
|
|
|
|
|
currPage: page,
|
|
|
|
|
|
|
|
pageSize
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (response.code === 200) {
|
|
|
|
|
|
|
|
const data = response.data;
|
|
|
|
|
|
|
|
setHistoryList(data.list);
|
|
|
|
|
|
|
|
setCurrPage(data.currPage);
|
|
|
|
|
|
|
|
setTotalCount(data.totalCount);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 只在第一次打开时默认选择第一个历史版本
|
|
|
|
|
|
|
|
if (data.list.length > 0 && page === 1) {
|
|
|
|
|
|
|
|
setSelectedHistory(data.list[0]);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
Message.error('获取历史版本失败');
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('获取历史版本失败:', error);
|
|
|
|
|
|
|
|
Message.error('获取历史版本失败');
|
|
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 当modal打开时获取历史版本列表
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
|
|
if (visible && currentAppData?.id) {
|
|
|
|
|
|
|
|
setSelectedHistory(null); // 重置选中状态
|
|
|
|
|
|
|
|
fetchHistoryList(1);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, [visible, currentAppData?.id]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化时间戳
|
|
|
|
|
|
|
|
const formatTimestamp = (timestamp: number) => {
|
|
|
|
|
|
|
|
return dayjs(Number(timestamp)).format('YYYY-MM-DD HH:mm:ss');
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用useMemo缓存转换后的画布数据,避免在渲染时调用convertFlowData导致Redux dispatch
|
|
|
|
|
|
|
|
const canvasData = useMemo(() => {
|
|
|
|
|
|
|
|
if (!selectedHistory) {
|
|
|
|
|
|
|
|
return { nodes: [], edges: [] };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const components = selectedHistory.res.main.components;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
return convertFlowData(components, false);
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
|
|
console.error('转换流程数据失败:', error);
|
|
|
|
|
|
|
|
return { nodes: [], edges: [] };
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}, [selectedHistory]);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染画布
|
|
|
|
|
|
|
|
const renderCanvas = () => {
|
|
|
|
|
|
|
|
if (!selectedHistory) {
|
|
|
|
|
|
|
|
return <div style={{ textAlign: 'center', padding: '50px' }}>请选择一个历史版本</div>;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<div style={{ width: '100%', height: '100%' }}>
|
|
|
|
|
|
|
|
<ReactFlowProvider>
|
|
|
|
|
|
|
|
<ReactFlow
|
|
|
|
|
|
|
|
key={`flow-${selectedHistory.timestamp}`}
|
|
|
|
|
|
|
|
nodes={canvasData.nodes}
|
|
|
|
|
|
|
|
edges={canvasData.edges}
|
|
|
|
|
|
|
|
nodeTypes={nodeTypes}
|
|
|
|
|
|
|
|
nodesDraggable={true}
|
|
|
|
|
|
|
|
nodesConnectable={false}
|
|
|
|
|
|
|
|
elementsSelectable={false}
|
|
|
|
|
|
|
|
panOnDrag={true}
|
|
|
|
|
|
|
|
zoomOnScroll={true}
|
|
|
|
|
|
|
|
fitView
|
|
|
|
|
|
|
|
proOptions={{ hideAttribution: true }}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Background />
|
|
|
|
|
|
|
|
<Controls />
|
|
|
|
|
|
|
|
</ReactFlow>
|
|
|
|
|
|
|
|
</ReactFlowProvider>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
|
|
title="历史版本"
|
|
|
|
|
|
|
|
visible={visible}
|
|
|
|
|
|
|
|
onCancel={onCancel}
|
|
|
|
|
|
|
|
footer={null}
|
|
|
|
|
|
|
|
style={{ width: '90vw', maxWidth: 1400 }}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<Spin loading={loading} style={{ width: '100%', height: '100%' }}>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', height: '100%' }}>
|
|
|
|
|
|
|
|
{/* 左侧历史版本列表 */}
|
|
|
|
|
|
|
|
<div
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
width: '300px',
|
|
|
|
|
|
|
|
borderRight: '1px solid #e5e6eb',
|
|
|
|
|
|
|
|
overflowY: 'auto',
|
|
|
|
|
|
|
|
padding: '16px'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<List
|
|
|
|
|
|
|
|
dataSource={historyList}
|
|
|
|
|
|
|
|
render={(item, index) => (
|
|
|
|
|
|
|
|
<List.Item
|
|
|
|
|
|
|
|
key={`${item.appResId}-${item.timestamp}-${index}`}
|
|
|
|
|
|
|
|
style={{
|
|
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
|
|
marginBottom: '8px',
|
|
|
|
|
|
|
|
border: '1px solid #e5e6eb',
|
|
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
|
|
backgroundColor: selectedHistory?.timestamp === item.timestamp ? '#f2f3f5' : '#fff'
|
|
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
onClick={() => setSelectedHistory(item)}
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
<div style={{ fontWeight: 500, marginBottom: '4px' }}>
|
|
|
|
|
|
|
|
{currentAppData.name} - {item.res.main.flowName}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div style={{ fontSize: '12px', color: '#86909c' }}>
|
|
|
|
|
|
|
|
{formatTimestamp(item.timestamp)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{item.remarks && (
|
|
|
|
|
|
|
|
<div style={{ fontSize: '12px', color: '#4e5969', marginTop: '4px' }}>
|
|
|
|
|
|
|
|
{item.remarks}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</List.Item>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'center', marginTop: '16px' }}>
|
|
|
|
|
|
|
|
<Pagination
|
|
|
|
|
|
|
|
current={currPage}
|
|
|
|
|
|
|
|
total={totalCount}
|
|
|
|
|
|
|
|
pageSize={pageSize}
|
|
|
|
|
|
|
|
onChange={(page) => fetchHistoryList(page)}
|
|
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
|
|
showTotal
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{/* 右侧画布区域 */}
|
|
|
|
|
|
|
|
<div style={{ flex: 1, position: 'relative', backgroundColor: '#f7f8fa' }}>
|
|
|
|
|
|
|
|
{renderCanvas()}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Spin>
|
|
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default HistoryVersionModal;
|