Files
yidaima/RuoYi-Vue/docs/superpowers/plans/2026-05-27-frontend-page-designer.md

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 to GenProject.
  • 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, and sql/db.sql: create/upgrade front_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.java
    • ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.java
    • ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontProjectPreviewServiceImplTest.java
    • ruoyi-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.