You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dianshang-qianduan/docs/superpowers/plans/2026-08-07-new-product-surv...

215 lines
10 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 新产品调查与分析第二步 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:** 让学生完成新产品调查与分析的第二步,并在提交前由后端验证平台、场景、关键词、产品名和 15 张产品图片。
**Architecture:**`new-product-survey.vue` 中旧的三行平台表格替换为单个调查表单,并将其 JSON 继续保存在现有 `StudentTrainingAnswer.step2Answer`。图片先沿用 `/common/upload` 上传,答案中只保存 `{ name, url }`;新增一个无状态的第二步校验服务与控制器入口,前端通过校验后才推进到第三步。
**Tech Stack:** Vue 3 Composition API、Element Plus、Vite、现有 RuoYi request 封装、Spring Boot、JUnit 5、Mockito。
## Global Constraints
- 仅调整学生端 `new-product-survey` 与对应学生答案接口;不改教师端、管理端或其余步骤。
- 案例资料按钮继续读取当前任务的 `materialUrl` 并下载,显示固定文案“下载案例资料”。
- `step2Answer` 必须兼容既有答案存储与本地草稿,不新建图片表。
- 第二步提交必须要求 1 至 5 张图片;选择“其他场景”时必须填写自定义场景。
- 演示模式只做本地校验与会话保存,不调用上传或新增后端校验接口。
---
### Task 1: 后端第二步提交校验
**Files:**
- Create: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/main/java/com/sztzjy/linkCommerce/entity/dto/NewProductSurveyStepTwoValidationRequest.java`
- Create: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/main/java/com/sztzjy/linkCommerce/entity/dto/NewProductSurveyStepTwoValidationResult.java`
- Create: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/main/java/com/sztzjy/linkCommerce/service/NewProductSurveyStepTwoService.java`
- Create: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/main/java/com/sztzjy/linkCommerce/service/impl/NewProductSurveyStepTwoServiceImpl.java`
- Modify: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/main/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerController.java`
- Test: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/test/java/com/sztzjy/linkCommerce/service/impl/NewProductSurveyStepTwoServiceImplTest.java`
- Test: `E:/workspace/dianshang/link_commerce/.worktrees/new-product-survey-step1/src/test/java/com/sztzjy/linkCommerce/controller/stu/StudentTrainingAnswerControllerTest.java`
**Interfaces:**
- Consumes: `JwtUser` and `POST /api/student-training-answers/new-product-survey/step-2/validate` request JSON.
- Produces: `NewProductSurveyStepTwoService.validate(NewProductSurveyStepTwoValidationRequest, JwtUser)` returning `{ valid: true, message: "校验通过" }` or a 4xx `ServiceException`.
- [ ] **Step 1: Write failing service tests**
```java
@Test
void validatesACompleteStudentSubmission() {
NewProductSurveyStepTwoValidationResult result = service.validate(request("京东", "AI儿童玩具", "", twoImages()), student());
assertTrue(result.isValid());
}
@Test
void rejectsOtherSceneWithoutCustomSceneAndMoreThanFiveImages() {
assertThrows(ServiceException.class, () -> service.validate(request("京东", "其他场景", "", oneImage()), student()));
assertThrows(ServiceException.class, () -> service.validate(request("京东", "AI儿童玩具", "", sixImages()), student()));
}
```
- [ ] **Step 2: Run the service test to verify it fails**
Run: `mvn -B '-Dtest=NewProductSurveyStepTwoServiceImplTest' test`
Expected: FAIL because the step-two DTO and service do not exist.
- [ ] **Step 3: Implement the DTO, service, and validation rules**
```java
public interface NewProductSurveyStepTwoService {
NewProductSurveyStepTwoValidationResult validate(NewProductSurveyStepTwoValidationRequest request, JwtUser user);
}
// validate roleId == 4; platform in 京东/天猫/抖音电商; scene is nonblank;
// customScene required only for 其他场景; keyword/productName nonblank;
// imageUrls contains 1..5 nonblank URLs. Throw ServiceException(BAD_REQUEST, message) on failure.
```
- [ ] **Step 4: Expose the controller endpoint and its test**
```java
@PostMapping("/new-product-survey/step-2/validate")
public ResultEntity<NewProductSurveyStepTwoValidationResult> validateNewProductSurveyStepTwo(
@RequestBody NewProductSurveyStepTwoValidationRequest answer, HttpServletRequest request) {
return new ResultEntity<>(HttpStatus.OK, "校验通过", newProductSurveyStepTwoService.validate(answer, TokenProvider.getJWTUser(request)));
}
```
Mock the new service in `StudentTrainingAnswerControllerTest` and assert the controller passes the request and JWT user through, returning HTTP 200.
- [ ] **Step 5: Run focused backend tests**
Run: `mvn -B '-Dtest=NewProductSurveyStepTwoServiceImplTest,StudentTrainingAnswerControllerTest' test`
Expected: PASS with zero failures.
- [ ] **Step 6: Commit the backend implementation**
```bash
git add src/main/java src/test/java
git commit -m "feat: validate new product survey step two"
```
### Task 2: 前端第二步表单、上传与答案回填
**Files:**
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/new-product-survey-step1/src/api/studentTrainingAnswer.js`
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/new-product-survey-step1/src/views/foundation/new-product-survey.vue`
- Modify: `E:/workspace/dianshang/e-commerce-internet/.worktrees/new-product-survey-step1/tests/new-product-survey-step-one.static.test.cjs`
**Interfaces:**
- Consumes: `uploadStudentTrainingFile(FormData)`, `checkNewProductSurveyStepTwo(form)` and the backend validation response.
- Produces: `step2Answer` JSON `{ platform, scene, customScene, keyword, productName, images: Array<{name, url}> }` and only advances the wizard after validation succeeds.
- [ ] **Step 1: Write failing frontend static assertions**
```js
assert.match(source, /AI智能家居[\s\S]*AI美妆辅助[\s\S]*其他场景/);
assert.match(source, /v-if="step2Form\.scene === '其他场景'"/);
assert.match(source, /MAX_STEP_TWO_IMAGES = 5/);
assert.match(source, /checkNewProductSurveyStepTwo/);
assert.match(source, /step2Answer: JSON\.stringify\(step2Form\.value\)/);
```
- [ ] **Step 2: Run the static test to verify it fails**
Run: `node tests/new-product-survey-step-one.static.test.cjs`
Expected: FAIL because the page still renders `step2Rows` and has no step-two validation client.
- [ ] **Step 3: Add the API client and local demo validator**
```js
export function checkNewProductSurveyStepTwo(form) {
if (isStudentDemo()) return Promise.resolve({ code: 200, data: validateStepTwoDemo(form) });
return request({
url: "/api/student-training-answers/new-product-survey/step-2/validate",
method: "post",
data: { ...form, imageUrls: form.images.map((image) => image.url) },
headers: { repeatSubmit: false },
});
}
```
`validateStepTwoDemo` must apply the same required-field, other-scene, and 15 image count rules and reject invalid data with an `Error`.
- [ ] **Step 4: Replace the old table with the second-step form**
```vue
<el-radio-group v-model="step2Form.platform">
<el-radio v-for="option in PLATFORM_OPTIONS" :key="option.value" :label="option.value">{{ option.label }}</el-radio>
</el-radio-group>
<input v-if="step2Form.scene === '其他场景'" v-model.trim="step2Form.customScene" />
<input type="file" accept="image/*" multiple @change="uploadStepTwoImages" />
```
Use constants for the three platform options and six scene options. Implement `uploadStepTwoImages`, `removeStepTwoImage`, and `previewStepTwoImage`: reject non-images and selections that would exceed five files; upload files with `FormData`; store only name and resolved URL; show thumbnails in a dialog-capable preview. Replace all `step2Rows` watch, reset, draft, save, outcome, and restoration paths with `step2Form`, while retaining safe fallback behavior for old row-array answers.
- [ ] **Step 5: Gate next-step navigation on step-two validation**
```js
if (currentStep.value === 2) {
await checkNewProductSurveyStepTwo(step2Form.value);
await saveAnswer("IN_PROGRESS", true);
advanceStep();
return;
}
```
Show a human-readable validation/upload error and leave the user on step two on failure. Keep step-one grading and steps three/four navigation behavior unchanged.
- [ ] **Step 6: Run frontend static test and production build**
Run: `node tests/new-product-survey-step-one.static.test.cjs`
Expected: PASS.
Run: `node 'E:\workspace\dianshang\e-commerce-internet\node_modules\vite\bin\vite.js' build --config '.\vite.config.js'`
Expected: build exits with code 0; retain only the repositorys pre-existing warnings.
- [ ] **Step 7: Commit the frontend implementation**
```bash
git add src/api/studentTrainingAnswer.js src/views/foundation/new-product-survey.vue tests/new-product-survey-step-one.static.test.cjs
git commit -m "feat: collect new product survey step two"
```
### Task 3: 全量验证与运行中的服务检查
**Files:**
- Modify: no source files expected.
**Interfaces:**
- Consumes: committed frontend and backend worktree changes.
- Produces: evidence that the new APIs compile and the updated student page is available on the already-started local services.
- [ ] **Step 1: Run the complete backend suite**
Run: `mvn -B test`
Expected: all tests pass with zero failures.
- [ ] **Step 2: Run all standalone frontend tests**
Run:
```powershell
$tests = Get-ChildItem tests -File | Where-Object { $_.Name -like '*.test.cjs' -or $_.Name -like '*.test.mjs' }
foreach ($test in $tests) { node $test.FullName }
```
Expected: every test file exits 0.
- [ ] **Step 3: Verify the running services**
Run: `Invoke-WebRequest http://127.0.0.1:147/index.html -UseBasicParsing` and `Invoke-WebRequest http://127.0.0.1:7548/doc.html -UseBasicParsing`.
Expected: both return HTTP 200. If the backend process predates the new commit, rebuild the backend JAR with `mvn -B package -DskipTests` and restart only the project JAR on port 7548.
- [ ] **Step 4: Report verification and commits**
Provide test/build outcomes, local URLs, and the two feature commit hashes. Do not merge the isolated worktree branches without an explicit user instruction.