# 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.java` - `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/OneClickProjectGenerationResult.java` - `ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/OneClickProjectGenerationException.java` - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IOneClickProjectGenerationService.java` - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationTaskServiceImpl.java` - `ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/AiGenerationTaskServiceImplTest.java` **Intent:** 固定一键任务的后端契约,让任务中心能创建 `one_click_project` 任务,并给前端稳定的结果 JSON。 - [ ] 在 `AiGenerationTaskServiceImplTest` 添加失败测试 `createTaskQueuesOneClickProjectGeneration`。 ```java 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`,字段和常量如下。 ```java 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 suggestedActions = new ArrayList(); } ``` - [ ] 新建 `OneClickProjectGenerationException`。 ```java 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`。 ```java public interface IOneClickProjectGenerationService { OneClickProjectGenerationResult generate(AiGenerationTask task, AiGenerationTaskCreateRequest request); } ``` - [ ] 修改 `AiGenerationTaskServiceImpl.validateRequest`,允许新类型。 ```java 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生成类型"); } ``` - [ ] 运行测试。 ```powershell mvn -q -pl ruoyi-generator -Dtest=AiGenerationTaskServiceImplTest test ``` Expected result: 新增的一键任务创建测试和既有任务服务测试全部通过。 --- ## Task 2: Worker Dispatch and Failure Payload Preservation **Files:** - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/AiGenerationTaskWorker.java` - `ruoyi-generator/src/test/java/com/ruoyi/generator/async/AiGenerationTaskWorkerTest.java` **Intent:** 让异步 worker 知道如何执行一键任务,并在最终失败时保留阶段化 `resultPayload`。 - [ ] 在 `AiGenerationTaskWorkerTest` 添加 mock 并注入。 ```java @Mock private IOneClickProjectGenerationService oneClickProjectGenerationService; setField(worker, "oneClickProjectGenerationService", oneClickProjectGenerationService); ``` - [ ] 添加失败测试 `runOneClickProjectTaskDelegatesToOrchestrator`。 ```java 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`。 ```java 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` 增加字段。 ```java @Autowired private IOneClickProjectGenerationService oneClickProjectGenerationService; ``` - [ ] 在任务执行分支中加入一键任务。 ```java if ("one_click_project".equals(task.getGenerateType())) { return oneClickProjectGenerationService.generate(task, request); } ``` - [ ] 在最终失败写库前处理 `OneClickProjectGenerationException`。 ```java if (e instanceof OneClickProjectGenerationException) { OneClickProjectGenerationResult result = ((OneClickProjectGenerationException) e).getResult(); task.setResultPayload(JSON.toJSONString(result)); task.setErrorCode(result.getFailedStage()); } ``` - [ ] 运行测试。 ```powershell mvn -q -pl ruoyi-generator -Dtest=AiGenerationTaskWorkerTest test ``` Expected result: worker 的既有测试、新增委派测试、新增失败结果测试全部通过。 --- ## Task 3: One-Click Orchestrator Happy Path **Files:** - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java` - `ruoyi-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`,前台和后台各 1 个页面初始化结果,以及状态为 `RUNNING` 的 `ProjectRunPreviewStatus`。 - [ ] 在该测试中断言返回结果。 ```java 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()); ``` - [ ] 在该测试中断言调用顺序。 ```java 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` 的主流程。 ```java @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 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。 ```java 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(); } ``` - [ ] 运行测试。 ```powershell mvn -q -pl ruoyi-generator -Dtest=OneClickProjectGenerationServiceImplTest test ``` Expected result: 快乐路径测试通过,并验证阶段顺序。 --- ## Task 4: Run Preview Polling, Disabled Frontend, and Failure Rules **Files:** - `ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/OneClickProjectGenerationServiceImpl.java` - `ruoyi-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。 ```java 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。 ```java 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 中把等待时间改短。 ```java setField(service, "runPreviewPollIntervalMillis", 0L); setField(service, "runPreviewTimeoutMillis", 10L); ``` - [ ] 运行测试。 ```powershell mvn -q -pl ruoyi-generator -Dtest=OneClickProjectGenerationServiceImplTest test ``` Expected result: 快乐路径、轮询、关闭用户前台、运行预览失败测试全部通过。 --- ## Task 5: Frontend API Helper and Source-Level Tests **Files:** - `easycode-web/src/api/project.js` - `easycode-web/src/views/generateView.test.mjs` **Intent:** 前端先拥有清晰的一键任务 API 调用点,测试锁定“项目名称即可触发一键任务”的关键行为。 - [ ] 在 `generateView.test.mjs` 添加失败测试。 ```javascript 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。 ```javascript export function createOneClickProjectTask(projectId, data) { return createAiTask(projectId, Object.assign({}, data, { generateType: 'one_click_project' })) } ``` - [ ] 保持 `createAiTask`、`retryAiTask`、`generateProject`、`startRunPreview`、`getRunPreviewStatus` 的既有导出不变。 - [ ] 运行前端测试并确认此时 `GenerateView.vue` 相关断言失败。 ```powershell cd easycode-web node --test src/views/generateView.test.mjs ``` Expected result: 新增断言因页面尚未改造而失败。 --- ## Task 6: GenerateView Default One-Click UX and Expert Mode **Files:** - `easycode-web/src/views/GenerateView.vue` - `easycode-web/src/views/generateView.test.mjs` **Intent:** 让第一屏回到用户初心:输入项目名称后直接生成完整项目。原有分步工具保留在高级调整中。 - [ ] 修改 `GenerateView.vue` 的 API import,加入 `createOneClickProjectTask`。 - [ ] 在 `