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',
name: intl.get('场景模式'),
icon: <IconFont type="ql-icon-scenario" />,
icon: <IconFont type="ql-icon-script" />,
component: '@/pages/scenario/index',
},
{

View File

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

View File

@ -558,9 +558,6 @@
"任一满足": "任一满足",
"字段名": "字段名",
"操作符": "操作符",
"包含": "包含",
"不包含": "不包含",
"值": "值",
"添加条件": "添加条件",
"动作配置": "动作配置",
"动作类型": "动作类型",
@ -570,28 +567,22 @@
"发送通知": "发送通知",
"变量名": "变量名",
"变量值": "变量值",
"命令": "命令",
"消息": "消息",
"添加动作": "添加动作",
"高级设置": "高级设置",
"延迟执行": "延迟执行",
"秒": "秒",
"失败熔断阈值": "失败熔断阈值",
"连续失败多少次后自动禁用": "连续失败多少次后自动禁用",
"最大重试次数": "最大重试次数",
"重试延迟": "重试延迟",
"退避倍数": "退避倍数",
"每次重试延迟的乘数": "每次重试延迟的乘数",
"启用": "启用",
"时间": "时间",
"条件匹配": "条件匹配",
"执行时间": "执行时间",
"重试次数": "重试次数",
"错误信息": "错误信息",
"是": "是",
"否": "否",
"共": "共",
"项": "项",
"日志名称": "日志名称",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成",
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /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 [selectedNode, setSelectedNode] = useState<FlowgramNode | null>(null);
const [drawerVisible, setDrawerVisible] = useState(false);
const [draggedNode, setDraggedNode] = useState<FlowgramNode | null>(null);
const [form] = Form.useForm();
// Initialize editor
@ -120,6 +121,42 @@ export const FlowgramEditor: React.FC<FlowgramEditorProps> = ({ value, onChange
notifyChange(newGraph);
}, [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
const handleValidate = useCallback(() => {
const validation = validateWorkflow(graph);
@ -367,18 +404,18 @@ export const FlowgramEditor: React.FC<FlowgramEditorProps> = ({ value, onChange
<div className="flowgram-canvas">
<div style={{ padding: '20px' }}>
<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) => (
<div
key={node.id}
className="node-card"
className={`flowgram-node-card ${draggedNode?.id === node.id ? 'flowgram-node-card-dragging' : ''}`}
onClick={() => handleNodeClick(node)}
draggable
onDragStart={(e) => handleDragStart(e, node)}
onDragOver={handleDragOver}
onDrop={(e) => handleDrop(e, node)}
style={{
padding: '10px',
border: '1px solid #d9d9d9',
borderRadius: '4px',
cursor: 'pointer',
background: '#fff',
background: draggedNode?.id === node.id ? '#f0f0f0' : '#fff',
}}
>
<div><strong>{node.data.label}</strong></div>

View File

@ -1,10 +1,56 @@
.flowgram-editor-container {
display: flex;
flex-direction: column;
height: 600px;
border: 1px solid #d9d9d9;
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;
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 {

View File

@ -80,7 +80,9 @@ const FlowgramWorkflowModal: React.FC<FlowgramWorkflowModalProps> = ({
onOk={handleOk}
onCancel={handleCancel}
confirmLoading={loading}
width={1000}
width={1400}
style={{ top: 20 }}
bodyStyle={{ height: '80vh', overflow: 'auto' }}
maskClosable={false}
>
<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) => {
setSelectedScenario(record);
setIsLogModalVisible(true);
@ -263,19 +288,17 @@ const Scenario = () => {
return (
<PageContainer
header={{
title: intl.get('场景模式'),
extra: [
<Button
key="create"
type="primary"
icon={<PlusOutlined />}
onClick={handleCreate}
>
{intl.get('新建场景')}
</Button>,
],
}}
title={intl.get('场景模式')}
extra={[
<Button
key="create"
type="primary"
icon={<PlusOutlined />}
onClick={handleCreate}
>
{intl.get('新建场景')}
</Button>,
]}
>
<Table
columns={columns}
@ -296,11 +319,7 @@ const Scenario = () => {
setIsModalVisible(false);
setSelectedScenario(null);
}}
onOk={() => {
setIsModalVisible(false);
setSelectedScenario(null);
fetchScenarios();
}}
onOk={handleModalOk}
/>
<ScenarioLogModal