소스 검색

添加标准化建模

Lin Qilong 6 달 전
부모
커밋
1f3062afb8
34개의 변경된 파일1114개의 추가작업 그리고 166개의 파일을 삭제
  1. 4 4
      ruoyi-admin/src/main/resources/application.yml
  2. 1 1
      ruoyi-admin/src/main/resources/logback.xml
  3. 12 0
      ruoyi-api-patform/pom.xml
  4. 18 18
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelInfoController.java
  5. 1 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelStepController.java
  6. 1 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelStepExecutionController.java
  7. 9 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelWorkflowController.java
  8. 1 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelWorkflowExecutionController.java
  9. 13 5
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/PtServiceController.java
  10. 1 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/WorkflowController.java
  11. 15 7
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelStepExecution.java
  12. 9 0
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelWorkflow.java
  13. 14 0
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelWorkflowExecution.java
  14. 1 0
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/service/MdModelWorkflowService.java
  15. 12 0
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/service/impl/MdModelWorkflowServiceImpl.java
  16. 12 2
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutionContext.java
  17. 1 21
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutionResult.java
  18. 0 20
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutorFactory.java
  19. 2 0
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/WorkflowEngine.java
  20. 0 39
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/WorkflowUtils.java
  21. 30 31
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/ApiStepExecutor.java
  22. 46 1
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/ConditionStepExecutor.java
  23. 29 7
      ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/DbStepExecutor.java
  24. 34 0
      ruoyi-common/src/main/java/com/ruoyi/common/core/accessor/MapAccessor.java
  25. 1 0
      ruoyi-ui/package.json
  26. 8 0
      ruoyi-ui/src/api/register/regCom.js
  27. 9 0
      ruoyi-ui/src/api/service/info.js
  28. 10 0
      ruoyi-ui/src/api/standardization/modeling.js
  29. 19 0
      ruoyi-ui/src/api/standardization/workflow.js
  30. 160 0
      ruoyi-ui/src/components/DynamicMap/index.vue
  31. 10 5
      ruoyi-ui/src/utils/index.js
  32. 61 0
      ruoyi-ui/src/views/standardization/modeling/components/SpecialEdge.vue
  33. 32 0
      ruoyi-ui/src/views/standardization/modeling/components/SpecialNode.vue
  34. 538 0
      ruoyi-ui/src/views/standardization/modeling/index.vue

+ 4 - 4
ruoyi-admin/src/main/resources/application.yml

@@ -7,7 +7,7 @@ ruoyi:
   # 版权年份
   copyrightYear: 2024
   # 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
-  profile: D:/ruoyi/uploadPath
+  profile: D:/tmp/sh-models/uploadPath
   # 获取ip地址开关
   addressEnabled: false
   # 验证码类型 math 数字计算 char 字符验证
@@ -121,11 +121,11 @@ xss:
 sys:
   report:
     upload:
-      path: D:/test/report/
+      path: D:/tmp/report/
   chart:
     upload:
-      path: D:/test/chart/
+      path: D:/tmp/chart/
   map:
     upload:
-      path: D:/test/map/
+      path: D:/tmp/map/
 

+ 1 - 1
ruoyi-admin/src/main/resources/logback.xml

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <configuration>
     <!-- 日志存放路径 -->
-	<property name="log.path" value="/home/sh-models/logs" />
+	<property name="log.path" value="/tmp/sh-models/logs" />
     <!-- 日志输出格式 -->
 	<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
 

+ 12 - 0
ruoyi-api-patform/pom.xml

@@ -7,6 +7,18 @@
         <groupId>com.ruoyi</groupId>
         <version>3.8.8</version>
     </parent>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>9</source>
+                    <target>9</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
     <modelVersion>4.0.0</modelVersion>
     <artifactId>ruoyi-api-patform</artifactId>
     <description>

+ 18 - 18
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelInfoController.java

@@ -1,28 +1,19 @@
 package com.ruoyi.interfaces.controller;
 
-import java.util.List;
-import javax.servlet.http.HttpServletResponse;
-
-import com.ruoyi.interfaces.domain.MdModelInfo;
-import com.ruoyi.interfaces.service.IMdModelInfoService;
-import io.swagger.annotations.ApiOperation;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
 import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
 import com.ruoyi.common.enums.BusinessType;
 import com.ruoyi.common.utils.poi.ExcelUtil;
-import com.ruoyi.common.core.page.TableDataInfo;
-import org.springframework.web.multipart.MultipartFile;
+import com.ruoyi.interfaces.domain.MdModelInfo;
+import com.ruoyi.interfaces.service.IMdModelInfoService;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
 
 /**
  * 模型信息Controller
@@ -47,6 +38,15 @@ public class MdModelInfoController extends BaseController {
         return getDataTable(list);
     }
 
+    /**
+     * 查询模型信息列表
+     */
+    @ApiOperation("查询模型信息列表")
+    @GetMapping("/list2")
+    public AjaxResult list2(MdModelInfo mdModelInfo) {
+        return AjaxResult.success(mdModelInfoService.selectMdModelInfoList(mdModelInfo));
+    }
+
     /**
      * 导出模型信息列表
      */

+ 1 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelStepController.java

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @Api(value = "步骤表", tags = "步骤表")
 @RestController
-@RequestMapping("/md/model/step")
+@RequestMapping("/model/step")
 public class MdModelStepController extends BaseController {
 
     @Autowired

+ 1 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelStepExecutionController.java

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @Api(value = "步骤执行记录", tags = "步骤执行记录")
 @RestController
-@RequestMapping("/md/model/step/execution")
+@RequestMapping("/model/step/execution")
 public class MdModelStepExecutionController extends BaseController {
 
     @Autowired

+ 9 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelWorkflowController.java

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @Api(value = "工作流", tags = "工作流")
 @RestController
-@RequestMapping("/md/model/workflow")
+@RequestMapping("/model/workflow")
 public class MdModelWorkflowController extends BaseController {
 
     @Autowired
@@ -41,5 +41,13 @@ public class MdModelWorkflowController extends BaseController {
         return AjaxResult.success(mdModelWorkflowService.getById(id));
     }
 
+    @ApiOperation(value = "根据ID获取工作流(单表)")
+    @GetMapping("/getByModelId/{modelId}")
+    public AjaxResult getByModelId(
+            @ApiParam(name = "modelId", value = "modelId", required = true)
+            @PathVariable String modelId
+    ) {
+        return AjaxResult.success(mdModelWorkflowService.getByModelId(modelId));
+    }
 
 }

+ 1 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/MdModelWorkflowExecutionController.java

@@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.*;
  */
 @Api(value = "工作流执行记录", tags = "工作流执行记录")
 @RestController
-@RequestMapping("/md/model/workflow/execution")
+@RequestMapping("/model/workflow/execution")
 public class MdModelWorkflowExecutionController extends BaseController {
 
     @Autowired

+ 13 - 5
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/PtServiceController.java

@@ -1,8 +1,8 @@
 package com.ruoyi.interfaces.controller;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.github.pagehelper.PageHelper;
 import com.github.pagehelper.PageInfo;
-import com.ruoyi.common.config.RuoYiConfig;
 import com.ruoyi.common.core.controller.BaseController;
 import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.page.TableDataInfo;
@@ -19,7 +19,6 @@ import com.ruoyi.interfaces.service.PtServiceService;
 import com.ruoyi.interfaces.service.SysCateService;
 import io.swagger.annotations.ApiOperation;
 import org.apache.commons.lang3.StringUtils;
-
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -53,14 +52,14 @@ public class PtServiceController extends BaseController {
     @ApiOperation("添加数据")
     @PostMapping(value = "/save")
     public AjaxResult save(@RequestBody PtService ptService) {
-        if(ptService.getViewNum() == null){  //viewNum不能为空
+        if (ptService.getViewNum() == null) {  //viewNum不能为空
             ptService.setViewNum(0);
         }
         boolean save = ptServiceService.save(ptService);
-        if (save){
+        if (save) {
             return AjaxResult.success(ptService);
 
-        }else {
+        } else {
             return AjaxResult.error("添加失败");
         }
     }
@@ -168,4 +167,13 @@ public class PtServiceController extends BaseController {
         return AjaxResult.success(ptServiceService.getRankingList());
     }
 
+    @ApiOperation("查询服务列表")
+    @GetMapping(value = "/listBySuccess")
+    public AjaxResult listBySuccess(PtService ptService) {
+        QueryWrapper<PtService> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("status", "1")
+                .like(StringUtils.isNotBlank(ptService.getName()), "name", ptService.getName());
+        return AjaxResult.success(ptServiceService.list(queryWrapper));
+    }
+
 }

+ 1 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/controller/WorkflowController.java

@@ -14,7 +14,7 @@ import java.util.Map;
  * @date 2025/7/16 10:08
  */
 @RestController
-@RequestMapping("/workflows")
+@RequestMapping("/model/workflows")
 public class WorkflowController extends BaseController {
 
     @Autowired

+ 15 - 7
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelStepExecution.java

@@ -2,6 +2,7 @@ package com.ruoyi.interfaces.domain;
 
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.ruoyi.common.utils.JsonUtils;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
@@ -38,8 +39,10 @@ public class MdModelStepExecution implements Serializable {
     private Long id;
     private Long executionId;
     private Long stepId;
-    private String stepName;         // 步骤名称
-    private MdModelStep.StepType stepType;  // 步骤类型
+    @ApiModelProperty("步骤名称")
+    private String stepName;
+    @ApiModelProperty("步骤类型")
+    private MdModelStep.StepType stepType;
     @ApiModelProperty("状态")
     private StepStatus status;
     @ApiModelProperty("步骤输入")
@@ -48,11 +51,16 @@ public class MdModelStepExecution implements Serializable {
     private String output;
     @ApiModelProperty("错误信息")
     private String errorMsg;
-    private Date executionTime;
-    private int retryCount = 0;      // 重试次数
-    private Date startTime;          // 开始时间
-    private Date endTime;            // 结束时间
-    private long duration;           // 耗时(毫秒)
+    @ApiModelProperty("重试次数")
+    private int retryCount = 0;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("开始时间")
+    private Date startTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("结束时间")
+    private Date endTime;
+    @ApiModelProperty("耗时(毫秒)")
+    private long duration;
 
     // 标记步骤开始
     public void markStart() {

+ 9 - 0
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelWorkflow.java

@@ -3,6 +3,7 @@ package com.ruoyi.interfaces.domain;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -26,6 +27,8 @@ public class MdModelWorkflow implements Serializable {
 
     @TableId
     private Integer id;
+    @ApiModelProperty("模型ID")
+    private String mdid;
     @ApiModelProperty("工作流名称")
     private String name;
     @ApiModelProperty("描述")
@@ -34,8 +37,14 @@ public class MdModelWorkflow implements Serializable {
     private Integer status;
     @ApiModelProperty("输入参数模板")
     private String inputTemplate;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("创建时间")
     private Date createTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("更新时间")
     private Date updateTime;
+    @ApiModelProperty("步骤")
+    private String graph;
 
     @TableField(exist = false)
     @ApiModelProperty("步骤列表")

+ 14 - 0
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/domain/MdModelWorkflowExecution.java

@@ -3,6 +3,8 @@ package com.ruoyi.interfaces.domain;
 import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.common.utils.JsonUtils;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Data;
@@ -11,6 +13,7 @@ import lombok.NoArgsConstructor;
 import java.io.Serializable;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 /**
  * entity:MdModelWorkflowExecution
@@ -30,7 +33,9 @@ public class MdModelWorkflowExecution implements Serializable {
 
     @TableId
     private Long id;
+    @ApiModelProperty("工作流ID")
     private Long workflowId;
+    @ApiModelProperty("状态")
     private ExecutionStatus status;
     @ApiModelProperty("执行输入")
     private String input;
@@ -38,10 +43,19 @@ public class MdModelWorkflowExecution implements Serializable {
     private String output;
     @ApiModelProperty("执行上下文")
     private String context;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("开始时间")
     private Date startTime;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty("结束时间")
     private Date endTime;
 
     @TableField(exist = false)
     @ApiModelProperty("步骤列表")
     private List<MdModelStepExecution> stepExecutions;
+
+    public void setOutput(Map<String, Object> output) {
+        this.output = JsonUtils.objectToJson(output);
+    }
+
 }

+ 1 - 0
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/service/MdModelWorkflowService.java

@@ -9,5 +9,6 @@ import com.ruoyi.interfaces.domain.MdModelWorkflow;
  */
 public interface MdModelWorkflowService extends IService<MdModelWorkflow> {
 
+    MdModelWorkflow getByModelId(String modelId);
 
 }

+ 12 - 0
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/service/impl/MdModelWorkflowServiceImpl.java

@@ -1,5 +1,6 @@
 package com.ruoyi.interfaces.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.ruoyi.interfaces.domain.MdModelWorkflow;
 import com.ruoyi.interfaces.mapper.MdModelWorkflowMapper;
@@ -19,6 +20,11 @@ public class MdModelWorkflowServiceImpl extends ServiceImpl<MdModelWorkflowMappe
 
     @Override
     public boolean save(MdModelWorkflow mdModelWorkflow) {
+        MdModelWorkflow mdModelWorkflow1 = getByModelId(mdModelWorkflow.getMdid());
+        if (mdModelWorkflow1 != null) {
+            mdModelWorkflow.setId(mdModelWorkflow1.getId());
+            return updateById(mdModelWorkflow);
+        }
         return super.save(mdModelWorkflow);
     }
 
@@ -27,4 +33,10 @@ public class MdModelWorkflowServiceImpl extends ServiceImpl<MdModelWorkflowMappe
         return super.updateById(mdModelWorkflow);
     }
 
+    @Override
+    public MdModelWorkflow getByModelId(String modelId) {
+        QueryWrapper<MdModelWorkflow> queryWrapper = new QueryWrapper<>();
+        queryWrapper.eq("mdid", modelId);
+        return getOne(queryWrapper);
+    }
 }

+ 12 - 2
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutionContext.java

@@ -1,8 +1,12 @@
 package com.ruoyi.interfaces.workflow;
 
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.context.expression.MapAccessor;
 import org.springframework.expression.ExpressionParser;
 import org.springframework.expression.common.TemplateParserContext;
 import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -12,6 +16,9 @@ import java.util.Map;
  * @date 2025/7/15 16:16
  */
 public class ExecutionContext {
+    @Setter
+    @Getter
+    private Long workflowId;
     private Map<String, Object> input;
     private Map<String, Object> contextData = new HashMap<>();
     private Map<String, Map<String, Object>> stepOutputs = new HashMap<>();
@@ -19,7 +26,7 @@ public class ExecutionContext {
     public ExecutionContext(Map<String, Object> input) {
         this.input = input;
         // 初始化时将输入数据放入上下文
-        contextData.put("input", input);
+        contextData.put("input", new HashMap<>(input));
     }
 
     // 设置步骤输出(按步骤名称存储)
@@ -42,8 +49,11 @@ public class ExecutionContext {
     // 使用SpEL表达式渲染模板
     public String renderTemplate(String template) {
         ExpressionParser parser = new SpelExpressionParser();
+        StandardEvaluationContext evalContext = new StandardEvaluationContext();
+        evalContext.setRootObject(contextData);
+        evalContext.addPropertyAccessor(new MapAccessor());
         return parser.parseExpression(template, new TemplateParserContext())
-                .getValue(this.contextData, String.class);
+                .getValue(evalContext, String.class);
     }
 
     // 使用SpEL解析JSON对象

+ 1 - 21
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutionResult.java

@@ -23,6 +23,7 @@ public class ExecutionResult {
         PARTIAL_SUCCESS // 部分成功
     }
 
+    // Getter & Setter 方法
     private String executionId;          // 执行ID
     private ExecutionStatus status;      // 整体执行状态
     private String workflowId;           // 工作流ID
@@ -85,26 +86,5 @@ public class ExecutionResult {
                 .orElse(null);
     }
 
-    // Getter & Setter 方法
-    public String getExecutionId() { return executionId; }
-    public void setExecutionId(String executionId) { this.executionId = executionId; }
-
-    public ExecutionStatus getStatus() { return status; }
-
-    public String getWorkflowId() { return workflowId; }
-
-    public Map<String, Object> getInput() { return input; }
-
-    public Map<String, Object> getOutput() { return output; }
-
-    public Map<String, Object> getContext() { return context; }
-
-    public List<MdModelStepExecution> getStepExecutions() { return stepExecutions; }
-
-    public String getErrorMessage() { return errorMessage; }
-
-    public long getStartTime() { return startTime; }
-
-    public long getEndTime() { return endTime; }
 }
 

+ 0 - 20
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/ExecutorFactory.java

@@ -6,8 +6,6 @@ package com.ruoyi.interfaces.workflow;
  */
 
 import com.ruoyi.interfaces.domain.MdModelStep;
-import com.ruoyi.interfaces.workflow.executor.ApiStepExecutor;
-import com.ruoyi.interfaces.workflow.executor.ConditionStepExecutor;
 import com.ruoyi.interfaces.workflow.executor.StepExecutor;
 import org.springframework.beans.BeansException;
 import org.springframework.context.ApplicationContext;
@@ -54,22 +52,4 @@ public class ExecutorFactory implements ApplicationContextAware {
         return executor;
     }
 
-    /**
-     * 获取API执行器
-     *
-     * @return API执行器实例
-     */
-    public ApiStepExecutor getApiExecutor() {
-        return (ApiStepExecutor) getExecutor(MdModelStep.StepType.API);
-    }
-
-    /**
-     * 获取条件分支执行器
-     *
-     * @return 条件分支执行器实例
-     */
-    public ConditionStepExecutor getConditionExecutor() {
-        return (ConditionStepExecutor) getExecutor(MdModelStep.StepType.CONDITION);
-    }
-
 }

+ 2 - 0
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/WorkflowEngine.java

@@ -46,6 +46,7 @@ public class WorkflowEngine {
         workflowExecution.setInput(JsonUtils.objectToJson(input));
         workflowExecution.setStartTime(new Date());
         executionService.save(workflowExecution);
+        context.setWorkflowId(workflowExecution.getWorkflowId());
 
         // 创建执行结果
         ExecutionResult result = new ExecutionResult(String.valueOf(workflowId), input);
@@ -87,6 +88,7 @@ public class WorkflowEngine {
 
             // 所有步骤成功完成
             result.markSuccess(context.getAllContextData());
+            workflowExecution.setOutput(context.getAllContextData());
             workflowExecution.setStatus(MdModelWorkflowExecution.ExecutionStatus.SUCCESS);
         } catch (StepExecutionException e) {
             result.markFailed(e.getMessage());

+ 0 - 39
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/WorkflowUtils.java

@@ -1,39 +0,0 @@
-package com.ruoyi.interfaces.workflow;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * @author LinQiLong
- * @date 2025/7/17 10:11
- */
-public class WorkflowUtils {
-
-    public static String renderTemplate(String template, ExecutionContext context) {
-        // 正则表达式匹配 {{placeholder}}
-        Pattern pattern = Pattern.compile("\\{\\{\\s*(.*?)\\s*\\}\\}");
-        Matcher matcher = pattern.matcher(template);
-        StringBuffer result = new StringBuffer();
-
-        while (matcher.find()) {
-            // 提取占位符中的 key(去掉空格)
-            String key = matcher.group(1);
-            Object value = context.getAllContextData().get(key);  // 从上下文获取值
-
-            // 处理值不存在或异常情况
-            String replacement;
-            if (value == null) {
-                replacement = "";
-            } else {
-                replacement = Matcher.quoteReplacement(value.toString());
-            }
-
-            // 替换匹配到的占位符
-            matcher.appendReplacement(result, replacement);
-        }
-
-        matcher.appendTail(result);
-        return result.toString();
-    }
-
-}

+ 30 - 31
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/ApiStepExecutor.java

@@ -1,21 +1,16 @@
 package com.ruoyi.interfaces.workflow.executor;
 
-/**
- * @author LinQiLong
- * @date 2025/7/16 9:13
- */
-
 import com.alibaba.fastjson2.JSONObject;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.ReadContext;
 import com.ruoyi.common.utils.HttpRequestUtil;
 import com.ruoyi.common.utils.JsonUtils;
 import com.ruoyi.interfaces.domain.MdModelStep;
 import com.ruoyi.interfaces.domain.MdModelStepExecution;
 import com.ruoyi.interfaces.service.MdModelStepExecutionService;
-import com.ruoyi.interfaces.service.MdModelWorkflowExecutionService;
 import com.ruoyi.interfaces.workflow.ExecutionContext;
-import com.ruoyi.interfaces.workflow.WorkflowUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
 import org.springframework.stereotype.Component;
 
@@ -25,6 +20,7 @@ import java.util.Map;
 /**
  * API调用步骤执行器
  */
+@Slf4j
 @Component
 public class ApiStepExecutor implements StepExecutor {
 
@@ -39,20 +35,21 @@ public class ApiStepExecutor implements StepExecutor {
     @Override
     public MdModelStepExecution execute(MdModelStep step, ExecutionContext context) {
         MdModelStepExecution stepExecution = new MdModelStepExecution();
+        stepExecution.setExecutionId(context.getWorkflowId());
         stepExecution.setStepId(step.getId());
         stepExecution.setStepName(step.getName());
         stepExecution.setStepType(MdModelStep.StepType.API);
         stepExecution.markStart();
-        stepExecutionService.save(stepExecution);
+        stepExecutionService.saveOrUpdate(stepExecution);
 
         try {
             // 准备请求
             Map<String, Object> config = (Map<String, Object>) JsonUtils.jsonToPojo(step.getConfig(), Map.class);
             HttpMethod method = HttpMethod.valueOf(config.get("method").toString());
-            String url = WorkflowUtils.renderTemplate(config.get("url").toString(), context);
+            String url = context.renderTemplate(config.get("url").toString());
             Map<String, String> headers = (Map<String, String>) config.get("headers");
-            String requestBody = buildRequestBody(config.get("body"), context);
-
+            String requestBody = JsonUtils.objectToJson(context.parseTemplate((Map<String, Object>) config.get("body")));
+            stepExecution.setInput(requestBody);
             // 执行API调用
             JSONObject response = HttpRequestUtil.getJSONObjectByRequest(url, method.toString(), headers, requestBody);
 
@@ -62,37 +59,27 @@ public class ApiStepExecutor implements StepExecutor {
             } else {
                 Map<String, Object> responseMap = new HashMap<>(response);
                 stepExecution.markSuccess(responseMap);
-                stepExecutionService.save(stepExecution);
+                stepExecutionService.saveOrUpdate(stepExecution);
                 context.setStepOutput(String.valueOf(step.getId()), responseMap);
 
                 // 保存到全局上下文
                 saveToGlobalContext(step, context, response);
             }
         } catch (Exception e) {
+            log.error(e.getMessage(), e);
             stepExecution.markFailed(e.getMessage());
-            stepExecutionService.save(stepExecution);
+            stepExecutionService.saveOrUpdate(stepExecution);
         }
 
         return stepExecution;
     }
 
-    private String buildRequestBody(Object bodyTemplate, ExecutionContext context) {
-        // 构建请求体
-        if (bodyTemplate instanceof String) {
-            return WorkflowUtils.renderTemplate(bodyTemplate.toString(), context);
-        } else if (bodyTemplate instanceof Map) {
-            ((Map<?, ?>) bodyTemplate).forEach((key, value) -> {
-                if (value instanceof String) {
-                    ((Map) bodyTemplate).put(key, WorkflowUtils.renderTemplate(value.toString(), context));
-                }
-            });
-            return JsonUtils.objectToJson(bodyTemplate);
-        }
-        return null; // 实际实现
-    }
-
     private void saveToGlobalContext(MdModelStep step, ExecutionContext context, Map<String, Object> response) {
         // 根据配置保存特定字段到全局上下文
+        if (step.getOutputMapping() == null) {
+            return;
+        }
+
         Map<String, Object> outputMapping = JsonUtils.jsonToPojo(step.getOutputMapping(), JSONObject.class);
         if (outputMapping != null) {
             outputMapping.forEach((responsePath, contextKey) -> {
@@ -105,8 +92,20 @@ public class ApiStepExecutor implements StepExecutor {
     }
 
     private Object extractFromResponse(Map<String, Object> response, String path) {
-        // 使用JSONPath提取特定字段
+        if (response == null || path == null || path.isEmpty()) {
+            return null;
+        }
 
-        return null;
+        try {
+            // 将 Map 转换为 JSON 字符串
+            String json = JsonUtils.objectToJson(response);
+
+            // 使用 JsonPath 解析
+            ReadContext ctx = JsonPath.parse(json);
+            return ctx.read(path);
+        } catch (Exception e) {
+            log.warn("JSONPath提取失败,path: {}, error: {}", path, e.getMessage());
+            return null;
+        }
     }
 }

+ 46 - 1
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/ConditionStepExecutor.java

@@ -1,14 +1,31 @@
 package com.ruoyi.interfaces.workflow.executor;
 
+import com.ruoyi.common.utils.JsonUtils;
 import com.ruoyi.interfaces.domain.MdModelStep;
 import com.ruoyi.interfaces.domain.MdModelStepExecution;
+import com.ruoyi.interfaces.service.MdModelStepExecutionService;
 import com.ruoyi.interfaces.workflow.ExecutionContext;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.expression.MapAccessor;
+import org.springframework.expression.ExpressionParser;
+import org.springframework.expression.spel.standard.SpelExpressionParser;
+import org.springframework.expression.spel.support.StandardEvaluationContext;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
 
 /**
  * @author LinQiLong
  * @date 2025/7/16 9:26
  */
+@Slf4j
+@Component
 public class ConditionStepExecutor implements StepExecutor {
+
+    @Autowired
+    private MdModelStepExecutionService stepExecutionService;
+
     @Override
     public MdModelStep.StepType getSupportedStepType() {
         return MdModelStep.StepType.CONDITION;
@@ -16,6 +33,34 @@ public class ConditionStepExecutor implements StepExecutor {
 
     @Override
     public MdModelStepExecution execute(MdModelStep step, ExecutionContext context) {
-        return null;
+        MdModelStepExecution stepExecution = new MdModelStepExecution();
+        stepExecution.setExecutionId(context.getWorkflowId());
+        stepExecution.setStepId(step.getId());
+        stepExecution.setStepName(step.getName());
+        stepExecution.setStepType(MdModelStep.StepType.API);
+        stepExecution.markStart();
+        stepExecutionService.saveOrUpdate(stepExecution);
+
+        Map<String, Object> config = (Map<String, Object>) JsonUtils.jsonToPojo(step.getConfig(), Map.class);
+        String expression = config.get("expression").toString();
+        boolean judge = isPremiumPackage(expression, context);
+        context.setContextValue("CONDITION_RESULT", judge);
+        stepExecution.markSuccess(Map.of("CONDITION_RESULT", judge));
+        stepExecutionService.saveOrUpdate(stepExecution);
+        return stepExecution;
     }
+
+    public boolean isPremiumPackage(String template, ExecutionContext contextData) {
+        ExpressionParser parser = new SpelExpressionParser();
+        StandardEvaluationContext evalContext = new StandardEvaluationContext();
+
+        Map<String, Object> rootContext = contextData.getAllContextData();
+        evalContext.setRootObject(rootContext);
+        evalContext.addPropertyAccessor(new MapAccessor());
+
+        Boolean result = parser.parseExpression(template)
+                .getValue(evalContext, Boolean.class);
+        return Boolean.TRUE.equals(result);
+    }
+
 }

+ 29 - 7
ruoyi-api-patform/src/main/java/com/ruoyi/interfaces/workflow/executor/DbStepExecutor.java

@@ -3,8 +3,9 @@ package com.ruoyi.interfaces.workflow.executor;
 import com.ruoyi.common.utils.JsonUtils;
 import com.ruoyi.interfaces.domain.MdModelStep;
 import com.ruoyi.interfaces.domain.MdModelStepExecution;
+import com.ruoyi.interfaces.service.MdModelStepExecutionService;
 import com.ruoyi.interfaces.workflow.ExecutionContext;
-import com.ruoyi.interfaces.workflow.WorkflowUtils;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Component;
@@ -16,9 +17,13 @@ import java.util.Map;
  * @author LinQiLong
  * @date 2025/7/17 10:08
  */
+@Slf4j
 @Component
 public class DbStepExecutor implements StepExecutor {
 
+    @Autowired
+    private MdModelStepExecutionService stepExecutionService;
+
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
@@ -29,18 +34,35 @@ public class DbStepExecutor implements StepExecutor {
 
     @Override
     public MdModelStepExecution execute(MdModelStep step, ExecutionContext context) {
+        MdModelStepExecution stepExecution = new MdModelStepExecution();
+        stepExecution.setExecutionId(context.getWorkflowId());
+        stepExecution.setStepId(step.getId());
+        stepExecution.setStepName(step.getName());
+        stepExecution.setStepType(MdModelStep.StepType.API);
+        stepExecution.markStart();
+        stepExecutionService.saveOrUpdate(stepExecution);
+
         Map<String, Object> config = (Map<String, Object>) JsonUtils.jsonToPojo(step.getConfig(), Map.class);
 
         // 1. 准备SQL
-        String sql = WorkflowUtils.renderTemplate(config.get("sql").toString(), context);
+        String sql = context.renderTemplate(config.get("sql").toString());
 
         // 2. 执行SQL
-        if (isQuery(sql)) {
-            return (MdModelStepExecution) jdbcTemplate.queryForMap(sql);
-        } else {
-            int affectedRows = jdbcTemplate.update(sql);
-            return (MdModelStepExecution) Collections.singletonMap("affectedRows", affectedRows);
+        try {
+            if (isQuery(sql)) {
+                Map<String, Object> jdbcResult = jdbcTemplate.queryForMap(sql);
+                stepExecution.markSuccess(jdbcResult);
+            } else {
+                int affectedRows = jdbcTemplate.update(sql);
+                Map<String, Object> jdbcResult = Collections.singletonMap("affectedRows", affectedRows);
+                stepExecution.markSuccess(jdbcResult);
+            }
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            stepExecution.markFailed(e.getMessage());
         }
+
+        return stepExecution;
     }
 
     private boolean isQuery(String sql) {

+ 34 - 0
ruoyi-common/src/main/java/com/ruoyi/common/core/accessor/MapAccessor.java

@@ -0,0 +1,34 @@
+package com.ruoyi.common.core.accessor;
+
+import org.springframework.expression.EvaluationContext;
+import org.springframework.expression.PropertyAccessor;
+import org.springframework.expression.TypedValue;
+
+import java.util.Map;
+
+public class MapAccessor implements PropertyAccessor {
+    @Override
+    public Class<?>[] getSpecificTargetClasses() {
+        return new Class[]{Map.class};
+    }
+
+    @Override
+    public boolean canRead(EvaluationContext context, Object target, String name) {
+        return target instanceof Map && ((Map<?, ?>) target).containsKey(name);
+    }
+
+    @Override
+    public TypedValue read(EvaluationContext context, Object target, String name) {
+        return (TypedValue) ((Map<?, ?>) target).get(name);
+    }
+
+    @Override
+    public boolean canWrite(EvaluationContext context, Object target, String name) {
+        return target instanceof Map;
+    }
+
+    @Override
+    public void write(EvaluationContext context, Object target, String name, Object newValue) {
+        ((Map<Object, Object>) target).put(name, newValue);
+    }
+}

+ 1 - 0
ruoyi-ui/package.json

@@ -18,6 +18,7 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "2.3.1",
+    "@vue-flow/core": "^1.45.0",
     "@vueup/vue-quill": "1.2.0",
     "@vueuse/core": "10.11.0",
     "axios": "0.28.1",

+ 8 - 0
ruoyi-ui/src/api/register/regCom.js

@@ -8,6 +8,14 @@ export function getModelList(query) {
   })
 }
 
+export function getModelList2(data) {
+  return request({
+    url: "model/info/list2",
+    method: 'get',
+    params:data
+  });
+}
+
 export function addModel(data) {
   return request.post("model/info", data);
 }

+ 9 - 0
ruoyi-ui/src/api/service/info.js

@@ -36,3 +36,12 @@ export function getServiceInfo(query) {
   });
 }
 
+
+export function getPtServiceList(query) {
+  return request({
+    url: "/pt/service/listBySuccess",
+    method: "get",
+    params: query,
+  });
+}
+

+ 10 - 0
ruoyi-ui/src/api/standardization/modeling.js

@@ -0,0 +1,10 @@
+import request from '@/utils/request'
+
+// 查询建模步骤
+export function listUser(query) {
+    return request({
+        url: '/model/step/list',
+        method: 'get',
+        params: query
+    })
+}

+ 19 - 0
ruoyi-ui/src/api/standardization/workflow.js

@@ -0,0 +1,19 @@
+import request from '@/utils/request'
+
+// 查询建模步骤
+export function saveWorkflow(data) {
+    return request({
+        url: '/model/workflow',
+        method: 'post',
+        data
+    })
+}
+
+
+// 查询建模步骤
+export function getWorkflowByModelId(modelId) {
+    return request({
+        url: `/model/workflow/getByModelId/${modelId}`,
+        method: 'get',
+    })
+}

+ 160 - 0
ruoyi-ui/src/components/DynamicMap/index.vue

@@ -0,0 +1,160 @@
+<template>
+  <div class="dynamic-map-container">
+    <el-row class="header">
+      <el-col :span="10">键</el-col>
+      <el-col :span="10">值</el-col>
+      <el-col :span="4">操作</el-col>
+    </el-row>
+    <el-row
+        v-for="(item, index) in items"
+        :key="item.id"
+        class="row"
+    >
+      <el-col :span="10">
+        <el-input
+            placeholder="请输入键"
+            v-model="item.key"
+            @input="debouncedEmitUpdate"
+        />
+      </el-col>
+      <el-col :span="10">
+        <el-input
+            placeholder="请输入值"
+            v-model="item.value"
+            @input="debouncedEmitUpdate"
+        />
+      </el-col>
+      <el-col :span="4" class="actions">
+        <el-button
+            type="danger"
+            icon="Delete"
+            @click="removeItem(index)"
+        />
+      </el-col>
+    </el-row>
+    <el-button
+        type="primary"
+        class="add-btn"
+        @click="addNewItem"
+    >
+      添加新项
+    </el-button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {onBeforeUnmount, ref, watch} from 'vue';
+import {debounce} from 'lodash-es';
+
+interface KeyValueItem {
+  id: number;
+  key: string;
+  value: string;
+}
+
+const props = defineProps({
+  modelValue: {
+    type: Object,
+    required: true,
+    default: () => ({})
+  }
+});
+
+const emit = defineEmits(['update:modelValue']);
+
+// 用于生成唯一ID
+let idCounter = 0;
+
+// 将对象转换为带ID的键值对数组
+const initItems = () => {
+  return Object.entries(props.modelValue || {})
+      .map(([key, value]) => ({
+        id: idCounter++,
+        key,
+        value: String(value)
+      }));
+};
+
+const items = ref<KeyValueItem[]>(initItems());
+
+// 防抖更新事件
+const debouncedEmitUpdate = debounce(() => {
+  emit('update:modelValue', arrayToObject(items.value));
+}, 300);
+
+// 添加行
+const addNewItem = () => {
+  items.value.push({
+    id: idCounter++,
+    key: '',
+    value: ''
+  });
+  emit('update:modelValue', arrayToObject(items.value));
+};
+
+// 删除行
+const removeItem = (index: number) => {
+  items.value.splice(index, 1);
+  emit('update:modelValue', arrayToObject(items.value));
+};
+
+// 数组转对象(处理重复键)
+const arrayToObject = (items: KeyValueItem[]) => {
+  return items.reduce((obj, item) => {
+    if (item.key) {
+      obj[item.key] = item.value;
+    }
+    return obj;
+  }, {} as Record<string, string>);
+};
+
+// 监听父组件传入值的变化
+watch(() => props.modelValue, (newValue) => {
+  const currentKeys = Object.keys(arrayToObject(items.value));
+  const newKeys = Object.keys(newValue);
+
+  // 仅在数据实际变化时更新(避免循环更新)
+  if (
+      newKeys.length !== currentKeys.length ||
+      newKeys.some(key => newValue[key] !== arrayToObject(items.value)[key])
+  ) {
+    items.value = initItems();
+  }
+}, {deep: true});
+
+// 清除防抖器
+onBeforeUnmount(() => {
+  debouncedEmitUpdate.cancel();
+});
+</script>
+
+<style scoped lang="scss">
+.dynamic-map-container {
+  width: 100%;
+  border: 1px solid #dcdfe6;
+  border-radius: 6px;
+  padding: 15px;
+
+  .header {
+    font-weight: bold;
+    padding: 0 0 10px 0;
+    margin-bottom: 8px;
+    border-bottom: 1px solid #ebeef5;
+  }
+
+  .row {
+    margin-bottom: 12px;
+  }
+
+  .actions {
+    display: flex;
+    align-items: center;
+    padding-left: 10px;
+  }
+
+  .add-btn {
+    width: 100%;
+    margin-top: 8px;
+  }
+}
+</style>

+ 10 - 5
ruoyi-ui/src/utils/index.js

@@ -5,12 +5,12 @@ import { parseTime } from './ruoyi'
  */
 export function formatDate(cellValue) {
   if (cellValue == null || cellValue == "") return "";
-  var date = new Date(cellValue) 
+  var date = new Date(cellValue)
   var year = date.getFullYear()
   var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
-  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() 
-  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() 
-  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() 
+  var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
+  var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
+  var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
   var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
   return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds
 }
@@ -177,6 +177,11 @@ export function objectMerge(target, source) {
   return target
 }
 
+export function copyObject(obj) {
+  return JSON.parse(JSON.stringify(obj))
+}
+
+
 /**
  * @param {HTMLElement} element
  * @param {string} className
@@ -330,7 +335,7 @@ export function makeMap(str, expectsLowerCase) {
     ? val => map[val.toLowerCase()]
     : val => map[val]
 }
- 
+
 export const exportDefault = 'export default '
 
 export const beautifierConf = {

+ 61 - 0
ruoyi-ui/src/views/standardization/modeling/components/SpecialEdge.vue

@@ -0,0 +1,61 @@
+<template>
+  <!-- You can use the `BaseEdge` component to create your own custom edge more easily -->
+  <BaseEdge :path="path[0]" />
+
+  <!-- Use the `EdgeLabelRenderer` to escape the SVG world of edges and render your own custom label in a `<div>` ctx -->
+  <EdgeLabelRenderer>
+    <div
+        :style="{
+        pointerEvents: 'all',
+        position: 'absolute',
+        transform: `translate(-50%, -50%) translate(${path[1]}px,${path[2]}px)`,
+      }"
+        class="nodrag nopan"
+    >
+      {{ data.hello }}
+    </div>
+  </EdgeLabelRenderer>
+</template>
+<script setup>
+import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@vue-flow/core'
+import { computed } from 'vue'
+
+const props = defineProps({
+  sourceX: {
+    type: Number,
+    required: true,
+  },
+  sourceY: {
+    type: Number,
+    required: true,
+  },
+  targetX: {
+    type: Number,
+    required: true,
+  },
+  targetY: {
+    type: Number,
+    required: true,
+  },
+  sourcePosition: {
+    type: String,
+    required: true,
+  },
+  targetPosition: {
+    type: String,
+    required: true,
+  },
+  data: {
+    type: Object,
+    required: true,
+  }
+})
+
+const path = computed(() => getBezierPath(props))
+</script>
+
+<script>
+export default {
+  inheritAttrs: false,
+}
+</script>

+ 32 - 0
ruoyi-ui/src/views/standardization/modeling/components/SpecialNode.vue

@@ -0,0 +1,32 @@
+<template>
+  <div class="vue-flow__node-default">
+    <div>
+      <el-tag>{{ data.type }}</el-tag>
+      {{ data.label }}
+    </div>
+    <Handle type="source" :position="Position.Right"/>
+    <Handle type="target" :position="Position.Left"/>
+  </div>
+</template>
+<script setup>
+import {computed} from 'vue'
+import {Handle, Position} from '@vue-flow/core'
+
+const props = defineProps({
+  id: {
+    type: String,
+    required: true,
+  },
+  data: {
+    type: Object,
+    required: true,
+  },
+  position: {
+    type: Object,
+    required: true,
+  }
+})
+
+const x = computed(() => `${Math.round(props.position.x)}px`)
+const y = computed(() => `${Math.round(props.position.y)}px`)
+</script>

+ 538 - 0
ruoyi-ui/src/views/standardization/modeling/index.vue

@@ -0,0 +1,538 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="20">
+      <el-col :span="4" class="sidebar-wrapper">
+        <el-input
+            v-model="modelQueryParams.name"
+            placeholder="请输入模型名称"
+            clearable
+            prefix-icon="Search"
+            style="margin-bottom: 10px"
+            @change="getModelList()"
+        />
+        <el-radio-group style="flex-wrap: nowrap;" v-model="modelQueryParams.isPublic" @change="getModelList">
+          <el-radio-button label="已发布" value="1"/>
+          <el-radio-button label="未发布" value="0"/>
+        </el-radio-group>
+        <div class="tool-container">
+          <div :class="{'active': modelId === item.mdid}" v-for="(item, index) in modelOptions" :key="index"
+               class="tool-item" @click="handleModelClick(item.mdid)">
+            {{ item.name }}
+          </div>
+        </div>
+      </el-col>
+      <!--    v-loading="loading"   -->
+      <el-col :span="16" style="position: relative;">
+        <el-button-group class="flow-button-group">
+          <el-button type="primary" :icon="Promotion" @click="saveStep">保存</el-button>
+          <!--          <el-button type="primary" :icon="Check">测试</el-button>-->
+        </el-button-group>
+        <VueFlow :nodes="nodes" :edges="edges" @drop="onDrop" @dragover="onDragOver" @dragleave="onDragLeave"
+                 @connect="onConnect" fit-view-on-init>
+          <template #node-special="specialNodeProps">
+            <SpecialNode :id="specialNodeProps.id" :position="specialNodeProps.position" :data="specialNodeProps.data"/>
+          </template>
+
+          <!-- bind your custom edge type to a component by using slots, slot names are always `edge-<type>` -->
+          <template #edge-special="specialEdgeProps">
+            <SpecialEdge v-bind="specialEdgeProps"/>
+          </template>
+        </VueFlow>
+      </el-col>
+      <el-col :span="4" class="sidebar-wrapper">
+        <el-input
+            v-model="toolQueryParams.name"
+            placeholder="请输入工具名称"
+            clearable
+            prefix-icon="Search"
+            style="margin-bottom: 10px"
+            @change="getServiceList()"
+        />
+        <el-radio-group style="flex-wrap: nowrap;" v-model="toolType">
+          <el-radio-button label="工具类" value="1"/>
+          <el-radio-button label="服务" value="0"/>
+        </el-radio-group>
+        <div v-show="toolType === '1'" class="tool-container">
+          <div></div>
+        </div>
+        <div v-show="toolType === '0'" class="tool-container">
+          <div v-for="(item, index) in serviceList" :key="index" class="tool-item" :draggable="true"
+               @dragstart="onDragStart($event, item)">
+            {{ item.label }}
+          </div>
+        </div>
+      </el-col>
+    </el-row>
+
+    <!-- 添加或修改部门对话框 -->
+    <el-dialog :title="title" v-model="open" width="600px" append-to-body>
+      <template #header="{ close, titleId, titleClass }">
+        <div style="display: flex;align-items: center;">
+          <el-tag>{{ form.type }}</el-tag>
+          <h4 style="margin: 0 0 0 5px;" :id="titleId" :class="titleClass">{{ form.label }}</h4>
+        </div>
+      </template>
+      <el-form ref="deptRef" :model="form" :rules="rules" label-width="80px" label-position="top">
+        <el-form-item label="API">
+          <el-row style="width: 100%;" :gutter="20">
+            <el-col :span="4">
+              <el-select v-model="form.config.method" style="width: 100px">
+                <el-option label="GET" value="GET"></el-option>
+                <el-option label="POST" value="POST"></el-option>
+                <el-option label="HEAD" value="HEAD"></el-option>
+                <el-option label="PATCH" value="PATCH"></el-option>
+                <el-option label="PUT" value="PUT"></el-option>
+                <el-option label="DELETE" value="DELETE"></el-option>
+              </el-select>
+            </el-col>
+            <el-col :span="20">
+              <el-input v-model="form.config.url" placeholder="请输入URL"/>
+            </el-col>
+          </el-row>
+        </el-form-item>
+        <el-form-item label="HEADERS">
+          <dynamic-map v-model="form.config.headers"></dynamic-map>
+        </el-form-item>
+        <el-form-item label="PARAMS">
+          <dynamic-map v-model="form.config.params"></dynamic-map>
+        </el-form-item>
+        <el-form-item label="BODY">
+          <!-- 单选 -->
+          <el-radio-group v-model="form.bodyType">
+            <el-radio value="none">none</el-radio>
+            <el-radio value="form-data">form-data</el-radio>
+            <el-radio value="x-www-form-urlencoded">x-www-form-urlencoded</el-radio>
+            <el-radio value="JSON">JSON</el-radio>
+            <el-radio value="raw">raw</el-radio>
+          </el-radio-group>
+          <dynamic-map v-if="form.bodyType === 'x-www-form-urlencoded'" v-model="form.config.body"></dynamic-map>
+        </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="失败处理">
+              <el-select v-model="form.errorPolicy" style="width: 100px">
+                <el-option label="报错" value="ABORT"></el-option>
+                <el-option label="忽视" value="IGNORE"></el-option>
+                <el-option label="重连" value="RETRY"></el-option>
+              </el-select>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item v-if="form.errorPolicy === 'RETRY'" label="失败重连次数">
+              <el-input-number v-model="form.retryCount" :min="1" :max="30"/>
+            </el-form-item>
+          </el-col>
+        </el-row>
+      </el-form>
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button type="primary" @click="submitForm">确 定</el-button>
+          <el-button @click="cancel">取 消</el-button>
+        </div>
+      </template>
+    </el-dialog>
+  </div>
+</template>
+<script setup>
+import DynamicMap from '@/components/DynamicMap/index.vue'
+import {Promotion} from '@element-plus/icons-vue'
+import {ref} from 'vue'
+import {useVueFlow, VueFlow} from '@vue-flow/core'
+import SpecialNode from './components/SpecialNode.vue'
+import SpecialEdge from './components/SpecialEdge.vue'
+import {getPtServiceList} from "@/api/service/info.js";
+import {getModelList2} from "@/api/register/regCom.js";
+import {copyObject} from "@/utils/index.js";
+import {getWorkflowByModelId, saveWorkflow} from "@/api/standardization/workflow.js";
+
+const {
+  onInit,
+  findNode,
+  fitView,
+  snapToGrid,
+  addEdges,
+  addNodes,
+  toObject,
+  screenToFlowCoordinate,
+  onNodesInitialized,
+  updateNode,
+  onNodeClick,
+  onEdgeClick,
+  getNodes,
+  getEdges,
+  removeNodes,
+  removeEdges,
+} = useVueFlow()
+// to enable snapping to grid
+snapToGrid.value = true
+
+const {proxy} = getCurrentInstance();
+const modelQueryParams = ref({
+  name: undefined,
+  isPublic: '0',
+});
+const toolQueryParams = ref({
+  name: undefined,
+});
+const modelOptions = ref(undefined);
+const modelId = ref(undefined);
+const loading = ref(true);
+const toolType = ref('0');
+const serviceList = ref([]);
+
+const draggedData = ref(undefined);
+const isDragging = ref(false);
+const isDragOver = ref(false);
+const nodes = ref([]);
+const edges = ref([]);
+
+const title = ref('')
+const open = ref(false)
+
+
+function handleModelClick(mid) {
+  modelId.value = mid
+  removeNodes(getNodes.value)
+  removeEdges(getEdges.value)
+  getWorkflow(mid)
+}
+
+onInit((instance) => {
+  fitView()
+  const node = findNode('1')
+  if (node) {
+    node.position = {x: 100, y: 100}
+  }
+})
+
+onNodeClick(({event, node}) => {
+  form.value = node.data
+  open.value = true
+  title.value = node.data.label
+  nodeId.value = node.id
+});
+
+// Edge click event handler
+onEdgeClick(({event, edge}) => {
+  console.log('Edge clicked:', edge, event);
+});
+
+const data = reactive({
+  form: {config: {}},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    userName: undefined,
+    phonenumber: undefined,
+    status: undefined,
+    deptId: undefined
+  },
+  rules: {
+    userName: [{required: true, message: "用户名称不能为空", trigger: "blur"}, {
+      min: 2,
+      max: 20,
+      message: "用户名称长度必须介于 2 和 20 之间",
+      trigger: "blur"
+    }],
+    nickName: [{required: true, message: "用户昵称不能为空", trigger: "blur"}],
+    password: [{required: true, message: "用户密码不能为空", trigger: "blur"}, {
+      min: 5,
+      max: 20,
+      message: "用户密码长度必须介于 5 和 20 之间",
+      trigger: "blur"
+    }, {pattern: /^[^<>"'|\\]+$/, message: "不能包含非法字符:< > \" ' \\\ |", trigger: "blur"}],
+    email: [{type: "email", message: "请输入正确的邮箱地址", trigger: ["blur", "change"]}],
+    phonenumber: [{pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, message: "请输入正确的手机号码", trigger: "blur"}]
+  }
+});
+
+const {queryParams, form, rules} = toRefs(data);
+const nodeId = ref(null)
+
+/**
+ * 开始拖拽选项的事件
+ * @param event
+ * @param data
+ */
+function onDragStart(event, data) {
+  if (event.dataTransfer) {
+    event.dataTransfer.setData('application/vueflow', data)
+    event.dataTransfer.effectAllowed = 'move'
+  }
+
+  draggedData.value = data
+  isDragging.value = true
+
+  document.addEventListener('drop', onDragEnd)
+}
+
+/**
+ * 拖拽到画布vueflow的事件
+ * @param event
+ */
+function onDragOver(event) {
+  event.preventDefault()
+
+  if (draggedData.value) {
+    isDragOver.value = true
+
+    if (event.dataTransfer) {
+      event.dataTransfer.dropEffect = 'move'
+    }
+  }
+}
+
+/**
+ * 拖拽放下的事件
+ * @param event
+ */
+function onDrop(event) {
+  const position = screenToFlowCoordinate({
+    x: event.clientX,
+    y: event.clientY,
+  })
+
+  const nodeId = Math.random() + "id";
+
+  const data = copyObject(draggedData.value)
+  const newNode = {
+    id: nodeId,
+    type: 'special',
+    position,
+    data,
+  }
+
+  // 更新位置到鼠标中心
+  const {off} = onNodesInitialized(() => {
+    updateNode(nodeId, (node) => ({
+      position: {x: node.position.x - node.dimensions.width / 2, y: node.position.y - node.dimensions.height / 2},
+    }))
+
+    off()
+  })
+
+  addNodes(newNode)
+}
+
+/**
+ * 拖拽到画布外面的的事件
+ */
+function onDragLeave() {
+  isDragOver.value = false
+}
+
+/**
+ * 拖拽结束
+ */
+function onDragEnd() {
+  isDragging.value = false
+  isDragOver.value = false
+  draggedData.value = null
+  document.removeEventListener('drop', onDragEnd)
+}
+
+function onConnect(params) {
+  console.log('on connect', params)
+  addEdges(params)
+}
+
+/** 查询模型列表 */
+function getModelList() {
+  getModelList2(modelQueryParams.value).then(res => {
+    loading.value = false;
+    modelOptions.value = res.data;
+  });
+}
+
+/** 通过条件过滤节点  */
+const filterNode = (value, data) => {
+  if (!value) return true;
+  return data.label.indexOf(value) !== -1;
+};
+
+/** 节点单击事件 */
+function handleNodeClick(data) {
+  queryParams.value.modeId = 1 // data.id;
+  handleQuery();
+}
+
+/** 搜索按钮操作 */
+function handleQuery() {
+  queryParams.value.pageNum = 1;
+  getList();
+}
+
+/** 查询流程图 */
+function getList() {
+  loading.value = true;
+  // listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(res => {
+  //   loading.value = false;
+  //   userList.value = res.rows;
+  //   total.value = res.total;
+  // });
+}
+
+/** 查询服务列表 */
+function getServiceList() {
+  getPtServiceList(toolQueryParams.value).then(res => {
+    serviceList.value = res.data.map(item => {
+      return {
+        id: item.id,
+        label: item.name,
+        type: 'API',
+        config: {
+          url: item.url,
+          method: item.rqtype,
+          params: item.params,
+          headers: item.headers,
+          body: item.body,
+        },
+        errorPolicy: 'ABORT',
+        retryCount: 0,
+        outputMapping: null,
+        data: item,
+      }
+    })
+  })
+}
+
+function submitForm() {
+  updateNode(nodeId.value, (node) => ({
+    data: form.value,
+  }))
+  cancel()
+}
+
+function cancel() {
+  form.value = {config: {}};
+  open.value = false;
+  title.value = ''
+}
+
+function saveStep() {
+
+  if (!modelId.value) {
+    proxy.$modal.msgError("请选择模型");
+    return
+  }
+
+  const nodes = getNodes.value.map(res => {
+    return {
+      id: res.id,
+      type: res.type,
+      position: res.position,
+      data: res.data,
+    }
+  })
+  const edges = getEdges.value.map(res => {
+    return {
+      source: res.source,
+      target: res.target,
+    }
+  })
+  const data = {
+    mdid: modelId.value,
+    graph: JSON.stringify({
+      nodes: nodes,
+      edges: edges,
+    }),
+  }
+  saveWorkflow(data).then(res => {
+    // 测试
+    proxy.$modal.msgError("请输入建表语句");
+  })
+}
+
+function getWorkflow(modelId) {
+  getWorkflowByModelId(modelId).then(res => {
+    if (res.data && res.data.graph) {
+      const {nodes, edges} = JSON.parse(res.data.graph)
+      addNodes(nodes)
+      addEdges(edges)
+    }
+  })
+}
+
+
+getModelList()
+getServiceList()
+</script>
+<style>
+@import '@vue-flow/core/dist/style.css';
+@import '@vue-flow/core/dist/theme-default.css';
+</style>
+<style scoped>
+.app-container {
+  height: 100%;
+
+  & > div {
+    height: 100%;
+
+    & > div {
+      height: 100%;
+    }
+  }
+
+  .sidebar-wrapper {
+    padding: 10px 0;
+    border-radius: 8px;
+    border: 1px solid var(--el-border-color);
+  }
+
+  .tool-container {
+    overflow: auto;
+    height: calc(100% - 74px);
+
+    .tool-item {
+      padding: 5px 10px;
+      border-radius: 6px;
+      cursor: pointer;
+
+      &:hover, &.active {
+        background-color: var(--el-color-primary);
+        color: #fff;
+      }
+
+    }
+
+  }
+
+  .flow-button-group {
+    position: absolute;
+    top: 10px;
+    left: 10px;
+    z-index: 10;
+  }
+
+}
+
+:deep(.el-radio-group) {
+  display: flex;
+  width: 100%;
+  text-align: center;
+}
+
+:deep(.el-radio-button) {
+  flex: 1;
+  border: var(--el-border);
+}
+
+:deep(.el-radio-button.is-active) {
+  background-color: var(--el-color-primary);
+}
+
+:deep(.el-radio-button:first-child) {
+  border-top-left-radius: 5px;
+  border-bottom-left-radius: 5px;
+}
+
+:deep(.el-radio-button:last-child) {
+  border-top-right-radius: 5px;
+  border-bottom-right-radius: 5px;
+}
+
+:deep(.el-radio-button__inner) {
+  transition: none;
+  border: none !important;
+}
+</style>