mirror of
https://github.com/whyour/qinglong.git
synced 2025-12-13 07:25:05 +08:00
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:
parent
ece9c9186d
commit
3112f9fc34
|
|
@ -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',
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -558,9 +558,6 @@
|
|||
"任一满足": "任一满足",
|
||||
"字段名": "字段名",
|
||||
"操作符": "操作符",
|
||||
"包含": "包含",
|
||||
"不包含": "不包含",
|
||||
"值": "值",
|
||||
"添加条件": "添加条件",
|
||||
"动作配置": "动作配置",
|
||||
"动作类型": "动作类型",
|
||||
|
|
@ -570,28 +567,22 @@
|
|||
"发送通知": "发送通知",
|
||||
"变量名": "变量名",
|
||||
"变量值": "变量值",
|
||||
"命令": "命令",
|
||||
"消息": "消息",
|
||||
"添加动作": "添加动作",
|
||||
"高级设置": "高级设置",
|
||||
"延迟执行": "延迟执行",
|
||||
"秒": "秒",
|
||||
"失败熔断阈值": "失败熔断阈值",
|
||||
"连续失败多少次后自动禁用": "连续失败多少次后自动禁用",
|
||||
"最大重试次数": "最大重试次数",
|
||||
"重试延迟": "重试延迟",
|
||||
"退避倍数": "退避倍数",
|
||||
"每次重试延迟的乘数": "每次重试延迟的乘数",
|
||||
"启用": "启用",
|
||||
"时间": "时间",
|
||||
"条件匹配": "条件匹配",
|
||||
"执行时间": "执行时间",
|
||||
"重试次数": "重试次数",
|
||||
"错误信息": "错误信息",
|
||||
"是": "是",
|
||||
"否": "否",
|
||||
"共": "共",
|
||||
"项": "项",
|
||||
"日志名称": "日志名称",
|
||||
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成",
|
||||
"自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null": "自定义日志文件夹名称,用于区分不同任务的日志,留空则自动生成。支持绝对路径如 /dev/null",
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user