docs: plan dynamic training steps
parent
008e83f282
commit
1ab8541f7e
@ -0,0 +1,224 @@
|
||||
# Dynamic Training Steps 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:** Teachers can manage arbitrary task steps; students can fill, save, and submit every added custom step without losing specialised built-in forms.
|
||||
|
||||
**Architecture:** `TrainingTask.steps` becomes a stable object array while retaining JSON text storage. The backend keeps legacy answers and adds a JSON map for custom-step answers. One reusable student workspace and launcher makes custom steps reachable from every task route.
|
||||
|
||||
**Tech Stack:** Vue 3, Element Plus, Spring Boot 2.7, MyBatis, JUnit 5, Mockito.
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Built-in task count remains fixed; step count is dynamic.
|
||||
- Preserve legacy string-array steps and step1 through step4 answers.
|
||||
- Each new step has a stable ID and `kind: "custom"`; a task must retain at least one step.
|
||||
- Built-in specialised forms follow step IDs, never array positions.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Dynamic step model in the backend
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `src/main/java/com/sztzjy/linkCommerce/entity/TrainingTaskStep.java`
|
||||
- Modify: `src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java`
|
||||
- Modify: `src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: legacy arrays such as `["调研","分析"]` and object arrays such as `[{"id":"custom-1","name":"补充分析","kind":"custom"}]`.
|
||||
- Produces: ordered `{id,name,kind}` JSON arrays with no four-step padding or truncation.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```java
|
||||
request.setSteps("[{\"id\":\"builtin-1\",\"name\":\"调研\",\"kind\":\"builtin\"},{\"id\":\"custom-1\",\"name\":\"补充分析\",\"kind\":\"custom\"},{\"id\":\"custom-2\",\"name\":\"成果复盘\",\"kind\":\"custom\"}]");
|
||||
TrainingTask saved = service.update("task-id", request);
|
||||
assertThat(saved.getSteps()).contains("custom-1", "custom-2", "补充分析", "成果复盘");
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the test fails**
|
||||
|
||||
Run: `mvn -Dtest=TrainingTaskServiceImplTest test`
|
||||
|
||||
Expected: FAIL because `normalizeFixedSteps` limits entries to the built-in count.
|
||||
|
||||
- [ ] **Step 3: Implement the normalizer**
|
||||
|
||||
```java
|
||||
private String normalizeSteps(String value, TrainingTask builtInTask) {
|
||||
List<TrainingTaskStep> steps = parseSteps(value, builtInTask);
|
||||
if (steps.isEmpty()) throw new IllegalArgumentException("至少保留一个步骤");
|
||||
return JSON.toJSONString(steps);
|
||||
}
|
||||
```
|
||||
|
||||
`parseSteps` must give historical string values deterministic `legacy-<position>` IDs, preserve valid IDs and `kind`, generate IDs for blank new IDs, and replace every create/update/import/class-save call to `normalizeFixedSteps`.
|
||||
|
||||
- [ ] **Step 4: Verify and commit**
|
||||
|
||||
Run: `mvn -Dtest=TrainingTaskServiceImplTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/TrainingTaskServiceImplTest.java
|
||||
git commit -m "feat: support dynamic training task steps"
|
||||
```
|
||||
|
||||
### Task 2: Dynamic student-answer persistence
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `src/main/java/com/sztzjy/linkCommerce/entity/StudentTrainingAnswer.java`
|
||||
- Modify: `src/main/java/com/sztzjy/linkCommerce/service/impl/StudentTrainingAnswerServiceImpl.java`
|
||||
- Modify: `src/test/java/com/sztzjy/linkCommerce/service/impl/StudentTrainingAnswerServiceImplTest.java`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: `dynamicStepAnswers` JSON keyed by stable step ID.
|
||||
- Produces: the same JSON map from the current student-answer API; `currentStep` can exceed four.
|
||||
|
||||
- [ ] **Step 1: Write the failing test**
|
||||
|
||||
```java
|
||||
request.setCurrentStep(6);
|
||||
request.setDynamicStepAnswers("{\"custom-1\":{\"content\":\"补充调研结论\",\"submitted\":true}}");
|
||||
StudentTrainingAnswer saved = service.save("new-product-survey", request, student());
|
||||
assertEquals(6, saved.getCurrentStep());
|
||||
assertThat(saved.getDynamicStepAnswers()).contains("custom-1", "补充调研结论");
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify the test fails**
|
||||
|
||||
Run: `mvn -Dtest=StudentTrainingAnswerServiceImplTest test`
|
||||
|
||||
Expected: FAIL because the model lacks `dynamicStepAnswers` and caps the current step at four.
|
||||
|
||||
- [ ] **Step 3: Implement model and migration**
|
||||
|
||||
Add `dynamicStepAnswers` to the entity, add `dynamic_step_answers longtext NULL` via the table-ensure migration, preserve it in `buildRecord`, clear it for RESET, validate JSON-object values, and remove the fixed `currentStep` upper bound. Keep the four legacy answer fields unchanged.
|
||||
|
||||
- [ ] **Step 4: Verify and commit**
|
||||
|
||||
Run: `mvn -Dtest=StudentTrainingAnswerServiceImplTest test`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
```bash
|
||||
git add src/main/java/com/sztzjy/linkCommerce/entity/StudentTrainingAnswer.java src/main/java/com/sztzjy/linkCommerce/service/impl/StudentTrainingAnswerServiceImpl.java src/test/java/com/sztzjy/linkCommerce/service/impl/StudentTrainingAnswerServiceImplTest.java
|
||||
git commit -m "feat: persist dynamic student step answers"
|
||||
```
|
||||
|
||||
### Task 3: Dynamic teacher and management editors
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify: `../e-commerce-internet/src/views/teacherEnd/trainingTask/index.vue`
|
||||
- Modify: `../e-commerce-internet/src/views/schoolAdmin/trainingTask/index.vue`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: legacy step strings or `{id,name,kind}` objects.
|
||||
- Produces: non-empty object-array JSON in the existing save payload.
|
||||
|
||||
- [ ] **Step 1: Create the failing browser check**
|
||||
|
||||
Open each editor; add a fifth row, rename it, delete a different row, and assert the final remaining row cannot be deleted.
|
||||
|
||||
- [ ] **Step 2: Verify current behavior fails**
|
||||
|
||||
Expected: four static inputs with no add/delete controls, and step five is discarded on save.
|
||||
|
||||
- [ ] **Step 3: Implement reusable row behavior in both editors**
|
||||
|
||||
```js
|
||||
function createStep(name = "", kind = "custom") {
|
||||
return { id: crypto.randomUUID(), name, kind }
|
||||
}
|
||||
function addStep() { taskForm.steps.push(createStep()) }
|
||||
function removeStep(index) { if (taskForm.steps.length > 1) taskForm.steps.splice(index, 1) }
|
||||
```
|
||||
|
||||
Bind each input to `taskForm.steps[index].name`, show a delete control per row, add `+ 新增步骤`, serialize valid rows as objects, and remove copy claiming a fixed step count.
|
||||
|
||||
- [ ] **Step 4: Verify and commit**
|
||||
|
||||
Use the browser to save and reopen a five-step task in both editors; assert ordering and IDs persist.
|
||||
|
||||
```bash
|
||||
git -C ../e-commerce-internet add src/views/teacherEnd/trainingTask/index.vue src/views/schoolAdmin/trainingTask/index.vue
|
||||
git -C ../e-commerce-internet commit -m "feat: edit training task steps dynamically"
|
||||
```
|
||||
|
||||
### Task 4: Student custom-step workspace
|
||||
|
||||
**Files:**
|
||||
|
||||
- Create: `../e-commerce-internet/src/views/training/DynamicTrainingStepPage.vue`
|
||||
- Create: `../e-commerce-internet/src/views/components/DynamicTrainingStepLauncher.vue`
|
||||
- Modify: `../e-commerce-internet/src/router/index.js`
|
||||
- Modify: `../e-commerce-internet/src/layout/components/AppMain.vue`
|
||||
- Modify: `../e-commerce-internet/src/api/studentTrainingAnswer.js`
|
||||
|
||||
**Interfaces:**
|
||||
|
||||
- Consumes: route params `{taskKey, stepId}`, custom step objects, and `dynamicStepAnswers`.
|
||||
- Produces: `dynamicStepAnswers[stepId] = {content, attachment, savedAt, submitted}` through the existing student-answer endpoint.
|
||||
|
||||
- [ ] **Step 1: Create the failing browser flow**
|
||||
|
||||
With a custom step configured, assert its launcher is visible on the student task, its route has text and attachment controls, and a saved value survives reload.
|
||||
|
||||
- [ ] **Step 2: Verify current behavior fails**
|
||||
|
||||
Expected: no custom-step launcher, route, or dynamic answer payload.
|
||||
|
||||
- [ ] **Step 3: Implement the shared workspace**
|
||||
|
||||
```js
|
||||
function buildPayload(action) {
|
||||
return {
|
||||
dynamicStepAnswers: JSON.stringify({
|
||||
...answers.value,
|
||||
[route.params.stepId]: { content: form.content, attachment: form.attachment, submitted: action === "SUBMIT", savedAt: new Date().toISOString() }
|
||||
}),
|
||||
currentStep: selectedStepIndex.value,
|
||||
saveAction: action
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The route must reject missing or built-in step IDs. The workspace must use the existing student upload endpoint, offer separate save and submit actions, and restore prior text and attachment metadata. The global launcher lists custom steps once for every student task route, leaving built-in specialised pages unchanged.
|
||||
|
||||
- [ ] **Step 4: Verify and commit**
|
||||
|
||||
Create a custom teacher step, then as a student save text and a document, reload, and assert both remain. Confirm an existing built-in task page remains usable.
|
||||
|
||||
```bash
|
||||
git -C ../e-commerce-internet add src/views/training/DynamicTrainingStepPage.vue src/views/components/DynamicTrainingStepLauncher.vue src/router/index.js src/layout/components/AppMain.vue src/api/studentTrainingAnswer.js
|
||||
git -C ../e-commerce-internet commit -m "feat: add student custom training step workspace"
|
||||
```
|
||||
|
||||
### Task 5: Regression verification
|
||||
|
||||
**Files:**
|
||||
|
||||
- Modify only failed regression tests from Tasks 1 and 2.
|
||||
|
||||
- [ ] **Step 1: Run backend suite**
|
||||
|
||||
Run: `mvn test`
|
||||
|
||||
Expected: all tests pass.
|
||||
|
||||
- [ ] **Step 2: Build frontend**
|
||||
|
||||
Run: `npm run build:prod`
|
||||
|
||||
Expected: production build completes without Vue compilation errors.
|
||||
|
||||
- [ ] **Step 3: Verify in browser**
|
||||
|
||||
Verify add, rename, and delete in both editors; student custom-step save/reload/submit; and the original specialised forms on two existing tasks.
|
||||
Loading…
Reference in New Issue