diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java index 19d12afd..bbdd0b38 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/enums/ResponseCode.java @@ -149,7 +149,11 @@ public enum ResponseCode { /** * 工作流变量反序列化错误 */ - WORKFLOW_VARIABLE_DESERIALIZE_ERROR(50302, "workflow.variable.deserialize.error"); + WORKFLOW_VARIABLE_DESERIALIZE_ERROR(50302, "workflow.variable.deserialize.error"), + + // 表单管理相关错误码 (2800-2899) + FORM_DEFINITION_NOT_FOUND(2800, "form.definition.not.found"), + FORM_DATA_NOT_FOUND(2801, "form.data.not.found"); private final int code; private final String messageKey; // 国际化消息key diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormCategoryApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormCategoryApiController.java new file mode 100644 index 00000000..7538d068 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormCategoryApiController.java @@ -0,0 +1,44 @@ +package com.qqchen.deploy.backend.workflow.api; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.dto.FormCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.FormCategory; +import com.qqchen.deploy.backend.workflow.service.IFormCategoryService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 表单分类API控制器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/forms/categories") +@Tag(name = "表单分类管理", description = "表单分类管理相关接口") +public class FormCategoryApiController extends BaseController { + + @Resource + private IFormCategoryService formCategoryService; + + @Operation(summary = "查询所有启用的分类") + @GetMapping("/enabled") + public Response> findAllEnabled() { + List result = formCategoryService.findAllEnabled(); + return Response.success(result); + } + + @Override + protected void exportData(jakarta.servlet.http.HttpServletResponse response, java.util.List data) { + // 暂不支持导出 + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java new file mode 100644 index 00000000..984ed71b --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDataApiController.java @@ -0,0 +1,52 @@ +package com.qqchen.deploy.backend.workflow.api; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.dto.FormDataDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery; +import com.qqchen.deploy.backend.workflow.entity.FormData; +import com.qqchen.deploy.backend.workflow.service.IFormDataService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 表单数据API控制器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/forms/data") +@Tag(name = "表单数据管理", description = "表单数据管理相关接口") +public class FormDataApiController extends BaseController { + + @Resource + private IFormDataService formDataService; + + @Operation(summary = "提交表单数据") + @PostMapping("/submit") + public Response submit(@RequestBody FormDataDTO dto) { + FormDataDTO result = formDataService.submit(dto); + return Response.success(result); + } + + @Operation(summary = "根据业务标识查询表单数据") + @GetMapping("/business/{businessKey}") + public Response getByBusinessKey( + @Parameter(description = "业务标识", required = true) @PathVariable String businessKey + ) { + FormDataDTO result = formDataService.getByBusinessKey(businessKey); + return Response.success(result); + } + + @Override + protected void exportData(jakarta.servlet.http.HttpServletResponse response, java.util.List data) { + // 暂不支持导出 + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java new file mode 100644 index 00000000..ee6259ea --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/FormDefinitionApiController.java @@ -0,0 +1,54 @@ +package com.qqchen.deploy.backend.workflow.api; + +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; +import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 表单定义API控制器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/forms/definitions") +@Tag(name = "表单定义管理", description = "表单定义管理相关接口") +public class FormDefinitionApiController extends BaseController { + + @Resource + private IFormDefinitionService formDefinitionService; + + @Operation(summary = "发布表单") + @PostMapping("/{id}/publish") + public Response publish( + @Parameter(description = "表单定义ID", required = true) @PathVariable Long id + ) { + FormDefinitionDTO result = formDefinitionService.publish(id); + return Response.success(result); + } + + @Operation(summary = "根据key获取最新版本表单") + @GetMapping("/latest/{key}") + public Response getLatestByKey( + @Parameter(description = "表单标识", required = true) @PathVariable String key + ) { + FormDefinitionDTO result = formDefinitionService.getLatestByKey(key); + return Response.success(result); + } + + @Override + protected void exportData(jakarta.servlet.http.HttpServletResponse response, java.util.List data) { + // 暂不支持导出 + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowCategoryApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowCategoryApiController.java new file mode 100644 index 00000000..262d3575 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/api/WorkflowCategoryApiController.java @@ -0,0 +1,33 @@ +package com.qqchen.deploy.backend.workflow.api; + +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.workflow.dto.WorkflowCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.WorkflowCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 工作流分类API Controller + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@RestController +@RequestMapping("/api/v1/workflow/categories") +@Tag(name = "工作流分类管理", description = "工作流分类相关接口") +public class WorkflowCategoryApiController extends BaseController { + + @Override + protected void exportData(HttpServletResponse response, List data) { + // TODO: 实现工作流分类数据导出逻辑 + log.warn("工作流分类数据导出功能暂未实现"); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormCategoryConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormCategoryConverter.java new file mode 100644 index 00000000..06cd087b --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormCategoryConverter.java @@ -0,0 +1,17 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.dto.FormCategoryDTO; +import com.qqchen.deploy.backend.workflow.entity.FormCategory; +import org.mapstruct.Mapper; + +/** + * 表单分类转换器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Mapper(config = BaseConverter.class) +public interface FormCategoryConverter extends BaseConverter { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java new file mode 100644 index 00000000..8e341bfb --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDataConverter.java @@ -0,0 +1,17 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.dto.FormDataDTO; +import com.qqchen.deploy.backend.workflow.entity.FormData; +import org.mapstruct.Mapper; + +/** + * 表单数据转换器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Mapper(config = BaseConverter.class) +public interface FormDataConverter extends BaseConverter { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java new file mode 100644 index 00000000..e991f0d7 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/FormDefinitionConverter.java @@ -0,0 +1,17 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; +import org.mapstruct.Mapper; + +/** + * 表单定义转换器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Mapper(config = BaseConverter.class) +public interface FormDefinitionConverter extends BaseConverter { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowCategoryConverter.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowCategoryConverter.java new file mode 100644 index 00000000..5828f59b --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/converter/WorkflowCategoryConverter.java @@ -0,0 +1,17 @@ +package com.qqchen.deploy.backend.workflow.converter; + +import com.qqchen.deploy.backend.framework.converter.BaseConverter; +import com.qqchen.deploy.backend.workflow.dto.WorkflowCategoryDTO; +import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory; +import org.mapstruct.Mapper; + +/** + * 工作流分类转换器 + * + * @author qqchen + * @date 2025-10-24 + */ +@Mapper(config = BaseConverter.class) +public interface WorkflowCategoryConverter extends BaseConverter { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormCategoryDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormCategoryDTO.java new file mode 100644 index 00000000..24ea2055 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormCategoryDTO.java @@ -0,0 +1,55 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单分类DTO + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "表单分类DTO") +public class FormCategoryDTO extends BaseDTO { + + /** + * 分类名称 + */ + @Schema(description = "分类名称", example = "审批表单") + private String name; + + /** + * 分类编码 + */ + @Schema(description = "分类编码", example = "APPROVAL") + private String code; + + /** + * 分类描述 + */ + @Schema(description = "分类描述", example = "用于审批流程的表单") + private String description; + + /** + * 图标 + */ + @Schema(description = "图标", example = "CheckCircleOutlined") + private String icon; + + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 是否启用 + */ + @Schema(description = "是否启用", example = "true") + private Boolean enabled; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java new file mode 100644 index 00000000..4205c8c8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDataDTO.java @@ -0,0 +1,96 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums; +import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 表单数据DTO + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "表单数据DTO") +public class FormDataDTO extends BaseDTO { + + /** + * 表单定义ID + */ + @Schema(description = "表单定义ID", example = "1") + private Long formDefinitionId; + + /** + * 表单标识(冗余存储,避免JOIN) + */ + @Schema(description = "表单标识", example = "employee_info_form") + private String formKey; + + /** + * 表单版本(冗余存储,用于历史追溯) + */ + @Schema(description = "表单版本", example = "1") + private Integer formVersion; + + /** + * 表单分类ID(冗余存储,便于统计和查询) + */ + @Schema(description = "表单分类ID", example = "1") + private Long categoryId; + + /** + * 表单分类信息(用于展示) + */ + @Schema(description = "表单分类信息") + private FormCategoryDTO category; + + /** + * 业务标识(如工作流实例ID、订单号等) + */ + @Schema(description = "业务标识", example = "workflow_instance_123") + private String businessKey; + + /** + * 业务类型(WORKFLOW-工作流、ORDER-订单、STANDALONE-独立表单) + */ + @Schema(description = "业务类型") + private FormBusinessTypeEnums businessType; + + /** + * 表单填写数据(用户提交的实际数据) + */ + @Schema(description = "表单填写数据") + private Map data; + + /** + * 表单Schema快照(用于历史追溯,确保数据可还原,原样存储) + */ + @Schema(description = "表单Schema快照") + private Map schemaSnapshot; + + /** + * 提交人 + */ + @Schema(description = "提交人", example = "admin") + private String submitter; + + /** + * 提交时间 + */ + @Schema(description = "提交时间", example = "2025-10-24T12:00:00") + private LocalDateTime submitTime; + + /** + * 状态(DRAFT-草稿、SUBMITTED-已提交、COMPLETED-已完成、REJECTED-已拒绝) + */ + @Schema(description = "状态") + private FormDataStatusEnums status; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java new file mode 100644 index 00000000..fa05c2be --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/FormDefinitionDTO.java @@ -0,0 +1,83 @@ +package com.qqchen.deploy.backend.workflow.dto; + +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.Map; + +/** + * 表单定义DTO + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "表单定义DTO") +public class FormDefinitionDTO extends BaseDTO { + + /** + * 表单名称 + */ + @Schema(description = "表单名称", example = "员工信息登记表") + private String name; + + /** + * 表单标识(业务唯一) + */ + @Schema(description = "表单标识", example = "employee_info_form") + private String key; + + /** + * 表单版本号 + */ + @Schema(description = "表单版本号", example = "1") + private Integer formVersion; + + /** + * 表单分类ID + */ + @Schema(description = "表单分类ID", example = "1") + private Long categoryId; + + /** + * 表单分类信息(用于展示) + */ + @Schema(description = "表单分类信息") + private FormCategoryDTO category; + + /** + * 表单描述 + */ + @Schema(description = "表单描述", example = "用于采集员工基本信息") + private String description; + + /** + * 表单Schema(前端设计器导出的JSON结构,原样存储) + */ + @Schema(description = "表单Schema") + private Map schema; + + /** + * 标签(用于分类和搜索) + */ + @Schema(description = "标签列表", example = "[\"员工管理\", \"信息采集\"]") + private List tags; + + /** + * 状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用) + */ + @Schema(description = "状态") + private FormDefinitionStatusEnums status; + + /** + * 是否为模板 + */ + @Schema(description = "是否为模板", example = "false") + private Boolean isTemplate; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowCategoryDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowCategoryDTO.java index ca3227b6..03d1d8a5 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowCategoryDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowCategoryDTO.java @@ -1,19 +1,64 @@ package com.qqchen.deploy.backend.workflow.dto; - +import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.List; +/** + * 工作流分类DTO + * + * @author qqchen + * @date 2025-10-24 + */ @Data -public class WorkflowCategoryDTO { +@EqualsAndHashCode(callSuper = true) +@Schema(description = "工作流分类DTO") +public class WorkflowCategoryDTO extends BaseDTO { + private Long id; + + /** + * 分类名称 + */ + @Schema(description = "分类名称", example = "脚本执行") + private String name; + + /** + * 分类编码(唯一) + */ + @Schema(description = "分类编码", example = "SCRIPT_EXECUTION") private String code; - private String lable; - + /** + * 分类描述 + */ + @Schema(description = "分类描述", example = "用于执行各类脚本的流程") private String description; - private List supportedTriggers; + /** + * 图标 + */ + @Schema(description = "图标", example = "CodeOutlined") + private String icon; + /** + * 排序 + */ + @Schema(description = "排序", example = "1") + private Integer sort; + + /** + * 支持的触发方式列表 + */ + @Schema(description = "支持的触发方式列表", example = "[\"MANUAL\",\"SCHEDULED\"]") + private List supportedTriggers; + + /** + * 是否启用 + */ + @Schema(description = "是否启用", example = "true") + private Boolean enabled; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java index 3ba3d323..21d12ae7 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/WorkflowDefinitionDTO.java @@ -1,16 +1,12 @@ package com.qqchen.deploy.backend.workflow.dto; -import com.fasterxml.jackson.databind.JsonNode; import com.qqchen.deploy.backend.framework.dto.BaseDTO; +import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO; import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph; -import com.qqchen.deploy.backend.workflow.enums.WorkflowCategoryEnum; import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnums; -import com.qqchen.deploy.backend.workflow.enums.WorkflowTriggerTypeEnum; import lombok.Data; import lombok.EqualsAndHashCode; -import java.util.List; - /** * 工作流定义DTO * @@ -32,14 +28,24 @@ public class WorkflowDefinitionDTO extends BaseDTO { private String key; /** - * 分类 + * 分类ID */ - private WorkflowCategoryEnum category; + private Long categoryId; /** - * 触发类型列表 + * 分类信息(用于展示) */ - private List triggers; + private WorkflowCategoryDTO category; + + /** + * 启动表单ID + */ + private Long formDefinitionId; + + /** + * 启动表单信息(用于展示) + */ + private FormDefinitionDTO formDefinition; /** * 流程版本 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormCategoryQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormCategoryQuery.java new file mode 100644 index 00000000..2f593fb6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormCategoryQuery.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.workflow.dto.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单分类查询对象 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class FormCategoryQuery extends BaseQuery { + + /** + * 分类名称(模糊查询) + */ + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + /** + * 分类编码(精确查询) + */ + @QueryField(field = "code", type = QueryType.EQUAL) + private String code; + + /** + * 是否启用(精确查询) + */ + @QueryField(field = "enabled", type = QueryType.EQUAL) + private Boolean enabled; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java new file mode 100644 index 00000000..39207b6c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDataQuery.java @@ -0,0 +1,63 @@ +package com.qqchen.deploy.backend.workflow.dto.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums; +import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单数据查询对象 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class FormDataQuery extends BaseQuery { + + /** + * 表单定义ID(精确查询) + */ + @QueryField(field = "formDefinitionId", type = QueryType.EQUAL) + private Long formDefinitionId; + + /** + * 表单标识(精确查询) + */ + @QueryField(field = "formKey", type = QueryType.EQUAL) + private String formKey; + + /** + * 表单分类ID(精确查询) + */ + @QueryField(field = "categoryId", type = QueryType.EQUAL) + private Long categoryId; + + /** + * 业务标识(精确查询) + */ + @QueryField(field = "businessKey", type = QueryType.EQUAL) + private String businessKey; + + /** + * 业务类型(精确查询) + */ + @QueryField(field = "businessType", type = QueryType.EQUAL) + private FormBusinessTypeEnums businessType; + + /** + * 提交人(精确查询) + */ + @QueryField(field = "submitter", type = QueryType.EQUAL) + private String submitter; + + /** + * 状态(精确查询) + */ + @QueryField(field = "status", type = QueryType.EQUAL) + private FormDataStatusEnums status; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java new file mode 100644 index 00000000..4b8fa635 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/FormDefinitionQuery.java @@ -0,0 +1,56 @@ +package com.qqchen.deploy.backend.workflow.dto.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单定义查询对象 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class FormDefinitionQuery extends BaseQuery { + + /** + * 表单名称(模糊查询) + */ + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + /** + * 表单标识(精确查询) + */ + @QueryField(field = "key", type = QueryType.EQUAL) + private String key; + + /** + * 表单版本(精确查询) + */ + @QueryField(field = "formVersion", type = QueryType.EQUAL) + private Integer formVersion; + + /** + * 表单分类ID(精确查询) + */ + @QueryField(field = "categoryId", type = QueryType.EQUAL) + private Long categoryId; + + /** + * 状态(精确查询) + */ + @QueryField(field = "status", type = QueryType.EQUAL) + private FormDefinitionStatusEnums status; + + /** + * 是否为模板(精确查询) + */ + @QueryField(field = "isTemplate", type = QueryType.EQUAL) + private Boolean isTemplate; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowCategoryQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowCategoryQuery.java new file mode 100644 index 00000000..c91087f6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowCategoryQuery.java @@ -0,0 +1,37 @@ +package com.qqchen.deploy.backend.workflow.dto.query; + +import com.qqchen.deploy.backend.framework.annotation.QueryField; +import com.qqchen.deploy.backend.framework.enums.QueryType; +import com.qqchen.deploy.backend.framework.query.BaseQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 工作流分类查询对象 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class WorkflowCategoryQuery extends BaseQuery { + + /** + * 分类名称(模糊查询) + */ + @QueryField(field = "name", type = QueryType.LIKE) + private String name; + + /** + * 分类编码(精确查询) + */ + @QueryField(field = "code", type = QueryType.EQUAL) + private String code; + + /** + * 是否启用(精确查询) + */ + @QueryField(field = "enabled", type = QueryType.EQUAL) + private Boolean enabled; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowDefinitionQuery.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowDefinitionQuery.java index 4aca4808..8c76187d 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowDefinitionQuery.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/dto/query/WorkflowDefinitionQuery.java @@ -3,6 +3,7 @@ package com.qqchen.deploy.backend.workflow.dto.query; import com.qqchen.deploy.backend.framework.annotation.QueryField; import com.qqchen.deploy.backend.framework.enums.QueryType; import com.qqchen.deploy.backend.framework.query.BaseQuery; +import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnums; import lombok.Data; import lombok.EqualsAndHashCode; @@ -25,9 +26,21 @@ public class WorkflowDefinitionQuery extends BaseQuery { @QueryField(field = "key", type = QueryType.EQUAL) private String key; + /** + * 分类ID + */ + @QueryField(field = "categoryId", type = QueryType.EQUAL) + private Long categoryId; + /** * 流程版本 */ - @QueryField(field = "version", type = QueryType.EQUAL) - private Integer version; + @QueryField(field = "flowVersion", type = QueryType.EQUAL) + private Integer flowVersion; + + /** + * 流程状态 + */ + @QueryField(field = "status", type = QueryType.EQUAL) + private WorkflowDefinitionStatusEnums status; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormCategory.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormCategory.java new file mode 100644 index 00000000..6fb220f1 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormCategory.java @@ -0,0 +1,58 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 表单分类实体 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@Table(name = "form_category") +@jakarta.persistence.Entity +@EqualsAndHashCode(callSuper = true) +@LogicDelete +public class FormCategory extends Entity { + + /** + * 分类名称 + */ + @Column(nullable = false, length = 100) + private String name; + + /** + * 分类编码(唯一) + */ + @Column(nullable = false, length = 50, unique = true) + private String code; + + /** + * 分类描述 + */ + @Column(length = 500) + private String description; + + /** + * 图标 + */ + @Column(length = 50) + private String icon; + + /** + * 排序 + */ + @Column(nullable = false) + private Integer sort; + + /** + * 是否启用 + */ + @Column(nullable = false) + private Boolean enabled; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java new file mode 100644 index 00000000..652d0ba8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormData.java @@ -0,0 +1,99 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.workflow.enums.FormBusinessTypeEnums; +import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums; +import com.vladmihalcea.hibernate.type.json.JsonType; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 表单数据实体 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@Table(name = "form_data") +@jakarta.persistence.Entity +@EqualsAndHashCode(callSuper = true) +@LogicDelete +public class FormData extends Entity { + + /** + * 表单定义ID + */ + @Column(name = "form_definition_id", nullable = false) + private Long formDefinitionId; + + /** + * 表单标识(冗余存储,避免JOIN) + */ + @Column(name = "form_key", nullable = false) + private String formKey; + + /** + * 表单版本(冗余存储,用于历史追溯) + */ + @Column(name = "form_version", nullable = false) + private Integer formVersion; + + /** + * 表单分类ID(冗余存储,便于统计和查询) + */ + @Column(name = "category_id") + private Long categoryId; + + /** + * 业务标识(如工作流实例ID、订单号等) + */ + @Column(name = "business_key") + private String businessKey; + + /** + * 业务类型(WORKFLOW-工作流、ORDER-订单、STANDALONE-独立表单) + */ + @Column(name = "business_type") + @Enumerated(EnumType.STRING) + private FormBusinessTypeEnums businessType; + + /** + * 表单填写数据(用户提交的实际数据) + */ + @Type(JsonType.class) + @Column(name = "data", nullable = false, columnDefinition = "json") + private Map data; + + /** + * 表单Schema快照(用于历史追溯,确保数据可还原,原样存储) + */ + @Type(JsonType.class) + @Column(name = "schema_snapshot", nullable = false, columnDefinition = "json") + private Map schemaSnapshot; + + /** + * 提交人 + */ + @Column(name = "submitter") + private String submitter; + + /** + * 提交时间 + */ + @Column(name = "submit_time") + private LocalDateTime submitTime; + + /** + * 状态(DRAFT-草稿、SUBMITTED-已提交、COMPLETED-已完成、REJECTED-已拒绝) + */ + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private FormDataStatusEnums status; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java new file mode 100644 index 00000000..d47b67e4 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/FormDefinition.java @@ -0,0 +1,85 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums; +import com.vladmihalcea.hibernate.type.json.JsonType; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; + +import java.util.List; +import java.util.Map; + +/** + * 表单定义实体 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@Table(name = "form_definition") +@jakarta.persistence.Entity +@EqualsAndHashCode(callSuper = true) +@LogicDelete +public class FormDefinition extends Entity { + + /** + * 表单名称 + */ + @Column(nullable = false) + private String name; + + /** + * 表单标识(业务唯一) + */ + @Column(name = "`key`", nullable = false) + private String key; + + /** + * 表单版本号 + */ + @Column(name = "form_version", nullable = false) + private Integer formVersion; + + /** + * 表单分类ID + */ + @Column(name = "category_id") + private Long categoryId; + + /** + * 表单描述 + */ + @Column(name = "description", columnDefinition = "TEXT") + private String description; + + /** + * 表单Schema(前端设计器导出的JSON结构,原样存储) + */ + @Type(JsonType.class) + @Column(name = "`schema`", nullable = false, columnDefinition = "json") + private Map schema; + + /** + * 标签(用于分类和搜索) + */ + @Type(JsonType.class) + @Column(name = "tags", columnDefinition = "json") + private List tags; + + /** + * 状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用) + */ + @Column(name = "status", nullable = false) + @Enumerated(EnumType.STRING) + private FormDefinitionStatusEnums status; + + /** + * 是否为模板 + */ + @Column(name = "is_template", nullable = false) + private Boolean isTemplate; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowCategory.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowCategory.java new file mode 100644 index 00000000..d33d89b5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowCategory.java @@ -0,0 +1,69 @@ +package com.qqchen.deploy.backend.workflow.entity; + +import com.qqchen.deploy.backend.framework.annotation.LogicDelete; +import com.qqchen.deploy.backend.framework.domain.Entity; +import com.vladmihalcea.hibernate.type.json.JsonType; +import jakarta.persistence.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; + +import java.util.List; + +/** + * 工作流分类实体 + * + * @author qqchen + * @date 2025-10-24 + */ +@Data +@Table(name = "workflow_category") +@jakarta.persistence.Entity +@EqualsAndHashCode(callSuper = true) +@LogicDelete +public class WorkflowCategory extends Entity { + + /** + * 分类名称 + */ + @Column(name = "name", nullable = false, length = 100) + private String name; + + /** + * 分类编码(唯一,如SCRIPT_EXECUTION) + */ + @Column(name = "code", nullable = false, length = 50, unique = true) + private String code; + + /** + * 分类描述 + */ + @Column(name = "description", length = 500) + private String description; + + /** + * 图标 + */ + @Column(name = "icon", length = 50) + private String icon; + + /** + * 排序 + */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; + + /** + * 支持的触发方式列表(JSON数组,如["MANUAL","SCHEDULED"]) + */ + @Type(JsonType.class) + @Column(name = "supported_triggers", columnDefinition = "json") + private List supportedTriggers; + + /** + * 是否启用 + */ + @Column(name = "enabled", nullable = false) + private Boolean enabled = true; +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java index 82deb7c0..93e2bfbd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/entity/WorkflowDefinition.java @@ -1,16 +1,11 @@ package com.qqchen.deploy.backend.workflow.entity; -import com.fasterxml.jackson.databind.JsonNode; import com.qqchen.deploy.backend.framework.annotation.LogicDelete; import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph; -import com.qqchen.deploy.backend.workflow.enums.WorkflowCategoryEnum; import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnums; -import com.qqchen.deploy.backend.workflow.enums.WorkflowTriggerTypeEnum; import com.qqchen.deploy.backend.workflow.entity.converter.WorkflowGraphType; import com.qqchen.deploy.backend.framework.domain.Entity; -import com.vladmihalcea.hibernate.type.json.JsonType; import jakarta.persistence.Column; -import jakarta.persistence.Convert; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import jakarta.persistence.Table; @@ -18,8 +13,6 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; -import java.util.List; - /** * 工作流定义实体 */ @@ -43,18 +36,18 @@ public class WorkflowDefinition extends Entity { private String key; /** - * 流程分类 + * 流程分类ID(外键关联workflow_category) */ - @Column(name = "category", nullable = false) - @Enumerated(EnumType.STRING) - private WorkflowCategoryEnum category; + @Column(name = "category_id") + private Long categoryId; + /** + * 启动表单ID(外键关联form_definition) + */ + @Column(name = "form_definition_id") + private Long formDefinitionId; - @Column(name = "triggers", nullable = false) - @Convert(converter = WorkflowTriggerTypeListConverter.class) - private List triggers; - - @Column(name = "process_definition_id", nullable = false) + @Column(name = "process_definition_id") private String processDefinitionId; /** @@ -89,12 +82,4 @@ public class WorkflowDefinition extends Entity { */ @Column(columnDefinition = "TEXT") private String description; - - - /** - * 流程标签,用于分组和过滤 - */ - @Type(JsonType.class) - @Column(name = "tags", columnDefinition = "json") - private JsonNode tags; } diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java new file mode 100644 index 00000000..aa8460e6 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormBusinessTypeEnums.java @@ -0,0 +1,28 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.Getter; + +/** + * 表单业务类型枚举 + * + * @author qqchen + * @date 2025-10-24 + */ +@Getter +public enum FormBusinessTypeEnums { + + WORKFLOW("WORKFLOW", "工作流"), + ORDER("ORDER", "订单"), + STANDALONE("STANDALONE", "独立表单"); + + private final String code; + + private final String description; + + FormBusinessTypeEnums(String code, String description) { + this.code = code; + this.description = description; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java new file mode 100644 index 00000000..ae4481ee --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDataStatusEnums.java @@ -0,0 +1,29 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.Getter; + +/** + * 表单数据状态枚举 + * + * @author qqchen + * @date 2025-10-24 + */ +@Getter +public enum FormDataStatusEnums { + + DRAFT("DRAFT", "草稿"), + SUBMITTED("SUBMITTED", "已提交"), + COMPLETED("COMPLETED", "已完成"), + REJECTED("REJECTED", "已拒绝"); + + private final String code; + + private final String description; + + FormDataStatusEnums(String code, String description) { + this.code = code; + this.description = description; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java new file mode 100644 index 00000000..1777a733 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/FormDefinitionStatusEnums.java @@ -0,0 +1,28 @@ +package com.qqchen.deploy.backend.workflow.enums; + +import lombok.Getter; + +/** + * 表单定义状态枚举 + * + * @author qqchen + * @date 2025-10-24 + */ +@Getter +public enum FormDefinitionStatusEnums { + + DRAFT("DRAFT", "草稿"), + PUBLISHED("PUBLISHED", "已发布"), + DISABLED("DISABLED", "已禁用"); + + private final String code; + + private final String description; + + FormDefinitionStatusEnums(String code, String description) { + this.code = code; + this.description = description; + } + +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowCategoryEnum.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowCategoryEnum.java deleted file mode 100644 index 720e22a3..00000000 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/enums/WorkflowCategoryEnum.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.qqchen.deploy.backend.workflow.enums; - - -import lombok.Getter; - -import java.util.Arrays; -import java.util.List; - -/** - * 工作流节点分类枚举 - */ -@Getter -public enum WorkflowCategoryEnum { - // 1. 脚本执行类 - SCRIPT_EXECUTION( - "脚本执行", - "用于执行各类脚本的流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL, WorkflowTriggerTypeEnum.SCHEDULED) - ), - - // 2. 部署类 - DEPLOYMENT( - "应用部署", - "用于应用部署的流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL, WorkflowTriggerTypeEnum.SCHEDULED, WorkflowTriggerTypeEnum.APPROVAL) - ), - - // 3. 数据同步类 - DATA_SYNC( - "数据同步", - "用于第三方系统数据同步的流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL, WorkflowTriggerTypeEnum.SCHEDULED) - ), - - // 4. 配置同步类 - CONFIG_SYNC( - "配置同步", - "用于配置中心数据同步的流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL, WorkflowTriggerTypeEnum.APPROVAL) - ), - - // 5. 审批流程类 - APPROVAL( - "审批流程", - "纯审批流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL) - ), - - // 6. 其他类型(用于扩展) - OTHER( - "其他", - "其他类型流程", - Arrays.asList(WorkflowTriggerTypeEnum.MANUAL) - ); - - private final String label; // 显示名称 - - private final String description; // 描述 - - private final List supportedTriggers; // 支持的触发方式 - - WorkflowCategoryEnum(String label, String description, List supportedTriggers) { - this.label = label; - this.description = description; - this.supportedTriggers = supportedTriggers; - } - - // 判断是否支持某种触发方式 - public boolean supportsTriggerType(WorkflowTriggerTypeEnum triggerType) { - return supportedTriggers.contains(triggerType); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormCategoryRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormCategoryRepository.java new file mode 100644 index 00000000..46b86b85 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormCategoryRepository.java @@ -0,0 +1,42 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.FormCategory; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 表单分类Repository接口 + * + * @author qqchen + * @date 2025-10-24 + */ +@Repository +public interface IFormCategoryRepository extends IBaseRepository { + + /** + * 根据编码查询 + * + * @param code 分类编码 + * @return 表单分类 + */ + Optional findByCodeAndDeletedFalse(String code); + + /** + * 检查编码是否存在 + * + * @param code 分类编码 + * @return 是否存在 + */ + boolean existsByCodeAndDeletedFalse(String code); + + /** + * 查询所有启用的分类(按排序) + * + * @return 分类列表 + */ + List findByEnabledTrueAndDeletedFalseOrderBySortAsc(); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java new file mode 100644 index 00000000..4e32245c --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDataRepository.java @@ -0,0 +1,65 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.FormData; +import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 表单数据Repository接口 + * + * @author qqchen + * @date 2025-10-24 + */ +@Repository +public interface IFormDataRepository extends IBaseRepository { + + /** + * 根据业务标识查询 + * + * @param businessKey 业务标识 + * @return 表单数据 + */ + Optional findByBusinessKeyAndDeletedFalse(String businessKey); + + /** + * 根据表单标识分页查询 + * + * @param formKey 表单标识 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByFormKeyAndDeletedFalse(String formKey, Pageable pageable); + + /** + * 根据提交人分页查询 + * + * @param submitter 提交人 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findBySubmitterAndDeletedFalse(String submitter, Pageable pageable); + + /** + * 根据状态分页查询 + * + * @param status 状态 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByStatusAndDeletedFalse(FormDataStatusEnums status, Pageable pageable); + + /** + * 根据表单定义ID查询列表 + * + * @param formDefinitionId 表单定义ID + * @return 表单数据列表 + */ + List findByFormDefinitionIdAndDeletedFalse(Long formDefinitionId); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java new file mode 100644 index 00000000..2d74c3e5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IFormDefinitionRepository.java @@ -0,0 +1,64 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; +import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +/** + * 表单定义Repository接口 + * + * @author qqchen + * @date 2025-10-24 + */ +@Repository +public interface IFormDefinitionRepository extends IBaseRepository { + + /** + * 根据表单标识和版本查询 + * + * @param key 表单标识 + * @param formVersion 表单版本 + * @return 表单定义 + */ + Optional findByKeyAndFormVersionAndDeletedFalse(String key, Integer formVersion); + + /** + * 根据表单标识查询最新版本 + * + * @param key 表单标识 + * @return 表单定义 + */ + Optional findFirstByKeyAndDeletedFalseOrderByFormVersionDesc(String key); + + /** + * 检查表单标识是否存在 + * + * @param key 表单标识 + * @return 是否存在 + */ + boolean existsByKeyAndDeletedFalse(String key); + + /** + * 根据状态分页查询 + * + * @param status 状态 + * @param pageable 分页参数 + * @return 分页结果 + */ + Page findByStatusAndDeletedFalse(FormDefinitionStatusEnums status, Pageable pageable); + + /** + * 根据状态查询列表 + * + * @param status 状态 + * @return 表单定义列表 + */ + List findByStatusAndDeletedFalse(FormDefinitionStatusEnums status); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowCategoryRepository.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowCategoryRepository.java new file mode 100644 index 00000000..0ee22389 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/repository/IWorkflowCategoryRepository.java @@ -0,0 +1,24 @@ +package com.qqchen.deploy.backend.workflow.repository; + +import com.qqchen.deploy.backend.framework.repository.IBaseRepository; +import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory; +import org.springframework.stereotype.Repository; + +/** + * 工作流分类Repository + * + * @author qqchen + * @date 2025-10-24 + */ +@Repository +public interface IWorkflowCategoryRepository extends IBaseRepository { + + /** + * 根据编码查询分类 + * + * @param code 分类编码 + * @return 工作流分类 + */ + WorkflowCategory findByCodeAndDeletedFalse(String code); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormCategoryService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormCategoryService.java new file mode 100644 index 00000000..fdb74a89 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormCategoryService.java @@ -0,0 +1,25 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.FormCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.FormCategory; + +import java.util.List; + +/** + * 表单分类服务接口 + * + * @author qqchen + * @date 2025-10-24 + */ +public interface IFormCategoryService extends IBaseService { + + /** + * 查询所有启用的分类 + * + * @return 分类列表 + */ + List findAllEnabled(); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java new file mode 100644 index 00000000..59a8a0a5 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDataService.java @@ -0,0 +1,32 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.FormDataDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery; +import com.qqchen.deploy.backend.workflow.entity.FormData; + +/** + * 表单数据服务接口 + * + * @author qqchen + * @date 2025-10-24 + */ +public interface IFormDataService extends IBaseService { + + /** + * 提交表单数据 + * + * @param dto 表单数据DTO + * @return 提交后的表单数据 + */ + FormDataDTO submit(FormDataDTO dto); + + /** + * 根据业务标识查询表单数据 + * + * @param businessKey 业务标识 + * @return 表单数据 + */ + FormDataDTO getByBusinessKey(String businessKey); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java new file mode 100644 index 00000000..863fb2e8 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IFormDefinitionService.java @@ -0,0 +1,33 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; + +/** + * 表单定义服务接口 + * + * @author qqchen + * @date 2025-10-24 + */ +public interface IFormDefinitionService extends IBaseService { + + /** + * 发布表单(版本不可变) + * 如果当前表单已发布,则创建新版本;否则直接发布 + * + * @param id 表单定义ID + * @return 发布后的表单定义 + */ + FormDefinitionDTO publish(Long id); + + /** + * 根据表单标识获取最新版本 + * + * @param key 表单标识 + * @return 表单定义 + */ + FormDefinitionDTO getLatestByKey(String key); +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowCategoryService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowCategoryService.java new file mode 100644 index 00000000..e26ed901 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowCategoryService.java @@ -0,0 +1,16 @@ +package com.qqchen.deploy.backend.workflow.service; + +import com.qqchen.deploy.backend.framework.service.IBaseService; +import com.qqchen.deploy.backend.workflow.dto.WorkflowCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.WorkflowCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory; + +/** + * 工作流分类Service接口 + * + * @author qqchen + * @date 2025-10-24 + */ +public interface IWorkflowCategoryService extends IBaseService { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java index 95e93e0c..3c9e6eb0 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/IWorkflowDefinitionService.java @@ -9,7 +9,6 @@ import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceCreateDTO; import com.qqchen.deploy.backend.workflow.dto.WorkflowInstanceDTO; import com.qqchen.deploy.backend.workflow.dto.query.WorkflowDefinitionQuery; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.enums.WorkflowCategoryEnum; import org.flowable.engine.repository.Deployment; import org.flowable.engine.runtime.ProcessInstance; import org.springframework.transaction.annotation.Transactional; diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormCategoryServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormCategoryServiceImpl.java new file mode 100644 index 00000000..80f7c621 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormCategoryServiceImpl.java @@ -0,0 +1,42 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.FormCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.FormCategory; +import com.qqchen.deploy.backend.workflow.repository.IFormCategoryRepository; +import com.qqchen.deploy.backend.workflow.service.IFormCategoryService; +import com.qqchen.deploy.backend.workflow.converter.FormCategoryConverter; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * 表单分类服务实现 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@Service +public class FormCategoryServiceImpl extends BaseServiceImpl implements IFormCategoryService { + + @Resource + private IFormCategoryRepository formCategoryRepository; + + @Resource + private FormCategoryConverter formCategoryConverter; + + @Override + public List findAllEnabled() { + log.info("查询所有启用的表单分类"); + List categories = formCategoryRepository.findByEnabledTrueAndDeletedFalseOrderBySortAsc(); + return categories.stream() + .map(formCategoryConverter::toDto) + .collect(Collectors.toList()); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java new file mode 100644 index 00000000..b2d34163 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDataServiceImpl.java @@ -0,0 +1,75 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.FormDataDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDataQuery; +import com.qqchen.deploy.backend.workflow.entity.FormData; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; +import com.qqchen.deploy.backend.workflow.enums.FormDataStatusEnums; +import com.qqchen.deploy.backend.workflow.repository.IFormDataRepository; +import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository; +import com.qqchen.deploy.backend.workflow.service.IFormDataService; +import com.qqchen.deploy.backend.workflow.converter.FormDataConverter; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 表单数据服务实现 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@Service +public class FormDataServiceImpl extends BaseServiceImpl implements IFormDataService { + + @Resource + private IFormDataRepository formDataRepository; + + @Resource + private IFormDefinitionRepository formDefinitionRepository; + + @Resource + private FormDataConverter formDataConverter; + + @Override + @Transactional + public FormDataDTO submit(FormDataDTO dto) { + log.info("提交表单数据: formKey={}, submitter={}", dto.getFormKey(), dto.getSubmitter()); + + // 1. 查询表单定义,获取schema快照 + FormDefinition formDefinition = formDefinitionRepository.findById(dto.getFormDefinitionId()) + .orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND)); + + // 2. 创建表单数据 + FormData formData = formDataConverter.toEntity(dto); + formData.setFormKey(formDefinition.getKey()); + formData.setFormVersion(formDefinition.getFormVersion()); + formData.setCategoryId(formDefinition.getCategoryId()); // 冗余存储分类ID + formData.setSchemaSnapshot(formDefinition.getSchema()); // 保存schema快照 + formData.setSubmitTime(LocalDateTime.now()); + formData.setStatus(FormDataStatusEnums.SUBMITTED); + + // 3. 保存 + FormData savedFormData = formDataRepository.save(formData); + log.info("表单数据提交成功: id={}, formKey={}", savedFormData.getId(), savedFormData.getFormKey()); + + return formDataConverter.toDto(savedFormData); + } + + @Override + public FormDataDTO getByBusinessKey(String businessKey) { + log.info("根据业务标识查询表单数据: businessKey={}", businessKey); + + FormData formData = formDataRepository.findByBusinessKeyAndDeletedFalse(businessKey) + .orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DATA_NOT_FOUND)); + + return formDataConverter.toDto(formData); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java new file mode 100644 index 00000000..2cfc082a --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/FormDefinitionServiceImpl.java @@ -0,0 +1,133 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.exception.BusinessException; +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.FormDefinitionDTO; +import com.qqchen.deploy.backend.workflow.dto.query.FormDefinitionQuery; +import com.qqchen.deploy.backend.workflow.entity.FormDefinition; +import com.qqchen.deploy.backend.workflow.enums.FormDefinitionStatusEnums; +import com.qqchen.deploy.backend.workflow.repository.IFormDefinitionRepository; +import com.qqchen.deploy.backend.workflow.service.IFormDefinitionService; +import com.qqchen.deploy.backend.workflow.converter.FormDefinitionConverter; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * 表单定义服务实现 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@Service +public class FormDefinitionServiceImpl extends BaseServiceImpl implements IFormDefinitionService { + + @Resource + private IFormDefinitionRepository formDefinitionRepository; + + @Resource + private FormDefinitionConverter formDefinitionConverter; + + @Override + @Transactional + public FormDefinitionDTO create(FormDefinitionDTO dto) { + log.info("创建表单定义: name={}, key={}", dto.getName(), dto.getKey()); + + // 1. 转换为实体 + FormDefinition formDefinition = formDefinitionConverter.toEntity(dto); + + // 2. 后端控制版本号和状态(前端传递的值会被覆盖) + formDefinition.setFormVersion(1); // 新建表单,版本号固定为 1 + formDefinition.setStatus(FormDefinitionStatusEnums.DRAFT); // 新建表单,状态固定为草稿 + + // 3. 保存 + FormDefinition savedForm = formDefinitionRepository.save(formDefinition); + log.info("表单定义创建成功: id={}, key={}, version={}", savedForm.getId(), savedForm.getKey(), savedForm.getFormVersion()); + + return formDefinitionConverter.toDto(savedForm); + } + + @Override + @Transactional + public FormDefinitionDTO update(Long id, FormDefinitionDTO dto) { + log.info("更新表单定义: id={}, name={}", id, dto.getName()); + + // 1. 查询原始表单定义 + FormDefinition existingForm = formDefinitionRepository.findById(id) + .orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND)); + + // 2. 保留后端控制的字段(版本号和状态由后端控制,不允许前端修改) + Integer originalFormVersion = existingForm.getFormVersion(); + FormDefinitionStatusEnums originalStatus = existingForm.getStatus(); + + // 3. 转换并更新允许修改的字段 + FormDefinition formDefinition = formDefinitionConverter.toEntity(dto); + formDefinition.setId(id); // 使用路径参数的ID + formDefinition.setFormVersion(originalFormVersion); // 保留原始版本号 + formDefinition.setStatus(originalStatus); // 保留原始状态 + + // 4. 保存 + FormDefinition savedForm = formDefinitionRepository.save(formDefinition); + log.info("表单定义更新成功: id={}, version={}, status={}", savedForm.getId(), savedForm.getFormVersion(), savedForm.getStatus()); + + return formDefinitionConverter.toDto(savedForm); + } + + @Override + @Transactional + public FormDefinitionDTO publish(Long id) { + log.info("发布表单定义,ID: {}", id); + + // 1. 查询当前表单定义 + FormDefinition currentForm = formDefinitionRepository.findById(id) + .orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND)); + + // 2. 检查是否已发布 + if (FormDefinitionStatusEnums.PUBLISHED.equals(currentForm.getStatus())) { + log.info("表单已发布,创建新版本: key={}, currentVersion={}", currentForm.getKey(), currentForm.getFormVersion()); + + // 创建新版本 + FormDefinition newVersion = new FormDefinition(); + newVersion.setName(currentForm.getName()); + newVersion.setKey(currentForm.getKey()); + newVersion.setFormVersion(currentForm.getFormVersion() + 1); + newVersion.setCategoryId(currentForm.getCategoryId()); + newVersion.setDescription(currentForm.getDescription()); + newVersion.setSchema(currentForm.getSchema()); + newVersion.setTags(currentForm.getTags()); + newVersion.setStatus(FormDefinitionStatusEnums.PUBLISHED); + newVersion.setIsTemplate(currentForm.getIsTemplate()); + + FormDefinition savedNewVersion = formDefinitionRepository.save(newVersion); + log.info("新版本创建成功: id={}, version={}", savedNewVersion.getId(), savedNewVersion.getFormVersion()); + + return formDefinitionConverter.toDto(savedNewVersion); + } else { + // 3. 首次发布,直接更新状态 + log.info("首次发布表单: key={}, version={}", currentForm.getKey(), currentForm.getFormVersion()); + currentForm.setStatus(FormDefinitionStatusEnums.PUBLISHED); + if (currentForm.getFormVersion() == null) { + currentForm.setFormVersion(1); + } + + FormDefinition savedForm = formDefinitionRepository.save(currentForm); + log.info("表单发布成功: id={}, version={}", savedForm.getId(), savedForm.getFormVersion()); + + return formDefinitionConverter.toDto(savedForm); + } + } + + @Override + public FormDefinitionDTO getLatestByKey(String key) { + log.info("根据key获取最新版本表单: key={}", key); + + FormDefinition formDefinition = formDefinitionRepository + .findFirstByKeyAndDeletedFalseOrderByFormVersionDesc(key) + .orElseThrow(() -> new BusinessException(com.qqchen.deploy.backend.framework.enums.ResponseCode.FORM_DEFINITION_NOT_FOUND)); + + return formDefinitionConverter.toDto(formDefinition); + } +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowCategoryServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowCategoryServiceImpl.java new file mode 100644 index 00000000..313903a9 --- /dev/null +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowCategoryServiceImpl.java @@ -0,0 +1,21 @@ +package com.qqchen.deploy.backend.workflow.service.impl; + +import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; +import com.qqchen.deploy.backend.workflow.dto.WorkflowCategoryDTO; +import com.qqchen.deploy.backend.workflow.dto.query.WorkflowCategoryQuery; +import com.qqchen.deploy.backend.workflow.entity.WorkflowCategory; +import com.qqchen.deploy.backend.workflow.service.IWorkflowCategoryService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 工作流分类Service实现 + * + * @author qqchen + * @date 2025-10-24 + */ +@Slf4j +@Service +public class WorkflowCategoryServiceImpl extends BaseServiceImpl implements IWorkflowCategoryService { +} + diff --git a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java index 5d84ba33..bfd71fdd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/workflow/service/impl/WorkflowDefinitionServiceImpl.java @@ -8,7 +8,6 @@ import com.qqchen.deploy.backend.workflow.dto.WorkflowTriggerTypeDTO; import com.qqchen.deploy.backend.workflow.dto.definition.workflow.WorkflowDefinitionGraph; import com.qqchen.deploy.backend.workflow.dto.query.WorkflowDefinitionQuery; import com.qqchen.deploy.backend.workflow.entity.WorkflowDefinition; -import com.qqchen.deploy.backend.workflow.enums.WorkflowCategoryEnum; import com.qqchen.deploy.backend.workflow.enums.WorkflowDefinitionStatusEnums; import com.qqchen.deploy.backend.workflow.enums.WorkflowNodeInstanceStatusEnums; import com.qqchen.deploy.backend.workflow.repository.IWorkflowDefinitionRepository; @@ -68,6 +67,11 @@ public class WorkflowDefinitionServiceImpl extends BaseServiceImpl getWorkflowCategories() { - return Arrays.stream(WorkflowCategoryEnum.values()) - .map(category -> { - WorkflowCategoryDTO dto = new WorkflowCategoryDTO(); - dto.setCode(category.name()); - dto.setLable(category.getLabel()); - dto.setDescription(category.getDescription()); - // 获取该类别支持的触发方式 - dto.setSupportedTriggers( - category.getSupportedTriggers().stream() - .map(triggerType -> { - WorkflowTriggerTypeDTO triggerDto = new WorkflowTriggerTypeDTO(); - triggerDto.setCode(triggerType.name()); - triggerDto.setLable(triggerType.getLabel()); - return triggerDto; - }) - .collect(Collectors.toList()) - ); - return dto; - }) + // 从数据库查询启用的工作流分类,按排序字段排序 + return workflowCategoryRepository.findAll().stream() + .filter(category -> category.getEnabled() && !category.getDeleted()) + .sorted((a, b) -> a.getSort().compareTo(b.getSort())) + .map(workflowCategoryConverter::toDto) .collect(Collectors.toList()); } diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql index 4f29c996..c5bc52be 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-data.sql @@ -91,7 +91,12 @@ VALUES (230, '集成工具', '/integration', 'Layout', 'ApiOutlined', 1, NULL, 6, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (231, 'Jenkins集成', '/deploy/jenkins-manager', '/src/pages/Deploy/JenkinsManager/List', 'BuildOutlined', 2, 230, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), (232, 'Git仓库', '/deploy/git-manager', '/src/pages/Deploy/GitManager/List', 'GithubOutlined', 2, 230, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), -(233, '外部服务', '/deploy/external', '/src/pages/Deploy/external/index', 'LinkOutlined', 2, 230, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE); +(233, '外部服务', '/deploy/external', '/src/pages/Deploy/external/index', 'LinkOutlined', 2, 230, 30, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), + +-- 📄 表单管理 +(300, '表单管理', '/form', 'Layout', 'FormOutlined', 1, NULL, 7, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(301, '表单定义', '/form/definitions', '/src/pages/form/definitions/index', 'FileTextOutlined', 2, 300, 10, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE), +(302, '表单数据', '/form/data', '/src/pages/form/data/index', 'DatabaseOutlined', 2, 300, 20, FALSE, TRUE, 'system', '2024-01-01 00:00:00', 0, FALSE); -- 初始化角色数据 INSERT INTO sys_role (id, create_time, code, name, type, description, sort) @@ -130,6 +135,7 @@ SELECT 1, id FROM sys_menu; -- 超级管理员拥有所有菜单权限 INSERT INTO sys_role_menu (role_id, menu_id) VALUES (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), -- 系统管理员拥有系统管理相关权限 +(2, 300), (2, 301), (2, 302), -- 系统管理员拥有表单管理权限 (3, 233); -- 普通用户拥有外部服务权限 -- 初始化权限模板 @@ -143,7 +149,9 @@ INSERT INTO sys_template_menu (template_id, menu_id) SELECT 1, id FROM sys_menu; -- 完整权限模板关联所有菜单 INSERT INTO sys_template_menu (template_id, menu_id) -VALUES (2, 233); -- 基础权限模板关联外部服务菜单 +VALUES +(2, 233), -- 基础权限模板关联外部服务菜单 +(2, 300), (2, 302); -- 基础权限模板关联表单管理和表单数据(不包括表单定义) -- 初始化权限数据 INSERT INTO sys_permission (id, create_time, menu_id, code, name, type, sort) @@ -166,7 +174,21 @@ VALUES (11, NOW(), 233, 'system:external:update', '外部服务修改', 'FUNCTION', 3), (12, NOW(), 233, 'system:external:delete', '外部服务删除', 'FUNCTION', 4), (13, NOW(), 233, 'system:external:test', '连接测试', 'FUNCTION', 5), -(14, NOW(), 233, 'system:external:sync', '数据同步', 'FUNCTION', 6); +(14, NOW(), 233, 'system:external:sync', '数据同步', 'FUNCTION', 6), + +-- 表单定义权限 +(15, NOW(), 301, 'form:definition:list', '表单定义列表', 'FUNCTION', 1), +(16, NOW(), 301, 'form:definition:create', '创建表单定义', 'FUNCTION', 2), +(17, NOW(), 301, 'form:definition:update', '修改表单定义', 'FUNCTION', 3), +(18, NOW(), 301, 'form:definition:delete', '删除表单定义', 'FUNCTION', 4), +(19, NOW(), 301, 'form:definition:publish', '发布表单', 'FUNCTION', 5), + +-- 表单数据权限 +(20, NOW(), 302, 'form:data:list', '表单数据列表', 'FUNCTION', 1), +(21, NOW(), 302, 'form:data:view', '查看表单数据', 'FUNCTION', 2), +(22, NOW(), 302, 'form:data:submit', '提交表单数据', 'FUNCTION', 3), +(23, NOW(), 302, 'form:data:delete', '删除表单数据', 'FUNCTION', 4), +(24, NOW(), 302, 'form:data:export', '导出表单数据', 'FUNCTION', 5); -- -------------------------------------------------------------------------------------- -- 初始化外部系统数据 @@ -197,12 +219,23 @@ INSERT INTO sys_external_system ( -- -------------------------------------------------------------------------------------- -- 初始化工作流相关数据 -- -------------------------------------------------------------------------------------- + +-- 工作流分类初始数据 +INSERT INTO workflow_category (name, code, description, icon, sort, supported_triggers, enabled, create_by, create_time, update_by, update_time, version, deleted) +VALUES +('脚本执行', 'SCRIPT_EXECUTION', '用于执行各类脚本的流程', 'CodeOutlined', 1, '["MANUAL","SCHEDULED"]', 1, 'system', NOW(), 'system', NOW(), 0, 0), +('应用部署', 'DEPLOYMENT', '用于应用部署的流程', 'DeploymentUnitOutlined', 2, '["MANUAL","SCHEDULED","APPROVAL"]', 1, 'system', NOW(), 'system', NOW(), 0, 0), +('数据同步', 'DATA_SYNC', '用于第三方系统数据同步的流程', 'SyncOutlined', 3, '["MANUAL","SCHEDULED"]', 1, 'system', NOW(), 'system', NOW(), 0, 0), +('配置同步', 'CONFIG_SYNC', '用于配置中心数据同步的流程', 'SettingOutlined', 4, '["MANUAL","APPROVAL"]', 1, 'system', NOW(), 'system', NOW(), 0, 0), +('审批流程', 'APPROVAL', '纯审批流程', 'AuditOutlined', 5, '["MANUAL"]', 1, 'system', NOW(), 'system', NOW(), 0, 0), +('其他', 'OTHER', '其他类型流程', 'AppstoreOutlined', 99, '["MANUAL"]', 1, 'system', NOW(), 'system', NOW(), 0, 0); + -- 工作流定义测试数据 INSERT INTO workflow_definition ( -- 基础信息 - name, `key`, process_definition_id, flow_version, description, category, triggers, + name, `key`, process_definition_id, flow_version, description, category_id, form_definition_id, -- 流程配置 - graph, tags, + graph, -- 流程属性 status, -- 审计字段 @@ -210,7 +243,7 @@ INSERT INTO workflow_definition ( ) VALUES -- 简单脚本流程:开始 -> 脚本任务 -> 结束 ( - '简单脚本流程', 'simple_script_flow', null, 1, '一个包含脚本任务的简单流程', 'SCRIPT_EXECUTION', null, + '简单脚本流程', 'simple_script_flow', null, 1, '一个包含脚本任务的简单流程', 1, null, '{ "nodes" : [ { "id" : "startEvent1", @@ -371,14 +404,13 @@ INSERT INTO workflow_definition ( "properties" : null } ] }', - '["simple","script","test"]', 'DRAFT', NOW(), NOW(), NULL, NULL, 0, 0 ), -- 复杂业务流程:开始 -> 脚本任务A -> 脚本任务B -> 结束 ( - '复杂业务流程', 'complex_business_flow', null, 1, '包含多个脚本任务节点的业务流程', 'SCRIPT_EXECUTION', null, + '复杂业务流程', 'complex_business_flow', null, 1, '包含多个脚本任务节点的业务流程', 1, null, '{ "nodes" : [ { "id" : "startEvent1", @@ -602,7 +634,6 @@ INSERT INTO workflow_definition ( "properties" : null } ] }', - '["complex","business","multi-task"]', 'DRAFT', NOW(), NOW(), NULL, NULL, 0, 0 ); @@ -835,3 +866,14 @@ VALUES -- 'DISABLED', -- '管理员邮件通知渠道(示例数据,请修改为实际配置)', -- 'admin', NOW(), 'admin', NOW(), 0, 0); + +-- -------------------------------------------------------------------------------------- +-- 初始化表单分类数据 +-- -------------------------------------------------------------------------------------- + +INSERT INTO form_category (name, code, description, icon, sort, enabled, create_by, create_time, update_by, update_time, version, deleted) +VALUES +('审批表单', 'APPROVAL', '用于审批流程的表单', 'CheckCircleOutlined', 1, 1, 'system', NOW(), 'system', NOW(), 0, 0), +('数据采集', 'DATA_COLLECTION', '用于数据采集的表单', 'DatabaseOutlined', 2, 1, 'system', NOW(), 'system', NOW(), 0, 0), +('问卷调查', 'SURVEY', '用于问卷调查的表单', 'FormOutlined', 3, 1, 'system', NOW(), 'system', NOW(), 0, 0), +('其他', 'OTHER', '其他类型的表单', 'FileOutlined', 99, 1, 'system', NOW(), 'system', NOW(), 0, 0); diff --git a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql index c5ac9400..37ffe732 100644 --- a/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql +++ b/backend/src/main/resources/db/changelog/changes/v1.0.0-schema.sql @@ -371,6 +371,113 @@ CREATE TABLE deploy_repo_branch -- 工作流相关表 -- -------------------------------------------------------------------------------------- +-- 工作流分类表 +CREATE TABLE workflow_category +( + -- 主键 + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + + -- 基础信息 + name VARCHAR(100) NOT NULL COMMENT '分类名称', + code VARCHAR(50) NOT NULL COMMENT '分类编码(唯一,如SCRIPT_EXECUTION)', + description VARCHAR(500) NULL COMMENT '分类描述', + icon VARCHAR(50) NULL COMMENT '图标', + sort INT NOT NULL DEFAULT 0 COMMENT '排序', + + -- 支持的触发方式(JSON数组,如["MANUAL","SCHEDULED"]) + supported_triggers JSON NULL COMMENT '支持的触发方式列表', + + -- 状态 + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + -- 审计字段 + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + -- 约束和索引 + UNIQUE KEY uk_code (code), + INDEX idx_enabled (enabled), + INDEX idx_deleted (deleted), + INDEX idx_sort (sort) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci + COMMENT = '工作流分类表'; + +CREATE TABLE form_category +( + -- 主键 + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + + -- 基础信息 + name VARCHAR(100) NOT NULL COMMENT '分类名称', + code VARCHAR(50) NOT NULL COMMENT '分类编码(唯一)', + description VARCHAR(500) NULL COMMENT '分类描述', + icon VARCHAR(50) NULL COMMENT '图标', + sort INT NOT NULL DEFAULT 0 COMMENT '排序', + + -- 状态 + enabled BIT NOT NULL DEFAULT 1 COMMENT '是否启用', + + -- 审计字段 + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + -- 约束和索引 + UNIQUE KEY uk_code (code), + INDEX idx_enabled (enabled), + INDEX idx_deleted (deleted), + INDEX idx_sort (sort) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci + COMMENT = '表单分类表'; + +-- 表单定义表 +CREATE TABLE form_definition +( + -- 主键 + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + + -- 基础信息 + name VARCHAR(255) NOT NULL COMMENT '表单名称', + `key` VARCHAR(255) NOT NULL COMMENT '表单标识(业务唯一)', + form_version INT NOT NULL DEFAULT 1 COMMENT '表单版本号', + category_id BIGINT NULL COMMENT '表单分类ID(外键关联form_category)', + description TEXT NULL COMMENT '表单描述', + + -- 表单配置 + `schema` JSON NOT NULL COMMENT '表单Schema(前端设计器导出的JSON结构)', + tags JSON NULL COMMENT '标签(用于分类和搜索)', + + -- 表单属性 + status VARCHAR(50) NOT NULL DEFAULT 'DRAFT' COMMENT '状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用)', + is_template BIT NOT NULL DEFAULT 0 COMMENT '是否为模板', + + -- 审计字段 + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + -- 约束和索引 + UNIQUE KEY uk_key_version (`key`, form_version), + INDEX idx_category_id (category_id), + INDEX idx_status (status), + INDEX idx_is_template (is_template), + INDEX idx_deleted (deleted), + + -- 外键约束 + CONSTRAINT fk_form_definition_category FOREIGN KEY (category_id) REFERENCES form_category(id) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci + COMMENT = '表单定义表'; + -- 工作流定义表 CREATE TABLE workflow_definition ( @@ -380,8 +487,8 @@ CREATE TABLE workflow_definition -- 基础信息 name VARCHAR(255) NOT NULL COMMENT '流程名称', `key` VARCHAR(255) NOT NULL COMMENT '流程标识', - category VARCHAR(100) COMMENT '流程分类', - triggers VARCHAR(200) COMMENT '流程分类', + category_id BIGINT NULL COMMENT '流程分类ID(外键关联workflow_category)', + form_definition_id BIGINT NULL COMMENT '启动表单ID(外键关联form_definition)', process_definition_id VARCHAR(100) NULL COMMENT '工作流定义ID', flow_version INT NOT NULL COMMENT '流程版本', description TEXT COMMENT '流程描述', @@ -389,7 +496,6 @@ CREATE TABLE workflow_definition -- 流程配置 bpmn_xml TEXT COMMENT 'BPMN XML内容', graph JSON COMMENT '流程图数据,包含节点和连线的位置、样式等信息', - tags JSON COMMENT '流程标签', -- 流程属性 status VARCHAR(50) NOT NULL COMMENT '流程状态(DRAFT-草稿、PUBLISHED-已发布、DISABLED-已禁用)', @@ -402,8 +508,14 @@ CREATE TABLE workflow_definition update_time DATETIME(6) NULL COMMENT '更新时间', version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', - -- 约束 - UNIQUE KEY uk_key_version (`key`, flow_version) + -- 约束和索引 + UNIQUE KEY uk_key_version (`key`, flow_version), + INDEX idx_category_id (category_id), + INDEX idx_form_definition_id (form_definition_id), + INDEX idx_status (status), + INDEX idx_deleted (deleted), + CONSTRAINT fk_workflow_definition_category FOREIGN KEY (category_id) REFERENCES workflow_category(id), + CONSTRAINT fk_workflow_definition_form FOREIGN KEY (form_definition_id) REFERENCES form_definition(id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT ='工作流定义表'; @@ -689,3 +801,51 @@ CREATE TABLE sys_notification_channel ( INDEX idx_deleted (deleted) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='通知渠道配置表'; +-- 表单数据表 +CREATE TABLE form_data +( + -- 主键 + id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', + + -- 关联表单定义 + form_definition_id BIGINT NOT NULL COMMENT '表单定义ID', + form_key VARCHAR(255) NOT NULL COMMENT '表单标识(冗余存储,避免JOIN)', + form_version INT NOT NULL COMMENT '表单版本(冗余存储,用于历史追溯)', + category_id BIGINT NULL COMMENT '表单分类ID(冗余存储,便于统计和查询)', + + -- 业务关联(松耦合) + business_key VARCHAR(255) NULL COMMENT '业务标识(如工作流实例ID、订单号等)', + business_type VARCHAR(50) NULL COMMENT '业务类型(WORKFLOW-工作流、ORDER-订单、STANDALONE-独立表单)', + + -- 表单数据 + data JSON NOT NULL COMMENT '表单填写数据(用户提交的实际数据)', + schema_snapshot JSON NOT NULL COMMENT '表单Schema快照(用于历史追溯,确保数据可还原)', + + -- 提交信息 + submitter VARCHAR(255) NULL COMMENT '提交人', + submit_time DATETIME(6) NULL COMMENT '提交时间', + + -- 状态 + status VARCHAR(50) NOT NULL DEFAULT 'SUBMITTED' COMMENT '状态(DRAFT-草稿、SUBMITTED-已提交、COMPLETED-已完成、REJECTED-已拒绝)', + + -- 审计字段 + create_by VARCHAR(255) NULL COMMENT '创建人', + create_time DATETIME(6) NULL COMMENT '创建时间', + deleted BIT NOT NULL DEFAULT 0 COMMENT '是否删除', + update_by VARCHAR(255) NULL COMMENT '更新人', + update_time DATETIME(6) NULL COMMENT '更新时间', + version INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', + + -- 索引 + INDEX idx_form_definition_id (form_definition_id), + INDEX idx_form_key (form_key), + INDEX idx_category_id (category_id), + INDEX idx_business_key (business_key), + INDEX idx_business_type (business_type), + INDEX idx_submitter (submitter), + INDEX idx_status (status), + INDEX idx_submit_time (submit_time), + INDEX idx_deleted (deleted) +) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci + COMMENT = '表单数据表'; + diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties index 769dedfa..e6f5cf6b 100644 --- a/backend/src/main/resources/messages.properties +++ b/backend/src/main/resources/messages.properties @@ -241,3 +241,7 @@ workflow.variable.serialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u5E8F\u5217\u53 workflow.variable.deserialize.error=\u5DE5\u4F5C\u6D41\u53D8\u91CF\u53CD\u5E8F\u5217\u5316\u5931\u8D25: {0} workflow.config.error=\u5DE5\u4F5C\u6D41\u914D\u7F6E\u9519\u8BEF: {0} + +# \u8868\u5355\u7BA1\u7406\u76F8\u5173\u9519\u8BEF\u7801 (2800-2899) +form.definition.not.found=\u8868\u5355\u5B9A\u4E49\u4E0D\u5B58\u5728\u6216\u5DF2\u5220\u9664 +form.data.not.found=\u8868\u5355\u6570\u636E\u4E0D\u5B58\u5728\u6216\u5DF2\u5220\u9664 \ No newline at end of file diff --git a/frontend/src/components/ui/dropdown-menu.tsx b/frontend/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..e804bca1 --- /dev/null +++ b/frontend/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,199 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { Check, ChevronRight, Circle } from "lucide-react" + +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className + )} + {...props} + /> +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/frontend/src/pages/Form/Category/service.ts b/frontend/src/pages/Form/Category/service.ts new file mode 100644 index 00000000..4b77289b --- /dev/null +++ b/frontend/src/pages/Form/Category/service.ts @@ -0,0 +1,46 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base'; +import type { + FormCategoryQuery, + FormCategoryResponse, + FormCategoryRequest, +} from './types'; + +const BASE_URL = '/api/v1/forms/categories'; + +/** + * 分页查询分类 + */ +export const getCategories = (params?: FormCategoryQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +/** + * 查询分类详情 + */ +export const getCategoryById = (id: number) => + request.get(`${BASE_URL}/${id}`); + +/** + * 查询所有启用的分类(常用) + */ +export const getEnabledCategories = () => + request.get(`${BASE_URL}/enabled`); + +/** + * 创建分类 + */ +export const createCategory = (data: FormCategoryRequest) => + request.post(BASE_URL, data); + +/** + * 更新分类 + */ +export const updateCategory = (id: number, data: FormCategoryRequest) => + request.put(`${BASE_URL}/${id}`, data); + +/** + * 删除分类 + */ +export const deleteCategory = (id: number) => + request.delete(`${BASE_URL}/${id}`); + diff --git a/frontend/src/pages/Form/Category/types.ts b/frontend/src/pages/Form/Category/types.ts new file mode 100644 index 00000000..7cb1b7c3 --- /dev/null +++ b/frontend/src/pages/Form/Category/types.ts @@ -0,0 +1,40 @@ +import { BaseQuery } from '@/types/base'; + +/** + * 表单分类查询参数 + */ +export interface FormCategoryQuery extends BaseQuery { + name?: string; + code?: string; + enabled?: boolean; +} + +/** + * 表单分类响应 + */ +export interface FormCategoryResponse { + id: number; + name: string; + code: string; + description?: string; + icon?: string; + sort: number; + enabled: boolean; + createBy?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 表单分类创建/更新请求 + */ +export interface FormCategoryRequest { + name: string; + code: string; + description?: string; + icon?: string; + sort?: number; + enabled?: boolean; +} + diff --git a/frontend/src/pages/Form/Data/Detail.tsx b/frontend/src/pages/Form/Data/Detail.tsx new file mode 100644 index 00000000..82d545d2 --- /dev/null +++ b/frontend/src/pages/Form/Data/Detail.tsx @@ -0,0 +1,150 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { FormRenderer } from '@/components/FormDesigner'; +import { ArrowLeft, Loader2 } from 'lucide-react'; +import { getFormDataById } from './service'; +import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types'; + +/** + * 表单数据详情页 + */ +const FormDataDetail: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(null); + + // 加载数据 + useEffect(() => { + if (id) { + loadData(Number(id)); + } + }, [id]); + + const loadData = async (dataId: number) => { + setLoading(true); + try { + const result = await getFormDataById(dataId); + setData(result); + } catch (error) { + console.error('加载表单数据失败:', error); + } finally { + setLoading(false); + } + }; + + // 返回列表 + const handleBack = () => { + navigate('/form/data'); + }; + + // 状态徽章 + const getStatusBadge = (status: FormDataStatus) => { + const statusMap: Record = { + DRAFT: { variant: 'outline', text: '草稿' }, + SUBMITTED: { variant: 'success', text: '已提交' }, + COMPLETED: { variant: 'default', text: '已完成' }, + }; + const statusInfo = statusMap[status]; + return {statusInfo.text}; + }; + + // 业务类型徽章 + const getBusinessTypeBadge = (type: FormDataBusinessType) => { + const typeMap: Record = { + STANDALONE: { variant: 'outline', text: '独立' }, + WORKFLOW: { variant: 'default', text: '工作流' }, + ORDER: { variant: 'secondary', text: '订单' }, + }; + const typeInfo = typeMap[type]; + return {typeInfo.text}; + }; + + // 描述项组件 + const DescriptionItem: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => ( +
+
{label}
+
{value}
+
+ ); + + if (loading) { + return ( +
+ + +
+ + 加载中... +
+
+
+
+ ); + } + + if (!data) { + return ( +
+ + +
数据不存在
+
+
+
+ ); + } + + return ( +
+ + +
+ 表单数据详情 + +
+
+ +
+ + + + + + + + + +
+
+
+ + + + 表单数据 + + + {/* 使用 FormRenderer 以只读模式展示数据 */} + + + +
+ ); +}; + +export default FormDataDetail; + diff --git a/frontend/src/pages/Form/Data/index.tsx b/frontend/src/pages/Form/Data/index.tsx new file mode 100644 index 00000000..daff70ba --- /dev/null +++ b/frontend/src/pages/Form/Data/index.tsx @@ -0,0 +1,408 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; +import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; +import { Table, TableHeader, TableBody, TableRow, TableHead, TableCell } from '@/components/ui/table'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { DataTablePagination } from '@/components/ui/pagination'; +import { + Loader2, Search, Eye, Trash2, Download, Folder, + Activity, Clock, CheckCircle2, FileCheck, Database +} from 'lucide-react'; +import { getFormDataList, deleteFormData, exportFormData } from './service'; +import { getEnabledCategories } from '../Category/service'; +import type { FormDataResponse, FormDataStatus, FormDataBusinessType } from './types'; +import type { FormCategoryResponse } from '../Category/types'; +import type { Page } from '@/types/base'; +import { DEFAULT_PAGE_SIZE, DEFAULT_CURRENT } from '@/utils/page'; +import dayjs from 'dayjs'; + +/** + * 表单数据列表页 + */ +const FormDataList: React.FC = () => { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const [loading, setLoading] = useState(false); + const [data, setData] = useState | null>(null); + const [categories, setCategories] = useState([]); + const [query, setQuery] = useState({ + pageNum: DEFAULT_CURRENT - 1, + pageSize: DEFAULT_PAGE_SIZE, + formDefinitionId: searchParams.get('formDefinitionId') ? Number(searchParams.get('formDefinitionId')) : undefined, + businessKey: '', + categoryId: undefined as number | undefined, + status: undefined as FormDataStatus | undefined, + businessType: undefined as FormDataBusinessType | undefined, + }); + + // 加载分类列表 + const loadCategories = async () => { + try { + const result = await getEnabledCategories(); + setCategories(result || []); + } catch (error) { + console.error('加载分类失败:', error); + } + }; + + // 加载数据 + const loadData = async () => { + setLoading(true); + try { + const result = await getFormDataList(query); + setData(result); + } catch (error) { + console.error('加载表单数据失败:', error); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + loadCategories(); + }, []); + + useEffect(() => { + loadData(); + }, [query]); + + // 搜索 + const handleSearch = () => { + setQuery(prev => ({ + ...prev, + pageNum: 0, + })); + }; + + // 重置搜索 + const handleReset = () => { + setQuery({ + pageNum: 0, + pageSize: DEFAULT_PAGE_SIZE, + formDefinitionId: undefined, + businessKey: '', + categoryId: undefined, + status: undefined, + businessType: undefined, + }); + }; + + // 根据分类 ID 获取分类信息 + const getCategoryInfo = (categoryId?: number) => { + return categories.find(cat => cat.id === categoryId); + }; + + // 查看详情 + const handleView = (record: FormDataResponse) => { + navigate(`/form/data/${record.id}`); + }; + + // 删除 + const handleDelete = async (record: FormDataResponse) => { + if (!confirm('确定要删除该数据吗?')) return; + try { + await deleteFormData(record.id); + loadData(); + } catch (error) { + console.error('删除数据失败:', error); + } + }; + + // 导出 + const handleExport = async () => { + try { + await exportFormData(query); + } catch (error) { + console.error('导出数据失败:', error); + } + }; + + // 状态徽章 + const getStatusBadge = (status: FormDataStatus) => { + const statusMap: Record = { + DRAFT: { variant: 'outline', text: '草稿', icon: Clock }, + SUBMITTED: { variant: 'success', text: '已提交', icon: CheckCircle2 }, + COMPLETED: { variant: 'default', text: '已完成', icon: FileCheck }, + }; + const statusInfo = statusMap[status]; + const Icon = statusInfo.icon; + return ( + + + {statusInfo.text} + + ); + }; + + // 业务类型徽章 + const getBusinessTypeBadge = (type: FormDataBusinessType) => { + const typeMap: Record = { + STANDALONE: { variant: 'outline', text: '独立' }, + WORKFLOW: { variant: 'default', text: '工作流' }, + ORDER: { variant: 'secondary', text: '订单' }, + }; + const typeInfo = typeMap[type]; + return {typeInfo.text}; + }; + + // 统计数据 + const stats = useMemo(() => { + const total = data?.totalElements || 0; + const draftCount = data?.content?.filter(d => d.status === 'DRAFT').length || 0; + const submittedCount = data?.content?.filter(d => d.status === 'SUBMITTED').length || 0; + const completedCount = data?.content?.filter(d => d.status === 'COMPLETED').length || 0; + return { total, draftCount, submittedCount, completedCount }; + }, [data]); + + const pageCount = data?.totalElements ? Math.ceil(data.totalElements / query.pageSize) : 0; + + return ( +
+
+

表单数据管理

+

+ 查看和管理用户提交的表单数据,支持按分类、状态、业务类型筛选。 +

+
+ + {/* 统计卡片 */} +
+ + + 总数据量 + + + +
{stats.total}
+

全部表单数据

+
+
+ + + 草稿 + + + +
{stats.draftCount}
+

暂存的数据

+
+
+ + + 已提交 + + + +
{stats.submittedCount}
+

待处理的数据

+
+
+ + + 已完成 + + + +
{stats.completedCount}
+

已处理完成

+
+
+
+ + + + 数据列表 + + + {/* 搜索栏 */} +
+
+ setQuery(prev => ({ ...prev, businessKey: e.target.value }))} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} + className="h-9" + /> +
+ + + + + + +
+ + {/* 表格 */} +
+ + + + 表单标识 + 分类 + 业务类型 + 业务标识 + 提交人 + 提交时间 + 状态 + 操作 + + + + {loading ? ( + + +
+ + 加载中... +
+
+
+ ) : data?.content && data.content.length > 0 ? ( + data.content.map((record) => { + const categoryInfo = getCategoryInfo(record.categoryId); + return ( + + + + {record.formKey} + + + + {categoryInfo ? ( + + {categoryInfo.icon && } + {categoryInfo.name} + + ) : ( + 未分类 + )} + + {getBusinessTypeBadge(record.businessType)} + + {record.businessKey || '-'} + + + {record.submitter || '匿名'} + + + + {record.submitTime ? dayjs(record.submitTime).format('YYYY-MM-DD HH:mm:ss') : '-'} + + + {getStatusBadge(record.status)} + +
+ + +
+
+
+ ); + }) + ) : ( + + +
+ +
暂无表单数据
+
用户提交的表单数据将在此显示。
+
+
+
+ )} +
+
+
+ + {/* 分页 */} + {pageCount > 1 && ( + setQuery(prev => ({ + ...prev, + pageNum: page - 1 + }))} + /> + )} +
+
+
+ ); +}; + +export default FormDataList; + diff --git a/frontend/src/pages/Form/Data/service.ts b/frontend/src/pages/Form/Data/service.ts new file mode 100644 index 00000000..439877c1 --- /dev/null +++ b/frontend/src/pages/Form/Data/service.ts @@ -0,0 +1,53 @@ +import request from '@/utils/request'; +import type { Page } from '@/types/base'; +import type { + FormDataQuery, + FormDataResponse, + FormDataSubmitRequest, + FormDataUpdateRequest, +} from './types'; + +const BASE_URL = '/api/v1/forms/data'; + +/** + * 分页查询表单数据列表 + */ +export const getFormDataList = (params?: FormDataQuery) => + request.get>(`${BASE_URL}/page`, { params }); + +/** + * 获取表单数据详情 + */ +export const getFormDataById = (id: number) => + request.get(`${BASE_URL}/${id}`); + +/** + * 提交表单数据 + */ +export const submitFormData = (data: FormDataSubmitRequest) => + request.post(BASE_URL, data); + +/** + * 更新表单数据 + */ +export const updateFormData = (id: number, data: FormDataUpdateRequest) => + request.put(`${BASE_URL}/${id}`, data); + +/** + * 删除表单数据 + */ +export const deleteFormData = (id: number) => + request.delete(`${BASE_URL}/${id}`); + +/** + * 批量删除表单数据 + */ +export const batchDeleteFormData = (ids: number[]) => + request.post(`${BASE_URL}/batch-delete`, { ids }); + +/** + * 导出表单数据 + */ +export const exportFormData = (params?: FormDataQuery) => + request.download(`${BASE_URL}/export`, undefined, { params }); + diff --git a/frontend/src/pages/Form/Data/types.ts b/frontend/src/pages/Form/Data/types.ts new file mode 100644 index 00000000..28323cad --- /dev/null +++ b/frontend/src/pages/Form/Data/types.ts @@ -0,0 +1,70 @@ +import { BaseQuery } from '@/types/base'; +import type { FormSchema } from '@/components/FormDesigner'; + +/** + * 业务类型 + */ +export type FormDataBusinessType = 'STANDALONE' | 'WORKFLOW' | 'ORDER'; + +/** + * 表单数据状态 + */ +export type FormDataStatus = 'DRAFT' | 'SUBMITTED' | 'COMPLETED'; + +/** + * 表单数据查询参数 + */ +export interface FormDataQuery extends BaseQuery { + formDefinitionId?: number; + formKey?: string; + categoryId?: number; // 分类ID(筛选) + businessType?: FormDataBusinessType; + businessKey?: string; + submitter?: string; + status?: FormDataStatus; + submitTimeStart?: string; + submitTimeEnd?: string; +} + +/** + * 表单数据响应 + */ +export interface FormDataResponse { + id: number; + formDefinitionId: number; + formKey: string; + formVersion: number; + categoryId?: number; // 分类ID + businessKey?: string; + businessType: FormDataBusinessType; + data: Record; + schemaSnapshot: FormSchema; + submitter?: string; + submitTime?: string; + status: FormDataStatus; + createBy?: string; + createTime?: string; + updateBy?: string; + updateTime?: string; +} + +/** + * 表单数据提交请求 + */ +export interface FormDataSubmitRequest { + formDefinitionId: number; + formKey: string; + businessType?: FormDataBusinessType; + businessKey?: string; + data: Record; + status?: FormDataStatus; +} + +/** + * 表单数据更新请求 + */ +export interface FormDataUpdateRequest { + data: Record; + status?: FormDataStatus; +} + diff --git a/frontend/src/pages/Form/Definition/Designer.tsx b/frontend/src/pages/Form/Definition/Designer.tsx new file mode 100644 index 00000000..1835e590 --- /dev/null +++ b/frontend/src/pages/Form/Definition/Designer.tsx @@ -0,0 +1,107 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Card, CardContent } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { FormDesigner } from '@/components/FormDesigner'; +import type { FormSchema } from '@/components/FormDesigner'; +import { ArrowLeft, Workflow } from 'lucide-react'; +import { getDefinitionById, updateDefinition } from './service'; +import type { FormDefinitionRequest } from './types'; + +/** + * 表单设计器页面 + */ +const FormDesignerPage: React.FC = () => { + const navigate = useNavigate(); + const { id } = useParams<{ id: string }>(); + const [formSchema, setFormSchema] = useState(null); + const [formDefinition, setFormDefinition] = useState(null); + + // 加载表单定义 + useEffect(() => { + if (id) { + loadFormDefinition(Number(id)); + } + }, [id]); + + const loadFormDefinition = async (definitionId: number) => { + try { + const result = await getDefinitionById(definitionId); + setFormDefinition(result); + setFormSchema(result.schema); + } catch (error) { + console.error('加载表单定义失败:', error); + } + }; + + // 保存表单(只更新 schema,不修改基本信息) + const handleSave = async (schema: FormSchema) => { + if (!id || !formDefinition) { + alert('表单信息加载失败'); + return; + } + + const request: FormDefinitionRequest = { + name: formDefinition.name, + key: formDefinition.key, + categoryId: formDefinition.categoryId, + description: formDefinition.description, + isTemplate: formDefinition.isTemplate, + schema, + status: formDefinition.status || 'DRAFT', + }; + + try { + await updateDefinition(Number(id), request); + alert('保存成功'); + } catch (error) { + console.error('保存表单失败:', error); + alert('保存失败'); + } + }; + + // 返回列表 + const handleBack = () => { + navigate('/form/definitions'); + }; + + return ( +
+ {/* 页面标题 */} +
+
+
+ +
+
+

+ {formDefinition?.name || '表单设计'} +

+

+ {formDefinition?.description || '拖拽左侧组件到画布,配置字段属性和验证规则'} +

+
+
+ +
+ + {/* 表单设计器区域 */} + + +
+ +
+
+
+
+ ); +}; + +export default FormDesignerPage; diff --git a/frontend/src/pages/Form/Definition/components/CreateModal.tsx b/frontend/src/pages/Form/Definition/components/CreateModal.tsx new file mode 100644 index 00000000..d17d2f6a --- /dev/null +++ b/frontend/src/pages/Form/Definition/components/CreateModal.tsx @@ -0,0 +1,283 @@ +import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Switch } from '@/components/ui/switch'; +import { Separator } from '@/components/ui/separator'; +import { FileText, Tag, Folder, AlignLeft, Copy, Loader2 } from 'lucide-react'; +import { useToast } from '@/components/ui/use-toast'; +import { createDefinition } from '../service'; +import { getEnabledCategories } from '../../Category/service'; +import type { FormDefinitionRequest } from '../types'; +import type { FormCategoryResponse } from '../../Category/types'; + +interface CreateModalProps { + visible: boolean; + onClose: () => void; + onSuccess: (id: number) => void; +} + +/** + * 表单定义创建弹窗(第一步:输入基本信息) + */ +const CreateModal: React.FC = ({ visible, onClose, onSuccess }) => { + const navigate = useNavigate(); + const { toast } = useToast(); + const [submitting, setSubmitting] = useState(false); + const [categories, setCategories] = useState([]); + const [formData, setFormData] = useState({ + name: '', + key: '', + categoryId: undefined as number | undefined, + description: '', + isTemplate: false, + }); + + // 加载分类列表 + useEffect(() => { + if (visible) { + loadCategories(); + } + }, [visible]); + + const loadCategories = async () => { + try { + const result = await getEnabledCategories(); + setCategories(result || []); + } catch (error) { + console.error('加载分类失败:', error); + } + }; + + // 重置表单 + const resetForm = () => { + setFormData({ + name: '', + key: '', + categoryId: undefined, + description: '', + isTemplate: false, + }); + }; + + // 关闭弹窗 + const handleClose = () => { + resetForm(); + onClose(); + }; + + // 提交表单 + const handleSubmit = async () => { + // 验证 + if (!formData.name.trim()) { + toast({ + title: '验证失败', + description: '请输入表单名称', + variant: 'destructive' + }); + return; + } + if (!formData.key.trim()) { + toast({ + title: '验证失败', + description: '请输入表单标识', + variant: 'destructive' + }); + return; + } + + // 验证表单标识格式(英文字母、数字、中划线) + if (!/^[a-zA-Z0-9-]+$/.test(formData.key)) { + toast({ + title: '验证失败', + description: '表单标识只能包含英文字母、数字和中划线', + variant: 'destructive' + }); + return; + } + + setSubmitting(true); + try { + // 创建表单定义(只保存基本信息,schema 为空) + const request: FormDefinitionRequest = { + name: formData.name, + key: formData.key, + categoryId: formData.categoryId, + description: formData.description, + isTemplate: formData.isTemplate, + status: 'DRAFT', + schema: { + version: '1.0', + formConfig: { + labelAlign: 'right', + size: 'middle' + }, + fields: [] + } + }; + + const result = await createDefinition(request); + + toast({ + title: '创建成功', + description: '表单基本信息已保存,现在可以开始设计表单' + }); + + // 跳转到设计器页面 + navigate(`/form/definitions/${result.id}/design`); + handleClose(); + onSuccess(result.id); + } catch (error) { + console.error('创建表单失败:', error); + toast({ + title: '创建失败', + description: error instanceof Error ? error.message : '创建表单失败', + variant: 'destructive' + }); + } finally { + setSubmitting(false); + } + }; + + return ( + + + + 创建表单定义 + + 第一步:输入表单的基本信息,然后点击"下一步"进入表单设计器 + + + + + +
+ {/* 第一行:表单名称 + 表单标识 */} +
+
+ + setFormData(prev => ({ ...prev, name: e.target.value }))} + className="h-10" + /> +

+ 将显示在表单列表和表单顶部 +

+
+ +
+ + setFormData(prev => ({ ...prev, key: e.target.value }))} + className="h-10 font-mono" + /> +

+ 英文字母、数字和中划线,用于 API 调用 +

+
+
+ + {/* 第二行:分类 + 设为模板 */} +
+
+ + +

+ 帮助用户快速找到相关表单 +

+
+ +
+ +
+
+ 启用模板功能 +
+ setFormData(prev => ({ ...prev, isTemplate: checked }))} + /> +
+

+ 模板表单可以被其他表单引用和复制 +

+
+
+ + {/* 第三行:描述 */} +
+ +