feat(flowEditor): 添加历史版本查看功能

master
钟良源 3 weeks ago
parent 04189e12b9
commit 21d6e7d472

@ -1,5 +1,5 @@
import axios from 'axios'; import axios from 'axios';
import { FlowDefinition, appFlowModel, queryParams } from '@/api/interface/index'; import { FlowDefinition, appFlowModel, historyPageParams } from '@/api/interface/index';
// 公共路径 // 公共路径
const urlPrefix = '/api/v1/bpms-workbench'; const urlPrefix = '/api/v1/bpms-workbench';
@ -66,7 +66,7 @@ export function importComponenets(data: appFlowModel, appId: string) {
} }
// 根据主流程id分页 // 根据主流程id分页
export function historyPage(data: queryParams) { export function historyPage(data: historyPageParams) {
return axios.post(`${urlPrefix}/appRes/historyPage`, data); return axios.post(`${urlPrefix}/appRes/historyPage`, data);
} }

@ -37,6 +37,12 @@ export interface queryParams {
total?: number; total?: number;
} }
export interface historyPageParams {
"pageSize": number,
"currPage": number,
"appId": string
}
export interface apiResData { export interface apiResData {
list: applicationModel[]; list: applicationModel[];
} }

@ -1,6 +1,7 @@
import React from 'react'; import React, { useState } from 'react';
import { Button } from '@arco-design/web-react'; import { Button } from '@arco-design/web-react';
import { IconSend } from '@arco-design/web-react/icon'; import { IconSend, IconOrderedList } from '@arco-design/web-react/icon';
import HistoryVersionModal from './historyVersionModal';
interface HandlerBarProps { interface HandlerBarProps {
onPublish?: () => void; onPublish?: () => void;
@ -8,20 +9,43 @@ interface HandlerBarProps {
} }
const HandlerBar: React.FC<HandlerBarProps> = ({ onPublish, isRunning }) => { const HandlerBar: React.FC<HandlerBarProps> = ({ onPublish, isRunning }) => {
const [historyModalVisible, setHistoryModalVisible] = useState(false);
const handleHistoryClick = () => {
setHistoryModalVisible(true);
};
return ( return (
<div className="handlex-bar" style={{ paddingRight: 50 }}> <>
<div className="handlex-bar-item"> <div className="handlex-bar" style={{ paddingRight: 50 }}>
<Button <div className="handlex-bar-item">
type="primary" <Button
shape="round" type="outline"
onClick={onPublish} shape="round"
disabled={isRunning} disabled={isRunning}
> style={{ marginRight: 8, padding: '0 8px', backgroundColor: '#fff' }}
<IconSend /> onClick={handleHistoryClick}
>
</Button> <IconOrderedList />
</Button>
<Button
type="primary"
shape="round"
onClick={onPublish}
disabled={isRunning}
>
<IconSend />
</Button>
</div>
</div> </div>
</div>
<HistoryVersionModal
visible={historyModalVisible}
onCancel={() => setHistoryModalVisible(false)}
/>
</>
); );
}; };

@ -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;
Loading…
Cancel
Save