41 KiB
Business Blocks 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 a configurable business-block page designer where each draggable block owns fixed Vue and backend generation templates, and users only map required tables and fields.
Architecture: Store business block instances inside existing front_project_page_design.layout_json using a new business-blocks-v1 canvas, avoiding a database migration for the first release. Add a backend block registry/validator that reads bundled block definitions, a frontend block designer that renders palette/canvas/schema forms, and a generator layer that turns configured block instances into page components plus fixed backend files.
Tech Stack: Vue 3, Element Plus, Java Spring Boot, MyBatis, Velocity templates, existing RuoYi generator services, Node test files under easycode-web/src/**/*.test.mjs, JUnit tests under ruoyi-generator/src/test/java.
Scope
This plan implements the first usable version:
- Business block palette and drag-to-page canvas.
- Schema-driven right-side configuration form.
- Backend block definition listing and config validation.
- Saving configured block instances in
layout_json. - Generator support for block page components and block-owned backend/frontend files.
- First sample block:
cart, with fixed query/update/delete/clear APIs and a generated Vue component.
This plan deliberately does not implement a general event graph, formula engine, arbitrary workflow designer, runtime interpreter, or visual grid layout engine. Business logic remains inside block templates.
File Structure
Frontend Workbench
- Modify
easycode-web/src/api/project.js- Add
listBusinessBlocks(projectId)API.
- Add
- Create
easycode-web/src/components/business-blocks/BusinessBlockDesigner.vue- Owns block palette, page canvas, block selection, config editing, and JSON payload emission.
- Create
easycode-web/src/components/business-blocks/BlockPalette.vue- Renders available block definitions grouped by category.
- Create
easycode-web/src/components/business-blocks/BlockCanvas.vue- Renders configured block instances and supports drag/drop + reorder.
- Create
easycode-web/src/components/business-blocks/BlockConfigForm.vue- Renders right-side schema form for table/field/boolean/text/select config.
- Create
easycode-web/src/components/business-blocks/blockLayout.js- Contains pure helpers: parse layout JSON, build default block layout, add/remove/reorder/update block instances, validate local required fields.
- Modify
easycode-web/src/components/FrontendPageDesigner.vue- Add a "业务块页面" mode when
layout.canvas === "business-blocks-v1"or selected page type isbusiness_block. - Delegate block editing to
BusinessBlockDesigner.vue.
- Add a "业务块页面" mode when
- Modify
easycode-web/src/views/GenerateView.vue- Load block definitions before page designer render.
- Pass definitions into
FrontendPageDesigner.
- Add tests:
easycode-web/src/components/businessBlockDesigner.test.mjs- Extend
easycode-web/src/components/frontendPageDesigner.test.mjs - Extend
easycode-web/src/views/generateView.test.mjs
Backend Definition and Validation
- Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockDefinition.java - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockConfigField.java - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockTemplateFile.java - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockInstance.java - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/PageBusinessBlockLayout.java - Create
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/BusinessBlockRegistryService.java- Reads
business-blocks/*/block.jsonfrom classpath. - Returns available definitions.
- Validates instance config against saved database tables and fields.
- Reads
- Modify
ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java- Add
GET /front/project/{projectId}/business-blocks.
- Add
- Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontendPageDesignService.java- When
layout_json.canvasisbusiness-blocks-v1, validate block instances throughBusinessBlockRegistryService.
- When
- Add tests:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/BusinessBlockRegistryServiceTest.java- Extend
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.java
Generator
- Create
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GeneratedBusinessBlockFile.java- Holds generated file path, category key, content template name, and block instance metadata.
- Create
ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java- Extracts configured business block instances from page designs.
- Builds Velocity context for each block instance.
- Renders block templates into generated files.
- Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java- Inject
BusinessBlockGenerationService. - Merge block-generated files into
getProjectStructure. - Recognize dynamic category keys such as
business-block:cart_001:frontendComponent. - Render dynamic block files in
generateFileContentandpreviewCode.
- Inject
- Modify
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java- Add page-level block route/component context.
- For
business-blocks-v1pages, route to@/views/pages/{pageCode}/index.vue.
- Add bundled page template:
ruoyi-generator/src/main/resources/business-blocks/page/business-block-page.vue.vm
- Add sample
cartblock package:ruoyi-generator/src/main/resources/business-blocks/cart/block.jsonruoyi-generator/src/main/resources/business-blocks/cart/templates/frontend/CartBlock.vue.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/frontend/cartApi.js.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/backend/CartBlockController.java.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/backend/CartBlockService.java.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/backend/CartBlockServiceImpl.java.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/backend/CartBlockMapper.java.vmruoyi-generator/src/main/resources/business-blocks/cart/templates/backend/CartBlockMapper.xml.vm
- Add tests:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java- Extend
ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java - Extend
ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
Business Block JSON Contract
Each block definition lives in ruoyi-generator/src/main/resources/business-blocks/{blockCode}/block.json.
Example for the first cart block:
{
"code": "cart",
"name": "购物车",
"category": "电商",
"description": "商品展示、数量修改、删除、清空、合计和结算入口。",
"version": 1,
"configs": [
{ "key": "cartTable", "label": "购物车表", "type": "table", "required": true },
{ "key": "productTable", "label": "商品表", "type": "table", "required": true },
{ "key": "cartProductId", "label": "购物车商品ID字段", "type": "field", "table": "cartTable", "required": true },
{ "key": "productId", "label": "商品表主键字段", "type": "field", "table": "productTable", "required": true },
{ "key": "productName", "label": "商品名称字段", "type": "field", "table": "productTable", "required": true },
{ "key": "productImage", "label": "商品图片字段", "type": "field", "table": "productTable", "required": false },
{ "key": "price", "label": "价格字段", "type": "field", "table": "productTable", "required": true },
{ "key": "quantity", "label": "数量字段", "type": "field", "table": "cartTable", "required": true },
{ "key": "userId", "label": "用户ID字段", "type": "field", "table": "cartTable", "required": true },
{ "key": "stock", "label": "库存字段", "type": "field", "table": "productTable", "required": false }
],
"templates": [
{
"key": "frontendComponent",
"template": "templates/frontend/CartBlock.vue.vm",
"outputPath": "src/views/pages/${pageCode}/blocks/${instanceCode}/CartBlock.vue"
},
{
"key": "frontendApi",
"template": "templates/frontend/cartApi.js.vm",
"outputPath": "src/api/business-blocks/${instanceCode}.js"
},
{
"key": "controller",
"template": "templates/backend/CartBlockController.java.vm",
"outputPath": "src/main/java/${packagePath}/controller/block/${InstanceClassName}Controller.java"
},
{
"key": "service",
"template": "templates/backend/CartBlockService.java.vm",
"outputPath": "src/main/java/${packagePath}/service/block/I${InstanceClassName}Service.java"
},
{
"key": "serviceImpl",
"template": "templates/backend/CartBlockServiceImpl.java.vm",
"outputPath": "src/main/java/${packagePath}/service/block/impl/${InstanceClassName}ServiceImpl.java"
},
{
"key": "mapper",
"template": "templates/backend/CartBlockMapper.java.vm",
"outputPath": "src/main/java/${packagePath}/mapper/block/${InstanceClassName}Mapper.java"
},
{
"key": "mapperXml",
"template": "templates/backend/CartBlockMapper.xml.vm",
"outputPath": "src/main/resources/mapper/block/${InstanceClassName}Mapper.xml"
}
]
}
Saved page layout example:
{
"canvas": "business-blocks-v1",
"blocks": [
{
"id": "cart_001",
"blockCode": "cart",
"name": "购物车",
"config": {
"cartTable": "shop_cart",
"productTable": "shop_product",
"cartProductId": "product_id",
"productId": "id",
"productName": "name",
"productImage": "image_url",
"price": "price",
"quantity": "quantity",
"userId": "user_id",
"stock": "stock"
}
}
]
}
Task 1: Frontend Block Layout Helpers
Files:
-
Create:
easycode-web/src/components/business-blocks/blockLayout.js -
Create:
easycode-web/src/components/businessBlockDesigner.test.mjs -
Step 1: Write helper tests
Add tests that assert these behaviors:
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import {
addBlockInstance,
businessBlockCanvas,
parseBusinessBlockLayout,
reorderBlockInstance,
updateBlockConfig,
validateRequiredConfig
} from './business-blocks/blockLayout.js'
describe('business block layout helpers', () => {
const cartDefinition = {
code: 'cart',
name: '购物车',
configs: [
{ key: 'cartTable', label: '购物车表', type: 'table', required: true },
{ key: 'productName', label: '商品名称字段', type: 'field', table: 'productTable', required: true }
]
}
it('creates a business block canvas from empty JSON', () => {
const layout = parseBusinessBlockLayout('')
assert.equal(layout.canvas, businessBlockCanvas)
assert.deepEqual(layout.blocks, [])
})
it('adds a block instance with stable generated id and empty config', () => {
const layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
assert.equal(layout.blocks[0].id, 'cart_001')
assert.equal(layout.blocks[0].blockCode, 'cart')
assert.deepEqual(layout.blocks[0].config, {})
})
it('updates block config without replacing other blocks', () => {
let layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
layout = updateBlockConfig(layout, 'cart_001', { cartTable: 'shop_cart' })
assert.equal(layout.blocks[0].config.cartTable, 'shop_cart')
})
it('reorders block instances', () => {
let layout = addBlockInstance(parseBusinessBlockLayout(''), cartDefinition, () => 'cart_001')
layout = addBlockInstance(layout, { ...cartDefinition, code: 'notice', name: '公告' }, () => 'notice_001')
layout = reorderBlockInstance(layout, 1, 0)
assert.equal(layout.blocks[0].id, 'notice_001')
})
it('reports missing required config labels', () => {
const missing = validateRequiredConfig(cartDefinition, { cartTable: 'shop_cart' })
assert.deepEqual(missing, ['商品名称字段'])
})
})
- Step 2: Run frontend helper test and verify failure
Run:
cd easycode-web
npm test -- businessBlockDesigner.test.mjs
Expected: fail because blockLayout.js does not exist.
- Step 3: Implement
blockLayout.js
Implement these exported functions:
export const businessBlockCanvas = 'business-blocks-v1'
export function parseBusinessBlockLayout(value) {
if (!value) {
return { canvas: businessBlockCanvas, blocks: [] }
}
try {
const parsed = typeof value === 'string' ? JSON.parse(value) : value
return {
...parsed,
canvas: businessBlockCanvas,
blocks: Array.isArray(parsed.blocks) ? parsed.blocks : []
}
} catch (error) {
return { canvas: businessBlockCanvas, blocks: [] }
}
}
export function addBlockInstance(layout, definition, idFactory = defaultIdFactory) {
const next = cloneLayout(layout)
const id = idFactory(definition.code)
next.blocks.push({
id,
blockCode: definition.code,
name: definition.name || definition.code,
config: {}
})
return next
}
export function updateBlockConfig(layout, blockId, config) {
const next = cloneLayout(layout)
next.blocks = next.blocks.map((block) => block.id === blockId ? { ...block, config: { ...config } } : block)
return next
}
export function reorderBlockInstance(layout, fromIndex, toIndex) {
const next = cloneLayout(layout)
if (fromIndex < 0 || toIndex < 0 || fromIndex >= next.blocks.length || toIndex >= next.blocks.length) {
return next
}
const [moved] = next.blocks.splice(fromIndex, 1)
next.blocks.splice(toIndex, 0, moved)
return next
}
export function validateRequiredConfig(definition, config) {
return (definition.configs || [])
.filter((item) => item.required && !config?.[item.key])
.map((item) => item.label || item.key)
}
function cloneLayout(layout) {
const parsed = parseBusinessBlockLayout(layout)
return JSON.parse(JSON.stringify(parsed))
}
function defaultIdFactory(code) {
return `${code}_${Date.now()}`
}
- Step 4: Run frontend helper test and verify pass
Run:
cd easycode-web
npm test -- businessBlockDesigner.test.mjs
Expected: PASS.
- Step 5: Commit
git add easycode-web/src/components/business-blocks/blockLayout.js easycode-web/src/components/businessBlockDesigner.test.mjs
git commit -m "Add business block layout helpers"
Task 2: Backend Business Block DTOs and Registry
Files:
-
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockDefinition.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockConfigField.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockTemplateFile.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/BusinessBlockInstance.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block/PageBusinessBlockLayout.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/BusinessBlockRegistryService.java -
Create:
ruoyi-generator/src/main/resources/business-blocks/cart/block.json -
Create:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/BusinessBlockRegistryServiceTest.java -
Step 1: Write registry tests
Add JUnit tests for:
listDefinitions()returns the bundledcartblock.findDefinition("cart")returns config fields.parseLayout()readsbusiness-blocks-v1and returns instances.validateInstanceConfig()rejects missingcartTable.validateInstanceConfig()rejects an unknown table.validateInstanceConfig()rejects a field that does not exist on the selected table.
Use in-memory GenTable and GenTableColumn objects in the test instead of a database.
- Step 2: Run registry test and verify failure
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockRegistryServiceTest test
Expected: fail because DTOs and service do not exist.
- Step 3: Add DTOs
DTO field contract:
public class BusinessBlockDefinition {
private String code;
private String name;
private String category;
private String description;
private Integer version;
private List<BusinessBlockConfigField> configs;
private List<BusinessBlockTemplateFile> templates;
}
public class BusinessBlockConfigField {
private String key;
private String label;
private String type;
private String table;
private Boolean required;
private List<String> options;
}
public class BusinessBlockTemplateFile {
private String key;
private String template;
private String outputPath;
}
public class BusinessBlockInstance {
private String id;
private String blockCode;
private String name;
private Map<String, String> config;
}
public class PageBusinessBlockLayout {
private String canvas;
private List<BusinessBlockInstance> blocks;
}
Each DTO gets ordinary getters and setters.
- Step 4: Add
business-blocks/cart/block.json
Use the JSON contract from this plan. Keep config keys exactly:
cartTable, productTable, cartProductId, productId, productName, productImage, price, quantity, userId, stock.
- Step 5: Implement registry service
Required methods:
public List<BusinessBlockDefinition> listDefinitions()
public BusinessBlockDefinition findDefinition(String code)
public PageBusinessBlockLayout parseLayout(String layoutJson)
public void validateLayout(String layoutJson, List<GenTable> tables)
public void validateInstanceConfig(BusinessBlockDefinition definition, BusinessBlockInstance instance, List<GenTable> tables)
Validation behavior:
-
Unknown
blockCode: throwServiceException("未知业务块:" + blockCode). -
Missing required config: throw
ServiceException("业务块配置缺失:" + fieldLabel). -
Unknown table config: throw
ServiceException("业务块表不存在:" + tableName). -
Unknown field config: throw
ServiceException("业务块字段不存在:" + tableName + "." + fieldName). -
Non-business-block layout: no-op.
-
Step 6: Run registry test and verify pass
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockRegistryServiceTest test
Expected: PASS.
- Step 7: Commit
git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/front/dto/block ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/BusinessBlockRegistryService.java ruoyi-generator/src/main/resources/business-blocks/cart/block.json ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/BusinessBlockRegistryServiceTest.java
git commit -m "Add business block registry"
Task 3: Business Block API
Files:
-
Modify:
ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java -
Modify:
easycode-web/src/api/project.js -
Extend:
ruoyi-admin/src/test/java/com/ruoyi/web/controller/front/FrontProjectControllerTest.javaif present; otherwise extend existing front controller test style inruoyi-admin/src/test/java/com/ruoyi/web/controller/front/FrontProjectControllerRunPreviewTest.java. -
Extend:
easycode-web/src/components/frontendPageDesigner.test.mjs -
Step 1: Write API tests
Backend assertion:
- Controller has
@GetMapping("/{projectId}/business-blocks"). - Method calls
businessBlockRegistryService.listDefinitions().
Frontend source assertion:
assert.equal(source.includes('export function listBusinessBlocks'), true)
assert.equal(source.includes('/business-blocks'), true)
- Step 2: Run tests and verify failure
Run:
mvn -pl ruoyi-admin -Dtest=FrontProjectControllerRunPreviewTest test
cd easycode-web
npm test -- frontendPageDesigner.test.mjs
Expected: fail before API exists.
- Step 3: Add controller endpoint
Inject:
@Autowired
private BusinessBlockRegistryService businessBlockRegistryService;
Add:
@GetMapping("/{projectId}/business-blocks")
public AjaxResult businessBlocks(@PathVariable Long projectId)
{
return AjaxResult.success(businessBlockRegistryService.listDefinitions());
}
The projectId stays in the path for consistency and future project-scoped filtering, even though the first implementation returns bundled blocks.
- Step 4: Add frontend API
In easycode-web/src/api/project.js:
export function listBusinessBlocks(projectId) {
return request({
url: `/front/project/${projectId}/business-blocks`,
method: 'get'
})
}
- Step 5: Run tests and verify pass
Run:
mvn -pl ruoyi-admin -Dtest=FrontProjectControllerRunPreviewTest test
cd easycode-web
npm test -- frontendPageDesigner.test.mjs
Expected: PASS.
- Step 6: Commit
git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/front/FrontProjectController.java easycode-web/src/api/project.js ruoyi-admin/src/test/java/com/ruoyi/web/controller/front easycode-web/src/components/frontendPageDesigner.test.mjs
git commit -m "Expose business block definitions"
Task 4: Business Block Designer UI
Files:
-
Create:
easycode-web/src/components/business-blocks/BusinessBlockDesigner.vue -
Create:
easycode-web/src/components/business-blocks/BlockPalette.vue -
Create:
easycode-web/src/components/business-blocks/BlockCanvas.vue -
Create:
easycode-web/src/components/business-blocks/BlockConfigForm.vue -
Extend:
easycode-web/src/components/businessBlockDesigner.test.mjs -
Step 1: Write UI source tests
Assert that:
-
BusinessBlockDesigner.vueimportsBlockPalette,BlockCanvas,BlockConfigForm. -
It emits
update:layout-json. -
It passes selected block config to
BlockConfigForm. -
BlockConfigForm.vuerendersel-selectfortableandfield. -
BlockCanvas.vuehasdraggable="true"and reorder handlers. -
Step 2: Run UI test and verify failure
Run:
cd easycode-web
npm test -- businessBlockDesigner.test.mjs
Expected: fail because components do not exist.
- Step 3: Implement
BlockPalette.vue
Behavior:
- Groups definitions by
category. - Emits
add-blockwith the selected definition. - Shows block name and description.
Required props/emits:
const props = defineProps({
definitions: { type: Array, default: () => [] }
})
const emit = defineEmits(['add-block'])
- Step 4: Implement
BlockCanvas.vue
Behavior:
- Renders block cards in current order.
- Emits
select-blockwhen clicked. - Emits
reorder-blockwith{ fromIndex, toIndex }. - Emits
remove-blockwith block id.
Required stable CSS layout:
-
Single-column stacked layout for first release.
-
Cards use 8px or smaller radius.
-
Empty state says
拖入左侧业务块开始配置页面. -
Step 5: Implement
BlockConfigForm.vue
Behavior:
- Accepts
definition,modelValue,tables. - For
type: table, renders alltables. - For
type: field, resolves the configured table fromfield.table. - Emits a full updated config object through
update:modelValue. - Marks required labels with Element Plus required indicator.
Field resolution rule:
const tableName = config[field.table]
const table = tables.find((item) => item.tableName === tableName)
const fields = table?.columns || table?.table?.columns || []
- Step 6: Implement
BusinessBlockDesigner.vue
Behavior:
- Parses incoming
layoutJsonwithparseBusinessBlockLayout. - Adds blocks from palette.
- Selects first block after add.
- Updates right-side form.
- Emits formatted JSON whenever layout changes.
Required props/emits:
const props = defineProps({
layoutJson: { type: String, default: '' },
blockDefinitions: { type: Array, default: () => [] },
tables: { type: Array, default: () => [] }
})
const emit = defineEmits(['update:layout-json'])
- Step 7: Run UI test and verify pass
Run:
cd easycode-web
npm test -- businessBlockDesigner.test.mjs
Expected: PASS.
- Step 8: Commit
git add easycode-web/src/components/business-blocks easycode-web/src/components/businessBlockDesigner.test.mjs
git commit -m "Add business block designer UI"
Task 5: Integrate Business Blocks into Existing Page Designer
Files:
-
Modify:
easycode-web/src/components/FrontendPageDesigner.vue -
Modify:
easycode-web/src/views/GenerateView.vue -
Modify:
easycode-web/src/api/project.js -
Extend:
easycode-web/src/components/frontendPageDesigner.test.mjs -
Extend:
easycode-web/src/views/generateView.test.mjs -
Step 1: Write integration tests
Assert:
-
GenerateView.vueimportslistBusinessBlocks. -
GenerateView.vuehasconst businessBlockDefinitions = ref([]). -
Project load calls
listBusinessBlocks(id). -
FrontendPageDesignerreceives:block-definitions="businessBlockDefinitions". -
FrontendPageDesigner.vueimportsBusinessBlockDesigner. -
FrontendPageDesigner.vuecontainsbusiness-blocks-v1. -
Saving a block page preserves
layoutJson. -
Step 2: Run integration tests and verify failure
Run:
cd easycode-web
npm test -- frontendPageDesigner.test.mjs generateView.test.mjs
Expected: fail before integration exists.
- Step 3: Modify
GenerateView.vue
Add API import:
listBusinessBlocks
Add state:
const businessBlockDefinitions = ref([])
During project load, fetch definitions in the existing parallel load group:
const [projectResult, databaseResult, pageDesignResult, adminPageDesignResult, blockDefinitionResult] = await Promise.all([
getProject(id),
getDatabase(id),
listPageDesigns(id),
listPageDesigns(id, 'admin'),
listBusinessBlocks(id)
])
businessBlockDefinitions.value = Array.isArray(blockDefinitionResult) ? blockDefinitionResult : []
Pass into frontend page designer:
:block-definitions="businessBlockDefinitions"
- Step 4: Modify
FrontendPageDesigner.vue
Add prop:
blockDefinitions: { type: Array, default: () => [] }
Add computed:
const isBusinessBlockLayout = computed(() => layout.value?.canvas === 'business-blocks-v1' || draft.value?.pageType === 'business_block')
When business block mode is active, render BusinessBlockDesigner in the main canvas area and bind:
<BusinessBlockDesigner
:layout-json="layoutJson"
:block-definitions="blockDefinitions"
:tables="tables"
@update:layout-json="applyBusinessBlockLayoutJson"
/>
Add method:
function applyBusinessBlockLayoutJson(value) {
layoutJson.value = value
applyLayoutJson()
}
- Step 5: Run integration tests and verify pass
Run:
cd easycode-web
npm test -- frontendPageDesigner.test.mjs generateView.test.mjs
Expected: PASS.
- Step 6: Commit
git add easycode-web/src/components/FrontendPageDesigner.vue easycode-web/src/views/GenerateView.vue easycode-web/src/api/project.js easycode-web/src/components/frontendPageDesigner.test.mjs easycode-web/src/views/generateView.test.mjs
git commit -m "Integrate business blocks into page designer"
Task 6: Validate Business Block Layout on Save
Files:
-
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontendPageDesignService.java -
Extend:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.java -
Step 1: Write save validation tests
Add tests:
-
Saving a page design with
canvas: business-blocks-v1and a validcartconfig succeeds. -
Missing required
cartTablethrowsServiceException. -
Unknown
cartTablethrowsServiceException. -
Unknown
productNamefield throwsServiceException. -
Existing normal list layout still validates as before.
-
Step 2: Run validation tests and verify failure
Run:
mvn -pl ruoyi-generator -Dtest=FrontendPageDesignServiceTest test
Expected: fail because save validation does not call business block registry.
- Step 3: Inject registry service
Add:
@Autowired
private BusinessBlockRegistryService businessBlockRegistryService;
- Step 4: Call validation during layout normalization
In normalizeLayout, after parsing layout JSON and before returning writeJson(layout), add:
if ("business-blocks-v1".equals(String.valueOf(layout.get("canvas"))))
{
businessBlockRegistryService.validateLayout(writeJson(layout), tables);
return writeJson(layout);
}
Use the existing available TableBundle/project table source in FrontendPageDesignService; if the method currently only has one bound table, extend it to load all project tables for business-block mode because a block can map multiple tables.
- Step 5: Run validation tests and verify pass
Run:
mvn -pl ruoyi-generator -Dtest=FrontendPageDesignServiceTest test
Expected: PASS.
- Step 6: Commit
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/front/FrontendPageDesignService.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/front/FrontendPageDesignServiceTest.java
git commit -m "Validate business block page layouts"
Task 7: Business Block Generation Service
Files:
-
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GeneratedBusinessBlockFile.java -
Create:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java -
Add templates under
ruoyi-generator/src/main/resources/business-blocks/cart/templates/ -
Create:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java -
Step 1: Write generation tests
Test these exact outputs for a configured cart_001 block:
-
Generated files include:
src/views/pages/cart_page/blocks/cart_001/CartBlock.vuesrc/api/business-blocks/cart_001.jssrc/main/java/com/ruoyi/generated/controller/block/Cart001Controller.javasrc/main/java/com/ruoyi/generated/service/block/ICart001Service.javasrc/main/java/com/ruoyi/generated/service/block/impl/Cart001ServiceImpl.javasrc/main/java/com/ruoyi/generated/mapper/block/Cart001Mapper.javasrc/main/resources/mapper/block/Cart001Mapper.xml
-
Mapper XML contains configured table names
shop_cartandshop_product. -
Vue component imports API from
@/api/business-blocks/cart_001. -
Service impl contains fixed methods:
list,updateQuantity,remove,clear. -
Step 2: Run generation tests and verify failure
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockGenerationServiceTest test
Expected: fail before generation service exists.
- Step 3: Add
GeneratedBusinessBlockFile
Fields:
private String category;
private String outputPath;
private String content;
private String blockCode;
private String instanceCode;
- Step 4: Implement
BusinessBlockGenerationService
Required methods:
public List<GeneratedBusinessBlockFile> buildFiles(GenProject project)
public GeneratedBusinessBlockFile findFile(GenProject project, String category)
Category format:
business-block:{instanceCode}:{templateKey}
Velocity context keys:
-
packageName -
packagePath -
projectName -
pageCode -
blockCode -
instanceCode -
InstanceClassName -
config -
cartTable -
productTable -
all individual configured keys as top-level values
-
Step 5: Add cart templates
The generated backend must be fixed and field-mapped:
list()joins cart table to product table by configured product id fields.updateQuantity(Long id, Integer quantity)updates quantity and rejects quantity below 1.remove(Long id)deletes one cart row.clear(Long userId)deletes cart rows for current user.
The generated frontend must call:
-
listCartItems -
updateCartQuantity -
removeCartItem -
clearCartItems -
Step 6: Run generation tests and verify pass
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockGenerationServiceTest test
Expected: PASS.
- Step 7: Commit
git add ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GeneratedBusinessBlockFile.java ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java ruoyi-generator/src/main/resources/business-blocks/cart/templates ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java
git commit -m "Generate cart business block files"
Task 8: Merge Block Files into Project Preview and Download
Files:
-
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java -
Extend:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java -
Step 1: Write project service tests
Add tests:
-
getProjectStructure(project, "frontend")includes block Vue/API generated files. -
getProjectStructure(project, "backend")includes block Controller/Service/Mapper generated files. -
previewCode(project, -1L, "business-block:cart_001:frontendComponent", "frontend")returns rendered Vue content. -
downloadStructure(project, "backend")includes mapper XML block file. -
Step 2: Run project service tests and verify failure
Run:
mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest test
Expected: fail because dynamic block files are not merged.
- Step 3: Inject generation service
Add:
@Autowired
private BusinessBlockGenerationService businessBlockGenerationService;
- Step 4: Merge dynamic files in structure
After existing template structure is built, append block-generated files for the requested type:
-
frontendgets paths starting withsrc/views/orsrc/api/. -
backendgets paths starting withsrc/main/java/orsrc/main/resources/mapper/. -
sqlgets no block files in MVP. -
Step 5: Render dynamic files
In generateFileContent, before resolving static template files, detect:
if (StringUtils.defaultString(category).startsWith("business-block:")) {
GeneratedBusinessBlockFile file = businessBlockGenerationService.findFile(project, category);
return file == null ? null : file.getContent();
}
- Step 6: Run project service tests and verify pass
Run:
mvn -pl ruoyi-generator -Dtest=GenProjectServiceImplTest test
Expected: PASS.
- Step 7: Commit
git add ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenProjectServiceImpl.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/GenProjectServiceImplTest.java
git commit -m "Include business block files in generation"
Task 9: Generate Page Component for Business Block Pages
Files:
-
Create:
ruoyi-generator/src/main/resources/business-blocks/page/business-block-page.vue.vm -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java -
Modify:
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java -
Extend:
ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java -
Extend:
ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java -
Step 1: Write page generation tests
Assert:
-
A business-block page with
pageCode = "cart_page"generatessrc/views/pages/cart_page/index.vue. -
The page imports
./blocks/cart_001/CartBlock.vue. -
The page renders
<CartBlock />. -
VelocityUtils.prepareContextProject(project)includes a route item whose component path is@/views/pages/cart_page/index.vue. -
Step 2: Run page generation tests and verify failure
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockGenerationServiceTest,QingTemplateSupportTest test
Expected: fail before page component and route context exist.
- Step 3: Add page template
Template shape:
<template>
<main class="business-block-page">
#foreach($block in $pageBlocks)
<${block.componentName} />
#end
</main>
</template>
<script setup>
#foreach($block in $pageBlocks)
import ${block.componentName} from './blocks/${block.instanceCode}/${block.componentName}.vue'
#end
</script>
- Step 4: Add page file generation
BusinessBlockGenerationService.buildFiles(project) must add one page component file per page design whose layout canvas is business-blocks-v1.
Category:
business-block-page:{pageCode}
Output path:
src/views/pages/{pageCode}/index.vue
- Step 5: Update route context
In VelocityUtils.prepareContextProject, when a frontend page has a saved page design with business-blocks-v1, route to:
@/views/pages/{pageCode}/index.vue
Keep old table route behavior for non-business-block pages.
- Step 6: Run tests and verify pass
Run:
mvn -pl ruoyi-generator -Dtest=BusinessBlockGenerationServiceTest,QingTemplateSupportTest test
Expected: PASS.
- Step 7: Commit
git add ruoyi-generator/src/main/resources/business-blocks/page/business-block-page.vue.vm ruoyi-generator/src/main/java/com/ruoyi/generator/service/BusinessBlockGenerationService.java ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java ruoyi-generator/src/test/java/com/ruoyi/generator/service/BusinessBlockGenerationServiceTest.java ruoyi-generator/src/test/java/com/ruoyi/generator/util/QingTemplateSupportTest.java
git commit -m "Generate business block page components"
Task 10: End-to-End Verification
Files:
-
No new files unless tests reveal gaps.
-
Step 1: Run frontend tests
Run:
cd easycode-web
npm test
Expected: all tests pass.
- Step 2: Run backend focused tests
Run:
mvn -pl ruoyi-generator,ruoyi-admin -Dtest=BusinessBlockRegistryServiceTest,BusinessBlockGenerationServiceTest,FrontendPageDesignServiceTest,GenProjectServiceImplTest,QingTemplateSupportTest,FrontProjectControllerRunPreviewTest test
Expected: all selected tests pass.
- Step 3: Run project build
Run:
mvn -pl ruoyi-generator,ruoyi-admin test
cd easycode-web
npm run build
Expected: Maven tests pass and frontend build succeeds.
- Step 4: Manual smoke test
In EasyCode workbench:
- Open an existing front project.
- Ensure database design has
shop_cartandshop_product. - Initialize page designs.
- Change one frontend page to business block mode.
- Drag
购物车block into the canvas. - Fill all required table and field mappings.
- Save page design.
- Generate preview.
- Confirm preview structure includes:
src/views/pages/{pageCode}/index.vuesrc/views/pages/{pageCode}/blocks/{instanceCode}/CartBlock.vuesrc/api/business-blocks/{instanceCode}.js- generated backend block Controller/Service/Mapper files.
- Step 5: Commit final verification fixes
git status --short
git add <changed-files>
git commit -m "Verify business block generation"
Only commit if verification requires additional fixes.
Rollout Order
- Merge Tasks 1-3 to expose block definitions safely.
- Merge Tasks 4-6 to let users configure and save block pages.
- Merge Tasks 7-9 to generate code.
- Run Task 10 before considering the feature complete.
Follow-Up Blocks After Cart
After cart proves the protocol, add these blocks one at a time:
notice-list: one table, title/content/time fields, frontend-only list API.carousel: one table, image/title/link fields, frontend-only list API.card-list: one table, image/title/subtitle/price fields, detail route config.master-detail: parent table, child table, parent key, child foreign key.checkout: cart table, order table, order item table, product table, fixed submit transaction.
Each new block must add:
business-blocks/{code}/block.json- block templates
- registry validation test
- generation test
- one frontend designer fixture test
Self-Review
- Spec coverage: The plan covers draggable business blocks, schema-driven right panel, fixed backend/Vue templates, table/field mapping, validation, saving, and generation.
- Placeholder scan: No task depends on arbitrary workflow design, custom SQL entry, or undefined future engines.
- Type consistency: The same terms are used throughout:
BusinessBlockDefinition,BusinessBlockInstance,PageBusinessBlockLayout,business-blocks-v1,blockCode,instanceCode, andconfig.