16 KiB
Frontend Page Designer 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: Build the first-phase frontend page designer with independent front_project_page_design storage.
Architecture: Add backend page-design persistence and initialization from appBlueprint.frontendPages, expose it through /front/project/{projectId}/page-designs, render a focused designer in GenerateView.vue, and pass saved page designs into generation so Qing frontend templates can honor saved field/action layout. Existing generation remains the fallback when no page design exists.
Tech Stack: Spring Boot, MyBatis XML mappers, JUnit 4, Mockito, Vue 3, Element Plus, Node node:test, Velocity templates.
File Structure
- Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProjectPageDesign.java: database entity. - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignRequest.java: save payload. - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignResponse.java: API response. - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignInitResponse.java: initialization response. - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/front/FrontProjectPageDesignMapper.java: mapper contract. - Create
ruoyi-generator/src/main/resources/mapper/front/FrontProjectPageDesignMapper.xml: mapper SQL. - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontendPageDesignService.java: validation, initialization, save, list. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java: delete page designs with projects and reuse project ownership checks. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/IFrontProjectService.java: expose page-design service only if needed by controller. - Modify
ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java: add page-design endpoints. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java: add transient page designs. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java: copy page designs toGenProject. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java: load page designs before conversion. - Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java: expose page design lookup helpers to Velocity. - Modify
ruoyi-generator/src/main/resources/qing/index.vue.vm: use page design fields/actions when present. - Modify
sql/front_workbench.sql,sql/front_project_upgrade.sql, andsql/db.sql: create/upgradefront_project_page_design. - Modify
easycode-web/src/api/project.js: add page design API calls. - Create
easycode-web/src/components/FrontendPageDesigner.vue: designer UI. - Create
easycode-web/src/components/frontendPageDesigner.test.mjs: frontend source-level tests. - Modify
easycode-web/src/views/GenerateView.vue: load/init/save page designs and mount designer. - Modify
easycode-web/src/views/generateView.test.mjs: verify designer integration. - Add/modify backend tests:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectSchemaScriptTest.javaruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.javaruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.javaruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
Task 1: Schema and Mapper
Files:
-
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/FrontProjectPageDesign.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/front/FrontProjectPageDesignMapper.java -
Create:
ruoyi-generator/src/main/resources/mapper/front/FrontProjectPageDesignMapper.xml -
Modify:
sql/front_workbench.sql -
Modify:
sql/front_project_upgrade.sql -
Modify:
sql/db.sql -
Modify test:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectSchemaScriptTest.java -
Step 1: Write failing schema test
Add assertions:
assertTrue("front_workbench.sql should create front_project_page_design",
workbenchSql.contains("create table front_project_page_design"));
assertTrue("db.sql should create front_project_page_design",
dbSql.contains("create table front_project_page_design"));
assertTrue("front_project_upgrade.sql should create front_project_page_design",
upgradeSql.contains("create table if not exists front_project_page_design"));
assertTrue("Page design mapper should map layout_json",
read(repoRoot.resolve("ruoyi-generator/src/main/resources/mapper/front/FrontProjectPageDesignMapper.xml"))
.contains("layout_json"));
- Step 2: Run schema test and verify failure
Run: mvn -pl ruoyi-generator -Dtest=FrontProjectSchemaScriptTest test
Expected: FAIL because front_project_page_design and mapper do not exist.
- Step 3: Add entity, mapper, mapper XML, and SQL
Implement entity fields matching the spec. Mapper methods:
List<FrontProjectPageDesign> selectDesignsByProjectId(Long projectId);
FrontProjectPageDesign selectDesignById(Long designId);
FrontProjectPageDesign selectDesignByProjectAndPageCode(@Param("projectId") Long projectId, @Param("pageCode") String pageCode);
int insertFrontProjectPageDesign(FrontProjectPageDesign design);
int updateFrontProjectPageDesign(FrontProjectPageDesign design);
int deleteDesignsByProjectId(Long projectId);
SQL table columns: design_id, project_id, user_id, page_code, menu_code, page_name, route_path, page_type, table_name, layout_json, action_json, status, version, create_time, update_time, remark.
- Step 4: Run schema test and verify pass
Run: mvn -pl ruoyi-generator -Dtest=FrontProjectSchemaScriptTest test
Expected: PASS.
Task 2: Backend Service and API
Files:
-
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignRequest.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignResponse.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/FrontendPageDesignInitResponse.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontendPageDesignService.java -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectServiceImpl.java -
Modify:
ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java -
Create test:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.java -
Step 1: Write failing initialization test
Test behavior:
List<FrontendPageDesignResponse> created = service.initializePageDesigns(7L, 10L).getDesigns();
assertEquals(1, created.size());
assertEquals("book_catalog_page", created.get(0).getPageCode());
assertTrue(created.get(0).getLayoutJson().contains("\"type\":\"query\""));
verify(pageDesignMapper).insertFrontProjectPageDesign(any(FrontProjectPageDesign.class));
- Step 2: Run test and verify failure
Run: mvn -pl ruoyi-generator -Dtest=FrontendPageDesignServiceTest test
Expected: FAIL because service and DTOs do not exist.
- Step 3: Implement initialization and list/detail
initializePageDesigns(userId, projectId):
FrontProject project = frontProjectService.getProject(userId, projectId);
AppBlueprintDesign blueprint = parse project.getAppBlueprint();
for each frontend page:
if no design exists for pageCode:
create default layout/action JSON from bound table columns and operations
return active designs and detached designs
Default layout regions:
[
{"id":"query","type":"query","fields":[]},
{"id":"toolbar","type":"toolbar","actions":["create"]},
{"id":"table","type":"table","fields":[],"rowActions":["view","edit","delete"]},
{"id":"form","type":"dialogForm","fields":[]}
]
- Step 4: Write failing save validation test
Test rejects unknown fields:
request.setLayoutJson("{\"regions\":[{\"id\":\"table\",\"type\":\"table\",\"fields\":[\"missingField\"]}]}");
assertThrows(ServiceException.class, () -> service.updatePageDesign(7L, 10L, 99L, request));
- Step 5: Run save validation test and verify failure
Run: mvn -pl ruoyi-generator -Dtest=FrontendPageDesignServiceTest test
Expected: FAIL until validation exists.
- Step 6: Implement save validation and controller endpoints
Validate ownership, page type, table name, version, fields, built-in actions, and business action codes.
Controller methods:
@GetMapping("/{projectId}/page-designs")
@PostMapping("/{projectId}/page-designs/init")
@GetMapping("/{projectId}/page-designs/{designId}")
@PutMapping("/{projectId}/page-designs/{designId}")
- Step 7: Run backend service tests
Run: mvn -pl ruoyi-generator -Dtest=FrontendPageDesignServiceTest test
Expected: PASS.
Task 3: Generator Integration
Files:
-
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenProject.java -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectConverter.java -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImpl.java -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java -
Modify test:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.java -
Modify test:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java -
Step 1: Write failing converter/preview test
Verify FrontProjectPreviewServiceImpl loads page designs and GenProject receives them:
when(frontProjectPageDesignMapper.selectDesignsByProjectId(10L)).thenReturn(Arrays.asList(design("book_catalog_page")));
service.getStructure(7L, 10L, "frontend");
ArgumentCaptor<GenProject> captor = ArgumentCaptor.forClass(GenProject.class);
verify(genProjectService).getProjectStructure(captor.capture(), eq("frontend"));
assertEquals(1, captor.getValue().getPageDesigns().size());
- Step 2: Run test and verify failure
Run: mvn -pl ruoyi-generator -Dtest=FrontProjectPreviewServiceImplTest test
Expected: FAIL because page designs are not loaded.
- Step 3: Add page designs to conversion path
Add List<FrontProjectPageDesign> pageDesigns to FrontProject and GenProject, load from mapper in preview service, and copy through converter.
- Step 4: Write failing Velocity context test
Verify VelocityUtils.prepareContextProject(project) exposes lookup:
Map<String, Object> designsByPageCode = (Map<String, Object>) context.get("frontendPageDesignsByCode");
assertTrue(designsByPageCode.containsKey("book_catalog_page"));
- Step 5: Run generator tests and verify failure
Run: mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest test
Expected: FAIL until Velocity context is extended.
- Step 6: Implement Velocity context lookup maps
Expose:
frontendPageDesigns
frontendPageDesignsByCode
frontendPageDesignsByTable
Use table name fallback when page code is not available.
- Step 7: Run generator integration tests
Run: mvn -pl ruoyi-generator -Dtest=FrontProjectPreviewServiceImplTest,GenProjectServiceImplTest test
Expected: PASS.
Task 4: Template Behavior
Files:
-
Modify:
ruoyi-generator/src/main/resources/qing/index.vue.vm -
Modify test:
ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java -
Step 1: Write failing template test
Add a test rendering qing/index.vue.vm with frontendPageDesignsByTable containing a design whose table region lists only productName.
Expected assertions:
assertTrue(rendered.contains("prop=\"productName\""));
assertFalse(rendered.contains("prop=\"productId\" label=\"ID\""));
assertTrue(rendered.contains("handleAdd"));
- Step 2: Run template test and verify failure
Run: mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest test
Expected: FAIL because template ignores page design.
- Step 3: Update template to resolve page design
At the top of index.vue.vm, resolve:
#set($pageDesign = $frontendPageDesignsByTable.get($tableName))
#set($layoutRegions = $pageDesign.layout.regions)
Then use helper lists exposed by backend if parsing inside Velocity is too awkward. Keep fallback loops over $columns.
- Step 4: Run template tests
Run: mvn -pl ruoyi-generator -Dtest=QingTemplateSupportTest test
Expected: PASS.
Task 5: Frontend API and Designer UI
Files:
-
Modify:
easycode-web/src/api/project.js -
Create:
easycode-web/src/components/FrontendPageDesigner.vue -
Modify:
easycode-web/src/views/GenerateView.vue -
Create test:
easycode-web/src/components/frontendPageDesigner.test.mjs -
Modify test:
easycode-web/src/views/generateView.test.mjs -
Step 1: Write failing frontend source tests
Tests:
assert.equal(projectApi.includes('listPageDesigns'), true)
assert.equal(projectApi.includes('initPageDesigns'), true)
assert.equal(projectApi.includes('savePageDesign'), true)
assert.equal(component.includes('defineEmits'), true)
assert.equal(component.includes('update:designs'), true)
assert.equal(generate.includes('<FrontendPageDesigner'), true)
- Step 2: Run frontend tests and verify failure
Run: node easycode-web/src/components/frontendPageDesigner.test.mjs && node easycode-web/src/views/generateView.test.mjs
Expected: FAIL because files/imports do not exist.
- Step 3: Add API functions
Add:
export function listPageDesigns(projectId) { ... }
export function initPageDesigns(projectId) { ... }
export function getPageDesign(projectId, designId) { ... }
export function savePageDesign(projectId, designId, data) { ... }
- Step 4: Build
FrontendPageDesigner.vue
Implement:
-
Empty state with initialize button.
-
Page list.
-
Selected page editor.
-
Table/query/form field toggles.
-
Action JSON textarea.
-
Save button.
-
Step 5: Integrate in
GenerateView.vue
Load designs after project/database load, initialize from blueprint, save selected design, and keep preview available after successful save.
- Step 6: Run frontend tests
Run: node easycode-web/src/components/frontendPageDesigner.test.mjs && node easycode-web/src/views/generateView.test.mjs
Expected: PASS.
Task 6: Verification
Files: All touched files.
- Step 1: Run focused backend tests
Run: mvn -pl ruoyi-generator -Dtest=FrontProjectSchemaScriptTest,FrontendPageDesignServiceTest,FrontProjectPreviewServiceImplTest,GenProjectServiceImplTest,QingTemplateSupportTest test
Expected: PASS.
- Step 2: Run focused frontend tests
Run: node easycode-web/src/components/frontendPageDesigner.test.mjs && node easycode-web/src/views/generateView.test.mjs
Expected: PASS.
- Step 3: Build frontend
Run: npm run build in easycode-web
Expected: PASS.
- Step 4: Check git diff
Run: git status --short and git diff --stat
Expected: only page-designer implementation files plus the already-created spec/plan and ignored .superpowers artifacts.