Fix scenario mode UI issues: add icon, fix blank space, enable node dragging, enlarge editor, fix API call

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2025-11-11 16:08:45 +00:00
parent ece9c9186d
commit 3112f9fc34
7 changed files with 134 additions and 48 deletions

View File

@ -69,7 +69,7 @@ export default {
{ {
path: '/scenario', path: '/scenario',
name: intl.get('场景模式'), name: intl.get('场景模式'),
icon: <IconFont type="ql-icon-scenario" />, icon: <IconFont type="ql-icon-script" />,
component: '@/pages/scenario/index', component: '@/pages/scenario/index',
}, },
{ {

View File

@ -558,9 +558,6 @@
"任一满足": "Any satisfied", "任一满足": "Any satisfied",
"字段名": "Field Name", "字段名": "Field Name",
"操作符": "Operator", "操作符": "Operator",
"包含": "Contains",
"不包含": "Not Contains",
"值": "Value",
"添加条件": "Add Condition", "添加条件": "Add Condition",
"动作配置": "Action Configuration", "动作配置": "Action Configuration",
"动作类型": "Action Type", "动作类型": "Action Type",
@ -570,28 +567,22 @@
"发送通知": "Send Notification", "发送通知": "Send Notification",
"变量名": "Variable Name", "变量名": "Variable Name",
"变量值": "Variable Value", "变量值": "Variable Value",
"命令": "Command",
"消息": "Message", "消息": "Message",
"添加动作": "Add Action", "添加动作": "Add Action",
"高级设置": "Advanced Settings", "高级设置": "Advanced Settings",
"延迟执行": "Delay Execution", "延迟执行": "Delay Execution",
"秒": "seconds",
"失败熔断阈值": "Failure Threshold", "失败熔断阈值": "Failure Threshold",
"连续失败多少次后自动禁用": "Auto-disable after N consecutive failures", "连续失败多少次后自动禁用": "Auto-disable after N consecutive failures",
"最大重试次数": "Max Retry Count", "最大重试次数": "Max Retry Count",
"重试延迟": "Retry Delay", "重试延迟": "Retry Delay",
"退避倍数": "Backoff Multiplier", "退避倍数": "Backoff Multiplier",
"每次重试延迟的乘数": "Multiplier for retry delay", "每次重试延迟的乘数": "Multiplier for retry delay",
"启用": "Enable",
"时间": "Time", "时间": "Time",
"条件匹配": "Condition Matched", "条件匹配": "Condition Matched",
"执行时间": "Execution Time", "执行时间": "Execution Time",
"重试次数": "Retry Count", "重试次数": "Retry Count",
"错误信息": "Error Message", "错误信息": "Error Message",
"是": "Yes",
"否": "No",
"共": "Total", "共": "Total",
"项": "items",
"日志名称": "Log Name", "日志名称": "Log Name",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate. Supports absolute paths like /dev/null", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "Custom log folder name to distinguish logs from different tasks. Leave blank to auto-generate. Supports absolute paths like /dev/null",

View File

@ -558,9 +558,6 @@
"任一满足": "任一满足", "任一满足": "任一满足",
"字段名": "字段名", "字段名": "字段名",
"操作符": "操作符", "操作符": "操作符",
"包含": "包含",
"不包含": "不包含",
"值": "值",
"添加条件": "添加条件", "添加条件": "添加条件",
"动作配置": "动作配置", "动作配置": "动作配置",
"动作类型": "动作类型", "动作类型": "动作类型",
@ -570,28 +567,22 @@
"发送通知": "发送通知", "发送通知": "发送通知",
"变量名": "变量名", "变量名": "变量名",
"变量值": "变量值", "变量值": "变量值",
"命令": "命令",
"消息": "消息", "消息": "消息",
"添加动作": "添加动作", "添加动作": "添加动作",
"高级设置": "高级设置", "高级设置": "高级设置",
"延迟执行": "延迟执行", "延迟执行": "延迟执行",
"秒": "秒",
"失败熔断阈值": "失败熔断阈值", "失败熔断阈值": "失败熔断阈值",
"连续失败多少次后自动禁用": "连续失败多少次后自动禁用", "连续失败多少次后自动禁用": "连续失败多少次后自动禁用",
"最大重试次数": "最大重试次数", "最大重试次数": "最大重试次数",
"重试延迟": "重试延迟", "重试延迟": "重试延迟",
"退避倍数": "退避倍数", "退避倍数": "退避倍数",
"每次重试延迟的乘数": "每次重试延迟的乘数", "每次重试延迟的乘数": "每次重试延迟的乘数",
"启用": "启用",
"时间": "时间", "时间": "时间",
"条件匹配": "条件匹配", "条件匹配": "条件匹配",
"执行时间": "执行时间", "执行时间": "执行时间",
"重试次数": "重试次数", "重试次数": "重试次数",
"错误信息": "错误信息", "错误信息": "错误信息",
"是": "是",
"否": "否",
"共": "共", "共": "共",
"项": "项",
"日志名称": "日志名称", "日志名称": "日志名称",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null", "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null",

View File

@ -25,6 +25,7 @@ export const FlowgramEditor: React.FC<FlowgramEditorProps> = ({ value, onChange
const [graph, setGraph] = useState<FlowgramGraph>(value || { nodes: [], edges: [] }); const [graph, setGraph] = useState<FlowgramGraph>(value || { nodes: [], edges: [] });
const [selectedNode, setSelectedNode] = useState<FlowgramNode | null>(null); const [selectedNode, setSelectedNode] = useState<FlowgramNode | null>(null);
const [drawerVisible, setDrawerVisible] = useState(false); const [drawerVisible, setDrawerVisible] = useState(false);
const [draggedNode, setDraggedNode] = useState<FlowgramNode | null>(null);
const [form] = Form.useForm(); const [form] = Form.useForm();
// Initialize editor // Initialize editor
@ -120,6 +121,42 @@ export const FlowgramEditor: React.FC<FlowgramEditorProps> = ({ value, onChange
notifyChange(newGraph); notifyChange(newGraph);
}, [graph, notifyChange]); }, [graph, notifyChange]);
// Handle node drag start
const handleDragStart = useCallback((e: React.DragEvent, node: FlowgramNode) => {
setDraggedNode(node);
e.dataTransfer.effectAllowed = 'move';
}, []);
// Handle node drag over
const handleDragOver = useCallback((e: React.DragEvent) => {
e.preventDefault();
e.dataTransfer.dropEffect = 'move';
}, []);
// Handle node drop
const handleDrop = useCallback((e: React.DragEvent, targetNode: FlowgramNode) => {
e.preventDefault();
if (!draggedNode || draggedNode.id === targetNode.id) return;
// Reorder nodes
const draggedIndex = graph.nodes.findIndex(n => n.id === draggedNode.id);
const targetIndex = graph.nodes.findIndex(n => n.id === targetNode.id);
if (draggedIndex === -1 || targetIndex === -1) return;
const newNodes = [...graph.nodes];
const [removed] = newNodes.splice(draggedIndex, 1);
newNodes.splice(targetIndex, 0, removed);
const newGraph = {
...graph,
nodes: newNodes,
};
notifyChange(newGraph);
setDraggedNode(null);
}, [draggedNode, graph, notifyChange]);
// Validate workflow // Validate workflow
const handleValidate = useCallback(() => { const handleValidate = useCallback(() => {
const validation = validateWorkflow(graph); const validation = validateWorkflow(graph);
@ -367,18 +404,18 @@ export const FlowgramEditor: React.FC<FlowgramEditorProps> = ({ value, onChange
<div className="flowgram-canvas"> <div className="flowgram-canvas">
<div style={{ padding: '20px' }}> <div style={{ padding: '20px' }}>
<h3> ({graph.nodes.length})</h3> <h3> ({graph.nodes.length})</h3>
<Space direction="vertical" style={{ width: '100%' }}> <Space direction="vertical" style={{ width: '100%' }} className="flowgram-node-list">
{graph.nodes.map((node) => ( {graph.nodes.map((node) => (
<div <div
key={node.id} key={node.id}
className="node-card" className={`flowgram-node-card ${draggedNode?.id === node.id ? 'flowgram-node-card-dragging' : ''}`}
onClick={() => handleNodeClick(node)} onClick={() => handleNodeClick(node)}
draggable
onDragStart={(e) => handleDragStart(e, node)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, node)}
style={{ style={{
padding: '10px', background: draggedNode?.id === node.id ? '#f0f0f0' : '#fff',
border: '1px solid #d9d9d9',
borderRadius: '4px',
cursor: 'pointer',
background: '#fff',
}} }}
> >
<div><strong>{node.data.label}</strong></div> <div><strong>{node.data.label}</strong></div>

View File

@ -1,10 +1,56 @@
.flowgram-editor-container { .flowgram-editor-container {
display: flex;
flex-direction: column;
height: 600px;
border: 1px solid #d9d9d9; border: 1px solid #d9d9d9;
border-radius: 4px; border-radius: 4px;
padding: 16px;
min-height: 600px;
display: flex;
flex-direction: column;
}
.flowgram-editor-toolbar {
margin-bottom: 16px;
display: flex;
gap: 8px;
flex-wrap: wrap;
flex-shrink: 0;
}
.flowgram-editor-canvas {
flex: 1;
min-height: 500px;
border: 1px dashed #d9d9d9;
border-radius: 4px;
padding: 16px;
background: #fafafa; background: #fafafa;
overflow-y: auto;
max-height: calc(80vh - 300px);
}
.flowgram-node-card {
padding: 12px;
background: white;
border: 1px solid #d9d9d9;
border-radius: 4px;
margin-bottom: 8px;
cursor: move;
user-select: none;
position: relative;
}
.flowgram-node-card:hover {
border-color: #40a9ff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.flowgram-node-card-dragging {
opacity: 0.5;
cursor: grabbing;
}
.flowgram-node-list {
display: flex;
flex-direction: column;
gap: 8px;
} }
.flowgram-toolbar { .flowgram-toolbar {

View File

@ -80,7 +80,9 @@ const FlowgramWorkflowModal: React.FC<FlowgramWorkflowModalProps> = ({
onOk={handleOk} onOk={handleOk}
onCancel={handleCancel} onCancel={handleCancel}
confirmLoading={loading} confirmLoading={loading}
width={1000} width={1400}
style={{ top: 20 }}
bodyStyle={{ height: '80vh', overflow: 'auto' }}
maskClosable={false} maskClosable={false}
> >
<Form form={form} layout="vertical"> <Form form={form} layout="vertical">

View File

@ -108,6 +108,31 @@ const Scenario = () => {
} }
}; };
const handleModalOk = async (values: any) => {
try {
if (selectedScenario) {
const { code } = await request.put('/api/scenarios', values);
if (code === 200) {
message.success(intl.get('更新成功'));
setIsModalVisible(false);
setSelectedScenario(null);
fetchScenarios();
}
} else {
const { code } = await request.post('/api/scenarios', values);
if (code === 200) {
message.success(intl.get('创建成功'));
setIsModalVisible(false);
setSelectedScenario(null);
fetchScenarios();
}
}
} catch (error) {
console.error('Failed to save scenario:', error);
message.error(intl.get('操作失败'));
}
};
const handleViewLogs = (record: any) => { const handleViewLogs = (record: any) => {
setSelectedScenario(record); setSelectedScenario(record);
setIsLogModalVisible(true); setIsLogModalVisible(true);
@ -263,19 +288,17 @@ const Scenario = () => {
return ( return (
<PageContainer <PageContainer
header={{ title={intl.get('场景模式')}
title: intl.get('场景模式'), extra={[
extra: [ <Button
<Button key="create"
key="create" type="primary"
type="primary" icon={<PlusOutlined />}
icon={<PlusOutlined />} onClick={handleCreate}
onClick={handleCreate} >
> {intl.get('新建场景')}
{intl.get('新建场景')} </Button>,
</Button>, ]}
],
}}
> >
<Table <Table
columns={columns} columns={columns}
@ -296,11 +319,7 @@ const Scenario = () => {
setIsModalVisible(false); setIsModalVisible(false);
setSelectedScenario(null); setSelectedScenario(null);
}} }}
onOk={() => { onOk={handleModalOk}
setIsModalVisible(false);
setSelectedScenario(null);
fetchScenarios();
}}
/> />
<ScenarioLogModal <ScenarioLogModal