diff --git a/docs/superpowers/plans/2026-08-07-product-development-factors-step3.md b/docs/superpowers/plans/2026-08-07-product-development-factors-step3.md new file mode 100644 index 0000000..e3dcc05 --- /dev/null +++ b/docs/superpowers/plans/2026-08-07-product-development-factors-step3.md @@ -0,0 +1,221 @@ +# 产品开发关键因素第 3 步 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:** 为学生端产品开发关键因素实训实现第 3 步商业化及市场反馈分析、服务端校验与只保存进入第 4 步的流程。 + +**Architecture:** 前端在现有 `product-development-factors.vue` 中增加固定三行的第 3 步表格,并把数据独立写入 `step3Answer`。后端按前两步的分层模式新增 DTO、服务及控制器入口,严格验证学生身份、三项固定维度及六个非空分析字段。 + +**Tech Stack:** Vue 3 Composition API、Element Plus、现有 request API;Spring Boot、JUnit 5、Mockito、Maven。 + +## Global Constraints + +- 仅修改学生端页面及学生答案校验接口;不调整教师端或任务配置。 +- 不新增数据库表或字段,使用既有答案记录的 `step3Answer` 与 `currentStep`。 +- 第 3 步保存固定使用 `SAVE`、`submitted: false`、`currentStep: 4`,不得提交任务。 +- 保存前必须通过正式或演示模式的一致校验:三个固定维度、六个分析字段全部非空。 + +--- + +### Task 1: 第 3 步后端校验服务 + +**Files:** +- Create: `src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepThreeValidationRequest.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepThreeValidationResult.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepThreeService.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepThreeServiceImpl.java` +- Test: `src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepThreeServiceImplTest.java` + +**Interfaces:** +- Consumes: `JwtUser`、`ServiceException`、Spring HTTP status conventions。 +- Produces: `ProductDevelopmentFactorsStepThreeService#validate(ProductDevelopmentFactorsStepThreeValidationRequest, JwtUser)`,返回 `{ valid, message }`。 + +- [ ] **Step 1: 写失败服务测试** + +```java +@Test void acceptsCompleteStudentAnalyses() { + assertTrue(new ProductDevelopmentFactorsStepThreeServiceImpl().validate(valid(), student()).isValid()); +} + +@Test void rejectsBlankOrForgedAnalyses() { + ProductDevelopmentFactorsStepThreeValidationRequest blank = valid(); + blank.getFactors().get(2).setFailedAnalysis(" "); + assertThrows(ServiceException.class, () -> service.validate(blank, student())); + ProductDevelopmentFactorsStepThreeValidationRequest forged = valid(); + forged.getFactors().get(2).setDimension("伪造维度"); + assertThrows(ServiceException.class, () -> service.validate(forged, student())); +} +``` + +- [ ] **Step 2: 运行测试确认失败** + +Run: `mvn -B -Dtest=ProductDevelopmentFactorsStepThreeServiceImplTest test` + +Expected: FAIL,因为 DTO 和服务尚不存在。 + +- [ ] **Step 3: 实现最小服务** + +```java +private static final Set DIMENSIONS = new LinkedHashSet<>(Arrays.asList( + "供应链与成本控制", "商业模式", "市场反馈")); + +if (user == null || StringUtils.isBlank(user.getUserId())) throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再提交"); +if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可提交实训答案"); +``` + +校验 `factors` 数量为 3、每个维度只出现一次且属于 `DIMENSIONS`,并要求每个 `successAnalysis`、`failedAnalysis` 均非空;合法时返回 `valid=true`、`message="校验通过"`。 + +- [ ] **Step 4: 运行服务测试确认通过** + +Run: `mvn -B -Dtest=ProductDevelopmentFactorsStepThreeServiceImplTest test` + +Expected: PASS,覆盖合法、空值、伪造维度和非学生身份。 + +- [ ] **Step 5: 提交后端服务** + +```bash +git add src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepThreeValidationRequest.java src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepThreeValidationResult.java src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepThreeService.java src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepThreeServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepThreeServiceImplTest.java +git commit -m "feat: validate product factors step three" +``` + +### Task 2: 第 3 步校验控制器入口 + +**Files:** +- Modify: `src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java` +- Modify: `src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java` + +**Interfaces:** +- Consumes: Task 1 的 `ProductDevelopmentFactorsStepThreeService`。 +- Produces: `POST /api/student-training-answers/product-development-factors/step-3/validate`。 + +- [ ] **Step 1: 写失败控制器测试** + +```java +@Test void validateProductDevelopmentFactorsStepThreeUsesTheAuthenticatedStudentAndReturnsTheValidationResult() { + ProductDevelopmentFactorsStepThreeService service = mock(ProductDevelopmentFactorsStepThreeService.class); + ReflectionTestUtils.setField(controller, "productDevelopmentFactorsStepThreeService", service); + when(service.validate(any(ProductDevelopmentFactorsStepThreeValidationRequest.class), any(JwtUser.class))).thenReturn(validation); + ResultEntity result = controller.validateProductDevelopmentFactorsStepThree(new ProductDevelopmentFactorsStepThreeValidationRequest(), authenticatedStudentRequest()); + assertEquals(HttpStatus.OK, result.getStatusCode()); +} +``` + +- [ ] **Step 2: 运行测试确认失败** + +Run: `mvn -B -Dtest=StudentTrainingAnswerControllerTest test` + +Expected: FAIL,因为第 3 步服务注入字段和控制器方法尚不存在。 + +- [ ] **Step 3: 增加路由与转发** + +```java +@PostMapping("/product-development-factors/step-3/validate") +public ResultEntity validateProductDevelopmentFactorsStepThree( + @RequestBody ProductDevelopmentFactorsStepThreeValidationRequest request, + HttpServletRequest servletRequest) { + return ResultEntity.ok(productDevelopmentFactorsStepThreeService.validate(request, getJwtUser(servletRequest))); +} +``` + +- [ ] **Step 4: 运行控制器测试确认通过** + +Run: `mvn -B -Dtest=StudentTrainingAnswerControllerTest,ProductDevelopmentFactorsStepThreeServiceImplTest test` + +Expected: PASS。 + +- [ ] **Step 5: 提交控制器** + +```bash +git add src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java +git commit -m "feat: expose product factors step three validation" +``` + +### Task 3: 第 3 步学生页面、草稿和 API + +**Files:** +- Modify: `src/views/foundation/product-development-factors.vue` +- Modify: `src/api/studentTrainingAnswer.js` +- Modify: `tests/product-development-factors-step-one.static.test.cjs` + +**Interfaces:** +- Consumes: Task 2 的正式校验 URL、既有 `saveStudentTrainingAnswer`、`getStudentTrainingAnswer`。 +- Produces: 第 3 步固定三行分析页面;`step3Answer`、`currentStep: 4`、`checkProductDevelopmentFactorsStepThree(payload)`。 + +- [ ] **Step 1: 先扩展前端静态契约测试** + +```js +assert.match(pageSource, /STEP_THREE_FACTOR_ROWS/); +assert.match(pageSource, /供应链与成本控制/); +assert.match(pageSource, /商业模式/); +assert.match(pageSource, /市场反馈/); +assert.match(pageSource, /step3Answer/); +assert.match(pageSource, /saveAndGoToStepFour/); +assert.match(apiSource, /product-development-factors\/step-3\/validate/); +``` + +- [ ] **Step 2: 运行静态测试确认失败** + +Run: `node tests/product-development-factors-step-one.static.test.cjs` + +Expected: FAIL,因为第 3 步常量、保存函数和 API 尚不存在。 + +- [ ] **Step 3: 实现前端最小改动** + +在 `currentStep === 2` 增加表格;建立 `STEP_THREE_FACTOR_ROWS`、`createStepThreeRows`、`stepThreeRows`,并扩展草稿、答案恢复、导出与清空。 + +```js +function buildStepThreeAnswerPayload(saveAction = "SAVE", persistedStep = 4) { + return { + step3Answer: JSON.stringify(buildStepThreeOutcome()), + currentStep: persistedStep, + submitted: false, + saveAction, + }; +} +``` + +实现正式 API 和演示模式的一致校验;`saveAndGoToStepFour` 成功后清理草稿并在第 4 步存在时将 `currentStep` 设为 `3`,否则显示已保存但未配置第 4 步的提示。 + +- [ ] **Step 4: 运行前端测试与生产构建** + +Run: `node tests/product-development-factors-step-one.static.test.cjs; npm run build:prod` + +Expected: 静态契约测试与生产构建均 PASS。 + +- [ ] **Step 5: 提交前端改动** + +```bash +git add src/views/foundation/product-development-factors.vue src/api/studentTrainingAnswer.js tests/product-development-factors-step-one.static.test.cjs +git commit -m "feat: add product factors step three analysis" +``` + +### Task 4: 全量回归与交付检查 + +**Files:** +- Verify only: 前后端工作树的已提交变更。 + +**Interfaces:** +- Consumes: Tasks 1–3。 +- Produces: 经过完整回归验证的前后端功能分支。 + +- [ ] **Step 1: 运行后端全量测试** + +Run: `mvn -B test` + +Expected: BUILD SUCCESS,所有测试通过。 + +- [ ] **Step 2: 运行前端完整验证** + +Run: `node tests/product-development-factors-step-one.static.test.cjs; npm run build:prod` + +Expected: 静态契约测试和生产构建成功。 + +- [ ] **Step 3: 审查工作树** + +Run: `git status --short; git log --oneline -3` + +Expected: 仅本功能的预期提交;不纳入主工作区已有未跟踪文件。 + +- [ ] **Step 4: 汇报并等待集成指令** + +报告功能、提交和验证结果,等待用户明确指示合并、推送或发布。 diff --git a/docs/superpowers/plans/2026-08-07-product-development-factors-step4.md b/docs/superpowers/plans/2026-08-07-product-development-factors-step4.md new file mode 100644 index 0000000..f224385 --- /dev/null +++ b/docs/superpowers/plans/2026-08-07-product-development-factors-step4.md @@ -0,0 +1,150 @@ +# 产品开发关键因素第 4 步 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:** 实现学生端产品开发关键因素第 4 步总结与最终任务提交,并提供后端非空校验。 + +**Architecture:** 前端在现有四步页面中增加两个总结文本框,把数据独立持久化到 `step4Answer`。后端延续前三步校验模式,新增 DTO、服务和控制器路由;校验通过后由前端以 `SUBMIT` 终结任务。 + +**Tech Stack:** Vue 3 Composition API、Element Plus、Spring Boot、JUnit 5、Mockito、Maven。 + +## Global Constraints + +- 仅修改学生端和学生答案校验接口,不修改教师端或任务配置。 +- 沿用当前隔离分支,最终分支同时包含第 3、4 步。 +- 不新增数据库字段,使用 `step4Answer` 与 `currentStep: 4`。 +- 最终保存使用 `saveAction: "SUBMIT"`、`submitted: true`,两个因素字段必须非空。 + +--- + +### Task 1: 第 4 步后端校验服务与路由 + +**Files:** +- Create: `src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepFourValidationRequest.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepFourValidationResult.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepFourService.java` +- Create: `src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepFourServiceImpl.java` +- Modify: `src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java` +- Test: `src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepFourServiceImplTest.java` +- Test: `src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java` + +**Interfaces:** +- Produces: `POST /api/student-training-answers/product-development-factors/step-4/validate`。 +- Request: `{ successFactors: String, failedFactors: String }`。 +- Response: `{ valid: boolean, message: String }`。 + +- [ ] **Step 1: 写失败服务测试并验证红灯** + +```java +@Test void acceptsCompleteStudentSummary() { + assertTrue(new ProductDevelopmentFactorsStepFourServiceImpl().validate(valid(), student()).isValid()); +} +@Test void rejectsBlankSummaryOrNonStudent() { + ProductDevelopmentFactorsStepFourValidationRequest blank = valid(); + blank.setFailedFactors(" "); + assertThrows(ServiceException.class, () -> service.validate(blank, student())); + assertThrows(ServiceException.class, () -> service.validate(valid(), teacher())); +} +``` + +Run: `mvn -B -Dtest=ProductDevelopmentFactorsStepFourServiceImplTest test` + +Expected: FAIL,因为第 4 步类型与服务尚未创建。 + +- [ ] **Step 2: 实现最小 DTO 与服务** + +```java +if (user == null || StringUtils.isBlank(user.getUserId())) throw new ServiceException(HttpStatus.UNAUTHORIZED, "请先登录后再提交"); +if (user.getRoleId() != 4) throw new ServiceException(HttpStatus.FORBIDDEN, "仅学生可提交实训答案"); +if (!StringUtils.isNotBlank(request.getSuccessFactors())) throw badRequest("请填写产品成功的关键因素"); +if (!StringUtils.isNotBlank(request.getFailedFactors())) throw badRequest("请填写产品失败的关键因素"); +``` + +- [ ] **Step 3: 写失败控制器测试、实现路由并验证绿灯** + +```java +@PostMapping("/product-development-factors/step-4/validate") +public ResultEntity validateProductDevelopmentFactorsStepFour( + @RequestBody ProductDevelopmentFactorsStepFourValidationRequest answer, HttpServletRequest request) { + return new ResultEntity<>(HttpStatus.OK, "校验通过", productDevelopmentFactorsStepFourService.validate(answer, TokenProvider.getJWTUser(request))); +} +``` + +Run: `mvn -B "-Dtest=StudentTrainingAnswerControllerTest,ProductDevelopmentFactorsStepFourServiceImplTest" test` + +Expected: PASS。 + +- [ ] **Step 4: 提交后端改动** + +```bash +git add src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepFourValidationRequest.java src/main/java/com/sztzjy/linkCommerce/entity/dto/ProductDevelopmentFactorsStepFourValidationResult.java src/main/java/com/sztzjy/linkCommerce/service/ProductDevelopmentFactorsStepFourService.java src/main/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepFourServiceImpl.java src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java src/test/java/com/sztzjy/linkCommerce/service/impl/ProductDevelopmentFactorsStepFourServiceImplTest.java src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java +git commit -m "feat: validate product factors step four" +``` + +### Task 2: 第 4 步学生页面与最终提交 + +**Files:** +- Modify: `src/views/foundation/product-development-factors.vue` +- Modify: `src/api/studentTrainingAnswer.js` +- Modify: `tests/product-development-factors-step-one.static.test.cjs` + +**Interfaces:** +- Consumes: `checkProductDevelopmentFactorsStepFour(payload)`。 +- Produces: `step4Answer`、`submitTraining()` 和最终 `SUBMIT` 保存。 + +- [ ] **Step 1: 写失败前端契约测试并验证红灯** + +```js +assert.match(page, /v-else-if="currentStep === 3"/); +assert.match(page, /产品成功的关键因素/); +assert.match(page, /产品失败的关键因素/); +assert.match(page, /step4Answer/); +assert.match(page, /await checkProductDevelopmentFactorsStepFour\(buildStepFourValidationPayload\(\)\)/); +assert.match(page, /buildStepFourAnswerPayload\("SUBMIT", 4\)/); +assert.match(api, /product-development-factors\/step-4\/validate/); +``` + +Run: `node tests/product-development-factors-step-one.static.test.cjs` + +Expected: FAIL,因为第 4 步表单、校验和提交动作尚不存在。 + +- [ ] **Step 2: 实现页面、恢复与演示校验** + +```js +function buildStepFourAnswerPayload(saveAction = "SUBMIT", persistedStep = 4) { + return { step4Answer: JSON.stringify(buildStepFourOutcome()), currentStep: persistedStep, submitted: true, saveAction }; +} +``` + +增加 `stepFourForm`、草稿恢复、导出和清空;演示模式与正式接口都要求 `successFactors`、`failedFactors` 非空。`submitTraining` 校验成功后保存、清理草稿并提示“任务提交成功”。 + +- [ ] **Step 3: 运行前端验证并提交** + +Run: `node tests/product-development-factors-step-one.static.test.cjs; npm run build:prod` + +Expected: PASS。 + +```bash +git add src/views/foundation/product-development-factors.vue src/api/studentTrainingAnswer.js tests/product-development-factors-step-one.static.test.cjs +git commit -m "feat: add product factors step four summary" +``` + +### Task 3: 全量回归与交付 + +- [ ] **Step 1: 运行后端全量测试** + +Run: `mvn -B test` + +Expected: BUILD SUCCESS。 + +- [ ] **Step 2: 运行前端测试和生产构建** + +Run: `node tests/product-development-factors-step-one.static.test.cjs; npm run build:prod` + +Expected: PASS。 + +- [ ] **Step 3: 核对提交范围并等待集成指令** + +Run: `git status --short; git log --oneline -6` + +Expected: 仅第 3、4 步的预期提交;不修改主工作区的已有未跟踪文件。 diff --git a/docs/superpowers/specs/2026-08-07-product-development-factors-step3-design.md b/docs/superpowers/specs/2026-08-07-product-development-factors-step3-design.md new file mode 100644 index 0000000..5ea4918 --- /dev/null +++ b/docs/superpowers/specs/2026-08-07-product-development-factors-step3-design.md @@ -0,0 +1,52 @@ +# 产品开发关键因素第 3 步设计 + +## 目标 + +为学生端“产品开发关键因素”实训补齐第 3 步“商业化及市场反馈”分析页。学生对成功产品与失败产品分别完成三项固定维度分析,保存后进入第 4 步,但不提交整个任务。 + +## 范围与约束 + +- 仅修改学生端页面及其学生答案校验接口;不调整教师端、任务配置端或其他模块。 +- 延续现有 `product-development-factors` 单页多步骤页面、答案存储和本地草稿机制。 +- 不新增数据库表或字段:第 3 步内容持久化到现有答案记录的 `step3Answer`,进度写入 `currentStep`。 +- 成功保存第 3 步后,保存动作固定为 `SAVE`、`submitted: false`、`currentStep: 4`;不得调用任务提交动作。 + +## 学生页面 + +当 `currentStep === 2` 时展示标题“商业化及市场反馈角度分析,填写下表:”,表头为“维度 / 评估项 / 成功产品 / 失败产品”。 + +固定三行及评估项: + +| 维度 | 评估项 | +| --- | --- | +| 供应链与成本控制 | 产品成本是否可控?是否支持规模化?是否具备备选供应链?关键元器件(如芯片、传感器)有≥2家可替换供应商 | +| 商业模式 | 盈利模式是否清晰?硬件销售毛利率≥50%,或有订阅服务(如云存储、AI 功能包)。是否构建“数据 - 反馈 - 迭代”闭环?产品使用数据可回传,驱动功能优化与新版本开发 | +| 市场反馈 | 是否处于需求上升期?功能、设计、服务、品牌是否有差异化壁垒?用户是否高留存、高互动、低退货? | + +每行的成功产品与失败产品列各提供多行文本输入。六个输入均为必填;保存按钮为“保存并进入第 4 步”,清空按钮沿用现有行为。页面恢复已保存答案和本地草稿;导出内容与清空逻辑覆盖前三步。 + +若任务配置少于四步,保存仍成功,提示“已保存,当前任务尚未配置第 4 步”,并留在当前页;否则切换到第 4 步占位页。 + +## 前端数据流 + +- 定义 `STEP_THREE_FACTOR_ROWS` 和 `stepThreeRows`,结构与前两步相同:`dimension`、`criteria`、`successAnalysis`、`failedAnalysis`。 +- 在 `buildDraft` 中新增 `stepThree`;在加载答案与草稿时恢复第 3 步。 +- 仅第 3 步保存时组装 `{ factors: [{ dimension, successAnalysis, failedAnalysis }] }` 发往校验接口;校验通过后,保存 `step3Answer`。 +- 演示模式使用与正式接口相同的固定维度和六项非空规则,保证离线体验一致。 + +## 后端校验接口 + +新增 `POST /api/student-training-answers/product-development-factors/step-3/validate`。 + +- 仅允许已登录、`roleId == 4` 的学生调用;未登录返回 401,非学生返回 403。 +- 请求体为 `factors` 数组,每项含 `dimension`、`successAnalysis`、`failedAnalysis`。 +- 必须且只能提交一次下列三个维度:供应链与成本控制、商业模式、市场反馈。 +- 每个维度的成功产品与失败产品分析去除空白后都必须有内容。 +- 合法请求返回 `{ valid: true, message: "校验通过" }`;缺项、重复、伪造维度或空内容返回明确的 400 错误信息。 + +## 测试 + +- 后端服务测试覆盖:完整合法数据、空内容、伪造或重复维度、非学生身份。 +- 控制器测试覆盖:接口把认证学生与请求参数传给第 3 步校验服务并返回结果。 +- 前端静态契约测试覆盖:三项固定维度、`step3Answer` 持久化、校验 API、六项非空演示校验,以及只保存进入第 4 步、不提交任务的行为。 +- 最终运行后端 `mvn -B test` 和前端静态契约测试、生产构建。 diff --git a/docs/superpowers/specs/2026-08-07-product-development-factors-step4-design.md b/docs/superpowers/specs/2026-08-07-product-development-factors-step4-design.md new file mode 100644 index 0000000..619cc07 --- /dev/null +++ b/docs/superpowers/specs/2026-08-07-product-development-factors-step4-design.md @@ -0,0 +1,48 @@ +# 产品开发关键因素第 4 步设计 + +## 目标 + +为学生端“产品开发关键因素”实训补齐第 4 步总结页。学生结合案例资料及前三步分析,填写成功产品与失败产品的关键因素;两个输入都完成后提交整个任务。 + +## 范围与约束 + +- 仅修改学生端页面及学生答案校验接口;不调整教师端、任务配置端或其他模块。 +- 在当前 `codex/product-development-factors-step3` 隔离分支继续开发,因此最终功能分支包含第 3、4 步,后续一次性合并。 +- 不新增数据库表或字段:第 4 步内容使用现有答案记录的 `step4Answer`,进度保持为 `currentStep: 4`。 +- 最终提交动作固定使用 `saveAction: "SUBMIT"` 与 `submitted: true`;不得在提交成功后跳转至不存在的下一步。 + +## 学生页面 + +当 `currentStep === 3` 时,显示说明:“结合案例信息及前述步骤,分析案例资料中产品一成功的关键因素是什么,产品二失败的关键因素是什么。” + +展示两个大文本框: + +1. 产品成功的关键因素 +2. 产品失败的关键因素 + +两个输入去除首尾空白后都必须有内容。提交按钮为“提交任务”,并提供既有“清空填写”按钮。草稿、保存答案加载和导出内容均包含第 4 步;清空操作同时清除前四步。 + +提交成功后保留在第 4 步,显示“任务提交成功”。重复点击时沿用现有 `saving` 防抖;接口校验失败时显示服务端错误信息。 + +## 前端数据流 + +- 使用 `stepFourForm` 保存 `successFactors`、`failedFactors`。 +- `buildDraft` 添加 `stepFour`;加载 `step4Answer` 与本地草稿时恢复该表单。 +- 点击提交先请求第 4 步校验接口,再保存 `{ step4Answer, currentStep: 4, submitted: true, saveAction: "SUBMIT" }`。 +- 演示模式和正式模式都必须执行两个字段非空的同一规则。 + +## 后端校验接口 + +新增 `POST /api/student-training-answers/product-development-factors/step-4/validate`。 + +- 仅允许已登录、`roleId == 4` 的学生调用;未登录返回 401,非学生返回 403。 +- 请求体包含 `successFactors` 和 `failedFactors`。 +- 两个字段去除空白后都必须非空。 +- 合法请求返回 `{ valid: true, message: "校验通过" }`;空字段返回明确的 400 错误信息。 + +## 测试 + +- 后端服务测试覆盖:合法提交、成功/失败关键因素为空、非学生身份。 +- 控制器测试覆盖:接口把认证学生与请求参数传给第 4 步校验服务并返回结果。 +- 前端静态契约测试覆盖:第 4 步文本框、`step4Answer`、校验 API、最终 `SUBMIT` 保存动作和演示校验。 +- 最终运行后端 `mvn -B test` 和前端静态契约测试、生产构建。 diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js index 28bbcf4..161310f 100644 --- a/src/api/studentTrainingAnswer.js +++ b/src/api/studentTrainingAnswer.js @@ -251,3 +251,37 @@ export function checkProductDevelopmentFactorsStepTwo(payload) { if (isStudentDemo()) { try { return Promise.resolve({ code: 200, data: validateProductDevelopmentFactorsStepTwoDemo(payload) }); } catch (error) { return Promise.reject(error); } } return request({ url: "/api/student-training-answers/product-development-factors/step-2/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } + +const productDevelopmentFactorsStepThreeDimensions = ["供应链与成本控制", "商业模式", "市场反馈"]; + +function validateProductDevelopmentFactorsStepThreeDemo(payload) { + const factors = Array.isArray(payload?.factors) ? payload.factors : []; + const dimensions = factors.map((factor) => String(factor?.dimension || "").trim()); + if (factors.length !== productDevelopmentFactorsStepThreeDimensions.length + || new Set(dimensions).size !== productDevelopmentFactorsStepThreeDimensions.length + || productDevelopmentFactorsStepThreeDimensions.some((dimension) => !dimensions.includes(dimension))) { + throw new Error("请完成供应链与成本控制、商业模式和市场反馈分析"); + } + const incomplete = factors.find((factor) => !String(factor?.successAnalysis || "").trim() + || !String(factor?.failedAnalysis || "").trim()); + if (incomplete) throw new Error(`请完成${incomplete.dimension || "该项"}的成功产品和失败产品分析`); + return { valid: true, message: "校验通过" }; +} + +export function checkProductDevelopmentFactorsStepThree(payload) { + if (isStudentDemo()) { + try { return Promise.resolve({ code: 200, data: validateProductDevelopmentFactorsStepThreeDemo(payload) }); } + catch (error) { return Promise.reject(error); } + } + return request({ url: "/api/student-training-answers/product-development-factors/step-3/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); +} + +export function checkProductDevelopmentFactorsStepFour(payload) { + const value = payload || {}; + if (isStudentDemo()) { + if (!String(value.successFactors || "").trim()) return Promise.reject(new Error("请填写产品成功的关键因素")); + if (!String(value.failedFactors || "").trim()) return Promise.reject(new Error("请填写产品失败的关键因素")); + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ url: "/api/student-training-answers/product-development-factors/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); +} diff --git a/src/views/foundation/product-development-factors.vue b/src/views/foundation/product-development-factors.vue index c219197..a5f258f 100644 --- a/src/views/foundation/product-development-factors.vue +++ b/src/views/foundation/product-development-factors.vue @@ -104,6 +104,20 @@
+