feat: expand EasyCode software factory workflows
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
# Architecture Diagram DSL Editor 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:** Replace the current generic architecture graph in the EasyCode diagram center with a DSL-driven layered architecture diagram editor.
|
||||
|
||||
**Architecture:** Add a pure frontend utility module that generates default DSL, parses DSL, computes layered SVG layout, and builds export SVG. Update `DiagramCenterView.vue` to show a dedicated architecture editor for `activeDiagram === 'architecture'`, reusing the existing project/database load flow and `front_project_diagram` save endpoint.
|
||||
|
||||
**Tech Stack:** Vue 3 Composition API, Vite, Element Plus, browser SVG/Canvas export, Node-based frontend utility tests.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Create `easycode-web/src/utils/architectureDiagram.js`: pure functions for default DSL generation, DSL parsing, layout, and SVG export.
|
||||
- Create `easycode-web/src/utils/architectureDiagram.test.mjs`: Node tests for generation, parsing, layout stats, and SVG export.
|
||||
- Modify `easycode-web/src/views/DiagramCenterView.vue`: render the architecture editor, bind DSL state, save/restore `architecture` diagrams, and export SVG/PNG.
|
||||
|
||||
## Task 1: Architecture Diagram Utility
|
||||
|
||||
**Files:**
|
||||
- Create: `easycode-web/src/utils/architectureDiagram.js`
|
||||
- Create: `easycode-web/src/utils/architectureDiagram.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write tests for default DSL and parsing**
|
||||
|
||||
Create `easycode-web/src/utils/architectureDiagram.test.mjs` with tests that import:
|
||||
|
||||
```js
|
||||
import assert from 'node:assert/strict'
|
||||
import {
|
||||
buildArchitectureDiagram,
|
||||
buildArchitectureExportSvg,
|
||||
buildDefaultArchitectureDsl,
|
||||
parseArchitectureDsl
|
||||
} from './architectureDiagram.js'
|
||||
```
|
||||
|
||||
Include a sample project:
|
||||
|
||||
```js
|
||||
const project = {
|
||||
projectName: '选课系统',
|
||||
frontFramework: 'Vue 3',
|
||||
backFramework: 'Spring Boot',
|
||||
appBlueprint: JSON.stringify({
|
||||
roles: [
|
||||
{ name: '学生', permissions: ['选课', '查成绩'] },
|
||||
{ name: '教师', permissions: ['课程管理', '成绩录入'] }
|
||||
],
|
||||
frontendMenus: [{ name: '选课大厅' }],
|
||||
adminMenus: [{ name: '课程管理' }, { name: '数据统计' }]
|
||||
})
|
||||
}
|
||||
|
||||
const database = {
|
||||
tables: [
|
||||
{ tableName: 'course', tableComment: '课程表' },
|
||||
{ tableName: 'student_course', tableComment: '选课记录' }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Assert that:
|
||||
|
||||
```js
|
||||
const dsl = buildDefaultArchitectureDsl(project, database)
|
||||
assert.match(dsl, /用户层/)
|
||||
assert.match(dsl, /表现层 - Vue 3/)
|
||||
assert.match(dsl, /数据层/)
|
||||
assert.match(dsl, /2 张业务表/)
|
||||
|
||||
const parsed = parseArchitectureDsl(dsl)
|
||||
assert.equal(parsed.layers[0].title, '用户层')
|
||||
assert.ok(parsed.layers.length >= 6)
|
||||
assert.ok(parsed.concerns.length >= 3)
|
||||
assert.ok(parsed.stats.componentCount >= 12)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write tests for DSL structure, layout, and SVG export**
|
||||
|
||||
Use this DSL:
|
||||
|
||||
```js
|
||||
const customDsl = `用户层
|
||||
学生
|
||||
选课 / 查成绩
|
||||
表现层 - Vue 3 前端
|
||||
Vue 3
|
||||
Composition API
|
||||
Vue Router 路由
|
||||
接口层 - Spring MVC Controller
|
||||
统一接口规范
|
||||
控制器分层
|
||||
横切关注点
|
||||
JWT 认证
|
||||
登录签发 Token
|
||||
全局异常处理
|
||||
统一异常响应`
|
||||
```
|
||||
|
||||
Assert:
|
||||
|
||||
```js
|
||||
const parsed = parseArchitectureDsl(customDsl)
|
||||
assert.equal(parsed.layers.length, 3)
|
||||
assert.equal(parsed.layers[1].subtitle, 'Vue 3 前端')
|
||||
assert.equal(parsed.layers[1].components[0].details.length, 2)
|
||||
assert.equal(parsed.concerns.length, 2)
|
||||
|
||||
const diagram = buildArchitectureDiagram(parsed)
|
||||
assert.equal(diagram.stats.layerCount, 3)
|
||||
assert.equal(diagram.stats.concernCount, 2)
|
||||
assert.ok(diagram.canvas.width >= 900)
|
||||
assert.ok(diagram.canvas.height >= 420)
|
||||
assert.equal(diagram.layers[0].components[0].title, '学生')
|
||||
|
||||
const svg = buildArchitectureExportSvg(diagram)
|
||||
assert.match(svg, /系统架构图/)
|
||||
assert.match(svg, /JWT 认证/)
|
||||
assert.match(svg, /Vue 3/)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run tests and confirm they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd easycode-web
|
||||
node src/utils/architectureDiagram.test.mjs
|
||||
```
|
||||
|
||||
Expected: module not found or missing exported functions.
|
||||
|
||||
- [ ] **Step 4: Implement `architectureDiagram.js`**
|
||||
|
||||
Implement exports:
|
||||
|
||||
```js
|
||||
export function buildDefaultArchitectureDsl(project = {}, database = {}) {}
|
||||
export function parseArchitectureDsl(dsl = '') {}
|
||||
export function buildArchitectureDiagram(parsed = {}, options = {}) {}
|
||||
export function buildArchitectureExportSvg(diagram = {}, options = {}) {}
|
||||
```
|
||||
|
||||
The implementation must:
|
||||
|
||||
- Parse `project.appBlueprint` safely when it is JSON text.
|
||||
- Produce fallback roles, services, tables, and runtime items when project data is sparse.
|
||||
- Split layer title/subtitle on ` - ` or `—`.
|
||||
- Treat `横切关注点` as `concerns`.
|
||||
- Return stable `stats` with `layerCount`, `componentCount`, and `concernCount`.
|
||||
- Escape XML text in export SVG.
|
||||
|
||||
- [ ] **Step 5: Run utility tests and fix failures**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd easycode-web
|
||||
node src/utils/architectureDiagram.test.mjs
|
||||
```
|
||||
|
||||
Expected: all assertions pass with no output beyond the test runner's normal completion.
|
||||
|
||||
## Task 2: Diagram Center Architecture Editor
|
||||
|
||||
**Files:**
|
||||
- Modify: `easycode-web/src/views/DiagramCenterView.vue`
|
||||
|
||||
- [ ] **Step 1: Add architecture state and imports**
|
||||
|
||||
Import these functions:
|
||||
|
||||
```js
|
||||
import {
|
||||
buildArchitectureDiagram,
|
||||
buildArchitectureExportSvg,
|
||||
buildDefaultArchitectureDsl,
|
||||
parseArchitectureDsl
|
||||
} from '@/utils/architectureDiagram'
|
||||
```
|
||||
|
||||
Add refs:
|
||||
|
||||
```js
|
||||
const architectureDsl = ref('')
|
||||
const architectureBlackWhite = ref(false)
|
||||
const architectureSvgRef = ref(null)
|
||||
```
|
||||
|
||||
Add computed values:
|
||||
|
||||
```js
|
||||
const parsedArchitecture = computed(() => parseArchitectureDsl(architectureDsl.value))
|
||||
const architectureDiagram = computed(() => buildArchitectureDiagram(parsedArchitecture.value, {
|
||||
blackWhite: architectureBlackWhite.value,
|
||||
title: `${project.projectName || '项目'}系统架构图`
|
||||
}))
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Replace architecture branch with dedicated editor**
|
||||
|
||||
In the template, add a branch before the existing custom graph branch:
|
||||
|
||||
```vue
|
||||
<section v-else-if="activeDiagram === 'architecture'" class="architecture-editor">
|
||||
<!-- editor toolbar, DSL textarea, SVG preview, export buttons -->
|
||||
</section>
|
||||
```
|
||||
|
||||
The editor must include:
|
||||
|
||||
- left DSL panel with `el-input type="textarea"` bound to `architectureDsl`;
|
||||
- buttons for clear, load example, refresh preview, save;
|
||||
- right SVG preview bound to `architectureDiagram`;
|
||||
- top stats for `layerCount` and `componentCount`;
|
||||
- checkbox bound to `architectureBlackWhite`;
|
||||
- export buttons using SVG and PNG handlers.
|
||||
|
||||
- [ ] **Step 3: Add save/restore behavior**
|
||||
|
||||
In `applySavedDrafts()`:
|
||||
|
||||
- Find saved `diagramType === 'architecture'`.
|
||||
- Parse `diagramJson`.
|
||||
- If it contains `dsl`, set `architectureDsl`.
|
||||
- If no saved DSL exists, set `architectureDsl = buildDefaultArchitectureDsl(project, database.value)`.
|
||||
- Restore `blackWhite` from saved payload when present.
|
||||
|
||||
In `loadSavedDiagram(diagram)`:
|
||||
|
||||
- When `diagram.diagramType === 'architecture'`, set `activeDiagram = 'architecture'`, apply saved DSL and display mode.
|
||||
|
||||
In `currentDiagramPayload()`:
|
||||
|
||||
- For architecture, save:
|
||||
|
||||
```js
|
||||
{
|
||||
diagramType: 'architecture',
|
||||
title: '系统架构图',
|
||||
description: 'DSL 驱动的分层系统架构图',
|
||||
diagramJson: JSON.stringify({
|
||||
dsl: architectureDsl.value,
|
||||
graph: architectureDiagram.value,
|
||||
blackWhite: architectureBlackWhite.value
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add architecture export handlers**
|
||||
|
||||
Add:
|
||||
|
||||
```js
|
||||
function architectureSvgText() {
|
||||
return buildArchitectureExportSvg(architectureDiagram.value, {
|
||||
blackWhite: architectureBlackWhite.value
|
||||
})
|
||||
}
|
||||
|
||||
function downloadArchitectureSvg() {}
|
||||
async function downloadArchitecturePng() {}
|
||||
```
|
||||
|
||||
PNG export should follow existing `downloadCurrentPng()` logic: create a Blob URL, load it into `Image`, draw on Canvas, save with `saveBlob`.
|
||||
|
||||
- [ ] **Step 5: Style the editor**
|
||||
|
||||
Add scoped SCSS for:
|
||||
|
||||
- `.architecture-editor`
|
||||
- `.architecture-workbench`
|
||||
- `.architecture-dsl-panel`
|
||||
- `.architecture-preview-panel`
|
||||
- `.architecture-toolbar`
|
||||
- `.architecture-preview-scroll`
|
||||
- `.architecture-svg`
|
||||
|
||||
The layout should be split-panel on desktop and single-column below 980px. Keep card radius at 8px or less and use restrained, workbench-style colors.
|
||||
|
||||
- [ ] **Step 6: Run frontend build**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd easycode-web
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: Vite production build completes successfully.
|
||||
|
||||
## Task 3: Manual UI Verification
|
||||
|
||||
**Files:**
|
||||
- Existing frontend files only.
|
||||
|
||||
- [ ] **Step 1: Start local dev server**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd easycode-web
|
||||
npm run dev -- --host 0.0.0.0
|
||||
```
|
||||
|
||||
Expected: Vite serves the app and prints a local URL.
|
||||
|
||||
- [ ] **Step 2: Open diagram center**
|
||||
|
||||
Use an authenticated project URL:
|
||||
|
||||
```text
|
||||
http://localhost:<port>/project/<projectId>/diagrams
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- system architecture tab shows DSL editor and layered preview;
|
||||
- default DSL appears when no saved architecture diagram exists;
|
||||
- editing DSL and clicking generate/refresh updates the preview;
|
||||
- saving persists and reload restores DSL;
|
||||
- SVG and PNG downloads start.
|
||||
|
||||
- [ ] **Step 3: Stop dev server**
|
||||
|
||||
Stop the Vite session after verification so no long-running command remains.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: default DSL, parsing, layered SVG, concerns sidebar, save/restore, export, and tests are covered.
|
||||
- Placeholder scan: no `TBD`, `TODO`, or vague implementation-only steps remain.
|
||||
- Type consistency: utility names and Vue computed/state names match across tasks.
|
||||
Reference in New Issue
Block a user