32 KiB
One-Click Project Generation Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 把当前分散的设计器和预览器流程收束成默认一键流程:用户输入项目名称,系统自动生成后端、用户前台、管理前台、SQL,并启动在线运行预览。高级调整继续保留原有分步能力。
Architecture: 复用现有 AI 任务中心,在 generateType=one_click_project 下新增后端编排服务。编排服务串联项目准备、应用蓝图、数据库设计、业务动作设计、页面初始化、源码预览、运行预览,并把阶段进度写回 front_ai_generation_task.current_step、progress、result_payload。前端默认展示一键入口、阶段进度、运行地址和源码预览入口,原有分步设计器移入高级调整。
Tech Stack: Java 8、Spring Boot、MyBatis、JUnit4、Mockito、Vue 3、Element Plus、Node test、Vite。
Task 1: Backend Result Contract and Task Type Acceptance
Files:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/OneClickProjectGenerationReport.javaruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/OneClickProjectGenerationResult.javaruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/OneClickProjectGenerationException.javaruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IOneClickProjectGenerationService.javaruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationTaskServiceImpl.javaruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerationTaskServiceImplTest.java
Intent: 固定一键任务的后端契约,让任务中心能创建 one_click_project 任务,并给前端稳定的结果 JSON。
-
在
AiGenerationTaskServiceImplTest添加失败测试createTaskQueuesOneClickProjectGeneration。AiGenerationTaskCreateRequest request = new AiGenerationTaskCreateRequest(); request.setGenerateType("one_click_project"); request.setProjectName("客户关系管理系统"); request.setProjectDesc(""); request.setExtraRequirements(""); AiGenerationTaskStatusResponse response = service.createTask(10L, 20L, request); assertNotNull(response); assertEquals("one_click_project", response.getGenerateType()); assertEquals("QUEUED", response.getStatus()); verify(taskMapper).insert(argThat(task -> "one_click_project".equals(task.getGenerateType()) && Long.valueOf(10L).equals(task.getUserId()) && Long.valueOf(20L).equals(task.getProjectId()))); verify(taskWorker).submit(any(AiGenerationTask.class)); -
新建
OneClickProjectGenerationReport,字段为tableCount、businessActionCount、frontendPageCount、adminPageCount,全部使用int,并提供标准 getter 和 setter。 -
新建
OneClickProjectGenerationResult,字段和常量如下。public class OneClickProjectGenerationResult { public static final String STAGE_PROJECT_PREPARE = "PROJECT_PREPARE"; public static final String STAGE_APP_BLUEPRINT = "APP_BLUEPRINT"; public static final String STAGE_DATABASE = "DATABASE"; public static final String STAGE_BUSINESS_BLUEPRINT = "BUSINESS_BLUEPRINT"; public static final String STAGE_PAGE_INITIALIZE = "PAGE_INITIALIZE"; public static final String STAGE_SOURCE_PREVIEW = "SOURCE_PREVIEW"; public static final String STAGE_RUN_PREVIEW = "RUN_PREVIEW"; public static final String STAGE_COMPLETED = "COMPLETED"; private Long projectId; private String stage; private String failedStage; private boolean downloadReady; private String previewStatus; private String backendUrl; private String frontendUrl; private String adminFrontendUrl; private String errorMessage; private OneClickProjectGenerationReport report; private List<String> suggestedActions = new ArrayList<String>(); } -
新建
OneClickProjectGenerationException。public class OneClickProjectGenerationException extends RuntimeException { private final OneClickProjectGenerationResult result; public OneClickProjectGenerationException(String message, OneClickProjectGenerationResult result, Throwable cause) { super(message, cause); this.result = result; } public OneClickProjectGenerationResult getResult() { return result; } } -
新建
IOneClickProjectGenerationService。public interface IOneClickProjectGenerationService { OneClickProjectGenerationResult generate(AiGenerationTask task, AiGenerationTaskCreateRequest request); } -
修改
AiGenerationTaskServiceImpl.validateRequest,允许新类型。if (!"app_blueprint".equals(request.getGenerateType()) && !"database".equals(request.getGenerateType()) && !"business_blueprint".equals(request.getGenerateType()) && !"one_click_project".equals(request.getGenerateType())) { throw new ServiceException("不支持的AI生成类型"); } -
运行测试。
mvn -q -pl ruoyi-generator -Dtest=AiGenerationTaskServiceImplTest testExpected result: 新增的一键任务创建测试和既有任务服务测试全部通过。
Task 2: Worker Dispatch and Failure Payload Preservation
Files:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationTaskWorker.javaruoyi-generator/src/test/java/com/ruoyi/generator/async/AiGenerationTaskWorkerTest.java
Intent: 让异步 worker 知道如何执行一键任务,并在最终失败时保留阶段化 resultPayload。
-
在
AiGenerationTaskWorkerTest添加 mock 并注入。@Mock private IOneClickProjectGenerationService oneClickProjectGenerationService; setField(worker, "oneClickProjectGenerationService", oneClickProjectGenerationService); -
添加失败测试
runOneClickProjectTaskDelegatesToOrchestrator。AiGenerationTask task = new AiGenerationTask(); task.setTaskId(7L); task.setProjectId(20L); task.setUserId(10L); task.setGenerateType("one_click_project"); task.setRequestPayload("{\"generateType\":\"one_click_project\",\"projectName\":\"客户关系管理系统\"}"); task.setAttempts(0); task.setMaxAttempts(3); OneClickProjectGenerationResult result = new OneClickProjectGenerationResult(); result.setProjectId(20L); result.setStage(OneClickProjectGenerationResult.STAGE_COMPLETED); result.setPreviewStatus("RUNNING"); result.setDownloadReady(true); when(oneClickProjectGenerationService.generate(eq(task), any(AiGenerationTaskCreateRequest.class))).thenReturn(result); worker.runTask(task); verify(oneClickProjectGenerationService).generate(eq(task), any(AiGenerationTaskCreateRequest.class)); verify(taskMapper).update(argThat(updated -> "SUCCEEDED".equals(updated.getStatus()) && Integer.valueOf(100).equals(updated.getProgress()) && updated.getResultPayload().contains("\"previewStatus\":\"RUNNING\""))); -
添加失败测试
runOneClickProjectTaskStoresPartialResultOnFailure。AiGenerationTask task = new AiGenerationTask(); task.setTaskId(8L); task.setProjectId(20L); task.setUserId(10L); task.setGenerateType("one_click_project"); task.setRequestPayload("{\"generateType\":\"one_click_project\",\"projectName\":\"客户关系管理系统\"}"); task.setAttempts(2); task.setMaxAttempts(3); OneClickProjectGenerationResult partial = new OneClickProjectGenerationResult(); partial.setProjectId(20L); partial.setStage(OneClickProjectGenerationResult.STAGE_SOURCE_PREVIEW); partial.setFailedStage(OneClickProjectGenerationResult.STAGE_RUN_PREVIEW); partial.setDownloadReady(true); partial.setPreviewStatus("FAILED"); partial.setErrorMessage("运行预览启动失败"); when(oneClickProjectGenerationService.generate(eq(task), any(AiGenerationTaskCreateRequest.class))) .thenThrow(new OneClickProjectGenerationException("运行预览启动失败", partial, null)); when(retryPolicy.shouldRetry(eq(task), any(RuntimeException.class))).thenReturn(false); worker.runTask(task); verify(taskMapper).update(argThat(updated -> "FAILED".equals(updated.getStatus()) && "RUN_PREVIEW".equals(updated.getErrorCode()) && updated.getResultPayload().contains("\"downloadReady\":true") && updated.getResultPayload().contains("\"failedStage\":\"RUN_PREVIEW\""))); -
在
AiGenerationTaskWorker增加字段。@Autowired private IOneClickProjectGenerationService oneClickProjectGenerationService; -
在任务执行分支中加入一键任务。
if ("one_click_project".equals(task.getGenerateType())) { return oneClickProjectGenerationService.generate(task, request); } -
在最终失败写库前处理
OneClickProjectGenerationException。if (e instanceof OneClickProjectGenerationException) { OneClickProjectGenerationResult result = ((OneClickProjectGenerationException) e).getResult(); task.setResultPayload(JSON.toJSONString(result)); task.setErrorCode(result.getFailedStage()); } -
运行测试。
mvn -q -pl ruoyi-generator -Dtest=AiGenerationTaskWorkerTest testExpected result: worker 的既有测试、新增委派测试、新增失败结果测试全部通过。
Task 3: One-Click Orchestrator Happy Path
Files:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.javaruoyi-generator/src/test/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImplTest.java
Intent: 新增核心编排服务,把已存在的 AI 生成、页面初始化、源码预览、运行预览串成默认成功链路。
-
新建失败测试类
OneClickProjectGenerationServiceImplTest,使用 JUnit4 + Mockito。mock 字段固定为:IAiGenerateService、FrontendPageDesignService、IFrontProjectPreviewService、IFrontProjectRunPreviewService、IFrontProjectService、AiGenerationTaskMapper。 -
添加失败测试
generateRunsEveryStageAndReturnsRunningPreview。测试准备一个frontendEnabled=1的FrontProject,一个AppBlueprintDesign,一个包含 1 张表的DatabaseDesignResponse,一个包含 1 个动作的List<BusinessActionDesign>,前台和后台各 1 个页面初始化结果,以及状态为RUNNING的ProjectRunPreviewStatus。 -
在该测试中断言返回结果。
OneClickProjectGenerationResult result = service.generate(task, request); assertEquals(Long.valueOf(20L), result.getProjectId()); assertEquals(OneClickProjectGenerationResult.STAGE_COMPLETED, result.getStage()); assertEquals(ProjectRunPreviewStatus.RUNNING, result.getPreviewStatus()); assertTrue(result.isDownloadReady()); assertEquals("http://localhost:18080", result.getBackendUrl()); assertEquals("http://localhost:18081", result.getFrontendUrl()); assertEquals("http://localhost:18082", result.getAdminFrontendUrl()); assertEquals(1, result.getReport().getTableCount()); assertEquals(1, result.getReport().getBusinessActionCount()); assertEquals(1, result.getReport().getFrontendPageCount()); assertEquals(1, result.getReport().getAdminPageCount()); -
在该测试中断言调用顺序。
InOrder inOrder = inOrder(aiGenerateService, frontendPageDesignService, frontProjectPreviewService, frontProjectRunPreviewService); inOrder.verify(aiGenerateService).generateAppBlueprint(eq(10L), eq(20L), any(GenerateAppBlueprintRequest.class)); inOrder.verify(aiGenerateService).generateDatabase(eq(10L), eq(20L), any(GenerateDatabaseRequest.class)); inOrder.verify(aiGenerateService).generateBusinessBlueprint(eq(10L), eq(20L), any(GenerateBusinessBlueprintRequest.class)); inOrder.verify(frontendPageDesignService).initializePageDesigns(10L, 20L, "frontend"); inOrder.verify(frontendPageDesignService).initializePageDesigns(10L, 20L, "admin"); inOrder.verify(frontProjectPreviewService).markPreviewReady(10L, 20L); inOrder.verify(frontProjectRunPreviewService).start(10L, 20L); verify(taskMapper, atLeast(6)).update(any(AiGenerationTask.class)); -
实现
OneClickProjectGenerationServiceImpl的主流程。@Service public class OneClickProjectGenerationServiceImpl implements IOneClickProjectGenerationService { private static final long DEFAULT_RUN_PREVIEW_TIMEOUT_MILLIS = 120000L; private static final long DEFAULT_RUN_PREVIEW_POLL_INTERVAL_MILLIS = 1000L; @Autowired private IAiGenerateService aiGenerateService; @Autowired private FrontendPageDesignService frontendPageDesignService; @Autowired private IFrontProjectPreviewService frontProjectPreviewService; @Autowired private IFrontProjectRunPreviewService frontProjectRunPreviewService; @Autowired private IFrontProjectService frontProjectService; @Autowired private AiGenerationTaskMapper taskMapper; private long runPreviewTimeoutMillis = DEFAULT_RUN_PREVIEW_TIMEOUT_MILLIS; private long runPreviewPollIntervalMillis = DEFAULT_RUN_PREVIEW_POLL_INTERVAL_MILLIS; @Override public OneClickProjectGenerationResult generate(AiGenerationTask task, AiGenerationTaskCreateRequest request) { OneClickProjectGenerationResult result = new OneClickProjectGenerationResult(); result.setProjectId(task.getProjectId()); OneClickProjectGenerationReport report = new OneClickProjectGenerationReport(); result.setReport(report); try { updateStage(task, result, OneClickProjectGenerationResult.STAGE_PROJECT_PREPARE, 5, "正在准备项目"); FrontProject project = frontProjectService.getProject(task.getUserId(), task.getProjectId()); boolean frontendEnabled = project == null || !"0".equals(project.getFrontendEnabled()); updateStage(task, result, OneClickProjectGenerationResult.STAGE_APP_BLUEPRINT, 15, "正在规划应用蓝图"); AppBlueprintDesign appBlueprint = aiGenerateService.generateAppBlueprint(task.getUserId(), task.getProjectId(), toAppBlueprintRequest(request)); String appBlueprintJson = JSON.toJSONString(appBlueprint); updateStage(task, result, OneClickProjectGenerationResult.STAGE_DATABASE, 35, "正在设计数据库和SQL"); DatabaseDesignResponse database = aiGenerateService.generateDatabase(task.getUserId(), task.getProjectId(), toDatabaseRequest(request, appBlueprintJson)); report.setTableCount(database == null || database.getTables() == null ? 0 : database.getTables().size()); updateStage(task, result, OneClickProjectGenerationResult.STAGE_BUSINESS_BLUEPRINT, 50, "正在生成业务流程"); List<BusinessActionDesign> businessActions = aiGenerateService.generateBusinessBlueprint(task.getUserId(), task.getProjectId(), toBusinessBlueprintRequest(request, appBlueprintJson)); report.setBusinessActionCount(businessActions == null ? 0 : businessActions.size()); updateStage(task, result, OneClickProjectGenerationResult.STAGE_PAGE_INITIALIZE, 65, "正在初始化前端页面"); if (frontendEnabled) { FrontendPageDesignInitResponse frontendInit = frontendPageDesignService.initializePageDesigns(task.getUserId(), task.getProjectId(), "frontend"); report.setFrontendPageCount(countPageDesigns(frontendInit)); } FrontendPageDesignInitResponse adminInit = frontendPageDesignService.initializePageDesigns(task.getUserId(), task.getProjectId(), "admin"); report.setAdminPageCount(countPageDesigns(adminInit)); updateStage(task, result, OneClickProjectGenerationResult.STAGE_SOURCE_PREVIEW, 78, "正在生成源码预览"); frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "backend"); if (frontendEnabled) { frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "frontend"); } frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "admin_frontend"); frontProjectPreviewService.getStructure(task.getUserId(), task.getProjectId(), "sql"); frontProjectPreviewService.markPreviewReady(task.getUserId(), task.getProjectId()); result.setDownloadReady(true); updateStage(task, result, OneClickProjectGenerationResult.STAGE_RUN_PREVIEW, 88, "正在启动在线运行预览"); ProjectRunPreviewStatus status = waitForRunningPreview(task.getUserId(), task.getProjectId()); applyPreviewStatus(result, status); updateStage(task, result, OneClickProjectGenerationResult.STAGE_COMPLETED, 100, "完整项目已生成并启动"); return result; } catch (RuntimeException e) { throw wrapFailure(result, e); } } } -
添加请求转换、阶段更新、计数 helper。
private void updateStage(AiGenerationTask task, OneClickProjectGenerationResult result, String stage, int progress, String currentStep) { result.setStage(stage); task.setCurrentStep(currentStep); task.setProgress(progress); task.setResultPayload(JSON.toJSONString(result)); taskMapper.update(task); } private GenerateAppBlueprintRequest toAppBlueprintRequest(AiGenerationTaskCreateRequest source) { GenerateAppBlueprintRequest request = new GenerateAppBlueprintRequest(); request.setProjectName(source.getProjectName()); request.setProjectDesc(source.getProjectDesc()); request.setIndustryTemplate(source.getIndustryTemplate()); request.setExtraRequirements(source.getExtraRequirements()); return request; } private GenerateDatabaseRequest toDatabaseRequest(AiGenerationTaskCreateRequest source, String appBlueprintJson) { GenerateDatabaseRequest request = new GenerateDatabaseRequest(); request.setProjectName(source.getProjectName()); request.setProjectDesc(source.getProjectDesc()); request.setIndustryTemplate(source.getIndustryTemplate()); request.setExtraRequirements(source.getExtraRequirements()); request.setAppBlueprint(appBlueprintJson); return request; } private GenerateBusinessBlueprintRequest toBusinessBlueprintRequest(AiGenerationTaskCreateRequest source, String appBlueprintJson) { GenerateBusinessBlueprintRequest request = new GenerateBusinessBlueprintRequest(); request.setProjectName(source.getProjectName()); request.setProjectDesc(source.getProjectDesc()); request.setIndustryTemplate(source.getIndustryTemplate()); request.setExtraRequirements(source.getExtraRequirements()); request.setAppBlueprint(appBlueprintJson); return request; } private int countPageDesigns(FrontendPageDesignInitResponse response) { return response == null || response.getDesigns() == null ? 0 : response.getDesigns().size(); } -
运行测试。
mvn -q -pl ruoyi-generator -Dtest=OneClickProjectGenerationServiceImplTest testExpected result: 快乐路径测试通过,并验证阶段顺序。
Task 4: Run Preview Polling, Disabled Frontend, and Failure Rules
Files:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.javaruoyi-generator/src/test/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImplTest.java
Intent: 补齐边界行为:运行预览等到 RUNNING 才算成功;用户前台关闭时跳过用户前台;失败时返回可恢复结果。
-
添加测试
generatePollsRunPreviewUntilRunning,让start返回STARTING,让status依次返回STARTING和RUNNING。断言最终previewStatus=RUNNING,并断言status被调用 2 次。 -
添加测试
generateSkipsUserFrontendWhenDisabled。设置project.setFrontendEnabled("0"),断言不会调用initializePageDesigns(10L, 20L, "frontend")和getStructure(10L, 20L, "frontend"),并断言管理前台仍初始化和生成结构。 -
添加测试
generateThrowsOneClickExceptionWithDownloadReadyResultWhenRunPreviewFails。让源码预览完成,让运行预览返回FAILED和错误信息后端端口启动失败。断言异常结果包含failedStage=RUN_PREVIEW、previewStatus=FAILED、downloadReady=true、建议动作源码预览已生成,可以先下载项目或进入高级调整调整后重试运行预览。 -
添加运行预览等待 helper。
private ProjectRunPreviewStatus waitForRunningPreview(Long userId, Long projectId) { ProjectRunPreviewStatus status = frontProjectRunPreviewService.start(userId, projectId); if (ProjectRunPreviewStatus.RUNNING.equals(status.getStatus()) || ProjectRunPreviewStatus.FAILED.equals(status.getStatus())) { return status; } long deadline = System.currentTimeMillis() + runPreviewTimeoutMillis; ProjectRunPreviewStatus latest = status; while (System.currentTimeMillis() <= deadline) { sleepBeforeNextPoll(); latest = frontProjectRunPreviewService.status(userId, projectId); if (ProjectRunPreviewStatus.RUNNING.equals(latest.getStatus()) || ProjectRunPreviewStatus.FAILED.equals(latest.getStatus())) { return latest; } } latest.setStatus(ProjectRunPreviewStatus.FAILED); latest.setErrorMessage("运行预览启动超时"); return latest; } private void sleepBeforeNextPoll() { if (runPreviewPollIntervalMillis <= 0L) { return; } try { Thread.sleep(runPreviewPollIntervalMillis); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException("运行预览等待被中断", e); } } -
添加预览状态处理和失败包装 helper。
private void applyPreviewStatus(OneClickProjectGenerationResult result, ProjectRunPreviewStatus status) { result.setPreviewStatus(status.getStatus()); result.setBackendUrl(status.getBackendUrl()); result.setFrontendUrl(status.getFrontendUrl()); result.setAdminFrontendUrl(status.getAdminFrontendUrl()); if (!ProjectRunPreviewStatus.RUNNING.equals(status.getStatus())) { result.setFailedStage(OneClickProjectGenerationResult.STAGE_RUN_PREVIEW); result.setErrorMessage(status.getErrorMessage()); result.getSuggestedActions().add("源码预览已生成,可以先下载项目或进入高级调整调整后重试运行预览"); throw new OneClickProjectGenerationException(status.getErrorMessage(), result, null); } } private OneClickProjectGenerationException wrapFailure(OneClickProjectGenerationResult result, RuntimeException e) { if (e instanceof OneClickProjectGenerationException) { return (OneClickProjectGenerationException) e; } String failedStage = result.getStage() == null ? OneClickProjectGenerationResult.STAGE_PROJECT_PREPARE : result.getStage(); result.setFailedStage(failedStage); result.setErrorMessage(e.getMessage()); result.getSuggestedActions().add("进入高级调整检查当前阶段输入和生成结果后重试"); return new OneClickProjectGenerationException(e.getMessage(), result, e); } -
在测试 setup 中把等待时间改短。
setField(service, "runPreviewPollIntervalMillis", 0L); setField(service, "runPreviewTimeoutMillis", 10L); -
运行测试。
mvn -q -pl ruoyi-generator -Dtest=OneClickProjectGenerationServiceImplTest testExpected result: 快乐路径、轮询、关闭用户前台、运行预览失败测试全部通过。
Task 5: Frontend API Helper and Source-Level Tests
Files:
easycode-web/src/api/project.jseasycode-web/src/views/generateView.test.mjs
Intent: 前端先拥有清晰的一键任务 API 调用点,测试锁定“项目名称即可触发一键任务”的关键行为。
-
在
generateView.test.mjs添加失败测试。test('GenerateView exposes one-click generation as the default flow', () => { assert.match(source, /one_click_project/) assert.match(source, /handleGenerateCompleteProject/) assert.match(source, /validateOneClickInput/) assert.match(source, /oneClickResult/) assert.match(source, /expertMode/) }) test('GenerateView one-click validation only requires project name', () => { assert.match(source, /validateOneClickInput[\s\S]*projectForm\.projectName/) assert.doesNotMatch(source, /validateOneClickInput[\s\S]*projectForm\.keyword/) }) -
在
project.js添加 API helper。export function createOneClickProjectTask(projectId, data) { return createAiTask(projectId, Object.assign({}, data, { generateType: 'one_click_project' })) } -
保持
createAiTask、retryAiTask、generateProject、startRunPreview、getRunPreviewStatus的既有导出不变。 -
运行前端测试并确认此时
GenerateView.vue相关断言失败。cd easycode-web node --test src/views/generateView.test.mjsExpected result: 新增断言因页面尚未改造而失败。
Task 6: GenerateView Default One-Click UX and Expert Mode
Files:
easycode-web/src/views/GenerateView.vueeasycode-web/src/views/generateView.test.mjs
Intent: 让第一屏回到用户初心:输入项目名称后直接生成完整项目。原有分步工具保留在高级调整中。
-
修改
GenerateView.vue的 API import,加入createOneClickProjectTask。 -
在
<script setup>中添加状态。const ONE_CLICK_GENERATE_TYPE = 'one_click_project' const oneClickGenerating = ref(false) const oneClickResult = ref(null) const expertMode = ref(false) -
添加只校验项目名称的一键校验函数。
function validateOneClickInput() { if (!projectForm.projectName || !projectForm.projectName.trim()) { ElMessage.warning('请输入项目名称') return false } return true } -
添加一键提交函数。
async function handleGenerateCompleteProject() { if (!validateOneClickInput()) return oneClickGenerating.value = true oneClickResult.value = null try { await ensureProject() const response = await createOneClickProjectTask(currentProjectId.value, { projectName: projectForm.projectName, projectDesc: projectForm.projectDesc, industryTemplate: projectForm.industryTemplate, extraRequirements: projectForm.extraRequirements }) aiTask.value = response.data || response await waitForAiTask(aiTask.value.taskId) ElMessage.success('完整项目已生成') } catch (error) { ElMessage.error(error.msg || error.message || '完整项目生成失败') } finally { oneClickGenerating.value = false } } -
在
applyAiTaskResult的现有分支前加入一键结果处理。if (task.generateType === ONE_CLICK_GENERATE_TYPE) { oneClickResult.value = parseAiTaskResult(task.resultPayload) return } -
添加结果解析函数。
function parseAiTaskResult(payload) { if (!payload) return null if (typeof payload === 'object') return payload try { return JSON.parse(payload) } catch (error) { return { errorMessage: payload } } } -
在模板顶部新增
生成完整项目卡片,字段包括项目名称、项目描述、用户前台开关、高级调整开关、主按钮生成完整项目。主按钮绑定handleGenerateCompleteProject,loading 绑定oneClickGenerating。 -
在一键卡片下方新增结果卡片。展示
backendUrl、frontendUrl、adminFrontendUrl、downloadReady,当errorMessage存在时展示 warning alert。 -
把原有应用蓝图、数据库设计、业务蓝图、页面设计、旧预览操作区整体包在
v-if="expertMode"区域内。保留原有 handler 和文案,避免破坏高级调整。 -
保留
validateRequirementInput给高级调整使用。它可以继续要求关键词或项目描述;一键生成使用新的validateOneClickInput。 -
添加布局 CSS。
.one-click-card :deep(.el-form-item__content) { min-width: 0; } .result-card { margin-top: 16px; } -
运行前端测试。
cd easycode-web node --test src/views/generateView.test.mjs src/views/previewView.test.mjsExpected result: GenerateView 和 PreviewView 的 source-level tests 全部通过。
Task 7: Verification and Regression Sweep
Files:
- Tasks 1-6 修改过的所有文件。
Intent: 确认后端编排、前端入口、旧高级调整、源码预览、运行预览没有互相冲突。
-
运行后端 focused tests。
mvn -q -pl ruoyi-generator -Dtest=AiGenerationTaskServiceImplTest,AiGenerationTaskWorkerTest,OneClickProjectGenerationServiceImplTest testExpected result: focused backend tests 全部通过。
-
运行后端模块测试。
mvn -q -pl ruoyi-generator,ruoyi-admin -am testExpected result: generator/admin 相关测试全部通过。
-
运行前端测试。
cd easycode-web node --test src/views/generateView.test.mjs src/views/previewView.test.mjsExpected result: 前端 source-level tests 全部通过。
-
运行前端构建。
cd easycode-web npm run buildExpected result: Vite build 成功完成。
-
在浏览器做手动冒烟验证。
- 打开生成页。
- 确认第一屏默认是
生成完整项目。 - 只输入
客户关系管理系统。 - 点击
生成完整项目。 - 后端服务可用时,确认运行预览进入
RUNNING。 - 打开
高级调整。 - 确认原有应用蓝图、数据库、业务蓝图、页面设计、源码预览控件仍可访问。
-
检查 diff。
git diff --stat git diff --checkExpected result: diff 范围只包含一键生成项目相关改动,
git diff --check无 whitespace 错误。
Acceptance Criteria
- 用户只输入项目名称即可创建一键任务。
- 一键任务使用
generateType=one_click_project,由现有 AI 任务中心排队、查询、重试、取消。 - 后端按固定阶段执行:项目准备、应用蓝图、数据库、业务蓝图、页面初始化、源码预览、运行预览。
RUN_PREVIEW进入RUNNING才算任务成功。- 源码预览完成但运行预览失败时,任务失败结果仍包含
downloadReady=true和failedStage=RUN_PREVIEW。 - 用户前台关闭时跳过用户前台页面初始化和用户前台源码结构生成,管理前台仍生成。
- 前端默认显示一键入口、阶段状态、运行地址、源码预览入口。
- 原有分步设计器保留在高级调整中。
- 后端 focused tests、后端模块测试、前端 source-level tests、前端 build 全部通过。
Implementation Notes
- 不新增数据库字段。第一版使用现有
current_step、progress、result_payload、error_code表达阶段和失败原因。 - 不引入模板库。模板库属于已确认的方案三,是当前版本之后的增强方向。
- 不移除旧的
app_blueprint、database、business_blueprint任务类型。它们继续服务高级调整和调试场景。 - 一键入口和高级调整共享同一个项目记录,因此
frontendEnabled、模块选择、项目描述仍通过现有项目保存逻辑持久化。