# 任务拆分 — 流程设计器优化 > 拆分顺序:数据模型 → 接口协议 → 后端实现 → 前端页面 → 前端组件改造 > 每个任务 = 可独立提交的原子变更(3-5 个文件) ## 前置条件 - [ ] 数据库可访问 - [ ] forge-plugin-flow 模块可编译 - [ ] forge-admin-ui 前端项目可启动 --- ## Task 1: 创建SPEL模板数据库表 - **目标**: 创建 `sys_flow_spel_template` 表并初始化默认数据 - **涉及文件**: - `forge-plugin-flow/sql/spel_template.sql` — 新增,创建表 + 初始化数据 - **SQL内容**: ```sql -- SPEL表达式模板表 CREATE TABLE IF NOT EXISTS `sys_flow_spel_template` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `tenant_id` bigint NOT NULL DEFAULT '1' COMMENT '租户编号', `template_name` varchar(100) NOT NULL COMMENT '模板名称', `template_code` varchar(50) NOT NULL COMMENT '模板编码', `expression` varchar(500) NOT NULL COMMENT 'SPEL表达式', `description` varchar(200) DEFAULT NULL COMMENT '描述说明', `category` varchar(50) DEFAULT 'general' COMMENT '分类:general/dept/role/region/custom', `example_params` varchar(500) DEFAULT NULL COMMENT '示例参数(JSON格式)', `status` int NOT NULL DEFAULT '1' COMMENT '状态:0-禁用,1-启用', `sort` int DEFAULT '100' COMMENT '排序', `remark` varchar(500) DEFAULT NULL COMMENT '备注', `create_by` bigint DEFAULT NULL COMMENT '创建人ID', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人ID', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` int DEFAULT '0' COMMENT '删除标志', PRIMARY KEY (`id`), UNIQUE KEY `uk_template_code` (`tenant_id`, `template_code`), KEY `idx_tenant_id` (`tenant_id`), KEY `idx_category` (`category`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程SPEL表达式模板'; -- 初始化默认模板数据(迁移自NodePropertiesPanel.vue硬编码模板) INSERT INTO `sys_flow_spel_template` (`tenant_id`, `template_name`, `template_code`, `expression`, `description`, `category`, `status`, `sort`) VALUES (1, '发起人', 'initiator', '${initiator}', '流程发起人作为审批人', 'general', 1, 1), (1, '发起人上级', 'initiatorLeader', '${initiatorLeader}', '流程发起人的直属上级', 'general', 1, 2), (1, '部门经理', 'deptManager', '${deptManager}', '当前部门的经理', 'general', 1, 3), (1, 'HR', 'hr', '${hr}', '人力资源部门负责人', 'general', 1, 4), (1, '根据部门查找负责人', 'findDeptManager', '${flowSpelService.findDeptManager(execution.getVariable("deptId"))}', '查找指定部门的负责人', 'dept', 1, 10), (1, '根据角色查找用户', 'findUsersByRole', '${flowSpelService.findUsersByRole(execution.getVariable("roleKey"))}', '查找具有指定角色的所有用户', 'role', 1, 20), (1, '根据行政区划查找负责人', 'findRegionManager', '${flowSpelService.findRegionManager(execution.getVariable("regionCode"))}', '查找指定行政区划的区域负责人', 'region', 1, 30), (1, '发起人的直属上级', 'getInitiatorLeader', '${flowSpelService.getInitiatorLeader(execution)}', '流程发起人的直属上级作为审批人', 'general', 1, 5), (1, '根据订单金额动态审批', 'amountDynamic', '${execution.getVariable("amount") > 10000 ? flowSpelService.findUsersByRole("finance_manager") : flowSpelService.findUsersByRole("finance_staff")}', '订单金额大于1万由财务经理审批', 'custom', 1, 50), (1, '部门+角色组合查询', 'deptAndRole', '${flowSpelService.findUsersByDeptAndRole(execution.getVariable("deptId"), "dept_manager")}', '查找指定部门的部门经理', 'dept', 1, 15) ON DUPLICATE KEY UPDATE `template_name` = VALUES(`template_name`); ``` - **验证**: 执行SQL后检查表结构和初始数据 --- ## Task 2: 后端Entity + Mapper - **目标**: 创建 `FlowSpelTemplate` 实体类和 Mapper - **涉及文件**: - `forge-plugin-flow/src/main/java/.../entity/FlowSpelTemplate.java` — 新增 - `forge-plugin-flow/src/main/java/.../mapper/FlowSpelTemplateMapper.java` — 新增 - `forge-plugin-flow/src/main/resources/mapper/FlowSpelTemplateMapper.xml` — 新增 - **Entity代码** (`FlowSpelTemplate.java`): ```java package com.mdframe.forge.starter.flow.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.io.Serializable; import java.time.LocalDateTime; @Data @TableName("sys_flow_spel_template") public class FlowSpelTemplate implements Serializable { private static final long serialVersionUID = 1L; @TableId(type = IdType.AUTO) private Long id; private Long tenantId; private String templateName; private String templateCode; private String expression; private String description; private String category; private String exampleParams; private Integer status; private Integer sort; private String remark; @TableField(fill = FieldFill.INSERT) private Long createBy; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private Long updateBy; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @TableLogic private Integer deleted; } ``` - **Mapper接口** (`FlowSpelTemplateMapper.java`): ```java package com.mdframe.forge.starter.flow.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.mdframe.forge.starter.flow.entity.FlowSpelTemplate; import org.apache.ibatis.annotations.Mapper; @Mapper public interface FlowSpelTemplateMapper extends BaseMapper { } ``` - **Mapper XML** (`FlowSpelTemplateMapper.xml`): ```xml ``` --- ## Task 3: 后端Service + Controller - **目标**: 创建 Service 和 Controller,提供 CRUD API - **涉及文件**: - `forge-plugin-flow/src/main/java/.../service/FlowSpelTemplateService.java` — 新增 - `forge-plugin-flow/src/main/java/.../service/impl/FlowSpelTemplateServiceImpl.java` — 新增 - `forge-flow-server/src/main/java/.../controller/FlowSpelTemplateController.java` — 新增 - **Service接口**: ```java package com.mdframe.forge.starter.flow.service; import com.baomidou.mybatisplus.extension.service.IService; import com.mdframe.forge.starter.flow.entity.FlowSpelTemplate; import java.util.List; public interface FlowSpelTemplateService extends IService { List getEnabledList(Long tenantId); } ``` - **ServiceImpl**: ```java package com.mdframe.forge.starter.flow.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.mdframe.forge.starter.flow.entity.FlowSpelTemplate; import com.mdframe.forge.starter.flow.mapper.FlowSpelTemplateMapper; import com.mdframe.forge.starter.flow.service.FlowSpelTemplateService; import org.springframework.stereotype.Service; import java.util.List; @Service public class FlowSpelTemplateServiceImpl extends ServiceImpl implements FlowSpelTemplateService { @Override public List getEnabledList(Long tenantId) { return baseMapper.selectEnabledList(tenantId); } } ``` - **Controller** (`FlowSpelTemplateController.java`): ```java package com.mdframe.forge.flow.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.mdframe.forge.starter.flow.entity.FlowSpelTemplate; import com.mdframe.forge.starter.flow.service.FlowSpelTemplateService; import com.mdframe.forge.starter.common.core.domain.RespInfo; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/flow/spelTemplate") @RequiredArgsConstructor public class FlowSpelTemplateController { private final FlowSpelTemplateService spelTemplateService; // 分页查询 @GetMapping("/page") public RespInfo page( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String templateName, @RequestParam(required = false) String category, @RequestParam(required = false) Integer status) { Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.like(templateName != null, FlowSpelTemplate::getTemplateName, templateName); wrapper.eq(category != null, FlowSpelTemplate::getCategory, category); wrapper.eq(status != null, FlowSpelTemplate::getStatus, status); wrapper.orderByAsc(FlowSpelTemplate::getSort); return RespInfo.success(spelTemplateService.page(page, wrapper)); } // 启用状态列表(节点配置下拉用) @GetMapping("/list") public RespInfo list() { Long tenantId = 1L; // 从上下文获取当前租户ID List list = spelTemplateService.getEnabledList(tenantId); return RespInfo.success(list); } // 详情 @GetMapping("/{id}") public RespInfo getById(@PathVariable Long id) { return RespInfo.success(spelTemplateService.getById(id)); } // 新增 @PostMapping public RespInfo save(@RequestBody FlowSpelTemplate entity) { spelTemplateService.save(entity); return RespInfo.success(); } // 修改 @PutMapping public RespInfo update(@RequestBody FlowSpelTemplate entity) { spelTemplateService.updateById(entity); return RespInfo.success(); } // 删除 @DeleteMapping("/{id}") public RespInfo delete(@PathVariable Long id) { spelTemplateService.removeById(id); return RespInfo.success(); } } ``` --- ## Task 4: 前端SPEL模板管理页面 - **目标**: 创建 `spelTemplate.vue` 管理页面 - **涉及文件**: - `forge-admin-ui/src/views/flow/spelTemplate.vue` — 新增 - **页面结构**: 使用 `AiCrudPage` 组件实现 CRUD ```vue ``` --- ## Task 5: NodePropertiesPanel改用Tab + 手动保存 - **目标**: 重构节点配置面板,使用 `n-tabs` 分隔 + 底部添加"保存配置"按钮 - **涉及文件**: - `forge-admin-ui/src/components/bpmn/NodePropertiesPanel.vue` — 重构 - **关键改动点**: 1. 将 `n-collapse` 替换为 `n-tabs` 2. Tab分组:基础属性 / 审批设置 / 会签配置 / 监听器 3. 底部添加固定"保存配置"按钮 4. 添加"未保存"状态提示 5. 从 API `/api/flow/spelTemplate/list` 加载 SPEL 模板 - **Tab结构设计**: ```vue ``` --- ## Task 6: NodePropertiesPanel从API加载SPEL模板 - **目标**: 替换硬编码模板,改为从 API 加载 - **涉及文件**: - `forge-admin-ui/src/components/bpmn/NodePropertiesPanel.vue` — 修改 - **关键改动**: 1. 删除 `spelTemplates` 硬编码数组(第705-742行) 2. 新增 `spelTemplatesFromApi` ref 3. 在 `onMounted` 中调用 `/api/flow/spelTemplate/list` 4. SPEL模板下拉框使用动态数据 --- ## Task 7: XML预览弹窗样式优化 - **目标**: 优化XML预览弹窗,添加下载按钮和语法高亮 - **涉及文件**: - `forge-admin-ui/src/components/bpmn/FlowModeler.vue` — 修改 - **关键改动**: 1. 弹窗添加 `max-height: 80vh` 2. 内部容器添加滚动和背景色 3. 添加"复制XML"和"下载XML"按钮 4. 代码块添加 `word-wrap` ```vue
``` --- ## Task 8: 新增菜单资源 - **目标**: 在流程配置目录下添加"SPEL模板管理"菜单 - **涉及文件**: - `forge-plugin-flow/sql/spel_template_menu.sql` — 新增 ```sql -- SPEL模板管理菜单(置于流程配置目录下) -- 假设流程配置目录ID为 @flow_admin_id INSERT INTO sys_resource (tenant_id, resource_name, parent_id, resource_type, sort, path, component, is_external, is_public, menu_status, visible, icon, keep_alive, always_show, remark, create_time, update_time) VALUES (0, 'SPEL模板管理', @flow_admin_id, 2, 5, '/flow/admin/spelTemplate', '/flow/spelTemplate', 0, 0, 1, 1, 'mdi:code-braces', 0, 0, 'SPEL表达式模板管理', NOW(), NOW()); SET @spel_template_menu_id = LAST_INSERT_ID(); -- 按钮权限 INSERT INTO sys_resource (tenant_id, resource_name, parent_id, resource_type, sort, perms, is_external, is_public, menu_status, visible, remark, create_time, update_time) VALUES (0, '新增模板', @spel_template_menu_id, 3, 1, 'flow:spelTemplate:create', 0, 0, 1, 1, '新增SPEL模板', NOW(), NOW()), (0, '编辑模板', @spel_template_menu_id, 3, 2, 'flow:spelTemplate:update', 0, 0, 1, 1, '编辑SPEL模板', NOW(), NOW()), (0, '删除模板', @spel_template_menu_id, 3, 3, 'flow:spelTemplate:delete', 0, 0, 1, 1, '删除SPEL模板', NOW(), NOW()); ``` --- ## Task 9: 验证与测试 - **目标**: 编译测试 + 功能验证 - **验证点**: 1. 后端编译通过 `mvn clean install` 2. 前端编译通过 `pnpm build` 3. SPEL模板管理页面 CRUD 功能正常 4. 节点配置 Tab 切换 + 保存按钮正常 5. XML 预览弹窗样式正常,下载功能正常