diff --git a/RuoYi-Vue/easycode-web/src/router/index.js b/RuoYi-Vue/easycode-web/src/router/index.js
index 9b7b6f9..dff561f 100644
--- a/RuoYi-Vue/easycode-web/src/router/index.js
+++ b/RuoYi-Vue/easycode-web/src/router/index.js
@@ -5,8 +5,13 @@ import HomeView from '@/views/HomeView.vue'
import LoginView from '@/views/LoginView.vue'
import RegisterView from '@/views/RegisterView.vue'
import GenerateView from '@/views/GenerateView.vue'
+import DiagramCenterView from '@/views/DiagramCenterView.vue'
+import ModuleDiagramEditorView from '@/views/ModuleDiagramEditorView.vue'
+import PageDesignerView from '@/views/PageDesignerView.vue'
+import PaperDraftView from '@/views/PaperDraftView.vue'
import ProjectListView from '@/views/ProjectListView.vue'
import PreviewView from '@/views/PreviewView.vue'
+import ProfileView from '@/views/ProfileView.vue'
import SourceDetailView from '@/views/SourceDetailView.vue'
import SourceStoreView from '@/views/SourceStoreView.vue'
@@ -16,8 +21,38 @@ const routes = [
{ path: '/register', name: 'register', component: RegisterView },
{ path: '/source-store', name: 'source-store', component: SourceStoreView },
{ path: '/source-store/:sourceId', name: 'source-detail', component: SourceDetailView },
+ { path: '/profile', name: 'profile', component: ProfileView, meta: { requiresAuth: true } },
{ path: '/projects', name: 'projects', component: ProjectListView, meta: { requiresAuth: true } },
{ path: '/generate', name: 'generate', component: GenerateView, meta: { requiresAuth: true } },
+ {
+ path: '/project/:projectId/page-designer',
+ name: 'page-designer',
+ component: PageDesignerView,
+ meta: { requiresAuth: true }
+ },
+ {
+ path: '/project/:projectId/diagrams',
+ name: 'diagram-center',
+ component: DiagramCenterView,
+ meta: { requiresAuth: true }
+ },
+ {
+ path: '/module-diagram',
+ name: 'module-diagram-editor-local',
+ component: ModuleDiagramEditorView
+ },
+ {
+ path: '/project/:projectId/module-diagram',
+ name: 'module-diagram-editor',
+ component: ModuleDiagramEditorView,
+ meta: { requiresAuth: true }
+ },
+ {
+ path: '/project/:projectId/paper-draft',
+ name: 'paper-draft',
+ component: PaperDraftView,
+ meta: { requiresAuth: true }
+ },
{
path: '/project/:projectId/preview',
name: 'preview',
diff --git a/RuoYi-Vue/easycode-web/src/views/ModuleDiagramEditorView.vue b/RuoYi-Vue/easycode-web/src/views/ModuleDiagramEditorView.vue
new file mode 100644
index 0000000..e9b16ea
--- /dev/null
+++ b/RuoYi-Vue/easycode-web/src/views/ModuleDiagramEditorView.vue
@@ -0,0 +1,270 @@
+
+
+
+
+
+
+
diff --git a/RuoYi-Vue/easycode-web/src/views/ProjectListView.vue b/RuoYi-Vue/easycode-web/src/views/ProjectListView.vue
index 9fa8b9a..7a3a30c 100644
--- a/RuoYi-Vue/easycode-web/src/views/ProjectListView.vue
+++ b/RuoYi-Vue/easycode-web/src/views/ProjectListView.vue
@@ -8,7 +8,7 @@
刷新
- 新建项目
+ 新建项目
@@ -17,7 +17,7 @@
{{ row.projectName || '未命名项目' }}
- {{ row.projectDesc || row.requirementKeyword || row.industryTemplate || '暂无描述' }}
+ {{ row.projectDesc || row.requirementKeyword || '暂无描述' }}
@@ -32,34 +32,30 @@
{{ row.updateTime || row.createTime || '-' }}
-
+
-
- 继续生成
-
-
- 预览
-
-
- 下载
-
-
- 删除
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -68,9 +64,7 @@
- 新建项目
-
+ />
@@ -80,7 +74,7 @@
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
-import { Delete, Download, EditPen, Plus, Refresh, View } from '@element-plus/icons-vue'
+import { Connection, DataAnalysis, Delete, Document, Download, EditPen, Plus, Refresh, View } from '@element-plus/icons-vue'
import { deleteProject, downloadProject, listProjects } from '@/api/project'
import { saveBlob, sourceZipName } from '@/utils/download'
@@ -121,7 +115,34 @@ function continueProject(project) {
ElMessage.warning('项目 ID 不存在,无法继续')
return
}
- router.push({ path: '/generate', query: { projectId } })
+ router.push({ path: '/generate', query: { projectId, mode: 'expert' } })
+}
+
+function openDiagrams(project) {
+ const projectId = projectIdOf(project)
+ if (!projectId) {
+ ElMessage.warning('项目 ID 不存在,无法打开图表中心')
+ return
+ }
+ router.push(`/project/${projectId}/diagrams`)
+}
+
+function openModuleDiagramEditor(project) {
+ const projectId = projectIdOf(project)
+ if (!projectId) {
+ ElMessage.warning('项目 ID 不存在,无法打开模块图')
+ return
+ }
+ router.push(`/project/${projectId}/module-diagram`)
+}
+
+function openPaperDraft(project) {
+ const projectId = projectIdOf(project)
+ if (!projectId) {
+ ElMessage.warning('项目 ID 不存在,无法打开论文初稿')
+ return
+ }
+ router.push(`/project/${projectId}/paper-draft`)
}
async function downloadSource(project) {
@@ -149,7 +170,7 @@ async function removeProject(project) {
if (!projectId) return
try {
- await ElMessageBox.confirm(`确认删除项目「${project.projectName || projectId}」?`, '删除项目', {
+ await ElMessageBox.confirm(`确认删除项目“${project.projectName || projectId}”?`, '删除项目', {
type: 'warning',
confirmButtonText: '删除',
cancelButtonText: '取消'
@@ -187,9 +208,16 @@ onMounted(loadProjects)
}
.project-desc {
+ display: -webkit-box;
+ max-width: 540px;
margin-top: 4px;
+ overflow: hidden;
color: #667085;
font-size: 13px;
+ line-height: 1.6;
+ overflow-wrap: anywhere;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
}
.row-actions {
@@ -203,17 +231,10 @@ onMounted(loadProjects)
margin-left: 0;
}
-.action-button {
- min-width: 72px;
- height: 32px;
- padding: 7px 10px;
- border-color: #d8e0eb;
- color: #475467;
- background: #ffffff;
-}
-
-.action-button-wide {
- min-width: 94px;
+.project-icon-button {
+ width: 36px;
+ height: 36px;
+ padding: 0;
}
@media (max-width: 860px) {
diff --git a/RuoYi-Vue/easycode-web/src/views/moduleDiagramEditorView.test.mjs b/RuoYi-Vue/easycode-web/src/views/moduleDiagramEditorView.test.mjs
new file mode 100644
index 0000000..569d4ce
--- /dev/null
+++ b/RuoYi-Vue/easycode-web/src/views/moduleDiagramEditorView.test.mjs
@@ -0,0 +1,48 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { existsSync, readFileSync } from 'node:fs'
+import { fileURLToPath } from 'node:url'
+import { dirname, resolve } from 'node:path'
+
+const currentDir = dirname(fileURLToPath(import.meta.url))
+const srcDir = resolve(currentDir, '..')
+
+function readSource(relativePath) {
+ return readFileSync(resolve(srcDir, relativePath), 'utf8')
+}
+
+test('standalone module diagram editor page exists and embeds the shared component', () => {
+ assert.equal(existsSync(resolve(srcDir, 'views/ModuleDiagramEditorView.vue')), true)
+ const source = readSource('views/ModuleDiagramEditorView.vue')
+
+ assert.equal(source.includes("import AppModuleDiagramView from '@/components/AppModuleDiagramView.vue'"), true)
+ assert.equal(source.includes('module-editor-layout'), true)
+ assert.equal(source.includes('module-editor-side'), true)
+ assert.equal(source.includes('module-editor-canvas'), true)
+ assert.equal(source.includes('v-model:source-mode="sourceMode"'), true)
+ assert.equal(source.includes('v-model:outline-text="outlineText"'), true)
+ assert.equal(source.includes('v-model:layout-options="layoutOptions"'), true)
+ assert.equal(source.includes(':editable="false"'), true)
+})
+
+test('standalone module diagram editor supports optional project loading and saving', () => {
+ const source = readSource('views/ModuleDiagramEditorView.vue')
+
+ assert.equal(source.includes('const projectId = computed'), true)
+ assert.equal(source.includes('getProject(projectId.value)'), true)
+ assert.equal(source.includes('listProjectDiagrams(projectId.value)'), true)
+ assert.equal(source.includes('saveProjectDiagram(projectId.value'), true)
+ assert.equal(source.includes('function editorDiagramPayload'), true)
+ assert.equal(source.includes('sourceMode: sourceMode.value'), true)
+ assert.equal(source.includes('outlineText: outlineText.value'), true)
+ assert.equal(source.includes('layoutOptions: layoutOptions.value'), true)
+})
+
+test('router exposes project and local module diagram editor routes', () => {
+ const source = readSource('router/index.js')
+
+ assert.equal(source.includes("import ModuleDiagramEditorView from '@/views/ModuleDiagramEditorView.vue'"), true)
+ assert.equal(source.includes("path: '/module-diagram'"), true)
+ assert.equal(source.includes("path: '/project/:projectId/module-diagram'"), true)
+ assert.equal(source.includes("name: 'module-diagram-editor'"), true)
+})
diff --git a/RuoYi-Vue/easycode-web/src/views/projectListView.test.mjs b/RuoYi-Vue/easycode-web/src/views/projectListView.test.mjs
new file mode 100644
index 0000000..af6a305
--- /dev/null
+++ b/RuoYi-Vue/easycode-web/src/views/projectListView.test.mjs
@@ -0,0 +1,54 @@
+import test from 'node:test'
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import { fileURLToPath } from 'node:url'
+import { dirname, resolve } from 'node:path'
+
+const currentDir = dirname(fileURLToPath(import.meta.url))
+
+function readView(name) {
+ return readFileSync(resolve(currentDir, name), 'utf8')
+}
+
+test('project list row actions use compact icon buttons like profile project actions', () => {
+ const source = readView('ProjectListView.vue')
+
+ assert.equal(source.includes('project-icon-button'), true)
+ assert.equal(source.includes('el-tooltip content="专家编辑"'), true)
+ assert.equal(source.includes('aria-label="专家编辑"'), true)
+ assert.equal(source.includes('aria-label="预览"'), true)
+ assert.equal(source.includes('aria-label="下载"'), true)
+ assert.equal(source.includes('aria-label="删除"'), true)
+ assert.match(source, //)
+ assert.doesNotMatch(source, /]*>\s*继续生成\s*<\/el-button>/)
+ assert.doesNotMatch(source, /]*>\s*预览\s*<\/el-button>/)
+ assert.doesNotMatch(source, /]*>\s*下载\s*<\/el-button>/)
+ assert.doesNotMatch(source, /]*>\s*删除\s*<\/el-button>/)
+})
+
+test('empty project state does not include a duplicate create project button', () => {
+ const source = readView('ProjectListView.vue')
+
+ assert.equal(source.includes("router.push({ path: '/generate', query: { mode: 'one-click' } })"), true)
+ assert.doesNotMatch(source, //)
+})
+
+test('project list truncates long project descriptions after two lines', () => {
+ const source = readView('ProjectListView.vue')
+
+ const styleMatch = source.match(/\.project-desc\s*\{([\s\S]*?)\n\}/)
+ assert.notEqual(styleMatch, null)
+ assert.match(styleMatch[1], /display:\s*-webkit-box;/)
+ assert.match(styleMatch[1], /-webkit-box-orient:\s*vertical;/)
+ assert.match(styleMatch[1], /-webkit-line-clamp:\s*2;/)
+ assert.match(styleMatch[1], /overflow:\s*hidden;/)
+})
+
+test('project list exposes compact module diagram action', () => {
+ const source = readView('ProjectListView.vue')
+
+ assert.equal(source.includes('el-tooltip content="模块图"'), true)
+ assert.equal(source.includes('aria-label="模块图"'), true)
+ assert.equal(source.includes('openModuleDiagramEditor'), true)
+ assert.equal(source.includes('`/project/${projectId}/module-diagram`'), true)
+})